| /******************************************************************************* |
| * Copyright (c) 2010 BSI Business Systems Integration AG. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * BSI Business Systems Integration AG - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.scout.rt.client.ui.basic.calendar; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.EventListener; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| |
| import org.eclipse.scout.commons.CollectionUtility; |
| import org.eclipse.scout.commons.CompositeObject; |
| import org.eclipse.scout.commons.ConfigurationUtility; |
| import org.eclipse.scout.commons.DateUtility; |
| import org.eclipse.scout.commons.EventListenerList; |
| import org.eclipse.scout.commons.Range; |
| import org.eclipse.scout.commons.StringUtility; |
| import org.eclipse.scout.commons.annotations.ConfigOperation; |
| import org.eclipse.scout.commons.annotations.ConfigProperty; |
| import org.eclipse.scout.commons.annotations.Order; |
| import org.eclipse.scout.commons.beans.AbstractPropertyObserver; |
| import org.eclipse.scout.commons.exception.ProcessingException; |
| import org.eclipse.scout.commons.logger.IScoutLogger; |
| import org.eclipse.scout.commons.logger.ScoutLogManager; |
| import org.eclipse.scout.rt.client.ui.action.ActionUtility; |
| import org.eclipse.scout.rt.client.ui.action.menu.CalendarMenuType; |
| import org.eclipse.scout.rt.client.ui.action.menu.IMenu; |
| import org.eclipse.scout.rt.client.ui.action.menu.root.ICalendarContextMenu; |
| import org.eclipse.scout.rt.client.ui.action.menu.root.internal.CalendarContextMenu; |
| import org.eclipse.scout.rt.client.ui.basic.calendar.provider.ICalendarItemProvider; |
| import org.eclipse.scout.rt.client.ui.basic.cell.Cell; |
| import org.eclipse.scout.rt.shared.services.common.calendar.ICalendarItem; |
| import org.eclipse.scout.rt.shared.services.common.exceptionhandler.IExceptionHandlerService; |
| import org.eclipse.scout.service.SERVICES; |
| |
| /** |
| * {@link ICalendarItemProducer} are defined as inner classes<br> |
| */ |
| public abstract class AbstractCalendar extends AbstractPropertyObserver implements ICalendar { |
| private static final IScoutLogger LOG = ScoutLogManager.getLogger(AbstractCalendar.class); |
| |
| private boolean m_initialized; |
| private List<ICalendarItemProvider> m_providers; |
| private final HashMap<Class<? extends ICalendarItemProvider>, Collection<CalendarComponent>> m_componentsByProvider; |
| private ICalendarUIFacade m_uiFacade; |
| private int m_calendarChanging; |
| private final DateTimeFormatFactory m_dateTimeFormatFactory; |
| private List<CalendarEvent> m_calendarEventBuffer; |
| private final EventListenerList m_listenerList; |
| |
| // internal usage of menus temporarily added of the current item provider |
| private List<IMenu> m_inheritedMenusOfSelectedProvider; |
| |
| public AbstractCalendar() { |
| this(true); |
| } |
| |
| public AbstractCalendar(boolean callInitializer) { |
| m_calendarEventBuffer = new ArrayList<CalendarEvent>(); |
| m_listenerList = new EventListenerList(); |
| m_dateTimeFormatFactory = new DateTimeFormatFactory(); |
| m_componentsByProvider = new HashMap<Class<? extends ICalendarItemProvider>, Collection<CalendarComponent>>(); |
| if (callInitializer) { |
| initConfig(); |
| } |
| } |
| |
| protected void callInitializer() { |
| if (!m_initialized) { |
| initConfig(); |
| m_initialized = true; |
| } |
| } |
| |
| /* |
| * Configuration |
| */ |
| @ConfigProperty(ConfigProperty.TEXT) |
| @Order(10) |
| protected String getConfiguredTitle() { |
| return null; |
| } |
| |
| @ConfigProperty(ConfigProperty.INTEGER) |
| @Order(500) |
| protected int getConfiguredStartHour() { |
| return 6; |
| } |
| |
| @ConfigProperty(ConfigProperty.INTEGER) |
| @Order(510) |
| protected int getConfiguredEndHour() { |
| return 19; |
| } |
| |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(520) |
| protected boolean getConfiguredUseOverflowCells() { |
| return true; |
| } |
| |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(530) |
| protected boolean getConfiguredShowDisplayModeSelection() { |
| return true; |
| } |
| |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(540) |
| protected boolean getConfiguredMarkNoonHour() { |
| return true; |
| } |
| |
| @ConfigProperty(ConfigProperty.BOOLEAN) |
| @Order(550) |
| protected boolean getConfiguredMarkOutOfMonthDays() { |
| return true; |
| } |
| |
| private List<Class<? extends ICalendarItemProvider>> getConfiguredProducers() { |
| Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass()); |
| List<Class<ICalendarItemProvider>> filtered = ConfigurationUtility.filterClasses(dca, ICalendarItemProvider.class); |
| List<Class<? extends ICalendarItemProvider>> foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(filtered, ICalendarItemProvider.class); |
| return ConfigurationUtility.removeReplacedClasses(foca); |
| } |
| |
| protected List<Class<? extends IMenu>> getDeclaredMenus() { |
| Class[] dca = ConfigurationUtility.getDeclaredPublicClasses(getClass()); |
| List<Class<IMenu>> filtered = ConfigurationUtility.filterClasses(dca, IMenu.class); |
| List<Class<? extends IMenu>> foca = ConfigurationUtility.sortFilteredClassesByOrderAnnotation(filtered, IMenu.class); |
| return ConfigurationUtility.removeReplacedClasses(foca); |
| } |
| |
| @ConfigOperation |
| @Order(10) |
| protected void execInitCalendar() throws ProcessingException { |
| } |
| |
| @ConfigOperation |
| @Order(15) |
| protected void execDisposeCalendar() throws ProcessingException { |
| } |
| |
| /** |
| * Filter and resolve item conflicts after some {@link ICalendarItemProvider} changed their items |
| * |
| * @param changedProviders |
| * is the list of provider types that changed their provided items |
| * @param componentsByProvider |
| * is the life map of all provider types with their provided items. |
| * <p> |
| * Changes to the componentsByProvider map are life applied to the calendar model.<br> |
| * Often the convenience method {@link #findConflictingItems(Map, Class...)} is used to calculate conflicting |
| * items of two or more providers for removal from the map. |
| * <p> |
| * The {@link ICalendarItem}s are wrapped into {@link CalendarComponent}s to hold their originating provider |
| * and other common info.<br> |
| * Use {@link CalendarComponent#getItem()} to access the {@link ICalendarItem}. |
| */ |
| @ConfigOperation |
| @Order(20) |
| protected void execFilterCalendarItems(Set<Class<? extends ICalendarItemProvider>> changedProviderTypes, Map<Class<? extends ICalendarItemProvider>, Collection<CalendarComponent>> componentsByProvider) { |
| } |
| |
| protected void initConfig() { |
| m_uiFacade = new P_UIFacade(); |
| setTitle(getConfiguredTitle()); |
| setSelectedDate(new Date()); |
| setStartHour(getConfiguredStartHour()); |
| setEndHour(getConfiguredEndHour()); |
| setUseOverflowCells(getConfiguredUseOverflowCells()); |
| setShowDisplayModeSelection(getConfiguredShowDisplayModeSelection()); |
| setMarkNoonHour(getConfiguredMarkNoonHour()); |
| setMarkOutOfMonthDays(getConfiguredMarkOutOfMonthDays()); |
| |
| // menus |
| List<IMenu> menuList = new ArrayList<IMenu>(); |
| for (Class<? extends IMenu> menuClazz : getDeclaredMenus()) { |
| try { |
| IMenu menu = ConfigurationUtility.newInnerInstance(this, menuClazz); |
| menuList.add(menu); |
| } |
| catch (Exception e) { |
| SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + menuClazz.getName() + "'.", e)); |
| } |
| } |
| try { |
| injectMenusInternal(menuList); |
| } |
| catch (Exception e) { |
| LOG.error("error occured while dynamically contributing menus.", e); |
| } |
| |
| // producers |
| List<ICalendarItemProvider> producerList = new ArrayList<ICalendarItemProvider>(); |
| for (Class<? extends ICalendarItemProvider> itemProviderClazz : getConfiguredProducers()) { |
| try { |
| ICalendarItemProvider provider = ConfigurationUtility.newInnerInstance(this, itemProviderClazz); |
| producerList.add(provider); |
| // add empty space menus to the context menu |
| menuList.addAll(ActionUtility.getActions(provider.getMenus(), ActionUtility.createMenuFilterMenuTypes(CalendarMenuType.EmptySpace))); |
| } |
| catch (Exception e) { |
| SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("error creating instance of class '" + itemProviderClazz.getName() + "'.", e)); |
| } |
| } |
| m_providers = producerList; |
| |
| ICalendarContextMenu contextMenu = new CalendarContextMenu(this, menuList); |
| setContextMenu(contextMenu); |
| |
| // attach change listener for item updates |
| for (final ICalendarItemProvider p : m_providers) { |
| p.addPropertyChangeListener( |
| new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent e) { |
| if (e.getPropertyName().equals(ICalendarItemProvider.PROP_ITEMS)) { |
| List<ICalendarItemProvider> modified = new ArrayList<ICalendarItemProvider>(1); |
| modified.add(p); |
| updateComponentsInternal(modified); |
| } |
| else if (e.getPropertyName().equals(ICalendarItemProvider.PROP_LOAD_IN_PROGRESS)) { |
| updateLoadInProgressInternal(); |
| } |
| } |
| } |
| ); |
| } |
| } |
| |
| /** |
| * Override this internal method only in order to make use of dynamic menus<br> |
| * Used to manage menu list and add/remove menus |
| * |
| * @param menuList |
| * live and mutable list of configured menus |
| */ |
| protected void injectMenusInternal(List<IMenu> menuList) { |
| } |
| |
| /* |
| * Runtime |
| */ |
| |
| /** |
| * This is the init of the runtime model after the table and columns are built |
| * and configured |
| */ |
| @Override |
| public void initCalendar() throws ProcessingException { |
| // init menus |
| ActionUtility.initActions(getMenus()); |
| execInitCalendar(); |
| /* |
| * add property change listener to - reload calendar items when view range |
| * changes |
| */ |
| addPropertyChangeListener(new PropertyChangeListener() { |
| @Override |
| public void propertyChange(PropertyChangeEvent e) { |
| if (e.getPropertyName().equals(PROP_VIEW_RANGE)) { |
| updateComponentsInternal(m_providers); |
| } |
| } |
| }); |
| updateComponentsInternal(m_providers); |
| } |
| |
| private void disposeCalendarInternal() { |
| for (ICalendarItemProvider p : m_providers) { |
| try { |
| p.disposeProvider(); |
| } |
| catch (Throwable t) { |
| LOG.warn(p.getClass().getName(), t); |
| } |
| } |
| } |
| |
| @Override |
| public void disposeCalendar() { |
| disposeCalendarInternal(); |
| try { |
| execDisposeCalendar(); |
| } |
| catch (Throwable t) { |
| LOG.warn(getClass().getName(), t); |
| } |
| } |
| |
| @Override |
| public String getTitle() { |
| return propertySupport.getPropertyString(PROP_TITLE); |
| } |
| |
| @Override |
| public void setTitle(String s) { |
| propertySupport.setPropertyString(PROP_TITLE, s); |
| } |
| |
| @Override |
| public int getEndHour() { |
| return (Integer) propertySupport.getProperty(PROP_END_HOUR); |
| } |
| |
| @Override |
| public void setEndHour(int hour) { |
| propertySupport.setProperty(PROP_END_HOUR, hour); |
| } |
| |
| @Override |
| public int getStartHour() { |
| return (Integer) propertySupport.getProperty(PROP_START_HOUR); |
| } |
| |
| @Override |
| public void setStartHour(int hour) { |
| propertySupport.setProperty(PROP_START_HOUR, hour); |
| } |
| |
| @Override |
| public boolean getUseOverflowCells() { |
| return (Boolean) propertySupport.getProperty(PROP_USE_OVERFLOW_CELLS); |
| } |
| |
| @Override |
| public void setUseOverflowCells(boolean useOverflowCells) { |
| propertySupport.setProperty(PROP_USE_OVERFLOW_CELLS, useOverflowCells); |
| } |
| |
| @Override |
| public boolean getShowDisplayModeSelection() { |
| return (Boolean) propertySupport.getProperty(PROP_SHOW_DISPLAY_MODE_SELECTION); |
| } |
| |
| @Override |
| public void setShowDisplayModeSelection(boolean showDisplayModeSelection) { |
| propertySupport.setProperty(PROP_SHOW_DISPLAY_MODE_SELECTION, showDisplayModeSelection); |
| } |
| |
| @Override |
| public boolean getMarkNoonHour() { |
| return (Boolean) propertySupport.getProperty(PROP_MARK_NOON_HOUR); |
| } |
| |
| @Override |
| public void setMarkNoonHour(boolean markNoonHour) { |
| propertySupport.setProperty(PROP_MARK_NOON_HOUR, markNoonHour); |
| } |
| |
| @Override |
| public boolean getMarkOutOfMonthDays() { |
| return (Boolean) propertySupport.getProperty(PROP_MARK_OUT_OF_MONTH_DAYS); |
| } |
| |
| @Override |
| public void setMarkOutOfMonthDays(boolean markOutOfMonthDays) { |
| propertySupport.setProperty(PROP_MARK_OUT_OF_MONTH_DAYS, markOutOfMonthDays); |
| } |
| |
| @Override |
| public boolean isLoadInProgress() { |
| return propertySupport.getPropertyBool(PROP_LOAD_IN_PROGRESS); |
| } |
| |
| @Override |
| public void setLoadInProgress(boolean b) { |
| propertySupport.setPropertyBool(PROP_LOAD_IN_PROGRESS, b); |
| } |
| |
| @Override |
| public boolean isCalendarChanging() { |
| return m_calendarChanging > 0; |
| } |
| |
| @Override |
| public void setCalendarChanging(boolean b) { |
| // use a stack counter because setTableChanging might be called in nested |
| // loops |
| if (b) { |
| m_calendarChanging++; |
| if (m_calendarChanging == 1) { |
| // 0 --> 1 |
| propertySupport.setPropertiesChanging(true); |
| } |
| } |
| else { |
| if (m_calendarChanging > 0) { |
| m_calendarChanging--; |
| if (m_calendarChanging == 0) { |
| try { |
| processChangeBuffer(); |
| } |
| finally { |
| propertySupport.setPropertiesChanging(false); |
| } |
| } |
| } |
| } |
| } |
| |
| private void processChangeBuffer() { |
| /* |
| * fire events tree changes are finished now, fire all buffered events and |
| * call lookups |
| */ |
| m_calendarEventBuffer = new ArrayList<CalendarEvent>(); |
| // coalesce ITEMS_CHANGED,ITEM_SELECTED events |
| Set<Integer> types = new HashSet<Integer>(); |
| List<CalendarEvent> coalescedEvents = new LinkedList<CalendarEvent>(); |
| // reverse traversal |
| CalendarEvent[] a = m_calendarEventBuffer.toArray(new CalendarEvent[0]); |
| for (int i = a.length - 1; i >= 0; i--) { |
| switch (a[i].getType()) { |
| case CalendarEvent.TYPE_COMPONENT_ACTION: { |
| if (!types.contains(a[i].getType())) { |
| coalescedEvents.add(0, a[i]); |
| types.add(a[i].getType()); |
| } |
| break; |
| } |
| default: { |
| coalescedEvents.add(0, a[i]); |
| } |
| } |
| } |
| // fire the batch |
| fireCalendarEventBatchInternal(coalescedEvents); |
| } |
| |
| @Override |
| public List<IMenu> getMenus() { |
| return getContextMenu().getChildActions(); |
| } |
| |
| protected void setContextMenu(ICalendarContextMenu contextMenu) { |
| propertySupport.setProperty(PROP_CONTEXT_MENU, contextMenu); |
| } |
| |
| @Override |
| public ICalendarContextMenu getContextMenu() { |
| return (ICalendarContextMenu) propertySupport.getProperty(PROP_CONTEXT_MENU); |
| } |
| |
| @Override |
| public List<ICalendarItemProvider> getCalendarItemProviders() { |
| return CollectionUtility.arrayList(m_providers); |
| } |
| |
| @Override |
| public int getDisplayMode() { |
| return propertySupport.getPropertyInt(PROP_DISPLAY_MODE); |
| } |
| |
| @Override |
| public void setDisplayMode(int mode) { |
| propertySupport.setPropertyInt(PROP_DISPLAY_MODE, mode); |
| } |
| |
| @Override |
| public boolean isDisplayCondensed() { |
| return propertySupport.getPropertyBool(PROP_DISPLAY_CONDENSED); |
| } |
| |
| @Override |
| public void setDisplayCondensed(boolean condensed) { |
| propertySupport.setPropertyBool(PROP_DISPLAY_CONDENSED, condensed); |
| } |
| |
| @Override |
| public Range<Date> getViewRange() { |
| @SuppressWarnings("unchecked") |
| Range<Date> propValue = (Range<Date>) propertySupport.getProperty(PROP_VIEW_RANGE); |
| // return a copy |
| return new Range<Date>(propValue); |
| } |
| |
| @Override |
| public void setViewRange(Date minDate, Date maxDate) { |
| setViewRangeInternal(new Range<Date>(minDate, maxDate)); |
| } |
| |
| @Override |
| public void setViewRange(Range<Date> viewRange) { |
| setViewRangeInternal(new Range<Date>(viewRange)); |
| } |
| |
| private void setViewRangeInternal(Range<Date> viewRange) { |
| propertySupport.setProperty(PROP_VIEW_RANGE, viewRange); |
| } |
| |
| @Override |
| public Date getSelectedDate() { |
| return (Date) propertySupport.getProperty(PROP_SELECTED_DATE); |
| } |
| |
| @Override |
| public void setSelectedDate(Date d) { |
| propertySupport.setProperty(PROP_SELECTED_DATE, d); |
| } |
| |
| @Override |
| public CalendarComponent getSelectedComponent() { |
| return (CalendarComponent) propertySupport.getProperty(PROP_SELECTED_COMPONENT); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T extends ICalendarItem> T getSelectedItem(Class<T> c) { |
| CalendarComponent comp = getSelectedComponent(); |
| if (comp != null && comp.getItem() != null) { |
| if (c.isAssignableFrom(comp.getItem().getClass())) { |
| return (T) comp.getItem(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void setSelectedComponent(CalendarComponent comp) { |
| comp = resolveComponent(comp); |
| // update temporarily added menus of current content provider |
| ICalendarItemProvider provider = null; |
| if (comp != null) { |
| provider = comp.getProvider(); |
| } |
| updateContentProviderMenus(provider); |
| propertySupport.setProperty(PROP_SELECTED_COMPONENT, comp); |
| } |
| |
| /** |
| * @param provider |
| */ |
| protected void updateContentProviderMenus(ICalendarItemProvider provider) { |
| // remove old |
| if (m_inheritedMenusOfSelectedProvider != null) { |
| getContextMenu().removeChildActions(m_inheritedMenusOfSelectedProvider); |
| m_inheritedMenusOfSelectedProvider = null; |
| } |
| // add menus of provider |
| if (provider != null) { |
| m_inheritedMenusOfSelectedProvider = ActionUtility.getActions(provider.getMenus(), ActionUtility.createMenuFilterMenuTypes(CalendarMenuType.CalendarComponent)); |
| getContextMenu().addChildActions(m_inheritedMenusOfSelectedProvider); |
| } |
| } |
| |
| private CalendarComponent resolveComponent(CalendarComponent comp) { |
| return comp; |
| } |
| |
| @Override |
| public DateTimeFormatFactory getDateTimeFormatFactory() { |
| return m_dateTimeFormatFactory; |
| } |
| |
| @Override |
| public Set<CalendarComponent> getComponents() { |
| return CollectionUtility.hashSet(propertySupport.<CalendarComponent> getPropertySet(PROP_COMPONENTS)); |
| } |
| |
| private void updateComponentsInternal(List<ICalendarItemProvider> changedProviders) { |
| Range<Date> d = getViewRange(); |
| if (d.getFrom() != null && d.getTo() != null) { |
| for (ICalendarItemProvider p : changedProviders) { |
| LinkedList<CalendarComponent> components = new LinkedList<CalendarComponent>(); |
| for (ICalendarItem item : p.getItems(d.getFrom(), d.getTo())) { |
| Cell cell = new Cell(); |
| p.decorateCell(cell, item); |
| components.add(new CalendarComponent(this, p, item, cell)); |
| } |
| m_componentsByProvider.put(p.getClass(), components); |
| } |
| // filter and resolve item conflicts |
| Set<Class<? extends ICalendarItemProvider>> providerTypes = new HashSet<Class<? extends ICalendarItemProvider>>(changedProviders.size()); |
| for (ICalendarItemProvider provider : changedProviders) { |
| providerTypes.add(provider.getClass()); |
| } |
| execFilterCalendarItems(providerTypes, m_componentsByProvider); |
| // complete list |
| TreeMap<CompositeObject, CalendarComponent> sortMap = new TreeMap<CompositeObject, CalendarComponent>(); |
| int index = 0; |
| for (Collection<CalendarComponent> c : m_componentsByProvider.values()) { |
| for (CalendarComponent comp : c) { |
| sortMap.put(new CompositeObject(comp.getFromDate(), index++), comp); |
| } |
| } |
| propertySupport.setPropertySet(PROP_COMPONENTS, CollectionUtility.hashSet(sortMap.values())); |
| // validate selection |
| setSelectedComponent(getSelectedComponent()); |
| } |
| } |
| |
| @Override |
| public Object getContainer() { |
| return propertySupport.getProperty(PROP_CONTAINER); |
| } |
| |
| /** |
| * do not use this internal method unless you are implementing a container that holds and controls an |
| * {@link ICalendar} |
| */ |
| public void setContainerInternal(Object container) { |
| propertySupport.setProperty(PROP_CONTAINER, container); |
| } |
| |
| /** |
| * @param componentsByProvider |
| * @param providerTypes |
| * {@link ICalendarItemProvider} classes |
| * @return |
| */ |
| public Collection<CalendarItemConflict> findConflictingItems(Map<Class<? extends ICalendarItemProvider>, Collection<CalendarComponent>> componentsByProvider, Class<?>... providerTypes) { |
| if (providerTypes != null && providerTypes.length >= 2) { |
| Map<String, List<CalendarComponent>> classificationMap = new HashMap<String, List<CalendarComponent>>(); |
| for (int i = 0; i < providerTypes.length; i++) { |
| Collection<CalendarComponent> a = componentsByProvider.get(providerTypes[i]); |
| if (a != null) { |
| for (CalendarComponent comp : a) { |
| String key = StringUtility.emptyIfNull(comp.getItem().getSubject()).toLowerCase().trim(); |
| List<CalendarComponent> list = classificationMap.get(key); |
| if (list == null) { |
| list = new ArrayList<CalendarComponent>(); |
| classificationMap.put(key, list); |
| } |
| list.add(comp); |
| } |
| } |
| } |
| List<CalendarItemConflict> conflicts = new ArrayList<CalendarItemConflict>(); |
| for (Map.Entry<String, List<CalendarComponent>> e : classificationMap.entrySet()) { |
| if (e.getValue().size() >= 2) { |
| List<CalendarComponent> list = e.getValue(); |
| // find CalendarComponents with same Provider, break them up in separate groups for duplicate check |
| // reason: all CalendarComponents of the same provider are assumed to be distinct |
| Map<ICalendarItemProvider, ArrayList<CalendarComponent>> groups = new HashMap<ICalendarItemProvider, ArrayList<CalendarComponent>>(); |
| for (CalendarComponent c : list) { |
| if (groups.containsKey(c.getProvider())) { |
| groups.get(c.getProvider()).add(c); |
| } |
| else { |
| ArrayList<CalendarComponent> tmp = new ArrayList<CalendarComponent>(); |
| tmp.add(c); |
| groups.put(c.getProvider(), tmp); |
| } |
| } |
| List<CalendarComponent> groupComp = new ArrayList<CalendarComponent>(); |
| for (ArrayList<CalendarComponent> g : groups.values()) { |
| if (g.size() > 1) { |
| groupComp.addAll(g); |
| } |
| } |
| if (groupComp.size() == 0) { |
| // no duplicate records of a provider found, start with first item |
| groupComp.add(list.get(0)); |
| } |
| for (CalendarComponent ref : groupComp) { |
| List<CalendarComponent> matchList = new ArrayList<CalendarComponent>(); |
| double matchSum = 0; |
| matchList.add(ref); |
| for (CalendarComponent test : list) { |
| if (ref == test || test.getProvider() == ref.getProvider()) { |
| continue; |
| } |
| if (DateUtility.intersects(test.getFromDate(), test.getToDate(), ref.getFromDate(), ref.getToDate())) { |
| matchList.add(test); |
| double minOfStart = Math.min(test.getFromDate().getTime(), ref.getFromDate().getTime()); |
| double maxOfStart = Math.max(test.getFromDate().getTime(), ref.getFromDate().getTime()); |
| double minOfEnd = Math.min(test.getToDate().getTime(), ref.getToDate().getTime()); |
| double maxOfEnd = Math.max(test.getToDate().getTime(), ref.getToDate().getTime()); |
| if (maxOfEnd - minOfStart > 1e-6) { |
| matchSum += (minOfEnd - maxOfStart) / (maxOfEnd - minOfStart); |
| } |
| else { |
| matchSum += 1.0d; |
| } |
| } |
| } |
| if (matchList.size() >= 2) { |
| conflicts.add(new CalendarItemConflict(componentsByProvider, matchList, matchSum / (matchList.size() - 1))); |
| } |
| } |
| } |
| } |
| return conflicts; |
| } |
| else { |
| return CollectionUtility.emptyArrayList(); |
| } |
| } |
| |
| private void updateLoadInProgressInternal() { |
| boolean b = false; |
| for (ICalendarItemProvider p : m_providers) { |
| if (p.isLoadInProgress()) { |
| b = true; |
| break; |
| } |
| } |
| setLoadInProgress(b); |
| } |
| |
| @Override |
| public void reloadCalendarItems() { |
| for (ICalendarItemProvider p : m_providers) { |
| p.reloadProvider(); |
| } |
| } |
| |
| /* |
| * Property Observer |
| */ |
| |
| @Override |
| public void addPropertyChangeListener(PropertyChangeListener listener) { |
| propertySupport.addPropertyChangeListener(listener); |
| } |
| |
| @Override |
| public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { |
| propertySupport.addPropertyChangeListener(propertyName, listener); |
| } |
| |
| @Override |
| public void removePropertyChangeListener(PropertyChangeListener listener) { |
| propertySupport.removePropertyChangeListener(listener); |
| } |
| |
| @Override |
| public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { |
| propertySupport.removePropertyChangeListener(propertyName, listener); |
| } |
| |
| /** |
| * Model Observer |
| */ |
| @Override |
| public void addCalendarListener(CalendarListener listener) { |
| m_listenerList.add(CalendarListener.class, listener); |
| } |
| |
| @Override |
| public void removeCalendarListener(CalendarListener listener) { |
| m_listenerList.remove(CalendarListener.class, listener); |
| } |
| |
| private void fireCalendarComponentAction() { |
| CalendarComponent comp = getSelectedComponent(); |
| if (comp != null) { |
| // single observer exec |
| try { |
| comp.getProvider().onItemAction(comp.getItem()); |
| } |
| catch (ProcessingException e) { |
| SERVICES.getService(IExceptionHandlerService.class).handleException(e); |
| } |
| fireCalendarEventInternal(new CalendarEvent(this, CalendarEvent.TYPE_COMPONENT_ACTION, comp)); |
| } |
| } |
| |
| // main handler |
| private void fireCalendarEventInternal(CalendarEvent e) { |
| if (isCalendarChanging()) { |
| // buffer the event for later batch firing |
| m_calendarEventBuffer.add(e); |
| } |
| else { |
| EventListener[] listeners = m_listenerList.getListeners(CalendarListener.class); |
| if (listeners != null && listeners.length > 0) { |
| for (int i = 0; i < listeners.length; i++) { |
| ((CalendarListener) listeners[i]).calendarChanged(e); |
| } |
| } |
| } |
| } |
| |
| // batch handler |
| private void fireCalendarEventBatchInternal(List<CalendarEvent> batch) { |
| if (isCalendarChanging()) { |
| LOG.error("Illegal State: firing a event batch while calendar is changing"); |
| } |
| else { |
| EventListener[] listeners = m_listenerList.getListeners(CalendarListener.class); |
| if (listeners != null && listeners.length > 0) { |
| for (int i = 0; i < listeners.length; i++) { |
| ((CalendarListener) listeners[i]).calendarChangedBatch(batch); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public ICalendarUIFacade getUIFacade() { |
| return m_uiFacade; |
| } |
| |
| /* |
| * UI Notifications |
| */ |
| private class P_UIFacade implements ICalendarUIFacade { |
| private int m_uiProcessorCount = 0; |
| |
| protected void pushUIProcessor() { |
| m_uiProcessorCount++; |
| } |
| |
| protected void popUIProcessor() { |
| m_uiProcessorCount--; |
| } |
| |
| @Override |
| public boolean isUIProcessing() { |
| return m_uiProcessorCount > 0; |
| } |
| |
| @Override |
| public void setSelectionFromUI(Date d, CalendarComponent comp) { |
| try { |
| pushUIProcessor(); |
| // |
| setSelectedDate(d); |
| setSelectedComponent(comp); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void setVisibleRangeFromUI(Range<Date> dateRange) { |
| setVisibleRangeFromUI(dateRange.getFrom(), dateRange.getTo()); |
| } |
| |
| @Override |
| public void setVisibleRangeFromUI(Date minDate, Date maxDate) { |
| try { |
| pushUIProcessor(); |
| // |
| setViewRange(minDate, maxDate); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireReloadFromUI() { |
| try { |
| pushUIProcessor(); |
| // |
| reloadCalendarItems(); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireComponentActionFromUI() { |
| try { |
| pushUIProcessor(); |
| // |
| fireCalendarComponentAction(); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| |
| @Override |
| public void fireComponentMovedFromUI(CalendarComponent comp, Date newDate) { |
| try { |
| pushUIProcessor(); |
| // |
| comp = resolveComponent(comp); |
| if (comp != null) { |
| try { |
| comp.getProvider().onItemMoved(comp.getItem(), newDate); |
| } |
| catch (ProcessingException e) { |
| SERVICES.getService(IExceptionHandlerService.class).handleException(e); |
| } |
| catch (Throwable e) { |
| SERVICES.getService(IExceptionHandlerService.class).handleException(new ProcessingException("Unexpected", e)); |
| } |
| } |
| fireCalendarComponentAction(); |
| } |
| finally { |
| popUIProcessor(); |
| } |
| } |
| } |
| } |