aboutsummaryrefslogtreecommitdiffstats
path: root/compatibility-server
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2016-08-18 22:10:47 +0300
committerArtur Signell <artur@vaadin.com>2016-08-20 00:12:18 +0300
commitfe3dca081a64af892a7f4c0416ecc643aec3ec5a (patch)
tree1901fb377336d3c5a772335322d9c434a4a75e24 /compatibility-server
parent65370e12a0605926cb80e205c2b0e74fefe83e5b (diff)
downloadvaadin-framework-fe3dca081a64af892a7f4c0416ecc643aec3ec5a.tar.gz
vaadin-framework-fe3dca081a64af892a7f4c0416ecc643aec3ec5a.zip
Move remaining selects and container implementations to compatibility package
Because of dependencies also moves Calendar, ColorPicker, SQLContainer, container filters Change-Id: I0594cb24f20486ebbca4be578827fea7cdf92108
Diffstat (limited to 'compatibility-server')
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/AbstractBeanContainer.java929
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/AbstractContainer.java307
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/AbstractInMemoryContainer.java1165
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/BeanContainer.java179
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/BeanItem.java264
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/BeanItemContainer.java253
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/ContainerHierarchicalWrapper.java865
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/FilesystemContainer.java924
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/GeneratedPropertyContainer.java776
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/HierarchicalContainer.java860
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/IndexedContainer.java1201
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheFlushNotifier.java103
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheMap.java43
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ColumnProperty.java357
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java50
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ReadOnlyRowId.java48
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/Reference.java68
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowId.java78
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowItem.java145
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLContainer.java1875
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLUtil.java51
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/TemporaryRowId.java44
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java84
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/JDBCConnectionPool.java53
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/SimpleJDBCConnectionPool.java181
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/AbstractTransactionalQuery.java185
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java495
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQueryDelegate.java130
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformStatementDelegate.java69
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/OrderBy.java58
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/QueryDelegate.java239
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/TableQuery.java869
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/DefaultSQLGenerator.java395
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/MSSQLGenerator.java115
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/OracleGenerator.java127
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/SQLGenerator.java100
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/StatementHelper.java179
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/AndTranslator.java35
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/BetweenTranslator.java37
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/CompareTranslator.java50
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/FilterTranslator.java28
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/IsNullTranslator.java34
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/LikeTranslator.java42
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/NotTranslator.java41
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/OrTranslator.java35
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/QueryBuilder.java110
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/SimpleStringTranslator.java42
-rw-r--r--compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/StringDecorator.java70
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/AbstractColorPicker.java588
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/AbstractSelect.java2353
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/Calendar.java2029
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/ColorPicker.java67
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/ColorPickerArea.java77
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/ComboBox.java925
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/Tree.java1984
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/TwinColSelect.java169
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvent.java51
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvents.java603
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarDateRange.java97
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarTargetDetails.java80
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/ContainerEventProvider.java566
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEvent.java265
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEventProvider.java177
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java42
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEvent.java147
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java112
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java91
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java96
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java70
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java74
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java70
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java94
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java82
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java43
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeListener.java42
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java144
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java258
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java217
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java759
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java198
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java235
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorSelector.java43
-rw-r--r--compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java36
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/AbstractFilterTestBase.java97
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/AndOrFilterTest.java246
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterDateTest.java142
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterTest.java322
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/IsNullFilterTest.java61
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/LikeFilterTest.java48
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/NotFilterTest.java54
-rw-r--r--compatibility-server/src/test/java/com/vaadin/data/util/filter/SimpleStringFilterTest.java139
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/AbstractBeanContainerListenersTest.java16
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/AbstractContainerListenersTest.java26
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/AbstractInMemoryContainerListenersTest.java14
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/IndexedContainerListenersTest.java26
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/SerializationTest.java135
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarBasicsTest.java290
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarDeclarativeTest.java62
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerDataSourceTest.java396
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerEventProviderTest.java88
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java87
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/ColorConversionsTest.java57
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java88
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxStateTest.java59
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/ListenersTest.java143
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java81
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeListenersTest.java33
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeTest.java178
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java74
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectStateTest.java60
-rw-r--r--compatibility-server/src/test/java/com/vaadin/tests/server/components/ComboBoxValueChangeTest.java46
111 files changed, 30340 insertions, 0 deletions
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/AbstractBeanContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractBeanContainer.java
new file mode 100644
index 0000000000..995e2f8675
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractBeanContainer.java
@@ -0,0 +1,929 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.Filterable;
+import com.vaadin.data.Container.PropertySetChangeNotifier;
+import com.vaadin.data.Container.SimpleFilterable;
+import com.vaadin.data.Container.Sortable;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.data.util.MethodProperty.MethodException;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ * An abstract base class for in-memory containers for JavaBeans.
+ *
+ * <p>
+ * The properties of the container are determined automatically by introspecting
+ * the used JavaBean class and explicitly adding or removing properties is not
+ * supported. Only beans of the same type can be added to the container.
+ * </p>
+ *
+ * <p>
+ * Subclasses should implement any public methods adding items to the container,
+ * typically calling the protected methods {@link #addItem(Object, Object)},
+ * {@link #addItemAfter(Object, Object, Object)} and
+ * {@link #addItemAt(int, Object, Object)}.
+ * </p>
+ *
+ * @param <IDTYPE>
+ * The type of the item identifier
+ * @param <BEANTYPE>
+ * The type of the Bean
+ *
+ * @since 6.5
+ */
+public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE>
+ extends AbstractInMemoryContainer<IDTYPE, String, BeanItem<BEANTYPE>>
+ implements Filterable, SimpleFilterable, Sortable, ValueChangeListener,
+ PropertySetChangeNotifier {
+
+ /**
+ * Resolver that maps beans to their (item) identifiers, removing the need
+ * to explicitly specify item identifiers when there is no need to customize
+ * this.
+ *
+ * Note that beans can also be added with an explicit id even if a resolver
+ * has been set.
+ *
+ * @param <IDTYPE>
+ * @param <BEANTYPE>
+ *
+ * @since 6.5
+ */
+ public static interface BeanIdResolver<IDTYPE, BEANTYPE>
+ extends Serializable {
+ /**
+ * Return the item identifier for a bean.
+ *
+ * @param bean
+ * @return
+ */
+ public IDTYPE getIdForBean(BEANTYPE bean);
+ }
+
+ /**
+ * A item identifier resolver that returns the value of a bean property.
+ *
+ * The bean must have a getter for the property, and the getter must return
+ * an object of type IDTYPE.
+ */
+ protected class PropertyBasedBeanIdResolver
+ implements BeanIdResolver<IDTYPE, BEANTYPE> {
+
+ private final Object propertyId;
+
+ public PropertyBasedBeanIdResolver(Object propertyId) {
+ if (propertyId == null) {
+ throw new IllegalArgumentException(
+ "Property identifier must not be null");
+ }
+ this.propertyId = propertyId;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public IDTYPE getIdForBean(BEANTYPE bean)
+ throws IllegalArgumentException {
+ VaadinPropertyDescriptor<BEANTYPE> pd = model.get(propertyId);
+ if (null == pd) {
+ throw new IllegalStateException(
+ "Property " + propertyId + " not found");
+ }
+ try {
+ Property<IDTYPE> property = (Property<IDTYPE>) pd
+ .createProperty(bean);
+ return property.getValue();
+ } catch (MethodException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ }
+
+ /**
+ * The resolver that finds the item ID for a bean, or null not to use
+ * automatic resolving.
+ *
+ * Methods that add a bean without specifying an ID must not be called if no
+ * resolver has been set.
+ */
+ private BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver = null;
+
+ /**
+ * Maps all item ids in the container (including filtered) to their
+ * corresponding BeanItem.
+ */
+ private final Map<IDTYPE, BeanItem<BEANTYPE>> itemIdToItem = new HashMap<IDTYPE, BeanItem<BEANTYPE>>();
+
+ /**
+ * The type of the beans in the container.
+ */
+ private final Class<? super BEANTYPE> type;
+
+ /**
+ * A description of the properties found in beans of type {@link #type}.
+ * Determines the property ids that are present in the container.
+ */
+ private final LinkedHashMap<String, VaadinPropertyDescriptor<BEANTYPE>> model;
+
+ /**
+ * Constructs a {@code AbstractBeanContainer} for beans of the given type.
+ *
+ * @param type
+ * the type of the beans that will be added to the container.
+ * @throws IllegalArgumentException
+ * If {@code type} is null
+ */
+ protected AbstractBeanContainer(Class<? super BEANTYPE> type) {
+ if (type == null) {
+ throw new IllegalArgumentException(
+ "The bean type passed to AbstractBeanContainer must not be null");
+ }
+ this.type = type;
+ model = BeanItem.getPropertyDescriptors((Class<BEANTYPE>) type);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ VaadinPropertyDescriptor<BEANTYPE> descriptor = model.get(propertyId);
+ if (descriptor == null) {
+ return null;
+ }
+ return descriptor.getPropertyType();
+ }
+
+ /**
+ * Create a BeanItem for a bean using pre-parsed bean metadata (based on
+ * {@link #getBeanType()}).
+ *
+ * @param bean
+ * @return created {@link BeanItem} or null if bean is null
+ */
+ protected BeanItem<BEANTYPE> createBeanItem(BEANTYPE bean) {
+ return bean == null ? null : new BeanItem<BEANTYPE>(bean, model);
+ }
+
+ /**
+ * Returns the type of beans this Container can contain.
+ *
+ * This comes from the bean type constructor parameter, and bean metadata
+ * (including container properties) is based on this.
+ *
+ * @return
+ */
+ public Class<? super BEANTYPE> getBeanType() {
+ return type;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
+ @Override
+ public Collection<String> getContainerPropertyIds() {
+ return model.keySet();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() {
+ int origSize = size();
+ IDTYPE firstItem = getFirstVisibleItem();
+
+ internalRemoveAllItems();
+
+ // detach listeners from all Items
+ for (Item item : itemIdToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ itemIdToItem.clear();
+
+ // fire event only if the visible view changed, regardless of whether
+ // filtered out items were removed or not
+ if (origSize != 0) {
+ fireItemsRemoved(0, firstItem, origSize);
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItem(java.lang.Object)
+ */
+ @Override
+ public BeanItem<BEANTYPE> getItem(Object itemId) {
+ // TODO return only if visible?
+ return getUnfilteredItem(itemId);
+ }
+
+ @Override
+ protected BeanItem<BEANTYPE> getUnfilteredItem(Object itemId) {
+ return itemIdToItem.get(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItemIds()
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<IDTYPE> getItemIds() {
+ return (List<IDTYPE>) super.getItemIds();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ Item item = getItem(itemId);
+ if (item == null) {
+ return null;
+ }
+ return item.getItemProperty(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId) {
+ // TODO should also remove items that are filtered out
+ int origSize = size();
+ Item item = getItem(itemId);
+ int position = indexOfId(itemId);
+
+ if (internalRemoveItem(itemId)) {
+ // detach listeners from Item
+ removeAllValueChangeListeners(item);
+
+ // remove item
+ itemIdToItem.remove(itemId);
+
+ // fire event only if the visible view changed, regardless of
+ // whether filtered out items were removed or not
+ if (size() != origSize) {
+ fireItemRemoved(position, itemId);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Re-filter the container when one of the monitored properties changes.
+ */
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ // if a property that is used in a filter is changed, refresh filtering
+ filterAll();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#addContainerFilter(java.lang.Object,
+ * java.lang.String, boolean, boolean)
+ */
+ @Override
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ try {
+ addFilter(new SimpleStringFilter(propertyId, filterString,
+ ignoreCase, onlyMatchPrefix));
+ } catch (UnsupportedFilterException e) {
+ // the filter instance created here is always valid for in-memory
+ // containers
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#removeAllContainerFilters()
+ */
+ @Override
+ public void removeAllContainerFilters() {
+ if (!getFilters().isEmpty()) {
+ for (Item item : itemIdToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ removeAllFilters();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#removeContainerFilters(java.lang
+ * .Object)
+ */
+ @Override
+ public void removeContainerFilters(Object propertyId) {
+ Collection<Filter> removedFilters = super.removeFilters(propertyId);
+ if (!removedFilters.isEmpty()) {
+ // stop listening to change events for the property
+ for (Item item : itemIdToItem.values()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ }
+ }
+
+ @Override
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException {
+ addFilter(filter);
+ }
+
+ @Override
+ public void removeContainerFilter(Filter filter) {
+ removeFilter(filter);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#hasContainerFilters()
+ */
+ @Override
+ public boolean hasContainerFilters() {
+ return super.hasContainerFilters();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return super.getContainerFilters();
+ }
+
+ /**
+ * Make this container listen to the given property provided it notifies
+ * when its value changes.
+ *
+ * @param item
+ * The {@link Item} that contains the property
+ * @param propertyId
+ * The id of the property
+ */
+ private void addValueChangeListener(Item item, Object propertyId) {
+ Property<?> property = item.getItemProperty(propertyId);
+ if (property instanceof ValueChangeNotifier) {
+ // avoid multiple notifications for the same property if
+ // multiple filters are in use
+ ValueChangeNotifier notifier = (ValueChangeNotifier) property;
+ notifier.removeListener(this);
+ notifier.addListener(this);
+ }
+ }
+
+ /**
+ * Remove this container as a listener for the given property.
+ *
+ * @param item
+ * The {@link Item} that contains the property
+ * @param propertyId
+ * The id of the property
+ */
+ private void removeValueChangeListener(Item item, Object propertyId) {
+ Property<?> property = item.getItemProperty(propertyId);
+ if (property instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) property).removeListener(this);
+ }
+ }
+
+ /**
+ * Remove this contains as a listener for all the properties in the given
+ * {@link Item}.
+ *
+ * @param item
+ * The {@link Item} that contains the properties
+ */
+ private void removeAllValueChangeListeners(Item item) {
+ for (Object propertyId : item.getItemPropertyIds()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ return getSortablePropertyIds();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ sortContainer(propertyId, ascending);
+ }
+
+ @Override
+ public ItemSorter getItemSorter() {
+ return super.getItemSorter();
+ }
+
+ @Override
+ public void setItemSorter(ItemSorter itemSorter) {
+ super.setItemSorter(itemSorter);
+ }
+
+ @Override
+ protected void registerNewItem(int position, IDTYPE itemId,
+ BeanItem<BEANTYPE> item) {
+ itemIdToItem.put(itemId, item);
+
+ // add listeners to be able to update filtering on property
+ // changes
+ for (Filter filter : getFilters()) {
+ for (String propertyId : getContainerPropertyIds()) {
+ if (filter.appliesToProperty(propertyId)) {
+ // addValueChangeListener avoids adding duplicates
+ addValueChangeListener(item, propertyId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check that a bean can be added to the container (is of the correct type
+ * for the container).
+ *
+ * @param bean
+ * @return
+ */
+ private boolean validateBean(BEANTYPE bean) {
+ return bean != null && getBeanType().isAssignableFrom(bean.getClass());
+ }
+
+ /**
+ * Adds the bean to the Container.
+ *
+ * Note: the behavior of this method changed in Vaadin 6.6 - now items are
+ * added at the very end of the unfiltered container and not after the last
+ * visible item if filtering is used.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ protected BeanItem<BEANTYPE> addItem(IDTYPE itemId, BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAtEnd(itemId, createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds the bean after the given bean.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object, Object)
+ */
+ protected BeanItem<BEANTYPE> addItemAfter(IDTYPE previousItemId,
+ IDTYPE newItemId, BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAfter(previousItemId, newItemId,
+ createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds a new bean at the given index.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @param index
+ * Index at which the bean should be added.
+ * @param newItemId
+ * The item id for the bean to add to the container.
+ * @param bean
+ * The bean to add to the container.
+ *
+ * @return Returns the new BeanItem or null if the operation fails.
+ */
+ protected BeanItem<BEANTYPE> addItemAt(int index, IDTYPE newItemId,
+ BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAt(index, newItemId, createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds a bean to the container using the bean item id resolver to find its
+ * identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItem(Object, Object)
+ *
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBean(BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItem(itemId, bean);
+ }
+
+ /**
+ * Adds a bean to the container after a specified item identifier, using the
+ * bean item id resolver to find its identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItemAfter(Object, Object, Object)
+ *
+ * @param previousItemId
+ * the identifier of the bean after which this bean should be
+ * added, null to add to the beginning
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBeanAfter(IDTYPE previousItemId,
+ BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItemAfter(previousItemId, itemId, bean);
+ }
+
+ /**
+ * Adds a bean at a specified (filtered view) position in the container
+ * using the bean item id resolver to find its identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItemAfter(Object, Object, Object)
+ *
+ * @param index
+ * the index (in the filtered view) at which to add the item
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBeanAt(int index, BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItemAt(index, itemId, bean);
+ }
+
+ /**
+ * Adds all the beans from a {@link Collection} in one operation using the
+ * bean item identifier resolver. More efficient than adding them one by
+ * one.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * Note: the behavior of this method changed in Vaadin 6.6 - now items are
+ * added at the very end of the unfiltered container and not after the last
+ * visible item if filtering is used.
+ *
+ * @param collection
+ * The collection of beans to add. Must not be null.
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if the resolver returns a null itemId for one of the beans in
+ * the collection
+ */
+ protected void addAll(Collection<? extends BEANTYPE> collection)
+ throws IllegalStateException, IllegalArgumentException {
+ boolean modified = false;
+ int origSize = size();
+
+ for (BEANTYPE bean : collection) {
+ // TODO skipping invalid beans - should not allow them in javadoc?
+ if (bean == null
+ || !getBeanType().isAssignableFrom(bean.getClass())) {
+ continue;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+
+ if (internalAddItemAtEnd(itemId, createBeanItem(bean),
+ false) != null) {
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ // Filter the contents when all items have been added
+ if (isFiltered()) {
+ doFilterContainer(!getFilters().isEmpty());
+ }
+ if (visibleNewItemsWasAdded(origSize)) {
+ // fire event about added items
+ int firstPosition = origSize;
+ IDTYPE firstItemId = getVisibleItemIds().get(firstPosition);
+ int affectedItems = size() - origSize;
+ fireItemsAdded(firstPosition, firstItemId, affectedItems);
+ }
+ }
+ }
+
+ private boolean visibleNewItemsWasAdded(int origSize) {
+ return size() > origSize;
+ }
+
+ /**
+ * Use the bean resolver to get the identifier for a bean.
+ *
+ * @param bean
+ * @return resolved bean identifier, null if could not be resolved
+ * @throws IllegalStateException
+ * if no bean resolver is set
+ */
+ protected IDTYPE resolveBeanId(BEANTYPE bean) {
+ if (beanIdResolver == null) {
+ throw new IllegalStateException(
+ "Bean item identifier resolver is required.");
+ }
+ return beanIdResolver.getIdForBean(bean);
+ }
+
+ /**
+ * Sets the resolver that finds the item id for a bean, or null not to use
+ * automatic resolving.
+ *
+ * Methods that add a bean without specifying an id must not be called if no
+ * resolver has been set.
+ *
+ * Note that methods taking an explicit id can be used whether a resolver
+ * has been defined or not.
+ *
+ * @param beanIdResolver
+ * to use or null to disable automatic id resolution
+ */
+ protected void setBeanIdResolver(
+ BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver) {
+ this.beanIdResolver = beanIdResolver;
+ }
+
+ /**
+ * Returns the resolver that finds the item ID for a bean.
+ *
+ * @return resolver used or null if automatic item id resolving is disabled
+ */
+ public BeanIdResolver<IDTYPE, BEANTYPE> getBeanIdResolver() {
+ return beanIdResolver;
+ }
+
+ /**
+ * Create an item identifier resolver using a named bean property.
+ *
+ * @param propertyId
+ * property identifier, which must map to a getter in BEANTYPE
+ * @return created resolver
+ */
+ protected BeanIdResolver<IDTYPE, BEANTYPE> createBeanPropertyResolver(
+ Object propertyId) {
+ return new PropertyBasedBeanIdResolver(propertyId);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by {@link #addPropertySetChangeListener}
+ **/
+ @Deprecated
+ @Override
+ public void addListener(Container.PropertySetChangeListener listener) {
+ addPropertySetChangeListener(listener);
+ }
+
+ @Override
+ public void addPropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ super.addPropertySetChangeListener(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Deprecated
+ @Override
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ removePropertySetChangeListener(listener);
+ }
+
+ @Override
+ public void removePropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ super.removePropertySetChangeListener(listener);
+ }
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Use addNestedContainerProperty(String) to add container properties to a "
+ + getClass().getSimpleName());
+ }
+
+ /**
+ * Adds a property for the container and all its items.
+ *
+ * Primarily for internal use, may change in future versions.
+ *
+ * @param propertyId
+ * @param propertyDescriptor
+ * @return true if the property was added
+ */
+ protected final boolean addContainerProperty(String propertyId,
+ VaadinPropertyDescriptor<BEANTYPE> propertyDescriptor) {
+ if (null == propertyId || null == propertyDescriptor) {
+ return false;
+ }
+
+ // Fails if the Property is already present
+ if (model.containsKey(propertyId)) {
+ return false;
+ }
+
+ model.put(propertyId, propertyDescriptor);
+ for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
+ item.addItemProperty(propertyId,
+ propertyDescriptor.createProperty(item.getBean()));
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+ /**
+ * Adds a nested container property for the container, e.g.
+ * "manager.address.street".
+ *
+ * All intermediate getters must exist and should return non-null values
+ * when the property value is accessed. If an intermediate getter returns
+ * null, a null value will be returned.
+ *
+ * @see NestedMethodProperty
+ *
+ * @param propertyId
+ * @return true if the property was added
+ */
+ public boolean addNestedContainerProperty(String propertyId) {
+ return addContainerProperty(propertyId,
+ new NestedPropertyDescriptor(propertyId, type));
+ }
+
+ /**
+ * Adds a nested container properties for all sub-properties of a named
+ * property to the container. The named property itself is removed from the
+ * model as its subproperties are added.
+ *
+ * All intermediate getters must exist and should return non-null values
+ * when the property value is accessed. If an intermediate getter returns
+ * null, a null value will be returned.
+ *
+ * @see NestedMethodProperty
+ * @see #addNestedContainerProperty(String)
+ *
+ * @param propertyId
+ */
+ @SuppressWarnings("unchecked")
+ public void addNestedContainerBean(String propertyId) {
+ Class<?> propertyType = getType(propertyId);
+ LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
+ .getPropertyDescriptors((Class<Object>) propertyType);
+ for (String subPropertyId : pds.keySet()) {
+ String qualifiedPropertyId = propertyId + "." + subPropertyId;
+ NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
+ qualifiedPropertyId, (Class<BEANTYPE>) type);
+ model.put(qualifiedPropertyId, pd);
+ model.remove(propertyId);
+ for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
+ item.addItemProperty(qualifiedPropertyId,
+ pd.createProperty(item.getBean()));
+ item.removeItemProperty(propertyId);
+ }
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+ }
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ // Fails if the Property is not present
+ if (!model.containsKey(propertyId)) {
+ return false;
+ }
+
+ // Removes the Property to Property list and types
+ model.remove(propertyId);
+
+ // If remove the Property from all Items
+ for (final Iterator<IDTYPE> i = getAllItemIds().iterator(); i
+ .hasNext();) {
+ getUnfilteredItem(i.next()).removeItemProperty(propertyId);
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/AbstractContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractContainer.java
new file mode 100644
index 0000000000..1995102345
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractContainer.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.LinkedList;
+
+import com.vaadin.data.Container;
+
+/**
+ * Abstract container class that manages event listeners and sending events to
+ * them ({@link PropertySetChangeNotifier}, {@link ItemSetChangeNotifier}).
+ *
+ * Note that this class provides the internal implementations for both types of
+ * events and notifiers as protected methods, but does not implement the
+ * {@link PropertySetChangeNotifier} and {@link ItemSetChangeNotifier}
+ * interfaces directly. This way, subclasses can choose not to implement them.
+ * Subclasses implementing those interfaces should also override the
+ * corresponding {@link #addListener()} and {@link #removeListener()} methods to
+ * make them public.
+ *
+ * @since 6.6
+ */
+public abstract class AbstractContainer implements Container {
+
+ /**
+ * List of all Property set change event listeners.
+ */
+ private Collection<Container.PropertySetChangeListener> propertySetChangeListeners = null;
+
+ /**
+ * List of all container Item set change event listeners.
+ */
+ private Collection<Container.ItemSetChangeListener> itemSetChangeListeners = null;
+
+ /**
+ * An <code>event</code> object specifying the container whose Property set
+ * has changed.
+ *
+ * This class does not provide information about which properties were
+ * concerned by the change, but subclasses can provide additional
+ * information about the changes.
+ */
+ protected static class BasePropertySetChangeEvent extends EventObject
+ implements Container.PropertySetChangeEvent, Serializable {
+
+ protected BasePropertySetChangeEvent(Container source) {
+ super(source);
+ }
+
+ @Override
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+ }
+
+ /**
+ * An <code>event</code> object specifying the container whose Item set has
+ * changed.
+ *
+ * This class does not provide information about the exact changes
+ * performed, but subclasses can add provide additional information about
+ * the changes.
+ */
+ protected static class BaseItemSetChangeEvent extends EventObject
+ implements Container.ItemSetChangeEvent, Serializable {
+
+ protected BaseItemSetChangeEvent(Container source) {
+ super(source);
+ }
+
+ @Override
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+ }
+
+ // PropertySetChangeNotifier
+
+ /**
+ * Implementation of the corresponding method in
+ * {@link PropertySetChangeNotifier}, override with the corresponding public
+ * method and implement the interface to use this.
+ *
+ * @see PropertySetChangeNotifier#addListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ protected void addPropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (getPropertySetChangeListeners() == null) {
+ setPropertySetChangeListeners(
+ new LinkedList<Container.PropertySetChangeListener>());
+ }
+ getPropertySetChangeListeners().add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addPropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Deprecated
+ protected void addListener(Container.PropertySetChangeListener listener) {
+ addPropertySetChangeListener(listener);
+ }
+
+ /**
+ * Implementation of the corresponding method in
+ * {@link PropertySetChangeNotifier}, override with the corresponding public
+ * method and implement the interface to use this.
+ *
+ * @see PropertySetChangeNotifier#removeListener(com.vaadin.data.Container.
+ * PropertySetChangeListener)
+ */
+ protected void removePropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (getPropertySetChangeListeners() != null) {
+ getPropertySetChangeListeners().remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Deprecated
+ protected void removeListener(
+ Container.PropertySetChangeListener listener) {
+ removePropertySetChangeListener(listener);
+ }
+
+ // ItemSetChangeNotifier
+
+ /**
+ * Implementation of the corresponding method in
+ * {@link ItemSetChangeNotifier}, override with the corresponding public
+ * method and implement the interface to use this.
+ *
+ * @see ItemSetChangeNotifier#addListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ protected void addItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (getItemSetChangeListeners() == null) {
+ setItemSetChangeListeners(
+ new LinkedList<Container.ItemSetChangeListener>());
+ }
+ getItemSetChangeListeners().add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Deprecated
+ protected void addListener(Container.ItemSetChangeListener listener) {
+ addItemSetChangeListener(listener);
+ }
+
+ /**
+ * Implementation of the corresponding method in
+ * {@link ItemSetChangeNotifier}, override with the corresponding public
+ * method and implement the interface to use this.
+ *
+ * @see ItemSetChangeNotifier#removeListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ protected void removeItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (getItemSetChangeListeners() != null) {
+ getItemSetChangeListeners().remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Deprecated
+ protected void removeListener(Container.ItemSetChangeListener listener) {
+ removeItemSetChangeListener(listener);
+ }
+
+ /**
+ * Sends a simple Property set change event to all interested listeners.
+ */
+ protected void fireContainerPropertySetChange() {
+ fireContainerPropertySetChange(new BasePropertySetChangeEvent(this));
+ }
+
+ /**
+ * Sends a Property set change event to all interested listeners.
+ *
+ * Use {@link #fireContainerPropertySetChange()} instead of this method
+ * unless additional information about the exact changes is available and
+ * should be included in the event.
+ *
+ * @param event
+ * the property change event to send, optionally with additional
+ * information
+ */
+ protected void fireContainerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ if (getPropertySetChangeListeners() != null) {
+ final Object[] l = getPropertySetChangeListeners().toArray();
+ for (int i = 0; i < l.length; i++) {
+ ((Container.PropertySetChangeListener) l[i])
+ .containerPropertySetChange(event);
+ }
+ }
+ }
+
+ /**
+ * Sends a simple Item set change event to all interested listeners,
+ * indicating that anything in the contents may have changed (items added,
+ * removed etc.).
+ */
+ protected void fireItemSetChange() {
+ fireItemSetChange(new BaseItemSetChangeEvent(this));
+ }
+
+ /**
+ * Sends an Item set change event to all registered interested listeners.
+ *
+ * @param event
+ * the item set change event to send, optionally with additional
+ * information
+ */
+ protected void fireItemSetChange(ItemSetChangeEvent event) {
+ if (getItemSetChangeListeners() != null) {
+ final Object[] l = getItemSetChangeListeners().toArray();
+ for (int i = 0; i < l.length; i++) {
+ ((Container.ItemSetChangeListener) l[i])
+ .containerItemSetChange(event);
+ }
+ }
+ }
+
+ /**
+ * Sets the property set change listener collection. For internal use only.
+ *
+ * @param propertySetChangeListeners
+ */
+ protected void setPropertySetChangeListeners(
+ Collection<Container.PropertySetChangeListener> propertySetChangeListeners) {
+ this.propertySetChangeListeners = propertySetChangeListeners;
+ }
+
+ /**
+ * Returns the property set change listener collection. For internal use
+ * only.
+ */
+ protected Collection<Container.PropertySetChangeListener> getPropertySetChangeListeners() {
+ return propertySetChangeListeners;
+ }
+
+ /**
+ * Sets the item set change listener collection. For internal use only.
+ *
+ * @param itemSetChangeListeners
+ */
+ protected void setItemSetChangeListeners(
+ Collection<Container.ItemSetChangeListener> itemSetChangeListeners) {
+ this.itemSetChangeListeners = itemSetChangeListeners;
+ }
+
+ /**
+ * Returns the item set change listener collection. For internal use only.
+ */
+ protected Collection<Container.ItemSetChangeListener> getItemSetChangeListeners() {
+ return itemSetChangeListeners;
+ }
+
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (Container.PropertySetChangeEvent.class
+ .isAssignableFrom(eventType)) {
+ if (propertySetChangeListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(propertySetChangeListeners);
+ }
+ } else if (Container.ItemSetChangeEvent.class
+ .isAssignableFrom(eventType)) {
+ if (itemSetChangeListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(itemSetChangeListeners);
+ }
+ }
+
+ return Collections.EMPTY_LIST;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/AbstractInMemoryContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractInMemoryContainer.java
new file mode 100644
index 0000000000..43b65ab75e
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/AbstractInMemoryContainer.java
@@ -0,0 +1,1165 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ * Abstract {@link Container} class that handles common functionality for
+ * in-memory containers. Concrete in-memory container classes can either inherit
+ * this class, inherit {@link AbstractContainer}, or implement the
+ * {@link Container} interface directly.
+ *
+ * Adding and removing items (if desired) must be implemented in subclasses by
+ * overriding the appropriate add*Item() and remove*Item() and removeAllItems()
+ * methods, calling the corresponding
+ * {@link #internalAddItemAfter(Object, Object, Item)},
+ * {@link #internalAddItemAt(int, Object, Item)},
+ * {@link #internalAddItemAtEnd(Object, Item, boolean)},
+ * {@link #internalRemoveItem(Object)} and {@link #internalRemoveAllItems()}
+ * methods.
+ *
+ * By default, adding and removing container properties is not supported, and
+ * subclasses need to implement {@link #getContainerPropertyIds()}. Optionally,
+ * subclasses can override {@link #addContainerProperty(Object, Class, Object)}
+ * and {@link #removeContainerProperty(Object)} to implement them.
+ *
+ * Features:
+ * <ul>
+ * <li>{@link Container.Ordered}
+ * <li>{@link Container.Indexed}
+ * <li>{@link Filterable} and {@link SimpleFilterable} (internal implementation,
+ * does not implement the interface directly)
+ * <li>{@link Sortable} (internal implementation, does not implement the
+ * interface directly)
+ * </ul>
+ *
+ * To implement {@link Sortable}, subclasses need to implement
+ * {@link #getSortablePropertyIds()} and call the superclass method
+ * {@link #sortContainer(Object[], boolean[])} in the method
+ * <code>sort(Object[], boolean[])</code>.
+ *
+ * To implement {@link Filterable}, subclasses need to implement the methods
+ * {@link Filterable#addContainerFilter(com.vaadin.data.Container.Filter)}
+ * (calling {@link #addFilter(Filter)}),
+ * {@link Filterable#removeAllContainerFilters()} (calling
+ * {@link #removeAllFilters()}) and
+ * {@link Filterable#removeContainerFilter(com.vaadin.data.Container.Filter)}
+ * (calling {@link #removeFilter(com.vaadin.data.Container.Filter)}).
+ *
+ * To implement {@link SimpleFilterable}, subclasses also need to implement the
+ * methods
+ * {@link SimpleFilterable#addContainerFilter(Object, String, boolean, boolean)}
+ * and {@link SimpleFilterable#removeContainerFilters(Object)} calling
+ * {@link #addFilter(com.vaadin.data.Container.Filter)} and
+ * {@link #removeFilters(Object)} respectively.
+ *
+ * @param <ITEMIDTYPE>
+ * the class of item identifiers in the container, use Object if can
+ * be any class
+ * @param <PROPERTYIDCLASS>
+ * the class of property identifiers for the items in the container,
+ * use Object if can be any class
+ * @param <ITEMCLASS>
+ * the (base) class of the Item instances in the container, use
+ * {@link Item} if unknown
+ *
+ * @since 6.6
+ */
+public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITEMCLASS extends Item>
+ extends AbstractContainer
+ implements ItemSetChangeNotifier, Container.Indexed {
+
+ /**
+ * An ordered {@link List} of all item identifiers in the container,
+ * including those that have been filtered out.
+ *
+ * Must not be null.
+ */
+ private List<ITEMIDTYPE> allItemIds;
+
+ /**
+ * An ordered {@link List} of item identifiers in the container after
+ * filtering, excluding those that have been filtered out.
+ *
+ * This is what the external API of the {@link Container} interface and its
+ * subinterfaces shows (e.g. {@link #size()}, {@link #nextItemId(Object)}).
+ *
+ * If null, the full item id list is used instead.
+ */
+ private List<ITEMIDTYPE> filteredItemIds;
+
+ /**
+ * Filters that are applied to the container to limit the items visible in
+ * it
+ */
+ private Set<Filter> filters = new HashSet<Filter>();
+
+ /**
+ * The item sorter which is used for sorting the container.
+ */
+ private ItemSorter itemSorter = new DefaultItemSorter();
+
+ // Constructors
+
+ /**
+ * Constructor for an abstract in-memory container.
+ */
+ protected AbstractInMemoryContainer() {
+ setAllItemIds(new ListSet<ITEMIDTYPE>());
+ }
+
+ // Container interface methods with more specific return class
+
+ // default implementation, can be overridden
+ @Override
+ public ITEMCLASS getItem(Object itemId) {
+ if (containsId(itemId)) {
+ return getUnfilteredItem(itemId);
+ } else {
+ return null;
+ }
+ }
+
+ private static abstract class BaseItemAddOrRemoveEvent extends EventObject
+ implements Serializable {
+ protected Object itemId;
+ protected int index;
+ protected int count;
+
+ public BaseItemAddOrRemoveEvent(Container source, Object itemId,
+ int index, int count) {
+ super(source);
+ this.itemId = itemId;
+ this.index = index;
+ this.count = count;
+ }
+
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ public Object getFirstItemId() {
+ return itemId;
+ }
+
+ public int getFirstIndex() {
+ return index;
+ }
+
+ public int getAffectedItemsCount() {
+ return count;
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the added
+ * items.
+ *
+ * <p>
+ * This class provides information about the first added item and the number
+ * of added items.
+ * </p>
+ *
+ * @since 7.4
+ */
+ protected static class BaseItemAddEvent extends BaseItemAddOrRemoveEvent
+ implements Container.Indexed.ItemAddEvent {
+
+ public BaseItemAddEvent(Container source, Object itemId, int index,
+ int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getAddedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the removed
+ * items.
+ *
+ * <p>
+ * This class provides information about the first removed item and the
+ * number of removed items.
+ * </p>
+ *
+ * @since 7.4
+ */
+ protected static class BaseItemRemoveEvent extends BaseItemAddOrRemoveEvent
+ implements Container.Indexed.ItemRemoveEvent {
+
+ public BaseItemRemoveEvent(Container source, Object itemId, int index,
+ int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getRemovedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
+ /**
+ * Get an item even if filtered out.
+ *
+ * For internal use only.
+ *
+ * @param itemId
+ * @return
+ */
+ protected abstract ITEMCLASS getUnfilteredItem(Object itemId);
+
+ // cannot override getContainerPropertyIds() and getItemIds(): if subclass
+ // uses Object as ITEMIDCLASS or PROPERTYIDCLASS, Collection<Object> cannot
+ // be cast to Collection<MyInterface>
+
+ // public abstract Collection<PROPERTYIDCLASS> getContainerPropertyIds();
+ // public abstract Collection<ITEMIDCLASS> getItemIds();
+
+ // Container interface method implementations
+
+ @Override
+ public int size() {
+ return getVisibleItemIds().size();
+ }
+
+ @Override
+ public boolean containsId(Object itemId) {
+ // only look at visible items after filtering
+ if (itemId == null) {
+ return false;
+ } else {
+ return getVisibleItemIds().contains(itemId);
+ }
+ }
+
+ @Override
+ public List<?> getItemIds() {
+ return Collections.unmodifiableList(getVisibleItemIds());
+ }
+
+ // Container.Ordered
+
+ @Override
+ public ITEMIDTYPE nextItemId(Object itemId) {
+ int index = indexOfId(itemId);
+ if (index >= 0 && index < size() - 1) {
+ return getIdByIndex(index + 1);
+ } else {
+ // out of bounds
+ return null;
+ }
+ }
+
+ @Override
+ public ITEMIDTYPE prevItemId(Object itemId) {
+ int index = indexOfId(itemId);
+ if (index > 0) {
+ return getIdByIndex(index - 1);
+ } else {
+ // out of bounds
+ return null;
+ }
+ }
+
+ @Override
+ public ITEMIDTYPE firstItemId() {
+ if (size() > 0) {
+ return getIdByIndex(0);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public ITEMIDTYPE lastItemId() {
+ if (size() > 0) {
+ return getIdByIndex(size() - 1);
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean isFirstId(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+ return itemId.equals(firstItemId());
+ }
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+ return itemId.equals(lastItemId());
+ }
+
+ // Container.Indexed
+
+ @Override
+ public ITEMIDTYPE getIdByIndex(int index) {
+ return getVisibleItemIds().get(index);
+ }
+
+ @Override
+ public List<ITEMIDTYPE> getItemIds(int startIndex, int numberOfIds) {
+ if (startIndex < 0) {
+ throw new IndexOutOfBoundsException(
+ "Start index cannot be negative! startIndex=" + startIndex);
+ }
+
+ if (startIndex > getVisibleItemIds().size()) {
+ throw new IndexOutOfBoundsException(
+ "Start index exceeds container size! startIndex="
+ + startIndex + " containerLastItemIndex="
+ + (getVisibleItemIds().size() - 1));
+ }
+
+ if (numberOfIds < 1) {
+ if (numberOfIds == 0) {
+ return Collections.emptyList();
+ }
+
+ throw new IllegalArgumentException(
+ "Cannot get negative amount of items! numberOfItems="
+ + numberOfIds);
+ }
+
+ int endIndex = startIndex + numberOfIds;
+
+ if (endIndex > getVisibleItemIds().size()) {
+ endIndex = getVisibleItemIds().size();
+ }
+
+ return Collections.unmodifiableList(
+ getVisibleItemIds().subList(startIndex, endIndex));
+
+ }
+
+ @Override
+ public int indexOfId(Object itemId) {
+ return getVisibleItemIds().indexOf(itemId);
+ }
+
+ // methods that are unsupported by default, override to support
+
+ @Override
+ public Object addItemAt(int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding items not supported. Override the relevant addItem*() methods if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Removing items not supported. Override the removeItem() method if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Removing items not supported. Override the removeAllItems() method if required as specified in AbstractInMemoryContainer javadoc.");
+ }
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Adding container properties not supported. Override the addContainerProperty() method if required.");
+ }
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Removing container properties not supported. Override the addContainerProperty() method if required.");
+ }
+
+ // ItemSetChangeNotifier
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Deprecated
+ @Override
+ public void addListener(Container.ItemSetChangeListener listener) {
+ addItemSetChangeListener(listener);
+ }
+
+ @Override
+ public void addItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ super.addItemSetChangeListener(listener);
+ }
+
+ @Override
+ public void removeItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ super.removeItemSetChangeListener(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Deprecated
+ @Override
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ removeItemSetChangeListener(listener);
+ }
+
+ // internal methods
+
+ // Filtering support
+
+ /**
+ * Filter the view to recreate the visible item list from the unfiltered
+ * items, and send a notification if the set of visible items changed in any
+ * way.
+ */
+ protected void filterAll() {
+ if (doFilterContainer(!getFilters().isEmpty())) {
+ fireItemSetChange();
+ }
+ }
+
+ /**
+ * Filters the data in the container and updates internal data structures.
+ * This method should reset any internal data structures and then repopulate
+ * them so {@link #getItemIds()} and other methods only return the filtered
+ * items.
+ *
+ * @param hasFilters
+ * true if filters has been set for the container, false
+ * otherwise
+ * @return true if the item set has changed as a result of the filtering
+ */
+ protected boolean doFilterContainer(boolean hasFilters) {
+ if (!hasFilters) {
+ boolean changed = getAllItemIds().size() != getVisibleItemIds()
+ .size();
+ setFilteredItemIds(null);
+ return changed;
+ }
+
+ // Reset filtered list
+ List<ITEMIDTYPE> originalFilteredItemIds = getFilteredItemIds();
+ boolean wasUnfiltered = false;
+ if (originalFilteredItemIds == null) {
+ originalFilteredItemIds = Collections.emptyList();
+ wasUnfiltered = true;
+ }
+ setFilteredItemIds(new ListSet<ITEMIDTYPE>());
+
+ // Filter
+ boolean equal = true;
+ Iterator<ITEMIDTYPE> origIt = originalFilteredItemIds.iterator();
+ for (final Iterator<ITEMIDTYPE> i = getAllItemIds().iterator(); i
+ .hasNext();) {
+ final ITEMIDTYPE id = i.next();
+ if (passesFilters(id)) {
+ // filtered list comes from the full list, can use ==
+ equal = equal && origIt.hasNext() && origIt.next() == id;
+ getFilteredItemIds().add(id);
+ }
+ }
+
+ return (wasUnfiltered && !getAllItemIds().isEmpty()) || !equal
+ || origIt.hasNext();
+ }
+
+ /**
+ * Checks if the given itemId passes the filters set for the container. The
+ * caller should make sure the itemId exists in the container. For
+ * non-existing itemIds the behavior is undefined.
+ *
+ * @param itemId
+ * An itemId that exists in the container.
+ * @return true if the itemId passes all filters or no filters are set,
+ * false otherwise.
+ */
+ protected boolean passesFilters(Object itemId) {
+ ITEMCLASS item = getUnfilteredItem(itemId);
+ if (getFilters().isEmpty()) {
+ return true;
+ }
+ final Iterator<Filter> i = getFilters().iterator();
+ while (i.hasNext()) {
+ final Filter f = i.next();
+ if (!f.passesFilter(itemId, item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Adds a container filter and re-filter the view.
+ *
+ * The filter must implement Filter and its sub-filters (if any) must also
+ * be in-memory filterable.
+ *
+ * This can be used to implement
+ * {@link Filterable#addContainerFilter(com.vaadin.data.Container.Filter)}
+ * and optionally also
+ * {@link SimpleFilterable#addContainerFilter(Object, String, boolean, boolean)}
+ * (with {@link SimpleStringFilter}).
+ *
+ * Note that in some cases, incompatible filters cannot be detected when
+ * added and an {@link UnsupportedFilterException} may occur when performing
+ * filtering.
+ *
+ * @throws UnsupportedFilterException
+ * if the filter is detected as not supported by the container
+ */
+ protected void addFilter(Filter filter) throws UnsupportedFilterException {
+ getFilters().add(filter);
+ filterAll();
+ }
+
+ /**
+ * Returns true if any filters have been applied to the container.
+ *
+ * @return true if the container has filters applied, false otherwise
+ * @since 7.1
+ */
+ protected boolean hasContainerFilters() {
+ return !getContainerFilters().isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#getContainerFilters()
+ */
+ protected Collection<Filter> getContainerFilters() {
+ return Collections.unmodifiableCollection(filters);
+ }
+
+ /**
+ * Remove a specific container filter and re-filter the view (if necessary).
+ *
+ * This can be used to implement
+ * {@link Filterable#removeContainerFilter(com.vaadin.data.Container.Filter)}
+ * .
+ */
+ protected void removeFilter(Filter filter) {
+ for (Iterator<Filter> iterator = getFilters().iterator(); iterator
+ .hasNext();) {
+ Filter f = iterator.next();
+ if (f.equals(filter)) {
+ iterator.remove();
+ filterAll();
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove all container filters for all properties and re-filter the view.
+ *
+ * This can be used to implement
+ * {@link Filterable#removeAllContainerFilters()}.
+ */
+ protected void removeAllFilters() {
+ if (getFilters().isEmpty()) {
+ return;
+ }
+ getFilters().clear();
+ filterAll();
+ }
+
+ /**
+ * Checks if there is a filter that applies to a given property.
+ *
+ * @param propertyId
+ * @return true if there is an active filter for the property
+ */
+ protected boolean isPropertyFiltered(Object propertyId) {
+ if (getFilters().isEmpty() || propertyId == null) {
+ return false;
+ }
+ final Iterator<Filter> i = getFilters().iterator();
+ while (i.hasNext()) {
+ final Filter f = i.next();
+ if (f.appliesToProperty(propertyId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Remove all container filters for a given property identifier and
+ * re-filter the view. This also removes filters applying to multiple
+ * properties including the one identified by propertyId.
+ *
+ * This can be used to implement
+ * {@link Filterable#removeContainerFilters(Object)}.
+ *
+ * @param propertyId
+ * @return Collection<Filter> removed filters
+ */
+ protected Collection<Filter> removeFilters(Object propertyId) {
+ if (getFilters().isEmpty() || propertyId == null) {
+ return Collections.emptyList();
+ }
+ List<Filter> removedFilters = new LinkedList<Filter>();
+ for (Iterator<Filter> iterator = getFilters().iterator(); iterator
+ .hasNext();) {
+ Filter f = iterator.next();
+ if (f.appliesToProperty(propertyId)) {
+ removedFilters.add(f);
+ iterator.remove();
+ }
+ }
+ if (!removedFilters.isEmpty()) {
+ filterAll();
+ return removedFilters;
+ }
+ return Collections.emptyList();
+ }
+
+ // sorting
+
+ /**
+ * Returns the ItemSorter used for comparing items in a sort. See
+ * {@link #setItemSorter(ItemSorter)} for more information.
+ *
+ * @return The ItemSorter used for comparing two items in a sort.
+ */
+ protected ItemSorter getItemSorter() {
+ return itemSorter;
+ }
+
+ /**
+ * Sets the ItemSorter used for comparing items in a sort. The
+ * {@link ItemSorter#compare(Object, Object)} method is called with item ids
+ * to perform the sorting. A default ItemSorter is used if this is not
+ * explicitly set.
+ *
+ * @param itemSorter
+ * The ItemSorter used for comparing two items in a sort (not
+ * null).
+ */
+ protected void setItemSorter(ItemSorter itemSorter) {
+ this.itemSorter = itemSorter;
+ }
+
+ /**
+ * Sort base implementation to be used to implement {@link Sortable}.
+ *
+ * Subclasses should call this from a public
+ * {@link #sort(Object[], boolean[])} method when implementing Sortable.
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+ protected void sortContainer(Object[] propertyId, boolean[] ascending) {
+ if (!(this instanceof Sortable)) {
+ throw new UnsupportedOperationException(
+ "Cannot sort a Container that does not implement Sortable");
+ }
+
+ // Set up the item sorter for the sort operation
+ getItemSorter().setSortProperties((Sortable) this, propertyId,
+ ascending);
+
+ // Perform the actual sort
+ doSort();
+
+ // Post sort updates
+ if (isFiltered()) {
+ filterAll();
+ } else {
+ fireItemSetChange();
+ }
+
+ }
+
+ /**
+ * Perform the sorting of the data structures in the container. This is
+ * invoked when the <code>itemSorter</code> has been prepared for the sort
+ * operation. Typically this method calls
+ * <code>Collections.sort(aCollection, getItemSorter())</code> on all arrays
+ * (containing item ids) that need to be sorted.
+ *
+ */
+ protected void doSort() {
+ Collections.sort(getAllItemIds(), getItemSorter());
+ }
+
+ /**
+ * Returns the sortable property identifiers for the container. Can be used
+ * to implement {@link Sortable#getSortableContainerPropertyIds()}.
+ */
+ protected Collection<?> getSortablePropertyIds() {
+ LinkedList<Object> sortables = new LinkedList<Object>();
+ for (Object propertyId : getContainerPropertyIds()) {
+ Class<?> propertyType = getType(propertyId);
+ if (Comparable.class.isAssignableFrom(propertyType)
+ || propertyType.isPrimitive()) {
+ sortables.add(propertyId);
+ }
+ }
+ return sortables;
+ }
+
+ // removing items
+
+ /**
+ * Removes all items from the internal data structures of this class. This
+ * can be used to implement {@link #removeAllItems()} in subclasses.
+ *
+ * No notification is sent, the caller has to fire a suitable item set
+ * change notification.
+ */
+ protected void internalRemoveAllItems() {
+ // Removes all Items
+ getAllItemIds().clear();
+ if (isFiltered()) {
+ getFilteredItemIds().clear();
+ }
+ }
+
+ /**
+ * Removes a single item from the internal data structures of this class.
+ * This can be used to implement {@link #removeItem(Object)} in subclasses.
+ *
+ * No notification is sent, the caller has to fire a suitable item set
+ * change notification.
+ *
+ * @param itemId
+ * the identifier of the item to remove
+ * @return true if an item was successfully removed, false if failed to
+ * remove or no such item
+ */
+ protected boolean internalRemoveItem(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+
+ boolean result = getAllItemIds().remove(itemId);
+ if (result && isFiltered()) {
+ getFilteredItemIds().remove(itemId);
+ }
+
+ return result;
+ }
+
+ // adding items
+
+ /**
+ * Adds the bean to all internal data structures at the given position.
+ * Fails if an item with itemId is already in the container. Returns a the
+ * item if it was added successfully, null otherwise.
+ *
+ * <p>
+ * Caller should initiate filtering after calling this method.
+ * </p>
+ *
+ * For internal use only - subclasses should use
+ * {@link #internalAddItemAtEnd(Object, Item, boolean)},
+ * {@link #internalAddItemAt(int, Object, Item, boolean)} and
+ * {@link #internalAddItemAfter(Object, Object, Item, boolean)} instead.
+ *
+ * @param position
+ * The position at which the item should be inserted in the
+ * unfiltered collection of items
+ * @param itemId
+ * The item identifier for the item to insert
+ * @param item
+ * The item to insert
+ *
+ * @return ITEMCLASS if the item was added successfully, null otherwise
+ */
+ private ITEMCLASS internalAddAt(int position, ITEMIDTYPE itemId,
+ ITEMCLASS item) {
+ if (position < 0 || position > getAllItemIds().size() || itemId == null
+ || item == null) {
+ return null;
+ }
+ // Make sure that the item has not been added previously
+ if (getAllItemIds().contains(itemId)) {
+ return null;
+ }
+
+ // "filteredList" will be updated in filterAll() which should be invoked
+ // by the caller after calling this method.
+ getAllItemIds().add(position, itemId);
+ registerNewItem(position, itemId, item);
+
+ return item;
+ }
+
+ /**
+ * Add an item at the end of the container, and perform filtering if
+ * necessary. An event is fired if the filtered view changes.
+ *
+ * @param newItemId
+ * @param item
+ * new item to add
+ * @param filter
+ * true to perform filtering and send event after adding the
+ * item, false to skip these operations for batch inserts - if
+ * false, caller needs to make sure these operations are
+ * performed at the end of the batch
+ * @return item added or null if no item was added
+ */
+ protected ITEMCLASS internalAddItemAtEnd(ITEMIDTYPE newItemId,
+ ITEMCLASS item, boolean filter) {
+ ITEMCLASS newItem = internalAddAt(getAllItemIds().size(), newItemId,
+ item);
+ if (newItem != null && filter) {
+ // TODO filter only this item, use fireItemAdded()
+ filterAll();
+ if (!isFiltered()) {
+ // TODO hack: does not detect change in filterAll() in this case
+ fireItemAdded(indexOfId(newItemId), newItemId, item);
+ }
+ }
+ return newItem;
+ }
+
+ /**
+ * Add an item after a given (visible) item, and perform filtering. An event
+ * is fired if the filtered view changes.
+ *
+ * The new item is added at the beginning if previousItemId is null.
+ *
+ * @param previousItemId
+ * item id of a visible item after which to add the new item, or
+ * null to add at the beginning
+ * @param newItemId
+ * @param item
+ * new item to add
+ * @param filter
+ * true to perform filtering and send event after adding the
+ * item, false to skip these operations for batch inserts - if
+ * false, caller needs to make sure these operations are
+ * performed at the end of the batch
+ * @return item added or null if no item was added
+ */
+ protected ITEMCLASS internalAddItemAfter(ITEMIDTYPE previousItemId,
+ ITEMIDTYPE newItemId, ITEMCLASS item, boolean filter) {
+ // only add if the previous item is visible
+ ITEMCLASS newItem = null;
+ if (previousItemId == null) {
+ newItem = internalAddAt(0, newItemId, item);
+ } else if (containsId(previousItemId)) {
+ newItem = internalAddAt(getAllItemIds().indexOf(previousItemId) + 1,
+ newItemId, item);
+ }
+ if (newItem != null && filter) {
+ // TODO filter only this item, use fireItemAdded()
+ filterAll();
+ if (!isFiltered()) {
+ // TODO hack: does not detect change in filterAll() in this case
+ fireItemAdded(indexOfId(newItemId), newItemId, item);
+ }
+ }
+ return newItem;
+ }
+
+ /**
+ * Add an item at a given (visible after filtering) item index, and perform
+ * filtering. An event is fired if the filtered view changes.
+ *
+ * @param index
+ * position where to add the item (visible/view index)
+ * @param newItemId
+ * @param item
+ * new item to add
+ * @param filter
+ * true to perform filtering and send event after adding the
+ * item, false to skip these operations for batch inserts - if
+ * false, caller needs to make sure these operations are
+ * performed at the end of the batch
+ * @return item added or null if no item was added
+ */
+ protected ITEMCLASS internalAddItemAt(int index, ITEMIDTYPE newItemId,
+ ITEMCLASS item, boolean filter) {
+ if (index < 0 || index > size()) {
+ return null;
+ } else if (index == 0) {
+ // add before any item, visible or not
+ return internalAddItemAfter(null, newItemId, item, filter);
+ } else {
+ // if index==size(), adds immediately after last visible item
+ return internalAddItemAfter(getIdByIndex(index - 1), newItemId,
+ item, filter);
+ }
+ }
+
+ /**
+ * Registers a new item as having been added to the container. This can
+ * involve storing the item or any relevant information about it in internal
+ * container-specific collections if necessary, as well as registering
+ * listeners etc.
+ *
+ * The full identifier list in {@link AbstractInMemoryContainer} has already
+ * been updated to reflect the new item when this method is called.
+ *
+ * @param position
+ * @param itemId
+ * @param item
+ */
+ protected void registerNewItem(int position, ITEMIDTYPE itemId,
+ ITEMCLASS item) {
+ }
+
+ // item set change notifications
+
+ /**
+ * Notify item set change listeners that an item has been added to the
+ * container.
+ *
+ * @since 7.4
+ *
+ * @param position
+ * position of the added item in the view
+ * @param itemId
+ * id of the added item
+ * @param item
+ * the added item
+ */
+ protected void fireItemAdded(int position, ITEMIDTYPE itemId,
+ ITEMCLASS item) {
+ fireItemsAdded(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been added to the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible added item in the view
+ * @param firstItemId
+ * id of the first visible added item
+ * @param numberOfItems
+ * the number of visible added items
+ */
+ protected void fireItemsAdded(int firstPosition, ITEMIDTYPE firstItemId,
+ int numberOfItems) {
+ BaseItemAddEvent addEvent = new BaseItemAddEvent(this, firstItemId,
+ firstPosition, numberOfItems);
+ fireItemSetChange(addEvent);
+ }
+
+ /**
+ * Notify item set change listeners that an item has been removed from the
+ * container.
+ *
+ * @since 7.4
+ *
+ * @param position
+ * position of the removed item in the view prior to removal (if
+ * was visible)
+ * @param itemId
+ * id of the removed item, of type {@link Object} to satisfy
+ * {@link Container#removeItem(Object)} API
+ */
+ protected void fireItemRemoved(int position, Object itemId) {
+ fireItemsRemoved(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been removed from the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible removed item in the view prior
+ * to removal
+ * @param firstItemId
+ * id of the first visible removed item, of type {@link Object}
+ * to satisfy {@link Container#removeItem(Object)} API
+ * @param numberOfItems
+ * the number of removed visible items
+ *
+ */
+ protected void fireItemsRemoved(int firstPosition, Object firstItemId,
+ int numberOfItems) {
+ BaseItemRemoveEvent removeEvent = new BaseItemRemoveEvent(this,
+ firstItemId, firstPosition, numberOfItems);
+ fireItemSetChange(removeEvent);
+ }
+
+ // visible and filtered item identifier lists
+
+ /**
+ * Returns the internal list of visible item identifiers after filtering.
+ *
+ * For internal use only.
+ */
+ protected List<ITEMIDTYPE> getVisibleItemIds() {
+ if (isFiltered()) {
+ return getFilteredItemIds();
+ } else {
+ return getAllItemIds();
+ }
+ }
+
+ /**
+ * Returns the item id of the first visible item after filtering. 'Null' is
+ * returned if there is no visible items.
+ * <p>
+ * For internal use only.
+ *
+ * @since 7.4
+ *
+ * @return item id of the first visible item
+ */
+ protected ITEMIDTYPE getFirstVisibleItem() {
+ if (!getVisibleItemIds().isEmpty()) {
+ return getVisibleItemIds().get(0);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true is the container has active filters.
+ *
+ * @return true if the container is currently filtered
+ */
+ protected boolean isFiltered() {
+ return filteredItemIds != null;
+ }
+
+ /**
+ * Internal helper method to set the internal list of filtered item
+ * identifiers. Should not be used outside this class except for
+ * implementing clone(), may disappear from future versions.
+ *
+ * @param filteredItemIds
+ */
+ @Deprecated
+ protected void setFilteredItemIds(List<ITEMIDTYPE> filteredItemIds) {
+ this.filteredItemIds = filteredItemIds;
+ }
+
+ /**
+ * Internal helper method to get the internal list of filtered item
+ * identifiers. Should not be used outside this class except for
+ * implementing clone(), may disappear from future versions - use
+ * {@link #getVisibleItemIds()} in other contexts.
+ *
+ * @return List<ITEMIDTYPE>
+ */
+ protected List<ITEMIDTYPE> getFilteredItemIds() {
+ return filteredItemIds;
+ }
+
+ /**
+ * Internal helper method to set the internal list of all item identifiers.
+ * Should not be used outside this class except for implementing clone(),
+ * may disappear from future versions.
+ *
+ * @param allItemIds
+ */
+ @Deprecated
+ protected void setAllItemIds(List<ITEMIDTYPE> allItemIds) {
+ this.allItemIds = allItemIds;
+ }
+
+ /**
+ * Internal helper method to get the internal list of all item identifiers.
+ * Avoid using this method outside this class, may disappear in future
+ * versions.
+ *
+ * @return List<ITEMIDTYPE>
+ */
+ protected List<ITEMIDTYPE> getAllItemIds() {
+ return allItemIds;
+ }
+
+ /**
+ * Set the internal collection of filters without performing filtering.
+ *
+ * This method is mostly for internal use, use
+ * {@link #addFilter(com.vaadin.data.Container.Filter)} and
+ * <code>remove*Filter*</code> (which also re-filter the container) instead
+ * when possible.
+ *
+ * @param filters
+ */
+ protected void setFilters(Set<Filter> filters) {
+ this.filters = filters;
+ }
+
+ /**
+ * Returns the internal collection of filters. The returned collection
+ * should not be modified by callers outside this class.
+ *
+ * @return Set<Filter>
+ */
+ protected Set<Filter> getFilters() {
+ return filters;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/BeanContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/BeanContainer.java
new file mode 100644
index 0000000000..01a5256199
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/BeanContainer.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.util.Collection;
+
+/**
+ * An in-memory container for JavaBeans.
+ *
+ * <p>
+ * The properties of the container are determined automatically by introspecting
+ * the used JavaBean class. Only beans of the same type can be added to the
+ * container.
+ * </p>
+ *
+ * <p>
+ * In BeanContainer (unlike {@link BeanItemContainer}), the item IDs do not have
+ * to be the beans themselves. The container can be used either with explicit
+ * item IDs or the item IDs can be generated when adding beans.
+ * </p>
+ *
+ * <p>
+ * To use explicit item IDs, use the methods {@link #addItem(Object, Object)},
+ * {@link #addItemAfter(Object, Object, Object)} and
+ * {@link #addItemAt(int, Object, Object)}.
+ * </p>
+ *
+ * <p>
+ * If a bean id resolver is set using
+ * {@link #setBeanIdResolver(com.vaadin.data.util.AbstractBeanContainer.BeanIdResolver)}
+ * or {@link #setBeanIdProperty(Object)}, the methods {@link #addBean(Object)},
+ * {@link #addBeanAfter(Object, Object)}, {@link #addBeanAt(int, Object)} and
+ * {@link #addAll(java.util.Collection)} can be used to add items to the
+ * container. If one of these methods is called, the resolver is used to
+ * generate an identifier for the item (must not return null).
+ * </p>
+ *
+ * <p>
+ * Note that explicit item identifiers can also be used when a resolver has been
+ * set by calling the addItem*() methods - the resolver is only used when adding
+ * beans using the addBean*() or {@link #addAll(Collection)} methods.
+ * </p>
+ *
+ * <p>
+ * It is not possible to add additional properties to the container.
+ * </p>
+ *
+ * @param <IDTYPE>
+ * The type of the item identifier
+ * @param <BEANTYPE>
+ * The type of the Bean
+ *
+ * @see AbstractBeanContainer
+ * @see BeanItemContainer
+ *
+ * @since 6.5
+ */
+public class BeanContainer<IDTYPE, BEANTYPE>
+ extends AbstractBeanContainer<IDTYPE, BEANTYPE> {
+
+ public BeanContainer(Class<? super BEANTYPE> type) {
+ super(type);
+ }
+
+ /**
+ * Adds the bean to the Container.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ @Override
+ public BeanItem<BEANTYPE> addItem(IDTYPE itemId, BEANTYPE bean) {
+ if (itemId != null && bean != null) {
+ return super.addItem(itemId, bean);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds the bean after the given item id.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object, Object)
+ */
+ @Override
+ public BeanItem<BEANTYPE> addItemAfter(IDTYPE previousItemId,
+ IDTYPE newItemId, BEANTYPE bean) {
+ if (newItemId != null && bean != null) {
+ return super.addItemAfter(previousItemId, newItemId, bean);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Adds a new bean at the given index.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @param index
+ * Index at which the bean should be added.
+ * @param newItemId
+ * The item id for the bean to add to the container.
+ * @param bean
+ * The bean to add to the container.
+ *
+ * @return Returns the new BeanItem or null if the operation fails.
+ */
+ @Override
+ public BeanItem<BEANTYPE> addItemAt(int index, IDTYPE newItemId,
+ BEANTYPE bean) {
+ if (newItemId != null && bean != null) {
+ return super.addItemAt(index, newItemId, bean);
+ } else {
+ return null;
+ }
+ }
+
+ // automatic item id resolution
+
+ /**
+ * Sets the bean id resolver to use a property of the beans as the
+ * identifier.
+ *
+ * @param propertyId
+ * the identifier of the property to use to find item identifiers
+ */
+ public void setBeanIdProperty(Object propertyId) {
+ setBeanIdResolver(createBeanPropertyResolver(propertyId));
+ }
+
+ @Override
+ // overridden to make public
+ public void setBeanIdResolver(
+ BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver) {
+ super.setBeanIdResolver(beanIdResolver);
+ }
+
+ @Override
+ // overridden to make public
+ public BeanItem<BEANTYPE> addBean(BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ return super.addBean(bean);
+ }
+
+ @Override
+ // overridden to make public
+ public BeanItem<BEANTYPE> addBeanAfter(IDTYPE previousItemId, BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ return super.addBeanAfter(previousItemId, bean);
+ }
+
+ @Override
+ // overridden to make public
+ public BeanItem<BEANTYPE> addBeanAt(int index, BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ return super.addBeanAt(index, bean);
+ }
+
+ @Override
+ // overridden to make public
+ public void addAll(Collection<? extends BEANTYPE> collection)
+ throws IllegalStateException {
+ super.addAll(collection);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/BeanItem.java b/compatibility-server/src/main/java/com/vaadin/data/util/BeanItem.java
new file mode 100644
index 0000000000..c0c4fdcaa6
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/BeanItem.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util;
+
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A wrapper class for adding the Item interface to any Java Bean.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class BeanItem<BT> extends PropertysetItem {
+
+ /**
+ * The bean which this Item is based on.
+ */
+ private final BT bean;
+
+ /**
+ * <p>
+ * Creates a new instance of <code>BeanItem</code> and adds all properties
+ * of a Java Bean to it. The properties are identified by their respective
+ * bean names.
+ * </p>
+ *
+ * <p>
+ * Note : This version only supports introspectable bean properties and
+ * their getter and setter methods. Stand-alone <code>is</code> and
+ * <code>are</code> methods are not supported.
+ * </p>
+ *
+ * @param bean
+ * the Java Bean to copy properties from.
+ *
+ */
+ public BeanItem(BT bean) {
+ this(bean, (Class<BT>) bean.getClass());
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>BeanItem</code> and adds all properties
+ * of a Java Bean to it. The properties are identified by their respective
+ * bean names.
+ * </p>
+ *
+ * <p>
+ * Note : This version only supports introspectable bean properties and
+ * their getter and setter methods. Stand-alone <code>is</code> and
+ * <code>are</code> methods are not supported.
+ * </p>
+ *
+ * @since 7.4
+ *
+ * @param bean
+ * the Java Bean to copy properties from.
+ * @param beanClass
+ * class of the {@code bean}
+ *
+ */
+ public BeanItem(BT bean, Class<BT> beanClass) {
+ this(bean, getPropertyDescriptors(beanClass));
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>BeanItem</code> using a pre-computed set
+ * of properties. The properties are identified by their respective bean
+ * names.
+ * </p>
+ *
+ * @param bean
+ * the Java Bean to copy properties from.
+ * @param propertyDescriptors
+ * pre-computed property descriptors
+ */
+ BeanItem(BT bean,
+ Map<String, VaadinPropertyDescriptor<BT>> propertyDescriptors) {
+
+ this.bean = bean;
+
+ for (VaadinPropertyDescriptor<BT> pd : propertyDescriptors.values()) {
+ addItemProperty(pd.getName(), pd.createProperty(bean));
+ }
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>BeanItem</code> and adds all listed
+ * properties of a Java Bean to it - in specified order. The properties are
+ * identified by their respective bean names.
+ * </p>
+ *
+ * <p>
+ * Note : This version only supports introspectable bean properties and
+ * their getter and setter methods. Stand-alone <code>is</code> and
+ * <code>are</code> methods are not supported.
+ * </p>
+ *
+ * @param bean
+ * the Java Bean to copy properties from.
+ * @param propertyIds
+ * id of the property.
+ */
+ public BeanItem(BT bean, Collection<?> propertyIds) {
+
+ this.bean = bean;
+
+ // Create bean information
+ LinkedHashMap<String, VaadinPropertyDescriptor<BT>> pds = getPropertyDescriptors(
+ (Class<BT>) bean.getClass());
+
+ // Add all the bean properties as MethodProperties to this Item
+ for (Object id : propertyIds) {
+ VaadinPropertyDescriptor<BT> pd = pds.get(id);
+ if (pd != null) {
+ addItemProperty(pd.getName(), pd.createProperty(bean));
+ }
+ }
+
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>BeanItem</code> and adds all listed
+ * properties of a Java Bean to it - in specified order. The properties are
+ * identified by their respective bean names.
+ * </p>
+ *
+ * <p>
+ * Note : This version only supports introspectable bean properties and
+ * their getter and setter methods. Stand-alone <code>is</code> and
+ * <code>are</code> methods are not supported.
+ * </p>
+ *
+ * @param bean
+ * the Java Bean to copy properties from.
+ * @param propertyIds
+ * ids of the properties.
+ */
+ public BeanItem(BT bean, String... propertyIds) {
+ this(bean, Arrays.asList(propertyIds));
+ }
+
+ /**
+ * <p>
+ * Perform introspection on a Java Bean class to find its properties.
+ * </p>
+ *
+ * <p>
+ * Note : This version only supports introspectable bean properties and
+ * their getter and setter methods. Stand-alone <code>is</code> and
+ * <code>are</code> methods are not supported.
+ * </p>
+ *
+ * @param beanClass
+ * the Java Bean class to get properties for.
+ * @return an ordered map from property names to property descriptors
+ */
+ static <BT> LinkedHashMap<String, VaadinPropertyDescriptor<BT>> getPropertyDescriptors(
+ final Class<BT> beanClass) {
+ final LinkedHashMap<String, VaadinPropertyDescriptor<BT>> pdMap = new LinkedHashMap<String, VaadinPropertyDescriptor<BT>>();
+
+ // Try to introspect, if it fails, we just have an empty Item
+ try {
+ List<PropertyDescriptor> propertyDescriptors = BeanUtil
+ .getBeanPropertyDescriptor(beanClass);
+
+ // Add all the bean properties as MethodProperties to this Item
+ // later entries on the list overwrite earlier ones
+ for (PropertyDescriptor pd : propertyDescriptors) {
+ final Method getMethod = pd.getReadMethod();
+ if ((getMethod != null)
+ && getMethod.getDeclaringClass() != Object.class) {
+ VaadinPropertyDescriptor<BT> vaadinPropertyDescriptor = new MethodPropertyDescriptor<BT>(
+ pd.getName(), pd.getPropertyType(),
+ pd.getReadMethod(), pd.getWriteMethod());
+ pdMap.put(pd.getName(), vaadinPropertyDescriptor);
+ }
+ }
+ } catch (final java.beans.IntrospectionException ignored) {
+ }
+
+ return pdMap;
+ }
+
+ /**
+ * Expands nested bean properties by replacing a top-level property with
+ * some or all of its sub-properties. The expansion is not recursive.
+ *
+ * @param propertyId
+ * property id for the property whose sub-properties are to be
+ * expanded,
+ * @param subPropertyIds
+ * sub-properties to expand, all sub-properties are expanded if
+ * not specified
+ */
+ public void expandProperty(String propertyId, String... subPropertyIds) {
+ Set<String> subPropertySet = new HashSet<String>(
+ Arrays.asList(subPropertyIds));
+
+ if (0 == subPropertyIds.length) {
+ // Enumerate all sub-properties
+ Class<?> propertyType = getItemProperty(propertyId).getType();
+ Map<String, ?> pds = getPropertyDescriptors(propertyType);
+ subPropertySet.addAll(pds.keySet());
+ }
+
+ for (String subproperty : subPropertySet) {
+ String qualifiedPropertyId = propertyId + "." + subproperty;
+ addNestedProperty(qualifiedPropertyId);
+ }
+
+ removeItemProperty(propertyId);
+ }
+
+ /**
+ * Adds a nested property to the item. The property must not exist in the
+ * item already and must of form "field1.field2" where field2 is a field in
+ * the object referenced to by field1. If an intermediate property returns
+ * null, the property will return a null value
+ *
+ * @param nestedPropertyId
+ * property id to add.
+ */
+ public void addNestedProperty(String nestedPropertyId) {
+ addItemProperty(nestedPropertyId,
+ new NestedMethodProperty<Object>(getBean(), nestedPropertyId));
+ }
+
+ /**
+ * Gets the underlying JavaBean object.
+ *
+ * @return the bean object.
+ */
+ public BT getBean() {
+ return bean;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/BeanItemContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/BeanItemContainer.java
new file mode 100644
index 0000000000..66e553164d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/BeanItemContainer.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.util.Collection;
+
+/**
+ * An in-memory container for JavaBeans.
+ *
+ * <p>
+ * The properties of the container are determined automatically by introspecting
+ * the used JavaBean class. Only beans of the same type can be added to the
+ * container.
+ * </p>
+ *
+ * <p>
+ * BeanItemContainer uses the beans themselves as identifiers. The
+ * {@link Object#hashCode()} of a bean is used when storing and looking up beans
+ * so it must not change during the lifetime of the bean (it should not depend
+ * on any part of the bean that can be modified). Typically this restricts the
+ * implementation of {@link Object#equals(Object)} as well in order for it to
+ * fulfill the contract between {@code equals()} and {@code hashCode()}.
+ * </p>
+ *
+ * <p>
+ * To add items to the container, use the methods {@link #addBean(Object)},
+ * {@link #addBeanAfter(Object, Object)} and {@link #addBeanAt(int, Object)}.
+ * Also {@link #addItem(Object)}, {@link #addItemAfter(Object, Object)} and
+ * {@link #addItemAt(int, Object)} can be used as synonyms for them.
+ * </p>
+ *
+ * <p>
+ * It is not possible to add additional properties to the container.
+ * </p>
+ *
+ * @param <BEANTYPE>
+ * The type of the Bean
+ *
+ * @since 5.4
+ */
+@SuppressWarnings("serial")
+public class BeanItemContainer<BEANTYPE>
+ extends AbstractBeanContainer<BEANTYPE, BEANTYPE> {
+
+ /**
+ * Bean identity resolver that returns the bean itself as its item
+ * identifier.
+ *
+ * This corresponds to the old behavior of {@link BeanItemContainer}, and
+ * requires suitable (identity-based) equals() and hashCode() methods on the
+ * beans.
+ *
+ * @param <BT>
+ *
+ * @since 6.5
+ */
+ private static class IdentityBeanIdResolver<BT>
+ implements BeanIdResolver<BT, BT> {
+
+ @Override
+ public BT getIdForBean(BT bean) {
+ return bean;
+ }
+
+ }
+
+ /**
+ * Constructs a {@code BeanItemContainer} for beans of the given type.
+ *
+ * @param type
+ * the type of the beans that will be added to the container.
+ * @throws IllegalArgumentException
+ * If {@code type} is null
+ */
+ public BeanItemContainer(Class<? super BEANTYPE> type)
+ throws IllegalArgumentException {
+ super(type);
+ super.setBeanIdResolver(new IdentityBeanIdResolver<BEANTYPE>());
+ }
+
+ /**
+ * Constructs a {@code BeanItemContainer} and adds the given beans to it.
+ * The collection must not be empty.
+ * {@link BeanItemContainer#BeanItemContainer(Class)} can be used for
+ * creating an initially empty {@code BeanItemContainer}.
+ *
+ * Note that when using this constructor, the actual class of the first item
+ * in the collection is used to determine the bean properties supported by
+ * the container instance, and only beans of that class or its subclasses
+ * can be added to the collection. If this is problematic or empty
+ * collections need to be supported, use {@link #BeanItemContainer(Class)}
+ * and {@link #addAll(Collection)} instead.
+ *
+ * @param collection
+ * a non empty {@link Collection} of beans.
+ * @throws IllegalArgumentException
+ * If the collection is null or empty.
+ *
+ * @deprecated As of 6.5, use {@link #BeanItemContainer(Class, Collection)}
+ * instead
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated
+ public BeanItemContainer(Collection<? extends BEANTYPE> collection)
+ throws IllegalArgumentException {
+ // must assume the class is BT
+ // the class information is erased by the compiler
+ this((Class<BEANTYPE>) getBeanClassForCollection(collection),
+ collection);
+ }
+
+ /**
+ * Internal helper method to support the deprecated {@link Collection}
+ * container.
+ *
+ * @param <BT>
+ * @param collection
+ * @return
+ * @throws IllegalArgumentException
+ */
+ @SuppressWarnings("unchecked")
+ @Deprecated
+ private static <BT> Class<? extends BT> getBeanClassForCollection(
+ Collection<? extends BT> collection)
+ throws IllegalArgumentException {
+ if (collection == null || collection.isEmpty()) {
+ throw new IllegalArgumentException(
+ "The collection passed to BeanItemContainer constructor must not be null or empty. Use the other BeanItemContainer constructor.");
+ }
+ return (Class<? extends BT>) collection.iterator().next().getClass();
+ }
+
+ /**
+ * Constructs a {@code BeanItemContainer} and adds the given beans to it.
+ *
+ * @param type
+ * the type of the beans that will be added to the container.
+ * @param collection
+ * a {@link Collection} of beans (can be empty or null).
+ * @throws IllegalArgumentException
+ * If {@code type} is null
+ */
+ public BeanItemContainer(Class<? super BEANTYPE> type,
+ Collection<? extends BEANTYPE> collection)
+ throws IllegalArgumentException {
+ super(type);
+ super.setBeanIdResolver(new IdentityBeanIdResolver<BEANTYPE>());
+
+ if (collection != null) {
+ addAll(collection);
+ }
+ }
+
+ /**
+ * Adds all the beans from a {@link Collection} in one go. More efficient
+ * than adding them one by one.
+ *
+ * @param collection
+ * The collection of beans to add. Must not be null.
+ */
+ @Override
+ public void addAll(Collection<? extends BEANTYPE> collection) {
+ super.addAll(collection);
+ }
+
+ /**
+ * Adds the bean after the given bean.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @param previousItemId
+ * the bean (of type BT) after which to add newItemId
+ * @param newItemId
+ * the bean (of type BT) to add (not null)
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object, Object)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public BeanItem<BEANTYPE> addItemAfter(Object previousItemId,
+ Object newItemId) throws IllegalArgumentException {
+ return super.addBeanAfter((BEANTYPE) previousItemId,
+ (BEANTYPE) newItemId);
+ }
+
+ /**
+ * Adds a new bean at the given index.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @param index
+ * Index at which the bean should be added.
+ * @param newItemId
+ * The bean to add to the container.
+ * @return Returns the new BeanItem or null if the operation fails.
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public BeanItem<BEANTYPE> addItemAt(int index, Object newItemId)
+ throws IllegalArgumentException {
+ return super.addBeanAt(index, (BEANTYPE) newItemId);
+ }
+
+ /**
+ * Adds the bean to the Container.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public BeanItem<BEANTYPE> addItem(Object itemId) {
+ return super.addBean((BEANTYPE) itemId);
+ }
+
+ /**
+ * Adds the bean to the Container.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ @Override
+ public BeanItem<BEANTYPE> addBean(BEANTYPE bean) {
+ return addItem(bean);
+ }
+
+ /**
+ * Unsupported in BeanItemContainer.
+ */
+ @Override
+ protected void setBeanIdResolver(
+ AbstractBeanContainer.BeanIdResolver<BEANTYPE, BEANTYPE> beanIdResolver)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "BeanItemContainer always uses an IdentityBeanIdResolver");
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/ContainerHierarchicalWrapper.java b/compatibility-server/src/main/java/com/vaadin/data/util/ContainerHierarchicalWrapper.java
new file mode 100644
index 0000000000..9e108cd615
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/ContainerHierarchicalWrapper.java
@@ -0,0 +1,865 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * A wrapper class for adding external hierarchy to containers not implementing
+ * the {@link com.vaadin.data.Container.Hierarchical} interface.
+ * </p>
+ *
+ * <p>
+ * If the wrapped container is changed directly (that is, not through the
+ * wrapper), and does not implement Container.ItemSetChangeNotifier and/or
+ * Container.PropertySetChangeNotifier the hierarchy information must be updated
+ * with the {@link #updateHierarchicalWrapper()} method.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class ContainerHierarchicalWrapper implements Container.Hierarchical,
+ Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier {
+
+ /** The wrapped container */
+ private final Container container;
+
+ /** Set of IDs of those contained Items that can't have children. */
+ private HashSet<Object> noChildrenAllowed = null;
+
+ /** Mapping from Item ID to parent Item ID */
+ private Hashtable<Object, Object> parent = null;
+
+ /** Mapping from Item ID to a list of child IDs */
+ private Hashtable<Object, LinkedList<Object>> children = null;
+
+ /** List that contains all root elements of the container. */
+ private LinkedHashSet<Object> roots = null;
+
+ /** Is the wrapped container hierarchical by itself ? */
+ private boolean hierarchical;
+
+ /**
+ * A comparator that sorts the listed items before other items. Otherwise,
+ * the order is undefined.
+ */
+ private static class ListedItemsFirstComparator
+ implements Comparator<Object>, Serializable {
+ private final Collection<?> itemIds;
+
+ private ListedItemsFirstComparator(Collection<?> itemIds) {
+ this.itemIds = itemIds;
+ }
+
+ @Override
+ public int compare(Object o1, Object o2) {
+ if (o1.equals(o2)) {
+ return 0;
+ }
+ for (Object id : itemIds) {
+ if (id == o1) {
+ return -1;
+ } else if (id == o2) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+ }
+
+ /**
+ * Constructs a new hierarchical wrapper for an existing Container. Works
+ * even if the to-be-wrapped container already implements the
+ * <code>Container.Hierarchical</code> interface.
+ *
+ * @param toBeWrapped
+ * the container that needs to be accessed hierarchically
+ * @see #updateHierarchicalWrapper()
+ */
+ public ContainerHierarchicalWrapper(Container toBeWrapped) {
+
+ container = toBeWrapped;
+ hierarchical = container instanceof Container.Hierarchical;
+
+ // Check arguments
+ if (container == null) {
+ throw new NullPointerException("Null can not be wrapped");
+ }
+
+ // Create initial order if needed
+ if (!hierarchical) {
+ noChildrenAllowed = new HashSet<Object>();
+ parent = new Hashtable<Object, Object>();
+ children = new Hashtable<Object, LinkedList<Object>>();
+ roots = new LinkedHashSet<Object>(container.getItemIds());
+ }
+
+ updateHierarchicalWrapper();
+
+ }
+
+ /**
+ * Updates the wrapper's internal hierarchy data to include all Items in the
+ * underlying container. If the contents of the wrapped container change
+ * without the wrapper's knowledge, this method needs to be called to update
+ * the hierarchy information of the Items.
+ */
+ public void updateHierarchicalWrapper() {
+
+ if (!hierarchical) {
+
+ // Recreate hierarchy and data structures if missing
+ if (noChildrenAllowed == null || parent == null || children == null
+ || roots == null) {
+ noChildrenAllowed = new HashSet<Object>();
+ parent = new Hashtable<Object, Object>();
+ children = new Hashtable<Object, LinkedList<Object>>();
+ roots = new LinkedHashSet<Object>(container.getItemIds());
+ }
+
+ // Check that the hierarchy is up-to-date
+ else {
+
+ // ensure order of root and child lists is same as in wrapped
+ // container
+ Collection<?> itemIds = container.getItemIds();
+ Comparator<Object> basedOnOrderFromWrappedContainer = new ListedItemsFirstComparator(
+ itemIds);
+
+ // Calculate the set of all items in the hierarchy
+ final HashSet<Object> s = new HashSet<Object>();
+ s.addAll(parent.keySet());
+ s.addAll(children.keySet());
+ s.addAll(roots);
+
+ // Remove unnecessary items
+ for (final Iterator<Object> i = s.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ if (!container.containsId(id)) {
+ removeFromHierarchyWrapper(id);
+ }
+ }
+
+ // Add all the missing items
+ final Collection<?> ids = container.getItemIds();
+ for (final Iterator<?> i = ids.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ if (!s.contains(id)) {
+ addToHierarchyWrapper(id);
+ s.add(id);
+ }
+ }
+
+ Object[] array = roots.toArray();
+ Arrays.sort(array, basedOnOrderFromWrappedContainer);
+ roots = new LinkedHashSet<Object>();
+ for (int i = 0; i < array.length; i++) {
+ roots.add(array[i]);
+ }
+ for (Object object : children.keySet()) {
+ LinkedList<Object> object2 = children.get(object);
+ Collections.sort(object2, basedOnOrderFromWrappedContainer);
+ }
+
+ }
+ }
+ }
+
+ /**
+ * Removes the specified Item from the wrapper's internal hierarchy
+ * structure.
+ * <p>
+ * Note : The Item is not removed from the underlying Container.
+ * </p>
+ *
+ * @param itemId
+ * the ID of the item to remove from the hierarchy.
+ */
+ private void removeFromHierarchyWrapper(Object itemId) {
+
+ LinkedList<Object> oprhanedChildren = children.remove(itemId);
+ if (oprhanedChildren != null) {
+ for (Object object : oprhanedChildren) {
+ // make orphaned children root nodes
+ setParent(object, null);
+ }
+ }
+
+ roots.remove(itemId);
+ final Object p = parent.get(itemId);
+ if (p != null) {
+ final LinkedList<Object> c = children.get(p);
+ if (c != null) {
+ c.remove(itemId);
+ }
+ }
+ parent.remove(itemId);
+ noChildrenAllowed.remove(itemId);
+ }
+
+ /**
+ * Adds the specified Item specified to the internal hierarchy structure.
+ * The new item is added as a root Item. The underlying container is not
+ * modified.
+ *
+ * @param itemId
+ * the ID of the item to add to the hierarchy.
+ */
+ private void addToHierarchyWrapper(Object itemId) {
+ roots.add(itemId);
+
+ }
+
+ /*
+ * Can the specified Item have any children? Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container)
+ .areChildrenAllowed(itemId);
+ }
+
+ if (noChildrenAllowed.contains(itemId)) {
+ return false;
+ }
+
+ return containsId(itemId);
+ }
+
+ /*
+ * Gets the IDs of the children of the specified Item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> getChildren(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).getChildren(itemId);
+ }
+
+ final Collection<?> c = children.get(itemId);
+ if (c == null) {
+ return null;
+ }
+ return Collections.unmodifiableCollection(c);
+ }
+
+ /*
+ * Gets the ID of the parent of the specified Item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Object getParent(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).getParent(itemId);
+ }
+
+ return parent.get(itemId);
+ }
+
+ /*
+ * Is the Item corresponding to the given ID a leaf node? Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean hasChildren(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).hasChildren(itemId);
+ }
+
+ LinkedList<Object> list = children.get(itemId);
+ return (list != null && !list.isEmpty());
+ }
+
+ /*
+ * Is the Item corresponding to the given ID a root node? Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean isRoot(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).isRoot(itemId);
+ }
+
+ if (parent.containsKey(itemId)) {
+ return false;
+ }
+
+ return containsId(itemId);
+ }
+
+ /*
+ * Gets the IDs of the root elements in the container. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> rootItemIds() {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).rootItemIds();
+ }
+
+ return Collections.unmodifiableCollection(roots);
+ }
+
+ /**
+ * <p>
+ * Sets the given Item's capability to have children. If the Item identified
+ * with the itemId already has children and the areChildrenAllowed is false
+ * this method fails and <code>false</code> is returned; the children must
+ * be first explicitly removed with
+ * {@link #setParent(Object itemId, Object newParentId)} or
+ * {@link com.vaadin.data.Container#removeItem(Object itemId)}.
+ * </p>
+ *
+ * @param itemId
+ * the ID of the Item in the container whose child capability is
+ * to be set.
+ * @param childrenAllowed
+ * the boolean value specifying if the Item can have children or
+ * not.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ */
+ @Override
+ public boolean setChildrenAllowed(Object itemId, boolean childrenAllowed) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container)
+ .setChildrenAllowed(itemId, childrenAllowed);
+ }
+
+ // Check that the item is in the container
+ if (!containsId(itemId)) {
+ return false;
+ }
+
+ // Update status
+ if (childrenAllowed) {
+ noChildrenAllowed.remove(itemId);
+ } else {
+ noChildrenAllowed.add(itemId);
+ }
+
+ return true;
+ }
+
+ /**
+ * <p>
+ * Sets the parent of an Item. The new parent item must exist and be able to
+ * have children. (<code>canHaveChildren(newParentId) == true</code>). It is
+ * also possible to detach a node from the hierarchy (and thus make it root)
+ * by setting the parent <code>null</code>.
+ * </p>
+ *
+ * @param itemId
+ * the ID of the item to be set as the child of the Item
+ * identified with newParentId.
+ * @param newParentId
+ * the ID of the Item that's to be the new parent of the Item
+ * identified with itemId.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ */
+ @Override
+ public boolean setParent(Object itemId, Object newParentId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).setParent(itemId,
+ newParentId);
+ }
+
+ // Check that the item is in the container
+ if (!containsId(itemId)) {
+ return false;
+ }
+
+ // Get the old parent
+ final Object oldParentId = parent.get(itemId);
+
+ // Check if no change is necessary
+ if ((newParentId == null && oldParentId == null)
+ || (newParentId != null && newParentId.equals(oldParentId))) {
+ return true;
+ }
+
+ // Making root
+ if (newParentId == null) {
+
+ // Remove from old parents children list
+ final LinkedList<Object> l = children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(itemId);
+ }
+ }
+
+ // Add to be a root
+ roots.add(itemId);
+
+ // Update parent
+ parent.remove(itemId);
+
+ fireItemSetChangeIfAbstractContainer();
+
+ return true;
+ }
+
+ // Check that the new parent exists in container and can have
+ // children
+ if (!containsId(newParentId)
+ || noChildrenAllowed.contains(newParentId)) {
+ return false;
+ }
+
+ // Check that setting parent doesn't result to a loop
+ Object o = newParentId;
+ while (o != null && !o.equals(itemId)) {
+ o = parent.get(o);
+ }
+ if (o != null) {
+ return false;
+ }
+
+ // Update parent
+ parent.put(itemId, newParentId);
+ LinkedList<Object> pcl = children.get(newParentId);
+ if (pcl == null) {
+ pcl = new LinkedList<Object>();
+ children.put(newParentId, pcl);
+ }
+ pcl.add(itemId);
+
+ // Remove from old parent or root
+ if (oldParentId == null) {
+ roots.remove(itemId);
+ } else {
+ final LinkedList<Object> l = children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(oldParentId);
+ }
+ }
+ }
+
+ fireItemSetChangeIfAbstractContainer();
+
+ return true;
+ }
+
+ /**
+ * inform container (if it is instance of AbstractContainer) about the
+ * change in hierarchy (#15421)
+ */
+ private void fireItemSetChangeIfAbstractContainer() {
+ if (container instanceof AbstractContainer) {
+ ((AbstractContainer) container).fireItemSetChange();
+ }
+ }
+
+ /**
+ * Creates a new Item into the Container, assigns it an automatic ID, and
+ * adds it to the hierarchy.
+ *
+ * @return the autogenerated ID of the new Item or <code>null</code> if the
+ * operation failed
+ * @throws UnsupportedOperationException
+ * if the addItem is not supported.
+ */
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+
+ final Object id = container.addItem();
+ if (!hierarchical && id != null) {
+ addToHierarchyWrapper(id);
+ }
+ return id;
+ }
+
+ /**
+ * Adds a new Item by its ID to the underlying container and to the
+ * hierarchy.
+ *
+ * @param itemId
+ * the ID of the Item to be created.
+ * @return the added Item or <code>null</code> if the operation failed.
+ * @throws UnsupportedOperationException
+ * if the addItem is not supported.
+ */
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ // Null ids are not accepted
+ if (itemId == null) {
+ return null;
+ }
+
+ final Item item = container.addItem(itemId);
+ if (!hierarchical && item != null) {
+ addToHierarchyWrapper(itemId);
+ }
+ return item;
+ }
+
+ /**
+ * Removes all items from the underlying container and from the hierarcy.
+ *
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ * @throws UnsupportedOperationException
+ * if the removeAllItems is not supported.
+ */
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+
+ final boolean success = container.removeAllItems();
+
+ if (!hierarchical && success) {
+ roots.clear();
+ parent.clear();
+ children.clear();
+ noChildrenAllowed.clear();
+ }
+ return success;
+ }
+
+ /**
+ * Removes an Item specified by the itemId from the underlying container and
+ * from the hierarchy.
+ *
+ * @param itemId
+ * the ID of the Item to be removed.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ * @throws UnsupportedOperationException
+ * if the removeItem is not supported.
+ */
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ final boolean success = container.removeItem(itemId);
+
+ if (!hierarchical && success) {
+ removeFromHierarchyWrapper(itemId);
+ }
+
+ return success;
+ }
+
+ /**
+ * Removes the Item identified by given itemId and all its children.
+ *
+ * @see #removeItem(Object)
+ * @param itemId
+ * the identifier of the Item to be removed
+ * @return true if the operation succeeded
+ */
+ public boolean removeItemRecursively(Object itemId) {
+ return HierarchicalContainer.removeItemRecursively(this, itemId);
+ }
+
+ /**
+ * Adds a new Property to all Items in the Container.
+ *
+ * @param propertyId
+ * the ID of the new Property.
+ * @param type
+ * the Data type of the new Property.
+ * @param defaultValue
+ * the value all created Properties are initialized to.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ * @throws UnsupportedOperationException
+ * if the addContainerProperty is not supported.
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ return container.addContainerProperty(propertyId, type, defaultValue);
+ }
+
+ /**
+ * Removes the specified Property from the underlying container and from the
+ * hierarchy.
+ * <p>
+ * Note : The Property will be removed from all Items in the Container.
+ * </p>
+ *
+ * @param propertyId
+ * the ID of the Property to remove.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ * @throws UnsupportedOperationException
+ * if the removeContainerProperty is not supported.
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ return container.removeContainerProperty(propertyId);
+ }
+
+ /*
+ * Does the container contain the specified Item? Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean containsId(Object itemId) {
+ return container.containsId(itemId);
+ }
+
+ /*
+ * Gets the specified Item from the container. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Item getItem(Object itemId) {
+ return container.getItem(itemId);
+ }
+
+ /*
+ * Gets the ID's of all Items stored in the Container Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> getItemIds() {
+ return container.getItemIds();
+ }
+
+ /*
+ * Gets the Property identified by the given itemId and propertyId from the
+ * Container Don't add a JavaDoc comment here, we use the default
+ * documentation from implemented interface.
+ */
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ return container.getContainerProperty(itemId, propertyId);
+ }
+
+ /*
+ * Gets the ID's of all Properties stored in the Container Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return container.getContainerPropertyIds();
+ }
+
+ /*
+ * Gets the data type of all Properties identified by the given Property ID.
+ * Don't add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return container.getType(propertyId);
+ }
+
+ /*
+ * Gets the number of Items in the Container. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public int size() {
+ int size = container.size();
+ assert size >= 0;
+ return size;
+ }
+
+ /*
+ * Registers a new Item set change listener for this Container. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void addItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .addItemSetChangeListener(new PiggybackListener(listener));
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Container.ItemSetChangeListener listener) {
+ addItemSetChangeListener(listener);
+ }
+
+ /*
+ * Removes a Item set change listener from the object. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void removeItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .removeItemSetChangeListener(
+ new PiggybackListener(listener));
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ removeItemSetChangeListener(listener);
+ }
+
+ /*
+ * Registers a new Property set change listener for this Container. Don't
+ * add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public void addPropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .addPropertySetChangeListener(
+ new PiggybackListener(listener));
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addPropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Container.PropertySetChangeListener listener) {
+ addPropertySetChangeListener(listener);
+ }
+
+ /*
+ * Removes a Property set change listener from the object. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public void removePropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .removePropertySetChangeListener(
+ new PiggybackListener(listener));
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ removePropertySetChangeListener(listener);
+ }
+
+ /**
+ * This listener 'piggybacks' on the real listener in order to update the
+ * wrapper when needed. It proxies equals() and hashCode() to the real
+ * listener so that the correct listener gets removed.
+ *
+ */
+ private class PiggybackListener
+ implements Container.PropertySetChangeListener,
+ Container.ItemSetChangeListener {
+
+ Object listener;
+
+ public PiggybackListener(Object realListener) {
+ listener = realListener;
+ }
+
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ updateHierarchicalWrapper();
+ ((Container.ItemSetChangeListener) listener)
+ .containerItemSetChange(event);
+
+ }
+
+ @Override
+ public void containerPropertySetChange(PropertySetChangeEvent event) {
+ updateHierarchicalWrapper();
+ ((Container.PropertySetChangeListener) listener)
+ .containerPropertySetChange(event);
+
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj == listener || (obj != null && obj.equals(listener));
+ }
+
+ @Override
+ public int hashCode() {
+ return listener.hashCode();
+ }
+
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/FilesystemContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/FilesystemContainer.java
new file mode 100644
index 0000000000..46b4f4bcd4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/FilesystemContainer.java
@@ -0,0 +1,924 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.server.Resource;
+import com.vaadin.util.FileTypeResolver;
+
+/**
+ * A hierarchical container wrapper for a filesystem.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class FilesystemContainer implements Container.Hierarchical {
+
+ /**
+ * String identifier of a file's "name" property.
+ */
+ public static String PROPERTY_NAME = "Name";
+
+ /**
+ * String identifier of a file's "size" property.
+ */
+ public static String PROPERTY_SIZE = "Size";
+
+ /**
+ * String identifier of a file's "icon" property.
+ */
+ public static String PROPERTY_ICON = "Icon";
+
+ /**
+ * String identifier of a file's "last modified" property.
+ */
+ public static String PROPERTY_LASTMODIFIED = "Last Modified";
+
+ /**
+ * List of the string identifiers for the available properties.
+ */
+ public static Collection<String> FILE_PROPERTIES;
+
+ private final static Method FILEITEM_LASTMODIFIED;
+
+ private final static Method FILEITEM_NAME;
+
+ private final static Method FILEITEM_ICON;
+
+ private final static Method FILEITEM_SIZE;
+
+ static {
+
+ FILE_PROPERTIES = new ArrayList<String>();
+ FILE_PROPERTIES.add(PROPERTY_NAME);
+ FILE_PROPERTIES.add(PROPERTY_ICON);
+ FILE_PROPERTIES.add(PROPERTY_SIZE);
+ FILE_PROPERTIES.add(PROPERTY_LASTMODIFIED);
+ FILE_PROPERTIES = Collections.unmodifiableCollection(FILE_PROPERTIES);
+ try {
+ FILEITEM_LASTMODIFIED = FileItem.class.getMethod("lastModified",
+ new Class[] {});
+ FILEITEM_NAME = FileItem.class.getMethod("getName", new Class[] {});
+ FILEITEM_ICON = FileItem.class.getMethod("getIcon", new Class[] {});
+ FILEITEM_SIZE = FileItem.class.getMethod("getSize", new Class[] {});
+ } catch (final NoSuchMethodException e) {
+ throw new RuntimeException(
+ "Internal error finding methods in FilesystemContainer");
+ }
+ }
+
+ private File[] roots = new File[] {};
+
+ private FilenameFilter filter = null;
+
+ private boolean recursive = true;
+
+ /**
+ * Constructs a new <code>FileSystemContainer</code> with the specified file
+ * as the root of the filesystem. The files are included recursively.
+ *
+ * @param root
+ * the root file for the new file-system container. Null values
+ * are ignored.
+ */
+ public FilesystemContainer(File root) {
+ if (root != null) {
+ roots = new File[] { root };
+ }
+ }
+
+ /**
+ * Constructs a new <code>FileSystemContainer</code> with the specified file
+ * as the root of the filesystem. The files are included recursively.
+ *
+ * @param root
+ * the root file for the new file-system container.
+ * @param recursive
+ * should the container recursively contain subdirectories.
+ */
+ public FilesystemContainer(File root, boolean recursive) {
+ this(root);
+ setRecursive(recursive);
+ }
+
+ /**
+ * Constructs a new <code>FileSystemContainer</code> with the specified file
+ * as the root of the filesystem.
+ *
+ * @param root
+ * the root file for the new file-system container.
+ * @param extension
+ * the Filename extension (w/o separator) to limit the files in
+ * container.
+ * @param recursive
+ * should the container recursively contain subdirectories.
+ */
+ public FilesystemContainer(File root, String extension, boolean recursive) {
+ this(root);
+ this.setFilter(extension);
+ setRecursive(recursive);
+ }
+
+ /**
+ * Constructs a new <code>FileSystemContainer</code> with the specified root
+ * and recursivity status.
+ *
+ * @param root
+ * the root file for the new file-system container.
+ * @param filter
+ * the Filename filter to limit the files in container.
+ * @param recursive
+ * should the container recursively contain subdirectories.
+ */
+ public FilesystemContainer(File root, FilenameFilter filter,
+ boolean recursive) {
+ this(root);
+ this.setFilter(filter);
+ setRecursive(recursive);
+ }
+
+ /**
+ * Adds new root file directory. Adds a file to be included as root file
+ * directory in the <code>FilesystemContainer</code>.
+ *
+ * @param root
+ * the File to be added as root directory. Null values are
+ * ignored.
+ */
+ public void addRoot(File root) {
+ if (root != null) {
+ final File[] newRoots = new File[roots.length + 1];
+ for (int i = 0; i < roots.length; i++) {
+ newRoots[i] = roots[i];
+ }
+ newRoots[roots.length] = root;
+ roots = newRoots;
+ }
+ }
+
+ /**
+ * Tests if the specified Item in the container may have children. Since a
+ * <code>FileSystemContainer</code> contains files and directories, this
+ * method returns <code>true</code> for directory Items only.
+ *
+ * @param itemId
+ * the id of the item.
+ * @return <code>true</code> if the specified Item is a directory,
+ * <code>false</code> otherwise.
+ */
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+ return itemId instanceof File && ((File) itemId).canRead()
+ && ((File) itemId).isDirectory();
+ }
+
+ /*
+ * Gets the ID's of all Items who are children of the specified Item. Don't
+ * add a JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public Collection<File> getChildren(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return Collections.unmodifiableCollection(new LinkedList<File>());
+ }
+ File[] f;
+ if (filter != null) {
+ f = ((File) itemId).listFiles(filter);
+ } else {
+ f = ((File) itemId).listFiles();
+ }
+
+ if (f == null) {
+ return Collections.unmodifiableCollection(new LinkedList<File>());
+ }
+
+ final List<File> l = Arrays.asList(f);
+ Collections.sort(l);
+
+ return Collections.unmodifiableCollection(l);
+ }
+
+ /*
+ * Gets the parent item of the specified Item. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Object getParent(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return null;
+ }
+ return ((File) itemId).getParentFile();
+ }
+
+ /*
+ * Tests if the specified Item has any children. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public boolean hasChildren(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return false;
+ }
+ String[] l;
+ if (filter != null) {
+ l = ((File) itemId).list(filter);
+ } else {
+ l = ((File) itemId).list();
+ }
+ return (l != null) && (l.length > 0);
+ }
+
+ /*
+ * Tests if the specified Item is the root of the filesystem. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean isRoot(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return false;
+ }
+ for (int i = 0; i < roots.length; i++) {
+ if (roots[i].equals(itemId)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ * Gets the ID's of all root Items in the container. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<File> rootItemIds() {
+
+ File[] f;
+
+ // in single root case we use children
+ if (roots.length == 1) {
+ if (filter != null) {
+ f = roots[0].listFiles(filter);
+ } else {
+ f = roots[0].listFiles();
+ }
+ } else {
+ f = roots;
+ }
+
+ if (f == null) {
+ return Collections.unmodifiableCollection(new LinkedList<File>());
+ }
+
+ final List<File> l = Arrays.asList(f);
+ Collections.sort(l);
+
+ return Collections.unmodifiableCollection(l);
+ }
+
+ /**
+ * Returns <code>false</code> when conversion from files to directories is
+ * not supported.
+ *
+ * @param itemId
+ * the ID of the item.
+ * @param areChildrenAllowed
+ * the boolean value specifying if the Item can have children or
+ * not.
+ * @return <code>true</code> if the operaton is successful otherwise
+ * <code>false</code>.
+ * @throws UnsupportedOperationException
+ * if the setChildrenAllowed is not supported.
+ */
+ @Override
+ public boolean setChildrenAllowed(Object itemId, boolean areChildrenAllowed)
+ throws UnsupportedOperationException {
+
+ throw new UnsupportedOperationException(
+ "Conversion file to/from directory is not supported");
+ }
+
+ /**
+ * Returns <code>false</code> when moving files around in the filesystem is
+ * not supported.
+ *
+ * @param itemId
+ * the ID of the item.
+ * @param newParentId
+ * the ID of the Item that's to be the new parent of the Item
+ * identified with itemId.
+ * @return <code>true</code> if the operation is successful otherwise
+ * <code>false</code>.
+ * @throws UnsupportedOperationException
+ * if the setParent is not supported.
+ */
+ @Override
+ public boolean setParent(Object itemId, Object newParentId)
+ throws UnsupportedOperationException {
+
+ throw new UnsupportedOperationException("File moving is not supported");
+ }
+
+ /*
+ * Tests if the filesystem contains the specified Item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean containsId(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return false;
+ }
+ boolean val = false;
+
+ // Try to match all roots
+ for (int i = 0; i < roots.length; i++) {
+ try {
+ val |= ((File) itemId).getCanonicalPath()
+ .startsWith(roots[i].getCanonicalPath());
+ } catch (final IOException e) {
+ // Exception ignored
+ }
+
+ }
+ if (val && filter != null) {
+ val &= filter.accept(((File) itemId).getParentFile(),
+ ((File) itemId).getName());
+ }
+ return val;
+ }
+
+ /*
+ * Gets the specified Item from the filesystem. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Item getItem(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return null;
+ }
+ return new FileItem((File) itemId);
+ }
+
+ /**
+ * Internal recursive method to add the files under the specified directory
+ * to the collection.
+ *
+ * @param col
+ * the collection where the found items are added
+ * @param f
+ * the root file where to start adding files
+ */
+ private void addItemIds(Collection<File> col, File f) {
+ File[] l;
+ if (filter != null) {
+ l = f.listFiles(filter);
+ } else {
+ l = f.listFiles();
+ }
+ if (l == null) {
+ // File.listFiles returns null if File does not exist or if there
+ // was an IO error (permission denied)
+ return;
+ }
+ final List<File> ll = Arrays.asList(l);
+ Collections.sort(ll);
+
+ for (final Iterator<File> i = ll.iterator(); i.hasNext();) {
+ final File lf = i.next();
+ col.add(lf);
+ if (lf.isDirectory()) {
+ addItemIds(col, lf);
+ }
+ }
+ }
+
+ /*
+ * Gets the IDs of Items in the filesystem. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Collection<File> getItemIds() {
+
+ if (recursive) {
+ final Collection<File> col = new ArrayList<File>();
+ for (int i = 0; i < roots.length; i++) {
+ addItemIds(col, roots[i]);
+ }
+ return Collections.unmodifiableCollection(col);
+ } else {
+ File[] f;
+ if (roots.length == 1) {
+ if (filter != null) {
+ f = roots[0].listFiles(filter);
+ } else {
+ f = roots[0].listFiles();
+ }
+ } else {
+ f = roots;
+ }
+
+ if (f == null) {
+ return Collections
+ .unmodifiableCollection(new LinkedList<File>());
+ }
+
+ final List<File> l = Arrays.asList(f);
+ Collections.sort(l);
+ return Collections.unmodifiableCollection(l);
+ }
+
+ }
+
+ /**
+ * Gets the specified property of the specified file Item. The available
+ * file properties are "Name", "Size" and "Last Modified". If propertyId is
+ * not one of those, <code>null</code> is returned.
+ *
+ * @param itemId
+ * the ID of the file whose property is requested.
+ * @param propertyId
+ * the property's ID.
+ * @return the requested property's value, or <code>null</code>
+ */
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+
+ if (!(itemId instanceof File)) {
+ return null;
+ }
+
+ if (propertyId.equals(PROPERTY_NAME)) {
+ return new MethodProperty<Object>(getType(propertyId),
+ new FileItem((File) itemId), FILEITEM_NAME, null);
+ }
+
+ if (propertyId.equals(PROPERTY_ICON)) {
+ return new MethodProperty<Object>(getType(propertyId),
+ new FileItem((File) itemId), FILEITEM_ICON, null);
+ }
+
+ if (propertyId.equals(PROPERTY_SIZE)) {
+ return new MethodProperty<Object>(getType(propertyId),
+ new FileItem((File) itemId), FILEITEM_SIZE, null);
+ }
+
+ if (propertyId.equals(PROPERTY_LASTMODIFIED)) {
+ return new MethodProperty<Object>(getType(propertyId),
+ new FileItem((File) itemId), FILEITEM_LASTMODIFIED, null);
+ }
+
+ return null;
+ }
+
+ /**
+ * Gets the collection of available file properties.
+ *
+ * @return Unmodifiable collection containing all available file properties.
+ */
+ @Override
+ public Collection<String> getContainerPropertyIds() {
+ return FILE_PROPERTIES;
+ }
+
+ /**
+ * Gets the specified property's data type. "Name" is a <code>String</code>,
+ * "Size" is a <code>Long</code>, "Last Modified" is a <code>Date</code>. If
+ * propertyId is not one of those, <code>null</code> is returned.
+ *
+ * @param propertyId
+ * the ID of the property whose type is requested.
+ * @return data type of the requested property, or <code>null</code>
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+
+ if (propertyId.equals(PROPERTY_NAME)) {
+ return String.class;
+ }
+ if (propertyId.equals(PROPERTY_ICON)) {
+ return Resource.class;
+ }
+ if (propertyId.equals(PROPERTY_SIZE)) {
+ return Long.class;
+ }
+ if (propertyId.equals(PROPERTY_LASTMODIFIED)) {
+ return Date.class;
+ }
+ return null;
+ }
+
+ /**
+ * Internal method to recursively calculate the number of files under a root
+ * directory.
+ *
+ * @param f
+ * the root to start counting from.
+ */
+ private int getFileCounts(File f) {
+ File[] l;
+ if (filter != null) {
+ l = f.listFiles(filter);
+ } else {
+ l = f.listFiles();
+ }
+
+ if (l == null) {
+ return 0;
+ }
+ int ret = l.length;
+ for (int i = 0; i < l.length; i++) {
+ if (l[i].isDirectory()) {
+ ret += getFileCounts(l[i]);
+ }
+ }
+ return ret;
+ }
+
+ /**
+ * Gets the number of Items in the container. In effect, this is the
+ * combined amount of files and directories.
+ *
+ * @return Number of Items in the container.
+ */
+ @Override
+ public int size() {
+
+ if (recursive) {
+ int counts = 0;
+ for (int i = 0; i < roots.length; i++) {
+ counts += getFileCounts(roots[i]);
+ }
+ return counts;
+ } else {
+ File[] f;
+ if (roots.length == 1) {
+ if (filter != null) {
+ f = roots[0].listFiles(filter);
+ } else {
+ f = roots[0].listFiles();
+ }
+ } else {
+ f = roots;
+ }
+
+ if (f == null) {
+ return 0;
+ }
+ return f.length;
+ }
+ }
+
+ /**
+ * A Item wrapper for files in a filesystem.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public class FileItem implements Item {
+
+ /**
+ * The wrapped file.
+ */
+ private final File file;
+
+ /**
+ * Constructs a FileItem from a existing file.
+ */
+ private FileItem(File file) {
+ this.file = file;
+ }
+
+ /*
+ * Gets the specified property of this file. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public Property getItemProperty(Object id) {
+ return getContainerProperty(file, id);
+ }
+
+ /*
+ * Gets the IDs of all properties available for this item Don't add a
+ * JavaDoc comment here, we use the default documentation from
+ * implemented interface.
+ */
+ @Override
+ public Collection<String> getItemPropertyIds() {
+ return getContainerPropertyIds();
+ }
+
+ /**
+ * Calculates a integer hash-code for the Property that's unique inside
+ * the Item containing the Property. Two different Properties inside the
+ * same Item contained in the same list always have different
+ * hash-codes, though Properties in different Items may have identical
+ * hash-codes.
+ *
+ * @return A locally unique hash-code as integer
+ */
+ @Override
+ public int hashCode() {
+ return file.hashCode() ^ FilesystemContainer.this.hashCode();
+ }
+
+ /**
+ * Tests if the given object is the same as the this object. Two
+ * Properties got from an Item with the same ID are equal.
+ *
+ * @param obj
+ * an object to compare with this object.
+ * @return <code>true</code> if the given object is the same as this
+ * object, <code>false</code> if not
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(obj instanceof FileItem)) {
+ return false;
+ }
+ final FileItem fi = (FileItem) obj;
+ return fi.getHost() == getHost() && fi.file.equals(file);
+ }
+
+ /**
+ * Gets the host of this file.
+ */
+ private FilesystemContainer getHost() {
+ return FilesystemContainer.this;
+ }
+
+ /**
+ * Gets the last modified date of this file.
+ *
+ * @return Date
+ */
+ public Date lastModified() {
+ return new Date(file.lastModified());
+ }
+
+ /**
+ * Gets the name of this file.
+ *
+ * @return file name of this file.
+ */
+ public String getName() {
+ return file.getName();
+ }
+
+ /**
+ * Gets the icon of this file.
+ *
+ * @return the icon of this file.
+ */
+ public Resource getIcon() {
+ return FileTypeResolver.getIcon(file);
+ }
+
+ /**
+ * Gets the size of this file.
+ *
+ * @return size
+ */
+ public long getSize() {
+ if (file.isDirectory()) {
+ return 0;
+ }
+ return file.length();
+ }
+
+ /**
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ if ("".equals(file.getName())) {
+ return file.getAbsolutePath();
+ }
+ return file.getName();
+ }
+
+ /**
+ * Filesystem container does not support adding new properties.
+ *
+ * @see com.vaadin.data.Item#addItemProperty(Object, Property)
+ */
+ @Override
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Filesystem container "
+ + "does not support adding new properties");
+ }
+
+ /**
+ * Filesystem container does not support removing properties.
+ *
+ * @see com.vaadin.data.Item#removeItemProperty(Object)
+ */
+ @Override
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Filesystem container does not support property removal");
+ }
+
+ }
+
+ /**
+ * Generic file extension filter for displaying only files having certain
+ * extension.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public class FileExtensionFilter implements FilenameFilter, Serializable {
+
+ private final String filter;
+
+ /**
+ * Constructs a new FileExtensionFilter using given extension.
+ *
+ * @param fileExtension
+ * the File extension without the separator (dot).
+ */
+ public FileExtensionFilter(String fileExtension) {
+ filter = "." + fileExtension;
+ }
+
+ /**
+ * Allows only files with the extension and directories.
+ *
+ * @see java.io.FilenameFilter#accept(File, String)
+ */
+ @Override
+ public boolean accept(File dir, String name) {
+ if (name.endsWith(filter)) {
+ return true;
+ }
+ return new File(dir, name).isDirectory();
+ }
+
+ }
+
+ /**
+ * Returns the file filter used to limit the files in this container.
+ *
+ * @return Used filter instance or null if no filter is assigned.
+ */
+ public FilenameFilter getFilter() {
+ return filter;
+ }
+
+ /**
+ * Sets the file filter used to limit the files in this container.
+ *
+ * @param filter
+ * The filter to set. <code>null</code> disables filtering.
+ */
+ public void setFilter(FilenameFilter filter) {
+ this.filter = filter;
+ }
+
+ /**
+ * Sets the file filter used to limit the files in this container.
+ *
+ * @param extension
+ * the Filename extension (w/o separator) to limit the files in
+ * container.
+ */
+ public void setFilter(String extension) {
+ filter = new FileExtensionFilter(extension);
+ }
+
+ /**
+ * Is this container recursive filesystem.
+ *
+ * @return <code>true</code> if container is recursive, <code>false</code>
+ * otherwise.
+ */
+ public boolean isRecursive() {
+ return recursive;
+ }
+
+ /**
+ * Sets the container recursive property. Set this to false to limit the
+ * files directly under the root file.
+ * <p>
+ * Note : This is meaningful only if the root really is a directory.
+ * </p>
+ *
+ * @param recursive
+ * the New value for recursive property.
+ */
+ public void setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem()
+ */
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object )
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/GeneratedPropertyContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/GeneratedPropertyContainer.java
new file mode 100644
index 0000000000..8d7a1512f9
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/GeneratedPropertyContainer.java
@@ -0,0 +1,776 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.sort.SortOrder;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+import com.vaadin.shared.data.sort.SortDirection;
+
+/**
+ * Container wrapper that adds support for generated properties. This container
+ * only supports adding new generated properties. Adding new normal properties
+ * should be done for the wrapped container.
+ *
+ * <p>
+ * Removing properties from this container does not remove anything from the
+ * wrapped container but instead only hides them from the results. These
+ * properties can be returned to this container by calling
+ * {@link #addContainerProperty(Object, Class, Object)} with same property id
+ * which was removed.
+ *
+ * <p>
+ * If wrapped container is Filterable and/or Sortable it should only be handled
+ * through this container as generated properties need to be handled in a
+ * specific way when sorting/filtering.
+ *
+ * <p>
+ * Items returned by this container do not support adding or removing
+ * properties. Generated properties are always read-only. Trying to make them
+ * editable throws an exception.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class GeneratedPropertyContainer extends AbstractContainer
+ implements Container.Indexed, Container.Sortable, Container.Filterable,
+ Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier {
+
+ private final Container.Indexed wrappedContainer;
+ private final Map<Object, PropertyValueGenerator<?>> propertyGenerators;
+ private final Map<Filter, List<Filter>> activeFilters;
+ private Sortable sortableContainer = null;
+ private Filterable filterableContainer = null;
+
+ /* Removed properties which are hidden but not actually removed */
+ private final Set<Object> removedProperties = new HashSet<Object>();
+
+ /**
+ * Property implementation for generated properties
+ */
+ protected static class GeneratedProperty<T> implements Property<T> {
+
+ private Item item;
+ private Object itemId;
+ private Object propertyId;
+ private PropertyValueGenerator<T> generator;
+
+ public GeneratedProperty(Item item, Object propertyId, Object itemId,
+ PropertyValueGenerator<T> generator) {
+ this.item = item;
+ this.itemId = itemId;
+ this.propertyId = propertyId;
+ this.generator = generator;
+ }
+
+ @Override
+ public T getValue() {
+ return generator.getValue(item, itemId, propertyId);
+ }
+
+ @Override
+ public void setValue(T newValue) throws ReadOnlyException {
+ throw new ReadOnlyException("Generated properties are read only");
+ }
+
+ @Override
+ public Class<? extends T> getType() {
+ return generator.getType();
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ if (newStatus) {
+ // No-op
+ return;
+ }
+ throw new UnsupportedOperationException(
+ "Generated properties are read only");
+ }
+ }
+
+ /**
+ * Item implementation for generated properties, used to wrap the Item that
+ * belongs to the wrapped container. To reach that Item use
+ * {@link #getWrappedItem()}
+ */
+ public class GeneratedPropertyItem implements Item {
+
+ private Item wrappedItem;
+ private Object itemId;
+
+ protected GeneratedPropertyItem(Object itemId, Item item) {
+ this.itemId = itemId;
+ wrappedItem = item;
+ }
+
+ @Override
+ public Property getItemProperty(Object id) {
+ if (propertyGenerators.containsKey(id)) {
+ return createProperty(wrappedItem, id, itemId,
+ propertyGenerators.get(id));
+ }
+ return wrappedItem.getItemProperty(id);
+ }
+
+ @Override
+ public Collection<?> getItemPropertyIds() {
+ Set<Object> wrappedProperties = new LinkedHashSet<Object>(
+ wrappedItem.getItemPropertyIds());
+ wrappedProperties.removeAll(removedProperties);
+ wrappedProperties.addAll(propertyGenerators.keySet());
+ return wrappedProperties;
+ }
+
+ @Override
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "GeneratedPropertyItem does not support adding properties");
+ }
+
+ @Override
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "GeneratedPropertyItem does not support removing properties");
+ }
+
+ /**
+ * Tests if the given object is the same as the this object. Two Items
+ * from the same container with the same ID are equal.
+ *
+ * @param obj
+ * an object to compare with this object
+ * @return <code>true</code> if the given object is the same as this
+ * object, <code>false</code> if not
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (obj == null
+ || !obj.getClass().equals(GeneratedPropertyItem.class)) {
+ return false;
+ }
+ final GeneratedPropertyItem li = (GeneratedPropertyItem) obj;
+ return getContainer() == li.getContainer()
+ && itemId.equals(li.itemId);
+ }
+
+ @Override
+ public int hashCode() {
+ return itemId.hashCode();
+ }
+
+ private GeneratedPropertyContainer getContainer() {
+ return GeneratedPropertyContainer.this;
+ }
+
+ /**
+ * Returns the wrapped Item that belongs to the wrapped container
+ *
+ * @return wrapped item.
+ * @since 7.6.8
+ */
+ public Item getWrappedItem() {
+ return wrappedItem;
+ }
+ };
+
+ /**
+ * Base implementation for item add or remove events. This is used when an
+ * event is fired from wrapped container and needs to be reconstructed to
+ * act like it actually came from this container.
+ */
+ protected abstract class GeneratedItemAddOrRemoveEvent
+ implements Serializable {
+
+ private Object firstItemId;
+ private int firstIndex;
+ private int count;
+
+ protected GeneratedItemAddOrRemoveEvent(Object itemId, int first,
+ int count) {
+ firstItemId = itemId;
+ firstIndex = first;
+ this.count = count;
+ }
+
+ public Container getContainer() {
+ return GeneratedPropertyContainer.this;
+ }
+
+ public Object getFirstItemId() {
+ return firstItemId;
+ }
+
+ public int getFirstIndex() {
+ return firstIndex;
+ }
+
+ public int getAffectedItemsCount() {
+ return count;
+ }
+ };
+
+ protected class GeneratedItemRemoveEvent
+ extends GeneratedItemAddOrRemoveEvent implements ItemRemoveEvent {
+
+ protected GeneratedItemRemoveEvent(ItemRemoveEvent event) {
+ super(event.getFirstItemId(), event.getFirstIndex(),
+ event.getRemovedItemsCount());
+ }
+
+ @Override
+ public int getRemovedItemsCount() {
+ return super.getAffectedItemsCount();
+ }
+ }
+
+ protected class GeneratedItemAddEvent extends GeneratedItemAddOrRemoveEvent
+ implements ItemAddEvent {
+
+ protected GeneratedItemAddEvent(ItemAddEvent event) {
+ super(event.getFirstItemId(), event.getFirstIndex(),
+ event.getAddedItemsCount());
+ }
+
+ @Override
+ public int getAddedItemsCount() {
+ return super.getAffectedItemsCount();
+ }
+
+ }
+
+ /**
+ * Constructor for GeneratedPropertyContainer.
+ *
+ * @param container
+ * underlying indexed container
+ */
+ public GeneratedPropertyContainer(Container.Indexed container) {
+ wrappedContainer = container;
+ propertyGenerators = new HashMap<Object, PropertyValueGenerator<?>>();
+
+ if (wrappedContainer instanceof Sortable) {
+ sortableContainer = (Sortable) wrappedContainer;
+ }
+
+ if (wrappedContainer instanceof Filterable) {
+ activeFilters = new HashMap<Filter, List<Filter>>();
+ filterableContainer = (Filterable) wrappedContainer;
+ } else {
+ activeFilters = null;
+ }
+
+ // ItemSetChangeEvents
+ if (wrappedContainer instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) wrappedContainer)
+ .addItemSetChangeListener(new ItemSetChangeListener() {
+
+ @Override
+ public void containerItemSetChange(
+ ItemSetChangeEvent event) {
+ if (event instanceof ItemAddEvent) {
+ final ItemAddEvent addEvent = (ItemAddEvent) event;
+ fireItemSetChange(
+ new GeneratedItemAddEvent(addEvent));
+ } else if (event instanceof ItemRemoveEvent) {
+ final ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
+ fireItemSetChange(new GeneratedItemRemoveEvent(
+ removeEvent));
+ } else {
+ fireItemSetChange();
+ }
+ }
+ });
+ }
+
+ // PropertySetChangeEvents
+ if (wrappedContainer instanceof PropertySetChangeNotifier) {
+ ((PropertySetChangeNotifier) wrappedContainer)
+ .addPropertySetChangeListener(
+ new PropertySetChangeListener() {
+
+ @Override
+ public void containerPropertySetChange(
+ PropertySetChangeEvent event) {
+ fireContainerPropertySetChange();
+ }
+ });
+ }
+ }
+
+ /* Functions related to generated properties */
+
+ /**
+ * Add a new PropertyValueGenerator with given property id. This will
+ * override any existing properties with the same property id. Fires a
+ * PropertySetChangeEvent.
+ *
+ * @param propertyId
+ * property id
+ * @param generator
+ * a property value generator
+ */
+ public void addGeneratedProperty(Object propertyId,
+ PropertyValueGenerator<?> generator) {
+ propertyGenerators.put(propertyId, generator);
+ fireContainerPropertySetChange();
+ }
+
+ /**
+ * Removes any possible PropertyValueGenerator with given property id. Fires
+ * a PropertySetChangeEvent.
+ *
+ * @param propertyId
+ * property id
+ */
+ public void removeGeneratedProperty(Object propertyId) {
+ if (propertyGenerators.containsKey(propertyId)) {
+ propertyGenerators.remove(propertyId);
+ fireContainerPropertySetChange();
+ }
+ }
+
+ private Item createGeneratedPropertyItem(final Object itemId,
+ final Item item) {
+ return new GeneratedPropertyItem(itemId, item);
+ }
+
+ private <T> Property<T> createProperty(final Item item,
+ final Object propertyId, final Object itemId,
+ final PropertyValueGenerator<T> generator) {
+ return new GeneratedProperty<T>(item, propertyId, itemId, generator);
+ }
+
+ /* Listener functionality */
+
+ @Override
+ public void addItemSetChangeListener(ItemSetChangeListener listener) {
+ super.addItemSetChangeListener(listener);
+ }
+
+ @Override
+ public void addListener(ItemSetChangeListener listener) {
+ super.addListener(listener);
+ }
+
+ @Override
+ public void removeItemSetChangeListener(ItemSetChangeListener listener) {
+ super.removeItemSetChangeListener(listener);
+ }
+
+ @Override
+ public void removeListener(ItemSetChangeListener listener) {
+ super.removeListener(listener);
+ }
+
+ @Override
+ public void addPropertySetChangeListener(
+ PropertySetChangeListener listener) {
+ super.addPropertySetChangeListener(listener);
+ }
+
+ @Override
+ public void addListener(PropertySetChangeListener listener) {
+ super.addListener(listener);
+ }
+
+ @Override
+ public void removePropertySetChangeListener(
+ PropertySetChangeListener listener) {
+ super.removePropertySetChangeListener(listener);
+ }
+
+ @Override
+ public void removeListener(PropertySetChangeListener listener) {
+ super.removeListener(listener);
+ }
+
+ /* Filtering functionality */
+
+ @Override
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException {
+ if (filterableContainer == null) {
+ throw new UnsupportedOperationException(
+ "Wrapped container is not filterable");
+ }
+
+ List<Filter> addedFilters = new ArrayList<Filter>();
+ for (Entry<?, PropertyValueGenerator<?>> entry : propertyGenerators
+ .entrySet()) {
+ Object property = entry.getKey();
+ if (filter.appliesToProperty(property)) {
+ // Have generated property modify filter to fit the original
+ // data in the container.
+ Filter modifiedFilter = entry.getValue().modifyFilter(filter);
+ filterableContainer.addContainerFilter(modifiedFilter);
+ // Keep track of added filters
+ addedFilters.add(modifiedFilter);
+ }
+ }
+
+ if (addedFilters.isEmpty()) {
+ // No generated property modified this filter, use it as is
+ addedFilters.add(filter);
+ filterableContainer.addContainerFilter(filter);
+ }
+ // Map filter to actually added filters
+ activeFilters.put(filter, addedFilters);
+ }
+
+ @Override
+ public void removeContainerFilter(Filter filter) {
+ if (filterableContainer == null) {
+ throw new UnsupportedOperationException(
+ "Wrapped container is not filterable");
+ }
+
+ if (activeFilters.containsKey(filter)) {
+ for (Filter f : activeFilters.get(filter)) {
+ filterableContainer.removeContainerFilter(f);
+ }
+ activeFilters.remove(filter);
+ }
+ }
+
+ @Override
+ public void removeAllContainerFilters() {
+ if (filterableContainer == null) {
+ throw new UnsupportedOperationException(
+ "Wrapped container is not filterable");
+ }
+ filterableContainer.removeAllContainerFilters();
+ activeFilters.clear();
+ }
+
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ if (filterableContainer == null) {
+ throw new UnsupportedOperationException(
+ "Wrapped container is not filterable");
+ }
+ return Collections.unmodifiableSet(activeFilters.keySet());
+ }
+
+ /* Sorting functionality */
+
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ if (sortableContainer == null) {
+ throw new UnsupportedOperationException(
+ "Wrapped container is not Sortable");
+ }
+
+ if (propertyId.length == 0) {
+ sortableContainer.sort(propertyId, ascending);
+ return;
+ }
+
+ List<Object> actualSortProperties = new ArrayList<Object>();
+ List<Boolean> actualSortDirections = new ArrayList<Boolean>();
+
+ for (int i = 0; i < propertyId.length; ++i) {
+ Object property = propertyId[i];
+ SortDirection direction;
+ boolean isAscending = i < ascending.length ? ascending[i] : true;
+ if (isAscending) {
+ direction = SortDirection.ASCENDING;
+ } else {
+ direction = SortDirection.DESCENDING;
+ }
+
+ if (propertyGenerators.containsKey(property)) {
+ // Sorting by a generated property. Generated property should
+ // modify sort orders to work with original properties in the
+ // container.
+ for (SortOrder s : propertyGenerators.get(property)
+ .getSortProperties(
+ new SortOrder(property, direction))) {
+ actualSortProperties.add(s.getPropertyId());
+ actualSortDirections
+ .add(s.getDirection() == SortDirection.ASCENDING);
+ }
+ } else {
+ actualSortProperties.add(property);
+ actualSortDirections.add(isAscending);
+ }
+ }
+
+ boolean[] actualAscending = new boolean[actualSortDirections.size()];
+ for (int i = 0; i < actualAscending.length; ++i) {
+ actualAscending[i] = actualSortDirections.get(i);
+ }
+
+ sortableContainer.sort(actualSortProperties.toArray(), actualAscending);
+ }
+
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ if (sortableContainer == null) {
+ return Collections.emptySet();
+ }
+
+ Set<Object> sortablePropertySet = new HashSet<Object>(
+ sortableContainer.getSortableContainerPropertyIds());
+ for (Entry<?, PropertyValueGenerator<?>> entry : propertyGenerators
+ .entrySet()) {
+ Object property = entry.getKey();
+ SortOrder order = new SortOrder(property, SortDirection.ASCENDING);
+ if (entry.getValue().getSortProperties(order).length > 0) {
+ sortablePropertySet.add(property);
+ } else {
+ sortablePropertySet.remove(property);
+ }
+ }
+
+ return sortablePropertySet;
+ }
+
+ /* Item related overrides */
+
+ @Override
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ Item item = wrappedContainer.addItemAfter(previousItemId, newItemId);
+ if (item == null) {
+ return null;
+ }
+ return createGeneratedPropertyItem(newItemId, item);
+ }
+
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ Item item = wrappedContainer.addItem(itemId);
+ if (item == null) {
+ return null;
+ }
+ return createGeneratedPropertyItem(itemId, item);
+ }
+
+ @Override
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ Item item = wrappedContainer.addItemAt(index, newItemId);
+ if (item == null) {
+ return null;
+ }
+ return createGeneratedPropertyItem(newItemId, item);
+ }
+
+ @Override
+ public Item getItem(Object itemId) {
+ Item item = wrappedContainer.getItem(itemId);
+ if (item == null) {
+ return null;
+ }
+
+ return createGeneratedPropertyItem(itemId, item);
+ }
+
+ /* Property related overrides */
+
+ @Override
+ public Property<?> getContainerProperty(Object itemId, Object propertyId) {
+ if (propertyGenerators.keySet().contains(propertyId)) {
+ return getItem(itemId).getItemProperty(propertyId);
+ } else if (!removedProperties.contains(propertyId)) {
+ return wrappedContainer.getContainerProperty(itemId, propertyId);
+ }
+ return null;
+ }
+
+ /**
+ * Returns a list of propety ids available in this container. This
+ * collection will contain properties for generated properties. Removed
+ * properties will not show unless there is a generated property overriding
+ * those.
+ */
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ Set<Object> wrappedProperties = new LinkedHashSet<Object>(
+ wrappedContainer.getContainerPropertyIds());
+ wrappedProperties.removeAll(removedProperties);
+ wrappedProperties.addAll(propertyGenerators.keySet());
+ return wrappedProperties;
+ }
+
+ /**
+ * Adds a previously removed property back to GeneratedPropertyContainer.
+ * Adding a property that is not previously removed causes an
+ * UnsupportedOperationException.
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ if (!removedProperties.contains(propertyId)) {
+ throw new UnsupportedOperationException(
+ "GeneratedPropertyContainer does not support adding properties.");
+ }
+ removedProperties.remove(propertyId);
+ fireContainerPropertySetChange();
+ return true;
+ }
+
+ /**
+ * Marks the given property as hidden. This property from wrapped container
+ * will be removed from {@link #getContainerPropertyIds()} and is no longer
+ * be available in Items retrieved from this container.
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ if (wrappedContainer.getContainerPropertyIds().contains(propertyId)
+ && removedProperties.add(propertyId)) {
+ fireContainerPropertySetChange();
+ return true;
+ }
+ return false;
+ }
+
+ /* Type related overrides */
+
+ @Override
+ public Class<?> getType(Object propertyId) {
+ if (propertyGenerators.containsKey(propertyId)) {
+ return propertyGenerators.get(propertyId).getType();
+ } else {
+ return wrappedContainer.getType(propertyId);
+ }
+ }
+
+ /* Unmodified functions */
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ return wrappedContainer.nextItemId(itemId);
+ }
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ return wrappedContainer.prevItemId(itemId);
+ }
+
+ @Override
+ public Object firstItemId() {
+ return wrappedContainer.firstItemId();
+ }
+
+ @Override
+ public Object lastItemId() {
+ return wrappedContainer.lastItemId();
+ }
+
+ @Override
+ public boolean isFirstId(Object itemId) {
+ return wrappedContainer.isFirstId(itemId);
+ }
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ return wrappedContainer.isLastId(itemId);
+ }
+
+ @Override
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ return wrappedContainer.addItemAfter(previousItemId);
+ }
+
+ @Override
+ public Collection<?> getItemIds() {
+ return wrappedContainer.getItemIds();
+ }
+
+ @Override
+ public int size() {
+ return wrappedContainer.size();
+ }
+
+ @Override
+ public boolean containsId(Object itemId) {
+ return wrappedContainer.containsId(itemId);
+ }
+
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+ return wrappedContainer.addItem();
+ }
+
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ return wrappedContainer.removeItem(itemId);
+ }
+
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ return wrappedContainer.removeAllItems();
+ }
+
+ @Override
+ public int indexOfId(Object itemId) {
+ return wrappedContainer.indexOfId(itemId);
+ }
+
+ @Override
+ public Object getIdByIndex(int index) {
+ return wrappedContainer.getIdByIndex(index);
+ }
+
+ @Override
+ public List<?> getItemIds(int startIndex, int numberOfItems) {
+ return wrappedContainer.getItemIds(startIndex, numberOfItems);
+ }
+
+ @Override
+ public Object addItemAt(int index) throws UnsupportedOperationException {
+ return wrappedContainer.addItemAt(index);
+ }
+
+ /**
+ * Returns the original underlying container.
+ *
+ * @return the original underlying container
+ */
+ public Container.Indexed getWrappedContainer() {
+ return wrappedContainer;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/HierarchicalContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/HierarchicalContainer.java
new file mode 100644
index 0000000000..115fd91791
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/HierarchicalContainer.java
@@ -0,0 +1,860 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+
+/**
+ * A specialized Container whose contents can be accessed like it was a
+ * tree-like structure.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class HierarchicalContainer extends IndexedContainer
+ implements Container.Hierarchical {
+
+ /**
+ * Set of IDs of those contained Items that can't have children.
+ */
+ private final HashSet<Object> noChildrenAllowed = new HashSet<Object>();
+
+ /**
+ * Mapping from Item ID to parent Item ID.
+ */
+ private final HashMap<Object, Object> parent = new HashMap<Object, Object>();
+
+ /**
+ * Mapping from Item ID to parent Item ID for items included in the filtered
+ * container.
+ */
+ private HashMap<Object, Object> filteredParent = null;
+
+ /**
+ * Mapping from Item ID to a list of child IDs.
+ */
+ private final HashMap<Object, LinkedList<Object>> children = new HashMap<Object, LinkedList<Object>>();
+
+ /**
+ * Mapping from Item ID to a list of child IDs when filtered
+ */
+ private HashMap<Object, LinkedList<Object>> filteredChildren = null;
+
+ /**
+ * List that contains all root elements of the container.
+ */
+ private final LinkedList<Object> roots = new LinkedList<Object>();
+
+ /**
+ * List that contains all filtered root elements of the container.
+ */
+ private LinkedList<Object> filteredRoots = null;
+
+ /**
+ * Determines how filtering of the container is done.
+ */
+ private boolean includeParentsWhenFiltering = true;
+
+ /**
+ * Counts how many nested contents change disable calls are in progress.
+ *
+ * Pending events are only fired when the counter reaches zero again.
+ */
+ private int contentChangedEventsDisabledCount = 0;
+
+ private boolean contentsChangedEventPending;
+
+ /*
+ * Can the specified Item have any children? Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+ if (noChildrenAllowed.contains(itemId)) {
+ return false;
+ }
+ return containsId(itemId);
+ }
+
+ /*
+ * Gets the IDs of the children of the specified Item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> getChildren(Object itemId) {
+ LinkedList<Object> c;
+
+ if (filteredChildren != null) {
+ c = filteredChildren.get(itemId);
+ } else {
+ c = children.get(itemId);
+ }
+
+ if (c == null) {
+ return null;
+ }
+ return Collections.unmodifiableCollection(c);
+ }
+
+ /*
+ * Gets the ID of the parent of the specified Item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Object getParent(Object itemId) {
+ if (filteredParent != null) {
+ return filteredParent.get(itemId);
+ }
+ return parent.get(itemId);
+ }
+
+ /*
+ * Is the Item corresponding to the given ID a leaf node? Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean hasChildren(Object itemId) {
+ if (filteredChildren != null) {
+ return filteredChildren.containsKey(itemId);
+ } else {
+ return children.containsKey(itemId);
+ }
+ }
+
+ /*
+ * Is the Item corresponding to the given ID a root node? Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public boolean isRoot(Object itemId) {
+ // If the container is filtered the itemId must be among filteredRoots
+ // to be a root.
+ if (filteredRoots != null) {
+ if (!filteredRoots.contains(itemId)) {
+ return false;
+ }
+ } else {
+ // Container is not filtered
+ if (parent.containsKey(itemId)) {
+ return false;
+ }
+ }
+
+ return containsId(itemId);
+ }
+
+ /*
+ * Gets the IDs of the root elements in the container. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ @Override
+ public Collection<?> rootItemIds() {
+ if (filteredRoots != null) {
+ return Collections.unmodifiableCollection(filteredRoots);
+ } else {
+ return Collections.unmodifiableCollection(roots);
+ }
+ }
+
+ /**
+ * <p>
+ * Sets the given Item's capability to have children. If the Item identified
+ * with the itemId already has children and the areChildrenAllowed is false
+ * this method fails and <code>false</code> is returned; the children must
+ * be first explicitly removed with
+ * {@link #setParent(Object itemId, Object newParentId)} or
+ * {@link com.vaadin.data.Container#removeItem(Object itemId)}.
+ * </p>
+ *
+ * @param itemId
+ * the ID of the Item in the container whose child capability is
+ * to be set.
+ * @param childrenAllowed
+ * the boolean value specifying if the Item can have children or
+ * not.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ */
+ @Override
+ public boolean setChildrenAllowed(Object itemId, boolean childrenAllowed) {
+
+ // Checks that the item is in the container
+ if (!containsId(itemId)) {
+ return false;
+ }
+
+ // Updates status
+ if (childrenAllowed) {
+ noChildrenAllowed.remove(itemId);
+ } else {
+ noChildrenAllowed.add(itemId);
+ }
+
+ return true;
+ }
+
+ /**
+ * <p>
+ * Sets the parent of an Item. The new parent item must exist and be able to
+ * have children. (<code>canHaveChildren(newParentId) == true</code>). It is
+ * also possible to detach a node from the hierarchy (and thus make it root)
+ * by setting the parent <code>null</code>.
+ * </p>
+ *
+ * @param itemId
+ * the ID of the item to be set as the child of the Item
+ * identified with newParentId.
+ * @param newParentId
+ * the ID of the Item that's to be the new parent of the Item
+ * identified with itemId.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ */
+ @Override
+ public boolean setParent(Object itemId, Object newParentId) {
+
+ // Checks that the item is in the container
+ if (!containsId(itemId)) {
+ return false;
+ }
+
+ // Gets the old parent
+ final Object oldParentId = parent.get(itemId);
+
+ // Checks if no change is necessary
+ if ((newParentId == null && oldParentId == null)
+ || ((newParentId != null) && newParentId.equals(oldParentId))) {
+ return true;
+ }
+
+ // Making root?
+ if (newParentId == null) {
+ // The itemId should become a root so we need to
+ // - Remove it from the old parent's children list
+ // - Add it as a root
+ // - Remove it from the item -> parent list (parent is null for
+ // roots)
+
+ // Removes from old parents children list
+ final LinkedList<Object> l = children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(oldParentId);
+ }
+
+ }
+
+ // Add to be a root
+ roots.add(itemId);
+
+ // Updates parent
+ parent.remove(itemId);
+
+ if (hasFilters()) {
+ // Refilter the container if setParent is called when filters
+ // are applied. Changing parent can change what is included in
+ // the filtered version (if includeParentsWhenFiltering==true).
+ doFilterContainer(hasFilters());
+ }
+
+ fireItemSetChange();
+
+ return true;
+ }
+
+ // We get here when the item should not become a root and we need to
+ // - Verify the new parent exists and can have children
+ // - Check that the new parent is not a child of the selected itemId
+ // - Updated the item -> parent mapping to point to the new parent
+ // - Remove the item from the roots list if it was a root
+ // - Remove the item from the old parent's children list if it was not a
+ // root
+
+ // Checks that the new parent exists in container and can have
+ // children
+ if (!containsId(newParentId)
+ || noChildrenAllowed.contains(newParentId)) {
+ return false;
+ }
+
+ // Checks that setting parent doesn't result to a loop
+ Object o = newParentId;
+ while (o != null && !o.equals(itemId)) {
+ o = parent.get(o);
+ }
+ if (o != null) {
+ return false;
+ }
+
+ // Updates parent
+ parent.put(itemId, newParentId);
+ LinkedList<Object> pcl = children.get(newParentId);
+ if (pcl == null) {
+ // Create an empty list for holding children if one were not
+ // previously created
+ pcl = new LinkedList<Object>();
+ children.put(newParentId, pcl);
+ }
+ pcl.add(itemId);
+
+ // Removes from old parent or root
+ if (oldParentId == null) {
+ roots.remove(itemId);
+ } else {
+ final LinkedList<Object> l = children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(oldParentId);
+ }
+ }
+ }
+
+ if (hasFilters()) {
+ // Refilter the container if setParent is called when filters
+ // are applied. Changing parent can change what is included in
+ // the filtered version (if includeParentsWhenFiltering==true).
+ doFilterContainer(hasFilters());
+ }
+
+ fireItemSetChange();
+
+ return true;
+ }
+
+ private boolean hasFilters() {
+ return (filteredRoots != null);
+ }
+
+ /**
+ * Moves a node (an Item) in the container immediately after a sibling node.
+ * The two nodes must have the same parent in the container.
+ *
+ * @param itemId
+ * the identifier of the moved node (Item)
+ * @param siblingId
+ * the identifier of the reference node (Item), after which the
+ * other node will be located
+ */
+ public void moveAfterSibling(Object itemId, Object siblingId) {
+ Object parent2 = getParent(itemId);
+ LinkedList<Object> childrenList;
+ if (parent2 == null) {
+ childrenList = roots;
+ } else {
+ childrenList = children.get(parent2);
+ }
+ if (siblingId == null) {
+ childrenList.remove(itemId);
+ childrenList.addFirst(itemId);
+
+ } else {
+ int oldIndex = childrenList.indexOf(itemId);
+ int indexOfSibling = childrenList.indexOf(siblingId);
+ if (indexOfSibling != -1 && oldIndex != -1) {
+ int newIndex;
+ if (oldIndex > indexOfSibling) {
+ newIndex = indexOfSibling + 1;
+ } else {
+ newIndex = indexOfSibling;
+ }
+ childrenList.remove(oldIndex);
+ childrenList.add(newIndex, itemId);
+ } else {
+ throw new IllegalArgumentException(
+ "Given identifiers no not have the same parent.");
+ }
+ }
+ fireItemSetChange();
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#addItem()
+ */
+ @Override
+ public Object addItem() {
+ disableContentsChangeEvents();
+ try {
+ final Object itemId = super.addItem();
+ if (itemId == null) {
+ return null;
+ }
+
+ if (!roots.contains(itemId)) {
+ roots.add(itemId);
+ if (filteredRoots != null) {
+ if (passesFilters(itemId)) {
+ filteredRoots.add(itemId);
+ }
+ }
+ }
+ return itemId;
+ } finally {
+ enableAndFireContentsChangeEvents();
+ }
+ }
+
+ @Override
+ protected void fireItemSetChange(
+ com.vaadin.data.Container.ItemSetChangeEvent event) {
+ if (contentsChangeEventsOn()) {
+ super.fireItemSetChange(event);
+ } else {
+ contentsChangedEventPending = true;
+ }
+ }
+
+ private boolean contentsChangeEventsOn() {
+ return contentChangedEventsDisabledCount == 0;
+ }
+
+ private void disableContentsChangeEvents() {
+ contentChangedEventsDisabledCount++;
+ }
+
+ private void enableAndFireContentsChangeEvents() {
+ if (contentChangedEventsDisabledCount <= 0) {
+ getLogger().log(Level.WARNING,
+ "Mismatched calls to disable and enable contents change events in HierarchicalContainer");
+ contentChangedEventsDisabledCount = 0;
+ } else {
+ contentChangedEventsDisabledCount--;
+ }
+ if (contentChangedEventsDisabledCount == 0) {
+ if (contentsChangedEventPending) {
+ fireItemSetChange();
+ }
+ contentsChangedEventPending = false;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) {
+ disableContentsChangeEvents();
+ try {
+ final Item item = super.addItem(itemId);
+ if (item == null) {
+ return null;
+ }
+
+ roots.add(itemId);
+
+ if (filteredRoots != null) {
+ if (passesFilters(itemId)) {
+ filteredRoots.add(itemId);
+ }
+ }
+ return item;
+ } finally {
+ enableAndFireContentsChangeEvents();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() {
+ disableContentsChangeEvents();
+ try {
+ final boolean success = super.removeAllItems();
+
+ if (success) {
+ roots.clear();
+ parent.clear();
+ children.clear();
+ noChildrenAllowed.clear();
+ if (filteredRoots != null) {
+ filteredRoots = null;
+ }
+ if (filteredChildren != null) {
+ filteredChildren = null;
+ }
+ if (filteredParent != null) {
+ filteredParent = null;
+ }
+ }
+ return success;
+ } finally {
+ enableAndFireContentsChangeEvents();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#removeItem(java.lang.Object )
+ */
+ @Override
+ public boolean removeItem(Object itemId) {
+ disableContentsChangeEvents();
+ try {
+ final boolean success = super.removeItem(itemId);
+
+ if (success) {
+ // Remove from roots if this was a root
+ if (roots.remove(itemId)) {
+
+ // If filtering is enabled we might need to remove it from
+ // the filtered list also
+ if (filteredRoots != null) {
+ filteredRoots.remove(itemId);
+ }
+ }
+
+ // Clear the children list. Old children will now become root
+ // nodes
+ LinkedList<Object> childNodeIds = children.remove(itemId);
+ if (childNodeIds != null) {
+ if (filteredChildren != null) {
+ filteredChildren.remove(itemId);
+ }
+ for (Object childId : childNodeIds) {
+ setParent(childId, null);
+ }
+ }
+
+ // Parent of the item that we are removing will contain the item
+ // id in its children list
+ final Object parentItemId = parent.get(itemId);
+ if (parentItemId != null) {
+ final LinkedList<Object> c = children.get(parentItemId);
+ if (c != null) {
+ c.remove(itemId);
+
+ if (c.isEmpty()) {
+ children.remove(parentItemId);
+ }
+
+ // Found in the children list so might also be in the
+ // filteredChildren list
+ if (filteredChildren != null) {
+ LinkedList<Object> f = filteredChildren
+ .get(parentItemId);
+ if (f != null) {
+ f.remove(itemId);
+ if (f.isEmpty()) {
+ filteredChildren.remove(parentItemId);
+ }
+ }
+ }
+ }
+ }
+ parent.remove(itemId);
+ if (filteredParent != null) {
+ // Item id no longer has a parent as the item id is not in
+ // the container.
+ filteredParent.remove(itemId);
+ }
+ noChildrenAllowed.remove(itemId);
+ }
+
+ return success;
+ } finally {
+ enableAndFireContentsChangeEvents();
+ }
+ }
+
+ /**
+ * Removes the Item identified by given itemId and all its children.
+ *
+ * @see #removeItem(Object)
+ * @param itemId
+ * the identifier of the Item to be removed
+ * @return true if the operation succeeded
+ */
+ public boolean removeItemRecursively(Object itemId) {
+ disableContentsChangeEvents();
+ try {
+ boolean removeItemRecursively = removeItemRecursively(this, itemId);
+ return removeItemRecursively;
+ } finally {
+ enableAndFireContentsChangeEvents();
+ }
+ }
+
+ /**
+ * Removes the Item identified by given itemId and all its children from the
+ * given Container.
+ *
+ * @param container
+ * the container where the item is to be removed
+ * @param itemId
+ * the identifier of the Item to be removed
+ * @return true if the operation succeeded
+ */
+ public static boolean removeItemRecursively(
+ Container.Hierarchical container, Object itemId) {
+ boolean success = true;
+ Collection<?> children2 = container.getChildren(itemId);
+ if (children2 != null) {
+ Object[] array = children2.toArray();
+ for (int i = 0; i < array.length; i++) {
+ boolean removeItemRecursively = removeItemRecursively(container,
+ array[i]);
+ if (!removeItemRecursively) {
+ success = false;
+ }
+ }
+ }
+ // remove the root of subtree if children where succesfully removed
+ if (success) {
+ success = container.removeItem(itemId);
+ }
+ return success;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#doSort()
+ */
+ @Override
+ protected void doSort() {
+ super.doSort();
+
+ Collections.sort(roots, getItemSorter());
+ for (LinkedList<Object> childList : children.values()) {
+ Collections.sort(childList, getItemSorter());
+ }
+ }
+
+ /**
+ * Used to control how filtering works. @see
+ * {@link #setIncludeParentsWhenFiltering(boolean)} for more information.
+ *
+ * @return true if all parents for items that match the filter are included
+ * when filtering, false if only the matching items are included
+ */
+ public boolean isIncludeParentsWhenFiltering() {
+ return includeParentsWhenFiltering;
+ }
+
+ /**
+ * Controls how the filtering of the container works. Set this to true to
+ * make filtering include parents for all matched items in addition to the
+ * items themselves. Setting this to false causes the filtering to only
+ * include the matching items and make items with excluded parents into root
+ * items.
+ *
+ * @param includeParentsWhenFiltering
+ * true to include all parents for items that match the filter,
+ * false to only include the matching items
+ */
+ public void setIncludeParentsWhenFiltering(
+ boolean includeParentsWhenFiltering) {
+ this.includeParentsWhenFiltering = includeParentsWhenFiltering;
+ if (filteredRoots != null) {
+ // Currently filtered so needs to be re-filtered
+ doFilterContainer(true);
+ }
+ }
+
+ /*
+ * Overridden to provide filtering for root & children items.
+ *
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#updateContainerFiltering()
+ */
+ @Override
+ protected boolean doFilterContainer(boolean hasFilters) {
+ if (!hasFilters) {
+ // All filters removed
+ filteredRoots = null;
+ filteredChildren = null;
+ filteredParent = null;
+
+ return super.doFilterContainer(hasFilters);
+ }
+
+ // Reset data structures
+ filteredRoots = new LinkedList<Object>();
+ filteredChildren = new HashMap<Object, LinkedList<Object>>();
+ filteredParent = new HashMap<Object, Object>();
+
+ if (includeParentsWhenFiltering) {
+ // Filter so that parents for items that match the filter are also
+ // included
+ HashSet<Object> includedItems = new HashSet<Object>();
+ for (Object rootId : roots) {
+ if (filterIncludingParents(rootId, includedItems)) {
+ filteredRoots.add(rootId);
+ addFilteredChildrenRecursively(rootId, includedItems);
+ }
+ }
+ // includedItemIds now contains all the item ids that should be
+ // included. Filter IndexedContainer based on this
+ filterOverride = includedItems;
+ super.doFilterContainer(hasFilters);
+ filterOverride = null;
+
+ return true;
+ } else {
+ // Filter by including all items that pass the filter and make items
+ // with no parent new root items
+
+ // Filter IndexedContainer first so getItemIds return the items that
+ // match
+ super.doFilterContainer(hasFilters);
+
+ LinkedHashSet<Object> filteredItemIds = new LinkedHashSet<Object>(
+ getItemIds());
+
+ for (Object itemId : filteredItemIds) {
+ Object itemParent = parent.get(itemId);
+ if (itemParent == null
+ || !filteredItemIds.contains(itemParent)) {
+ // Parent is not included or this was a root, in both cases
+ // this should be a filtered root
+ filteredRoots.add(itemId);
+ } else {
+ // Parent is included. Add this to the children list (create
+ // it first if necessary)
+ addFilteredChild(itemParent, itemId);
+ }
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * Adds the given childItemId as a filteredChildren for the parentItemId and
+ * sets it filteredParent.
+ *
+ * @param parentItemId
+ * @param childItemId
+ */
+ private void addFilteredChild(Object parentItemId, Object childItemId) {
+ LinkedList<Object> parentToChildrenList = filteredChildren
+ .get(parentItemId);
+ if (parentToChildrenList == null) {
+ parentToChildrenList = new LinkedList<Object>();
+ filteredChildren.put(parentItemId, parentToChildrenList);
+ }
+ filteredParent.put(childItemId, parentItemId);
+ parentToChildrenList.add(childItemId);
+
+ }
+
+ /**
+ * Recursively adds all items in the includedItems list to the
+ * filteredChildren map in the same order as they are in the children map.
+ * Starts from parentItemId and recurses down as long as child items that
+ * should be included are found.
+ *
+ * @param parentItemId
+ * The item id to start recurse from. Not added to a
+ * filteredChildren list
+ * @param includedItems
+ * Set containing the item ids for the items that should be
+ * included in the filteredChildren map
+ */
+ private void addFilteredChildrenRecursively(Object parentItemId,
+ HashSet<Object> includedItems) {
+ LinkedList<Object> childList = children.get(parentItemId);
+ if (childList == null) {
+ return;
+ }
+
+ for (Object childItemId : childList) {
+ if (includedItems.contains(childItemId)) {
+ addFilteredChild(parentItemId, childItemId);
+ addFilteredChildrenRecursively(childItemId, includedItems);
+ }
+ }
+ }
+
+ /**
+ * Scans the itemId and all its children for which items should be included
+ * when filtering. All items which passes the filters are included.
+ * Additionally all items that have a child node that should be included are
+ * also themselves included.
+ *
+ * @param itemId
+ * @param includedItems
+ * @return true if the itemId should be included in the filtered container.
+ */
+ private boolean filterIncludingParents(Object itemId,
+ HashSet<Object> includedItems) {
+ boolean toBeIncluded = passesFilters(itemId);
+
+ LinkedList<Object> childList = children.get(itemId);
+ if (childList != null) {
+ for (Object childItemId : children.get(itemId)) {
+ toBeIncluded |= filterIncludingParents(childItemId,
+ includedItems);
+ }
+ }
+
+ if (toBeIncluded) {
+ includedItems.add(itemId);
+ }
+ return toBeIncluded;
+ }
+
+ private Set<Object> filterOverride = null;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.IndexedContainer#passesFilters(java.lang.Object)
+ */
+ @Override
+ protected boolean passesFilters(Object itemId) {
+ if (filterOverride != null) {
+ return filterOverride.contains(itemId);
+ } else {
+ return super.passesFilters(itemId);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(HierarchicalContainer.class.getName());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/IndexedContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/IndexedContainer.java
new file mode 100644
index 0000000000..4ff253394a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/IndexedContainer.java
@@ -0,0 +1,1201 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ * An implementation of the <code>{@link Container.Indexed}</code> interface
+ * with all important features.
+ * </p>
+ *
+ * Features:
+ * <ul>
+ * <li>{@link Container.Indexed}
+ * <li>{@link Container.Ordered}
+ * <li>{@link Container.Sortable}
+ * <li>{@link Container.Filterable}
+ * <li>{@link Cloneable} (deprecated, might be removed in the future)
+ * <li>Sends all needed events on content changes.
+ * </ul>
+ *
+ * @see com.vaadin.data.Container
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+
+@SuppressWarnings("serial")
+// item type is really IndexedContainerItem, but using Item not to show it in
+// public API
+public class IndexedContainer
+ extends AbstractInMemoryContainer<Object, Object, Item>
+ implements Container.PropertySetChangeNotifier,
+ Property.ValueChangeNotifier, Container.Sortable, Cloneable,
+ Container.Filterable, Container.SimpleFilterable {
+
+ /* Internal structure */
+
+ /**
+ * Linked list of ordered Property IDs.
+ */
+ private ArrayList<Object> propertyIds = new ArrayList<Object>();
+
+ /**
+ * Property ID to type mapping.
+ */
+ private Hashtable<Object, Class<?>> types = new Hashtable<Object, Class<?>>();
+
+ /**
+ * Hash of Items, where each Item is implemented as a mapping from Property
+ * ID to Property value.
+ */
+ private Hashtable<Object, Map<Object, Object>> items = new Hashtable<Object, Map<Object, Object>>();
+
+ /**
+ * Set of properties that are read-only.
+ */
+ private HashSet<Property<?>> readOnlyProperties = new HashSet<Property<?>>();
+
+ /**
+ * List of all Property value change event listeners listening all the
+ * properties.
+ */
+ private LinkedList<Property.ValueChangeListener> propertyValueChangeListeners = null;
+
+ /**
+ * Data structure containing all listeners interested in changes to single
+ * Properties. The data structure is a hashtable mapping Property IDs to a
+ * hashtable that maps Item IDs to a linked list of listeners listening
+ * Property identified by given Property ID and Item ID.
+ */
+ private Hashtable<Object, Map<Object, List<Property.ValueChangeListener>>> singlePropertyValueChangeListeners = null;
+
+ private HashMap<Object, Object> defaultPropertyValues;
+
+ private int nextGeneratedItemId = 1;
+
+ /* Container constructors */
+
+ public IndexedContainer() {
+ super();
+ }
+
+ public IndexedContainer(Collection<?> itemIds) {
+ this();
+ if (items != null) {
+ for (final Iterator<?> i = itemIds.iterator(); i.hasNext();) {
+ Object itemId = i.next();
+ internalAddItemAtEnd(itemId, new IndexedContainerItem(itemId),
+ false);
+ }
+ filterAll();
+ }
+ }
+
+ /* Container methods */
+
+ @Override
+ protected Item getUnfilteredItem(Object itemId) {
+ if (itemId != null && items.containsKey(itemId)) {
+ return new IndexedContainerItem(itemId);
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /**
+ * Gets the type of a Property stored in the list.
+ *
+ * @param id
+ * the ID of the Property.
+ * @return Type of the requested Property
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return types.get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ // map lookup more efficient than propertyIds if there are many
+ // properties
+ if (!containsId(itemId) || propertyId == null
+ || !types.containsKey(propertyId)) {
+ return null;
+ }
+
+ return new IndexedContainerProperty(itemId, propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) {
+
+ // Fails, if nulls are given
+ if (propertyId == null || type == null) {
+ return false;
+ }
+
+ // Fails if the Property is already present
+ if (propertyIds.contains(propertyId)) {
+ return false;
+ }
+
+ // Adds the Property to Property list and types
+ propertyIds.add(propertyId);
+ types.put(propertyId, type);
+
+ // If default value is given, set it
+ if (defaultValue != null) {
+ // for existing rows
+ for (final Iterator<?> i = getAllItemIds().iterator(); i
+ .hasNext();) {
+ getItem(i.next()).getItemProperty(propertyId)
+ .setValue(defaultValue);
+ }
+ // store for next rows
+ if (defaultPropertyValues == null) {
+ defaultPropertyValues = new HashMap<Object, Object>();
+ }
+ defaultPropertyValues.put(propertyId, defaultValue);
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() {
+ int origSize = size();
+ Object firstItem = getFirstVisibleItem();
+
+ internalRemoveAllItems();
+
+ items.clear();
+
+ // fire event only if the visible view changed, regardless of whether
+ // filtered out items were removed or not
+ if (origSize != 0) {
+ // Sends a change event
+ fireItemsRemoved(0, firstItem, origSize);
+ }
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
+ */
+ @Override
+ public Object addItem() {
+
+ // Creates a new id
+ final Object id = generateId();
+
+ // Adds the Item into container
+ addItem(id);
+
+ return id;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) {
+ Item item = internalAddItemAtEnd(itemId,
+ new IndexedContainerItem(itemId), false);
+ if (item == null) {
+ return null;
+ } else if (!isFiltered()) {
+ // always the last item
+ fireItemAdded(size() - 1, itemId, item);
+ } else if (passesFilters(itemId) && !containsId(itemId)) {
+ getFilteredItemIds().add(itemId);
+ // always the last item
+ fireItemAdded(size() - 1, itemId, item);
+ }
+ return item;
+ }
+
+ /**
+ * Helper method to add default values for items if available
+ *
+ * @param t
+ * data table of added item
+ */
+ private void addDefaultValues(Hashtable<Object, Object> t) {
+ if (defaultPropertyValues != null) {
+ for (Object key : defaultPropertyValues.keySet()) {
+ t.put(key, defaultPropertyValues.get(key));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId) {
+ if (itemId == null || items.remove(itemId) == null) {
+ return false;
+ }
+ int origSize = size();
+ int position = indexOfId(itemId);
+ if (internalRemoveItem(itemId)) {
+ // fire event only if the visible view changed, regardless of
+ // whether filtered out items were removed or not
+ if (size() != origSize) {
+ fireItemRemoved(position, itemId);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object )
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId) {
+
+ // Fails if the Property is not present
+ if (!propertyIds.contains(propertyId)) {
+ return false;
+ }
+
+ // Removes the Property to Property list and types
+ propertyIds.remove(propertyId);
+ types.remove(propertyId);
+ if (defaultPropertyValues != null) {
+ defaultPropertyValues.remove(propertyId);
+ }
+
+ // If remove the Property from all Items
+ for (final Iterator<Object> i = getAllItemIds().iterator(); i
+ .hasNext();) {
+ items.get(i.next()).remove(propertyId);
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+ /* Container.Ordered methods */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override
+ public Item addItemAfter(Object previousItemId, Object newItemId) {
+ return internalAddItemAfter(previousItemId, newItemId,
+ new IndexedContainerItem(newItemId), true);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
+ */
+ @Override
+ public Object addItemAfter(Object previousItemId) {
+
+ // Creates a new id
+ final Object id = generateId();
+
+ if (addItemAfter(previousItemId, id) != null) {
+ return id;
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int, java.lang.Object)
+ */
+ @Override
+ public Item addItemAt(int index, Object newItemId) {
+ return internalAddItemAt(index, newItemId,
+ new IndexedContainerItem(newItemId), true);
+ }
+
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
+ */
+ @Override
+ public Object addItemAt(int index) {
+
+ // Creates a new id
+ final Object id = generateId();
+
+ // Adds the Item into container
+ addItemAt(index, id);
+
+ return id;
+ }
+
+ /**
+ * Generates an unique identifier for use as an item id. Guarantees that the
+ * generated id is not currently used as an id.
+ *
+ * @return
+ */
+ private Serializable generateId() {
+ Serializable id;
+ do {
+ id = Integer.valueOf(nextGeneratedItemId++);
+ } while (items.containsKey(id));
+
+ return id;
+ }
+
+ @Override
+ protected void registerNewItem(int index, Object newItemId, Item item) {
+ Hashtable<Object, Object> t = new Hashtable<Object, Object>();
+ items.put(newItemId, t);
+ addDefaultValues(t);
+ }
+
+ /* Event notifiers */
+
+ /**
+ * An <code>event</code> object specifying the list whose Item set has
+ * changed.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public static class ItemSetChangeEvent extends BaseItemSetChangeEvent {
+
+ private final int addedItemIndex;
+
+ private ItemSetChangeEvent(IndexedContainer source,
+ int addedItemIndex) {
+ super(source);
+ this.addedItemIndex = addedItemIndex;
+ }
+
+ /**
+ * Iff one item is added, gives its index.
+ *
+ * @return -1 if either multiple items are changed or some other change
+ * than add is done.
+ */
+ public int getAddedItemIndex() {
+ return addedItemIndex;
+ }
+
+ }
+
+ /**
+ * An <code>event</code> object specifying the Property in a list whose
+ * value has changed.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ private static class PropertyValueChangeEvent extends EventObject
+ implements Property.ValueChangeEvent, Serializable {
+
+ private PropertyValueChangeEvent(Property source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeEvent#getProperty()
+ */
+ @Override
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ @Override
+ public void addPropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ super.addPropertySetChangeListener(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addPropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Deprecated
+ @Override
+ public void addListener(Container.PropertySetChangeListener listener) {
+ addPropertySetChangeListener(listener);
+ }
+
+ @Override
+ public void removePropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ super.removePropertySetChangeListener(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Deprecated
+ @Override
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ removePropertySetChangeListener(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeNotifier#addListener(com.
+ * vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void addValueChangeListener(Property.ValueChangeListener listener) {
+ if (propertyValueChangeListeners == null) {
+ propertyValueChangeListeners = new LinkedList<Property.ValueChangeListener>();
+ }
+ propertyValueChangeListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Property.ValueChangeListener listener) {
+ addValueChangeListener(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeNotifier#removeListener(com
+ * .vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void removeValueChangeListener(
+ Property.ValueChangeListener listener) {
+ if (propertyValueChangeListeners != null) {
+ propertyValueChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeValueChangeListener(listener);
+ }
+
+ /**
+ * Sends a Property value change event to all interested listeners.
+ *
+ * @param source
+ * the IndexedContainerProperty object.
+ */
+ private void firePropertyValueChange(IndexedContainerProperty source) {
+
+ // Sends event to listeners listening all value changes
+ if (propertyValueChangeListeners != null) {
+ final Object[] l = propertyValueChangeListeners.toArray();
+ final Property.ValueChangeEvent event = new IndexedContainer.PropertyValueChangeEvent(
+ source);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ValueChangeListener) l[i]).valueChange(event);
+ }
+ }
+
+ // Sends event to single property value change listeners
+ if (singlePropertyValueChangeListeners != null) {
+ final Map<Object, List<Property.ValueChangeListener>> propertySetToListenerListMap = singlePropertyValueChangeListeners
+ .get(source.propertyId);
+ if (propertySetToListenerListMap != null) {
+ final List<Property.ValueChangeListener> listenerList = propertySetToListenerListMap
+ .get(source.itemId);
+ if (listenerList != null) {
+ final Property.ValueChangeEvent event = new IndexedContainer.PropertyValueChangeEvent(
+ source);
+ Object[] listeners = listenerList.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ ((Property.ValueChangeListener) listeners[i])
+ .valueChange(event);
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (Property.ValueChangeEvent.class.isAssignableFrom(eventType)) {
+ if (propertyValueChangeListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(propertyValueChangeListeners);
+ }
+ }
+ return super.getListeners(eventType);
+ }
+
+ @Override
+ protected void fireItemAdded(int position, Object itemId, Item item) {
+ if (position >= 0) {
+ super.fireItemAdded(position, itemId, item);
+ }
+ }
+
+ @Override
+ protected void fireItemSetChange() {
+ fireItemSetChange(new IndexedContainer.ItemSetChangeEvent(this, -1));
+ }
+
+ /**
+ * Adds new single Property change listener.
+ *
+ * @param propertyId
+ * the ID of the Property to add.
+ * @param itemId
+ * the ID of the Item .
+ * @param listener
+ * the listener to be added.
+ */
+ private void addSinglePropertyChangeListener(Object propertyId,
+ Object itemId, Property.ValueChangeListener listener) {
+ if (listener != null) {
+ if (singlePropertyValueChangeListeners == null) {
+ singlePropertyValueChangeListeners = new Hashtable<Object, Map<Object, List<Property.ValueChangeListener>>>();
+ }
+ Map<Object, List<Property.ValueChangeListener>> propertySetToListenerListMap = singlePropertyValueChangeListeners
+ .get(propertyId);
+ if (propertySetToListenerListMap == null) {
+ propertySetToListenerListMap = new Hashtable<Object, List<Property.ValueChangeListener>>();
+ singlePropertyValueChangeListeners.put(propertyId,
+ propertySetToListenerListMap);
+ }
+ List<Property.ValueChangeListener> listenerList = propertySetToListenerListMap
+ .get(itemId);
+ if (listenerList == null) {
+ listenerList = new LinkedList<Property.ValueChangeListener>();
+ propertySetToListenerListMap.put(itemId, listenerList);
+ }
+ listenerList.add(listener);
+ }
+ }
+
+ /**
+ * Removes a previously registered single Property change listener.
+ *
+ * @param propertyId
+ * the ID of the Property to remove.
+ * @param itemId
+ * the ID of the Item.
+ * @param listener
+ * the listener to be removed.
+ */
+ private void removeSinglePropertyChangeListener(Object propertyId,
+ Object itemId, Property.ValueChangeListener listener) {
+ if (listener != null && singlePropertyValueChangeListeners != null) {
+ final Map<Object, List<Property.ValueChangeListener>> propertySetToListenerListMap = singlePropertyValueChangeListeners
+ .get(propertyId);
+ if (propertySetToListenerListMap != null) {
+ final List<Property.ValueChangeListener> listenerList = propertySetToListenerListMap
+ .get(itemId);
+ if (listenerList != null) {
+ listenerList.remove(listener);
+ if (listenerList.isEmpty()) {
+ propertySetToListenerListMap.remove(itemId);
+ }
+ }
+ if (propertySetToListenerListMap.isEmpty()) {
+ singlePropertyValueChangeListeners.remove(propertyId);
+ }
+ }
+ if (singlePropertyValueChangeListeners.isEmpty()) {
+ singlePropertyValueChangeListeners = null;
+ }
+ }
+ }
+
+ /* Internal Item and Property implementations */
+
+ /*
+ * A class implementing the com.vaadin.data.Item interface to be contained
+ * in the list.
+ *
+ * @author Vaadin Ltd.
+ *
+ *
+ * @since 3.0
+ */
+ class IndexedContainerItem implements Item {
+
+ /**
+ * Item ID in the host container for this Item.
+ */
+ private final Object itemId;
+
+ /**
+ * Constructs a new ListItem instance and connects it to a host
+ * container.
+ *
+ * @param itemId
+ * the Item ID of the new Item.
+ */
+ private IndexedContainerItem(Object itemId) {
+ this.itemId = itemId;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Item#getItemProperty(java.lang.Object)
+ */
+ @Override
+ public Property getItemProperty(Object id) {
+ if (!propertyIds.contains(id)) {
+ return null;
+ }
+
+ return new IndexedContainerProperty(itemId, id);
+ }
+
+ @Override
+ public Collection<?> getItemPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /**
+ * Gets the <code>String</code> representation of the contents of the
+ * Item. The format of the string is a space separated catenation of the
+ * <code>String</code> representations of the values of the Properties
+ * contained by the Item.
+ *
+ * @return <code>String</code> representation of the Item contents
+ */
+ @Override
+ public String toString() {
+ String retValue = "";
+
+ for (final Iterator<?> i = propertyIds.iterator(); i.hasNext();) {
+ final Object propertyId = i.next();
+ retValue += getItemProperty(propertyId).getValue();
+ if (i.hasNext()) {
+ retValue += " ";
+ }
+ }
+
+ return retValue;
+ }
+
+ /**
+ * Calculates a integer hash-code for the Item that's unique inside the
+ * list. Two Items inside the same list have always different
+ * hash-codes, though Items in different lists may have identical
+ * hash-codes.
+ *
+ * @return A locally unique hash-code as integer
+ */
+ @Override
+ public int hashCode() {
+ return itemId.hashCode();
+ }
+
+ /**
+ * Tests if the given object is the same as the this object. Two Items
+ * got from a list container with the same ID are equal.
+ *
+ * @param obj
+ * an object to compare with this object
+ * @return <code>true</code> if the given object is the same as this
+ * object, <code>false</code> if not
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null
+ || !obj.getClass().equals(IndexedContainerItem.class)) {
+ return false;
+ }
+ final IndexedContainerItem li = (IndexedContainerItem) obj;
+ return getHost() == li.getHost() && itemId.equals(li.itemId);
+ }
+
+ private IndexedContainer getHost() {
+ return IndexedContainer.this;
+ }
+
+ /**
+ * IndexedContainerItem does not support adding new properties. Add
+ * properties at container level. See
+ * {@link IndexedContainer#addContainerProperty(Object, Class, Object)}
+ *
+ * @see com.vaadin.data.Item#addProperty(Object, Property)
+ */
+ @Override
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("Indexed container item "
+ + "does not support adding new properties");
+ }
+
+ /**
+ * Indexed container does not support removing properties. Remove
+ * properties at container level. See
+ * {@link IndexedContainer#removeContainerProperty(Object)}
+ *
+ * @see com.vaadin.data.Item#removeProperty(Object)
+ */
+ @Override
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Indexed container item does not support property removal");
+ }
+
+ }
+
+ /**
+ * A class implementing the {@link Property} interface to be contained in
+ * the {@link IndexedContainerItem} contained in the
+ * {@link IndexedContainer}.
+ *
+ * @author Vaadin Ltd.
+ *
+ * @since 3.0
+ */
+ private class IndexedContainerProperty<T>
+ implements Property<T>, Property.ValueChangeNotifier {
+
+ /**
+ * ID of the Item, where this property resides.
+ */
+ private final Object itemId;
+
+ /**
+ * Id of the Property.
+ */
+ private final Object propertyId;
+
+ /**
+ * Constructs a new {@link IndexedContainerProperty} object.
+ *
+ * @param itemId
+ * the ID of the Item to connect the new Property to.
+ * @param propertyId
+ * the Property ID of the new Property.
+ * @param host
+ * the list that contains the Item to contain the new
+ * Property.
+ */
+ private IndexedContainerProperty(Object itemId, Object propertyId) {
+ if (itemId == null || propertyId == null) {
+ // Null ids are not accepted
+ throw new NullPointerException(
+ "Container item or property ids can not be null");
+ }
+ this.propertyId = propertyId;
+ this.itemId = itemId;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#getType()
+ */
+ @Override
+ public Class<T> getType() {
+ return (Class<T>) types.get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#getValue()
+ */
+ @Override
+ public T getValue() {
+ return (T) items.get(itemId).get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return readOnlyProperties.contains(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#setReadOnly(boolean)
+ */
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ if (newStatus) {
+ readOnlyProperties.add(this);
+ } else {
+ readOnlyProperties.remove(this);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#setValue(java.lang.Object)
+ */
+ @Override
+ public void setValue(Object newValue)
+ throws Property.ReadOnlyException {
+ // Gets the Property set
+ final Map<Object, Object> propertySet = items.get(itemId);
+
+ // Support null values on all types
+ if (newValue == null) {
+ propertySet.remove(propertyId);
+ } else if (getType().isAssignableFrom(newValue.getClass())) {
+ propertySet.put(propertyId, newValue);
+ } else {
+ throw new IllegalArgumentException(
+ "Value is of invalid type, got "
+ + newValue.getClass().getName() + " but "
+ + getType().getName() + " was expected");
+ }
+
+ // update the container filtering if this property is being filtered
+ if (isPropertyFiltered(propertyId)) {
+ filterAll();
+ }
+
+ firePropertyValueChange(this);
+ }
+
+ /**
+ * Calculates a integer hash-code for the Property that's unique inside
+ * the Item containing the Property. Two different Properties inside the
+ * same Item contained in the same list always have different
+ * hash-codes, though Properties in different Items may have identical
+ * hash-codes.
+ *
+ * @return A locally unique hash-code as integer
+ */
+ @Override
+ public int hashCode() {
+ return itemId.hashCode() ^ propertyId.hashCode();
+ }
+
+ /**
+ * Tests if the given object is the same as the this object. Two
+ * Properties got from an Item with the same ID are equal.
+ *
+ * @param obj
+ * an object to compare with this object
+ * @return <code>true</code> if the given object is the same as this
+ * object, <code>false</code> if not
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null
+ || !obj.getClass().equals(IndexedContainerProperty.class)) {
+ return false;
+ }
+ final IndexedContainerProperty lp = (IndexedContainerProperty) obj;
+ return lp.getHost() == getHost() && lp.propertyId.equals(propertyId)
+ && lp.itemId.equals(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeNotifier#addListener(
+ * com.vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void addValueChangeListener(
+ Property.ValueChangeListener listener) {
+ addSinglePropertyChangeListener(propertyId, itemId, listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Property.ValueChangeListener listener) {
+ addValueChangeListener(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeNotifier#removeListener
+ * (com.vaadin.data.Property.ValueChangeListener)
+ */
+ @Override
+ public void removeValueChangeListener(
+ Property.ValueChangeListener listener) {
+ removeSinglePropertyChangeListener(propertyId, itemId, listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeValueChangeListener(com.vaadin.data.Property.ValueChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeValueChangeListener(listener);
+ }
+
+ private IndexedContainer getHost() {
+ return IndexedContainer.this;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ sortContainer(propertyId, ascending);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds
+ * ()
+ */
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ return getSortablePropertyIds();
+ }
+
+ @Override
+ public ItemSorter getItemSorter() {
+ return super.getItemSorter();
+ }
+
+ @Override
+ public void setItemSorter(ItemSorter itemSorter) {
+ super.setItemSorter(itemSorter);
+ }
+
+ /**
+ * Supports cloning of the IndexedContainer cleanly.
+ *
+ * @throws CloneNotSupportedException
+ * if an object cannot be cloned. .
+ *
+ * @deprecated As of 6.6. Cloning support might be removed from
+ * IndexedContainer in the future
+ */
+ @Deprecated
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+
+ // Creates the clone
+ final IndexedContainer nc = new IndexedContainer();
+
+ // Clone the shallow properties
+ nc.setAllItemIds(getAllItemIds() != null
+ ? (ListSet<Object>) ((ListSet<Object>) getAllItemIds()).clone()
+ : null);
+ nc.setItemSetChangeListeners(getItemSetChangeListeners() != null
+ ? new LinkedList<Container.ItemSetChangeListener>(
+ getItemSetChangeListeners())
+ : null);
+ nc.propertyIds = propertyIds != null
+ ? (ArrayList<Object>) propertyIds.clone() : null;
+ nc.setPropertySetChangeListeners(getPropertySetChangeListeners() != null
+ ? new LinkedList<Container.PropertySetChangeListener>(
+ getPropertySetChangeListeners())
+ : null);
+ nc.propertyValueChangeListeners = propertyValueChangeListeners != null
+ ? (LinkedList<Property.ValueChangeListener>) propertyValueChangeListeners
+ .clone()
+ : null;
+ nc.readOnlyProperties = readOnlyProperties != null
+ ? (HashSet<Property<?>>) readOnlyProperties.clone() : null;
+ nc.singlePropertyValueChangeListeners = singlePropertyValueChangeListeners != null
+ ? (Hashtable<Object, Map<Object, List<Property.ValueChangeListener>>>) singlePropertyValueChangeListeners
+ .clone()
+ : null;
+
+ nc.types = types != null ? (Hashtable<Object, Class<?>>) types.clone()
+ : null;
+
+ nc.setFilters(
+ (HashSet<Filter>) ((HashSet<Filter>) getFilters()).clone());
+
+ nc.setFilteredItemIds(getFilteredItemIds() == null ? null
+ : (ListSet<Object>) ((ListSet<Object>) getFilteredItemIds())
+ .clone());
+
+ // Clone property-values
+ if (items == null) {
+ nc.items = null;
+ } else {
+ nc.items = new Hashtable<Object, Map<Object, Object>>();
+ for (final Iterator<?> i = items.keySet().iterator(); i
+ .hasNext();) {
+ final Object id = i.next();
+ final Hashtable<Object, Object> it = (Hashtable<Object, Object>) items
+ .get(id);
+ nc.items.put(id, (Map<Object, Object>) it.clone());
+ }
+ }
+
+ return nc;
+ }
+
+ @Override
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ try {
+ addFilter(new SimpleStringFilter(propertyId, filterString,
+ ignoreCase, onlyMatchPrefix));
+ } catch (UnsupportedFilterException e) {
+ // the filter instance created here is always valid for in-memory
+ // containers
+ }
+ }
+
+ @Override
+ public void removeAllContainerFilters() {
+ removeAllFilters();
+ }
+
+ @Override
+ public void removeContainerFilters(Object propertyId) {
+ removeFilters(propertyId);
+ }
+
+ @Override
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException {
+ addFilter(filter);
+ }
+
+ @Override
+ public void removeContainerFilter(Filter filter) {
+ removeFilter(filter);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public boolean hasContainerFilters() {
+ return super.hasContainerFilters();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return super.getContainerFilters();
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheFlushNotifier.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheFlushNotifier.java
new file mode 100644
index 0000000000..d3559b5652
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheFlushNotifier.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.data.util.sqlcontainer.query.FreeformQuery;
+import com.vaadin.data.util.sqlcontainer.query.QueryDelegate;
+import com.vaadin.data.util.sqlcontainer.query.TableQuery;
+
+/**
+ * CacheFlushNotifier is a simple static notification mechanism to inform other
+ * SQLContainers that the contents of their caches may have become stale.
+ */
+class CacheFlushNotifier implements Serializable {
+ /*
+ * SQLContainer instance reference list and dead reference queue. Used for
+ * the cache flush notification feature.
+ */
+ private static List<WeakReference<SQLContainer>> allInstances = new ArrayList<WeakReference<SQLContainer>>();
+ private static ReferenceQueue<SQLContainer> deadInstances = new ReferenceQueue<SQLContainer>();
+
+ /**
+ * Adds the given SQLContainer to the cache flush notification receiver list
+ *
+ * @param c
+ * Container to add
+ */
+ public static void addInstance(SQLContainer c) {
+ removeDeadReferences();
+ if (c != null) {
+ allInstances.add(new WeakReference<SQLContainer>(c, deadInstances));
+ }
+ }
+
+ /**
+ * Removes dead references from instance list
+ */
+ private static void removeDeadReferences() {
+ java.lang.ref.Reference<? extends SQLContainer> dead = deadInstances
+ .poll();
+ while (dead != null) {
+ allInstances.remove(dead);
+ dead = deadInstances.poll();
+ }
+ }
+
+ /**
+ * Iterates through the instances and notifies containers which are
+ * connected to the same table or are using the same query string.
+ *
+ * @param c
+ * SQLContainer that issued the cache flush notification
+ */
+ public static void notifyOfCacheFlush(SQLContainer c) {
+ removeDeadReferences();
+ for (WeakReference<SQLContainer> wr : allInstances) {
+ if (wr.get() != null) {
+ SQLContainer wrc = wr.get();
+ if (wrc == null) {
+ continue;
+ }
+ /*
+ * If the reference points to the container sending the
+ * notification, do nothing.
+ */
+ if (wrc.equals(c)) {
+ continue;
+ }
+ /* Compare QueryDelegate types and tableName/queryString */
+ QueryDelegate wrQd = wrc.getQueryDelegate();
+ QueryDelegate qd = c.getQueryDelegate();
+ if (wrQd instanceof TableQuery && qd instanceof TableQuery
+ && ((TableQuery) wrQd).getTableName()
+ .equals(((TableQuery) qd).getTableName())) {
+ wrc.refresh();
+ } else if (wrQd instanceof FreeformQuery
+ && qd instanceof FreeformQuery
+ && ((FreeformQuery) wrQd).getQueryString().equals(
+ ((FreeformQuery) qd).getQueryString())) {
+ wrc.refresh();
+ }
+ }
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheMap.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheMap.java
new file mode 100644
index 0000000000..77919f72b2
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/CacheMap.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * CacheMap extends LinkedHashMap, adding the possibility to adjust maximum
+ * number of items. In SQLContainer this is used for RowItem -cache. Cache size
+ * will be two times the page length parameter of the container.
+ */
+class CacheMap<K, V> extends LinkedHashMap<K, V> {
+ private static final long serialVersionUID = 679999766473555231L;
+ private int cacheLimit = SQLContainer.CACHE_RATIO
+ * SQLContainer.DEFAULT_PAGE_LENGTH;
+
+ @Override
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
+ return size() > cacheLimit;
+ }
+
+ void setCacheLimit(int limit) {
+ cacheLimit = limit > 0 ? limit : SQLContainer.DEFAULT_PAGE_LENGTH;
+ }
+
+ int getCacheLimit() {
+ return cacheLimit;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ColumnProperty.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ColumnProperty.java
new file mode 100644
index 0000000000..e7d83e118d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ColumnProperty.java
@@ -0,0 +1,357 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.sql.Date;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Property;
+import com.vaadin.v7.data.util.converter.LegacyConverter.ConversionException;
+
+/**
+ * ColumnProperty represents the value of one column in a RowItem. In addition
+ * to the value, ColumnProperty also contains some basic column attributes such
+ * as nullability status, read-only status and data type.
+ *
+ * Note that depending on the QueryDelegate in use this does not necessarily map
+ * into an actual column in a database table.
+ */
+final public class ColumnProperty implements Property {
+ private static final long serialVersionUID = -3694463129581802457L;
+
+ private RowItem owner;
+
+ private String propertyId;
+
+ private boolean readOnly;
+ private boolean allowReadOnlyChange = true;
+ private boolean nullable = true;
+
+ private Object value;
+ private Object changedValue;
+ private Class<?> type;
+
+ private boolean modified;
+
+ private boolean versionColumn;
+ private boolean primaryKey = false;
+
+ /**
+ * Prevent instantiation without required parameters.
+ */
+ @SuppressWarnings("unused")
+ private ColumnProperty() {
+ }
+
+ /**
+ * Deprecated constructor for ColumnProperty. If this is used the primary
+ * keys are not identified correctly in some cases for some databases (i.e.
+ * Oracle). See http://dev.vaadin.com/ticket/9145.
+ *
+ * @param propertyId
+ * @param readOnly
+ * @param allowReadOnlyChange
+ * @param nullable
+ * @param value
+ * @param type
+ *
+ * @deprecated As of 7.0. Use
+ * {@link #ColumnProperty(String, boolean, boolean, boolean, boolean, Object, Class)
+ * instead
+ */
+ @Deprecated
+ public ColumnProperty(String propertyId, boolean readOnly,
+ boolean allowReadOnlyChange, boolean nullable, Object value,
+ Class<?> type) {
+ this(propertyId, readOnly, allowReadOnlyChange, nullable, false, value,
+ type);
+ }
+
+ /**
+ * Creates a new ColumnProperty instance.
+ *
+ * @param propertyId
+ * The ID of this property.
+ * @param readOnly
+ * Whether this property is read-only.
+ * @param allowReadOnlyChange
+ * Whether the read-only status of this property can be changed.
+ * @param nullable
+ * Whether this property accepts null values.
+ * @param primaryKey
+ * Whether this property corresponds to a database primary key.
+ * @param value
+ * The value of this property.
+ * @param type
+ * The type of this property.
+ */
+ public ColumnProperty(String propertyId, boolean readOnly,
+ boolean allowReadOnlyChange, boolean nullable, boolean primaryKey,
+ Object value, Class<?> type) {
+
+ if (propertyId == null) {
+ throw new IllegalArgumentException("Properties must be named.");
+ }
+ if (type == null) {
+ throw new IllegalArgumentException("Property type must be set.");
+ }
+ this.propertyId = propertyId;
+ this.type = type;
+ this.value = value;
+
+ this.allowReadOnlyChange = allowReadOnlyChange;
+ this.nullable = nullable;
+ this.readOnly = readOnly;
+ this.primaryKey = primaryKey;
+ }
+
+ /**
+ * Returns the current value for this property. To get the previous value
+ * (if one exists) for a modified property use {@link #getOldValue()}.
+ *
+ * @return
+ */
+ @Override
+ public Object getValue() {
+ if (isModified()) {
+ return changedValue;
+ }
+ return value;
+ }
+
+ /**
+ * Returns the original non-modified value of this property if it has been
+ * modified.
+ *
+ * @return The original value if <code>isModified()</code> is true,
+ * <code>getValue()</code> otherwise.
+ */
+ public Object getOldValue() {
+ return value;
+ }
+
+ @Override
+ public void setValue(Object newValue)
+ throws ReadOnlyException, ConversionException {
+ if (newValue == null && !nullable) {
+ throw new NotNullableException(
+ "Null values are not allowed for this property.");
+ }
+ if (readOnly) {
+ throw new ReadOnlyException(
+ "Cannot set value for read-only property.");
+ }
+
+ /* Check if this property is a date property. */
+ boolean isDateProperty = Time.class.equals(getType())
+ || Date.class.equals(getType())
+ || Timestamp.class.equals(getType());
+
+ if (newValue != null) {
+ /* Handle SQL dates, times and Timestamps given as java.util.Date */
+ if (isDateProperty) {
+ /*
+ * Try to get the millisecond value from the new value of this
+ * property. Possible type to convert from is java.util.Date.
+ */
+ long millis = 0;
+ if (newValue instanceof java.util.Date) {
+ millis = ((java.util.Date) newValue).getTime();
+ /*
+ * Create the new object based on the millisecond value,
+ * according to the type of this property.
+ */
+ if (Time.class.equals(getType())) {
+ newValue = new Time(millis);
+ } else if (Date.class.equals(getType())) {
+ newValue = new Date(millis);
+ } else if (Timestamp.class.equals(getType())) {
+ newValue = new Timestamp(millis);
+ }
+ }
+ }
+
+ if (!getType().isAssignableFrom(newValue.getClass())) {
+ throw new IllegalArgumentException(
+ "Illegal value type for ColumnProperty");
+ }
+
+ /*
+ * If the value to be set is the same that has already been set, do
+ * not set it again.
+ */
+ if (isValueAlreadySet(newValue)) {
+ return;
+ }
+ }
+
+ /* Set the new value and notify container of the change. */
+ changedValue = newValue;
+ modified = true;
+ owner.getContainer().itemChangeNotification(owner);
+ }
+
+ private boolean isValueAlreadySet(Object newValue) {
+ Object referenceValue = isModified() ? changedValue : value;
+
+ return (isNullable() && newValue == null && referenceValue == null)
+ || newValue.equals(referenceValue);
+ }
+
+ @Override
+ public Class<?> getType() {
+ return type;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Returns whether the read-only status of this property can be changed
+ * using {@link #setReadOnly(boolean)}.
+ * <p>
+ * Used to prevent setting to read/write mode a property that is not allowed
+ * to be written by the underlying database. Also used for values like
+ * VERSION and AUTO_INCREMENT fields that might be set to read-only by the
+ * container but the database still allows writes.
+ *
+ * @return true if the read-only status can be changed, false otherwise.
+ */
+ public boolean isReadOnlyChangeAllowed() {
+ return allowReadOnlyChange;
+ }
+
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ if (allowReadOnlyChange) {
+ readOnly = newStatus;
+ }
+ }
+
+ public boolean isPrimaryKey() {
+ return primaryKey;
+ }
+
+ public String getPropertyId() {
+ return propertyId;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(ColumnProperty.class.getName());
+ }
+
+ public void setOwner(RowItem owner) {
+ if (owner == null) {
+ throw new IllegalArgumentException("Owner can not be set to null.");
+ }
+ if (this.owner != null) {
+ throw new IllegalStateException(
+ "ColumnProperties can only be bound once.");
+ }
+ this.owner = owner;
+ }
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ public boolean isVersionColumn() {
+ return versionColumn;
+ }
+
+ public void setVersionColumn(boolean versionColumn) {
+ this.versionColumn = versionColumn;
+ }
+
+ public boolean isNullable() {
+ return nullable;
+ }
+
+ /**
+ * Return whether the value of this property should be persisted to the
+ * database.
+ *
+ * @return true if the value should be written to the database, false
+ * otherwise.
+ */
+ public boolean isPersistent() {
+ if (isVersionColumn()) {
+ return false;
+ } else if (isReadOnlyChangeAllowed() && !isReadOnly()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether or not this property is used as a row identifier.
+ *
+ * @return true if the property is a row identifier, false otherwise.
+ */
+ public boolean isRowIdentifier() {
+ return isPrimaryKey() || isVersionColumn();
+ }
+
+ /**
+ * An exception that signals that a <code>null</code> value was passed to
+ * the <code>setValue</code> method, but the value of this property can not
+ * be set to <code>null</code>.
+ */
+ @SuppressWarnings("serial")
+ public class NotNullableException extends RuntimeException {
+
+ /**
+ * Constructs a new <code>NotNullableException</code> without a detail
+ * message.
+ */
+ public NotNullableException() {
+ }
+
+ /**
+ * Constructs a new <code>NotNullableException</code> with the specified
+ * detail message.
+ *
+ * @param msg
+ * the detail message
+ */
+ public NotNullableException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs a new <code>NotNullableException</code> from another
+ * exception.
+ *
+ * @param cause
+ * The cause of the failure
+ */
+ public NotNullableException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ public void commit() {
+ if (isModified()) {
+ modified = false;
+ value = changedValue;
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java
new file mode 100644
index 0000000000..1a29d531bc
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/OptimisticLockException.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import com.vaadin.data.util.sqlcontainer.query.TableQuery;
+
+/**
+ * An OptimisticLockException is thrown when trying to update or delete a row
+ * that has been changed since last read from the database.
+ *
+ * OptimisticLockException is a runtime exception because optimistic locking is
+ * turned off by default, and as such will never be thrown in a default
+ * configuration. In order to turn on optimistic locking, you need to specify
+ * the version column in your TableQuery instance.
+ *
+ * @see TableQuery#setVersionColumn(String)
+ *
+ * @author Jonatan Kronqvist / Vaadin Ltd
+ */
+public class OptimisticLockException extends RuntimeException {
+
+ private final RowId rowId;
+
+ public OptimisticLockException(RowId rowId) {
+ super();
+ this.rowId = rowId;
+ }
+
+ public OptimisticLockException(String msg, RowId rowId) {
+ super(msg);
+ this.rowId = rowId;
+ }
+
+ public RowId getRowId() {
+ return rowId;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ReadOnlyRowId.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ReadOnlyRowId.java
new file mode 100644
index 0000000000..367135782f
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/ReadOnlyRowId.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+public class ReadOnlyRowId extends RowId {
+ private static final long serialVersionUID = -2626764781642012467L;
+ private final Integer rowNum;
+
+ public ReadOnlyRowId(int rowNum) {
+ super();
+ this.rowNum = rowNum;
+ }
+
+ @Override
+ public int hashCode() {
+ return getRowNum();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(ReadOnlyRowId.class.equals(obj.getClass()))) {
+ return false;
+ }
+ return getRowNum() == (((ReadOnlyRowId) obj).getRowNum());
+ }
+
+ public int getRowNum() {
+ return rowNum;
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(getRowNum());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/Reference.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/Reference.java
new file mode 100644
index 0000000000..ce6680089d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/Reference.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.io.Serializable;
+
+/**
+ * The reference class represents a simple [usually foreign key] reference to
+ * another SQLContainer. Actual foreign key reference in the database is not
+ * required, but it is recommended to make sure that certain constraints are
+ * followed.
+ */
+@SuppressWarnings("serial")
+class Reference implements Serializable {
+
+ /**
+ * The SQLContainer that this reference points to.
+ */
+ private SQLContainer referencedContainer;
+
+ /**
+ * The column ID/name in the referencing SQLContainer that contains the key
+ * used for the reference.
+ */
+ private String referencingColumn;
+
+ /**
+ * The column ID/name in the referenced SQLContainer that contains the key
+ * used for the reference.
+ */
+ private String referencedColumn;
+
+ /**
+ * Constructs a new reference to be used within the SQLContainer to
+ * reference another SQLContainer.
+ */
+ Reference(SQLContainer referencedContainer, String referencingColumn,
+ String referencedColumn) {
+ this.referencedContainer = referencedContainer;
+ this.referencingColumn = referencingColumn;
+ this.referencedColumn = referencedColumn;
+ }
+
+ SQLContainer getReferencedContainer() {
+ return referencedContainer;
+ }
+
+ String getReferencingColumn() {
+ return referencingColumn;
+ }
+
+ String getReferencedColumn() {
+ return referencedColumn;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowId.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowId.java
new file mode 100644
index 0000000000..5f78df79e1
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowId.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+/**
+ * RowId represents identifiers of a single database result set row.
+ *
+ * The data structure of a RowId is an Object array which contains the values of
+ * the primary key columns of the identified row. This allows easy equals()
+ * -comparison of RowItems.
+ */
+public class RowId implements Serializable {
+ private static final long serialVersionUID = -3161778404698901258L;
+ protected Object[] id;
+
+ /**
+ * Prevent instantiation without required parameters.
+ */
+ protected RowId() {
+ }
+
+ public RowId(Object... id) {
+ if (id == null) {
+ throw new IllegalArgumentException(
+ "id parameter must not be null!");
+ }
+ this.id = id;
+ }
+
+ public Object[] getId() {
+ return id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(getId());
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(RowId.class.equals(obj.getClass()))) {
+ return false;
+ }
+ return Arrays.equals(getId(), ((RowId) obj).getId());
+ }
+
+ @Override
+ public String toString() {
+ if (getId() == null) {
+ return "";
+ }
+ StringBuilder builder = new StringBuilder();
+ for (Object id : getId()) {
+ builder.append(id);
+ builder.append('/');
+ }
+ if (builder.length() > 0) {
+ return builder.substring(0, builder.length() - 1);
+ }
+ return builder.toString();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowItem.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowItem.java
new file mode 100644
index 0000000000..ffc8bfc6f7
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/RowItem.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * RowItem represents one row of a result set obtained from a QueryDelegate.
+ *
+ * Note that depending on the QueryDelegate in use this does not necessarily map
+ * into an actual row in a database table.
+ */
+public final class RowItem implements Item {
+ private static final long serialVersionUID = -6228966439127951408L;
+ private SQLContainer container;
+ private RowId id;
+ private Collection<ColumnProperty> properties;
+
+ /**
+ * Prevent instantiation without required parameters.
+ */
+ @SuppressWarnings("unused")
+ private RowItem() {
+ }
+
+ public RowItem(SQLContainer container, RowId id,
+ Collection<ColumnProperty> properties) {
+ if (container == null) {
+ throw new IllegalArgumentException("Container cannot be null.");
+ }
+ if (id == null) {
+ throw new IllegalArgumentException("Row ID cannot be null.");
+ }
+ this.container = container;
+ this.properties = properties;
+ /* Set this RowItem as owner to the properties */
+ if (properties != null) {
+ for (ColumnProperty p : properties) {
+ p.setOwner(this);
+ }
+ }
+ this.id = id;
+ }
+
+ @Override
+ public Property getItemProperty(Object id) {
+ if (id instanceof String && id != null) {
+ for (ColumnProperty cp : properties) {
+ if (id.equals(cp.getPropertyId())) {
+ return cp;
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Collection<?> getItemPropertyIds() {
+ Collection<String> ids = new ArrayList<String>(properties.size());
+ for (ColumnProperty cp : properties) {
+ ids.add(cp.getPropertyId());
+ }
+ return Collections.unmodifiableCollection(ids);
+ }
+
+ /**
+ * Adding properties is not supported. Properties are generated by
+ * SQLContainer.
+ */
+ @Override
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removing properties is not supported. Properties are generated by
+ * SQLContainer.
+ */
+ @Override
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ public RowId getId() {
+ return id;
+ }
+
+ public SQLContainer getContainer() {
+ return container;
+ }
+
+ public boolean isModified() {
+ if (properties != null) {
+ for (ColumnProperty p : properties) {
+ if (p.isModified()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer s = new StringBuffer();
+ s.append("ID:");
+ s.append(getId().toString());
+ for (Object propId : getItemPropertyIds()) {
+ s.append("|");
+ s.append(propId.toString());
+ s.append(":");
+ Object value = getItemProperty(propId).getValue();
+ s.append((null != value) ? value.toString() : null);
+ }
+ return s.toString();
+ }
+
+ public void commit() {
+ if (properties != null) {
+ for (ColumnProperty p : properties) {
+ p.commit();
+ }
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLContainer.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLContainer.java
new file mode 100644
index 0000000000..dde57d3610
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLContainer.java
@@ -0,0 +1,1875 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.io.IOException;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.ConcurrentModificationException;
+import java.util.Date;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.ContainerHelpers;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.filter.Compare.Equal;
+import com.vaadin.data.util.filter.Like;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+import com.vaadin.data.util.sqlcontainer.query.OrderBy;
+import com.vaadin.data.util.sqlcontainer.query.QueryDelegate;
+import com.vaadin.data.util.sqlcontainer.query.QueryDelegate.RowIdChangeListener;
+import com.vaadin.data.util.sqlcontainer.query.TableQuery;
+import com.vaadin.data.util.sqlcontainer.query.generator.MSSQLGenerator;
+import com.vaadin.data.util.sqlcontainer.query.generator.OracleGenerator;
+
+public class SQLContainer implements Container, Container.Filterable,
+ Container.Indexed, Container.Sortable, Container.ItemSetChangeNotifier {
+
+ /** Query delegate */
+ private QueryDelegate queryDelegate;
+ /** Auto commit mode, default = false */
+ private boolean autoCommit = false;
+
+ /** Page length = number of items contained in one page */
+ private int pageLength = DEFAULT_PAGE_LENGTH;
+ public static final int DEFAULT_PAGE_LENGTH = 100;
+
+ /** Number of items to cache = CACHE_RATIO x pageLength */
+ public static final int CACHE_RATIO = 2;
+
+ /** Amount of cache to overlap with previous page */
+ private int cacheOverlap = pageLength;
+
+ /** Item and index caches */
+ private final Map<Integer, RowId> itemIndexes = new HashMap<Integer, RowId>();
+ private final CacheMap<RowId, RowItem> cachedItems = new CacheMap<RowId, RowItem>();
+
+ /** Container properties = column names, data types and statuses */
+ private final List<String> propertyIds = new ArrayList<String>();
+ private final Map<String, Class<?>> propertyTypes = new HashMap<String, Class<?>>();
+ private final Map<String, Boolean> propertyReadOnly = new HashMap<String, Boolean>();
+ private final Map<String, Boolean> propertyPersistable = new HashMap<String, Boolean>();
+ private final Map<String, Boolean> propertyNullable = new HashMap<String, Boolean>();
+ private final Map<String, Boolean> propertyPrimaryKey = new HashMap<String, Boolean>();
+
+ /** Filters (WHERE) and sorters (ORDER BY) */
+ private final List<Filter> filters = new ArrayList<Filter>();
+ private final List<OrderBy> sorters = new ArrayList<OrderBy>();
+
+ /**
+ * Total number of items available in the data source using the current
+ * query, filters and sorters.
+ */
+ private int size;
+
+ /**
+ * Size updating logic. Do not update size from data source if it has been
+ * updated in the last sizeValidMilliSeconds milliseconds.
+ */
+ private final int sizeValidMilliSeconds = 10000;
+ private boolean sizeDirty = true;
+ private Date sizeUpdated = new Date();
+
+ /** Starting row number of the currently fetched page */
+ private int currentOffset;
+
+ /** ItemSetChangeListeners */
+ private LinkedList<Container.ItemSetChangeListener> itemSetChangeListeners;
+
+ /**
+ * Temporary storage for modified items and items to be removed and added
+ */
+ private final Map<RowId, RowItem> removedItems = new HashMap<RowId, RowItem>();
+ private final List<RowItem> addedItems = new ArrayList<RowItem>();
+ private final List<RowItem> modifiedItems = new ArrayList<RowItem>();
+
+ /** List of references to other SQLContainers */
+ private final Map<SQLContainer, Reference> references = new HashMap<SQLContainer, Reference>();
+
+ /** Cache flush notification system enabled. Disabled by default. */
+ private boolean notificationsEnabled;
+
+ /**
+ * Prevent instantiation without a QueryDelegate.
+ */
+ @SuppressWarnings("unused")
+ private SQLContainer() {
+ }
+
+ /**
+ * Creates and initializes SQLContainer using the given QueryDelegate
+ *
+ * @param delegate
+ * QueryDelegate implementation
+ * @throws SQLException
+ */
+ public SQLContainer(QueryDelegate delegate) throws SQLException {
+ if (delegate == null) {
+ throw new IllegalArgumentException(
+ "QueryDelegate must not be null.");
+ }
+ queryDelegate = delegate;
+ getPropertyIds();
+ cachedItems.setCacheLimit(CACHE_RATIO * getPageLength() + cacheOverlap);
+ }
+
+ /**************************************/
+ /** Methods from interface Container **/
+ /**************************************/
+
+ /**
+ * Note! If auto commit mode is enabled, this method will still return the
+ * temporary row ID assigned for the item. Implement
+ * QueryDelegate.RowIdChangeListener to receive the actual Row ID value
+ * after the addition has been committed.
+ *
+ * {@inheritDoc}
+ */
+
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+ Object emptyKey[] = new Object[queryDelegate.getPrimaryKeyColumns()
+ .size()];
+ RowId itemId = new TemporaryRowId(emptyKey);
+ // Create new empty column properties for the row item.
+ List<ColumnProperty> itemProperties = new ArrayList<ColumnProperty>();
+ for (String propertyId : propertyIds) {
+ /* Default settings for new item properties. */
+ ColumnProperty cp = new ColumnProperty(propertyId,
+ propertyReadOnly.get(propertyId),
+ propertyPersistable.get(propertyId),
+ propertyNullable.get(propertyId),
+ propertyPrimaryKey.get(propertyId), null,
+ getType(propertyId));
+
+ itemProperties.add(cp);
+ }
+ RowItem newRowItem = new RowItem(this, itemId, itemProperties);
+
+ if (autoCommit) {
+ /* Add and commit instantly */
+ try {
+ if (queryDelegate instanceof TableQuery) {
+ itemId = ((TableQuery) queryDelegate)
+ .storeRowImmediately(newRowItem);
+ } else {
+ queryDelegate.beginTransaction();
+ queryDelegate.storeRow(newRowItem);
+ queryDelegate.commit();
+ }
+ refresh();
+ if (notificationsEnabled) {
+ CacheFlushNotifier.notifyOfCacheFlush(this);
+ }
+ getLogger().log(Level.FINER, "Row added to DB...");
+ return itemId;
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING,
+ "Failed to add row to DB. Rolling back.", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ getLogger().log(Level.SEVERE,
+ "Failed to roll back row addition", e);
+ }
+ return null;
+ }
+ } else {
+ addedItems.add(newRowItem);
+ fireContentsChange();
+ return itemId;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#containsId(java.lang.Object)
+ */
+
+ @Override
+ public boolean containsId(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+
+ if (cachedItems.containsKey(itemId)) {
+ return true;
+ } else {
+ for (RowItem item : addedItems) {
+ if (item.getId().equals(itemId)) {
+ return itemPassesFilters(item);
+ }
+ }
+ }
+ if (removedItems.containsKey(itemId)) {
+ return false;
+ }
+
+ if (itemId instanceof ReadOnlyRowId) {
+ int rowNum = ((ReadOnlyRowId) itemId).getRowNum();
+ return rowNum >= 0 && rowNum < size;
+ }
+
+ if (itemId instanceof RowId && !(itemId instanceof TemporaryRowId)) {
+ try {
+ return queryDelegate
+ .containsRowWithKey(((RowId) itemId).getId());
+ } catch (Exception e) {
+ /* Query failed, just return false. */
+ getLogger().log(Level.WARNING, "containsId query failed", e);
+ }
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
+
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ Item item = getItem(itemId);
+ if (item == null) {
+ return null;
+ }
+ return item.getItemProperty(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
+
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return Collections.unmodifiableCollection(propertyIds);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItem(java.lang.Object)
+ */
+
+ @Override
+ public Item getItem(Object itemId) {
+ if (!cachedItems.containsKey(itemId)) {
+ int index = indexOfId(itemId);
+ if (index >= size) {
+ // The index is in the added items
+ int offset = index - size;
+ RowItem item = addedItems.get(offset);
+ if (itemPassesFilters(item)) {
+ return item;
+ } else {
+ return null;
+ }
+ } else {
+ // load the item into cache
+ updateOffsetAndCache(index);
+ }
+ }
+ return cachedItems.get(itemId);
+ }
+
+ /**
+ * Bypasses in-memory filtering to return items that are cached in memory.
+ * <em>NOTE</em>: This does not bypass database-level filtering.
+ *
+ * @param itemId
+ * the id of the item to retrieve.
+ * @return the item represented by itemId.
+ */
+ public Item getItemUnfiltered(Object itemId) {
+ if (!cachedItems.containsKey(itemId)) {
+ for (RowItem item : addedItems) {
+ if (item.getId().equals(itemId)) {
+ return item;
+ }
+ }
+ }
+ return cachedItems.get(itemId);
+ }
+
+ /**
+ * NOTE! Do not use this method if in any way avoidable. This method doesn't
+ * (and cannot) use lazy loading, which means that all rows in the database
+ * will be loaded into memory.
+ *
+ * {@inheritDoc}
+ */
+
+ @Override
+ public Collection<?> getItemIds() {
+ updateCount();
+ ArrayList<RowId> ids = new ArrayList<RowId>();
+ ResultSet rs = null;
+ try {
+ // Load ALL rows :(
+ queryDelegate.beginTransaction();
+ rs = queryDelegate.getResults(0, 0);
+ List<String> pKeys = queryDelegate.getPrimaryKeyColumns();
+ while (rs.next()) {
+ RowId id = null;
+ if (pKeys.isEmpty()) {
+ /* Create a read only itemId */
+ id = new ReadOnlyRowId(rs.getRow());
+ } else {
+ /* Generate itemId for the row based on primary key(s) */
+ Object[] itemId = new Object[pKeys.size()];
+ for (int i = 0; i < pKeys.size(); i++) {
+ itemId[i] = rs.getObject(pKeys.get(i));
+ }
+ id = new RowId(itemId);
+ }
+ if (id != null && !removedItems.containsKey(id)) {
+ ids.add(id);
+ }
+ }
+ rs.getStatement().close();
+ rs.close();
+ queryDelegate.commit();
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING, "getItemIds() failed, rolling back.",
+ e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException e1) {
+ getLogger().log(Level.SEVERE, "Failed to roll back state", e1);
+ }
+ try {
+ rs.getStatement().close();
+ rs.close();
+ } catch (SQLException e1) {
+ getLogger().log(Level.WARNING, "Closing session failed", e1);
+ }
+ throw new RuntimeException("Failed to fetch item indexes.", e);
+ }
+ for (RowItem item : getFilteredAddedItems()) {
+ ids.add(item.getId());
+ }
+ return Collections.unmodifiableCollection(ids);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
+
+ @Override
+ public Class<?> getType(Object propertyId) {
+ if (!propertyIds.contains(propertyId)) {
+ return null;
+ }
+ return propertyTypes.get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#size()
+ */
+
+ @Override
+ public int size() {
+ updateCount();
+ return size + sizeOfAddedItems() - removedItems.size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ if (!containsId(itemId)) {
+ return false;
+ }
+ for (RowItem item : addedItems) {
+ if (item.getId().equals(itemId)) {
+ addedItems.remove(item);
+ fireContentsChange();
+ return true;
+ }
+ }
+
+ if (autoCommit) {
+ /* Remove and commit instantly. */
+ Item i = getItem(itemId);
+ if (i == null) {
+ return false;
+ }
+ try {
+ queryDelegate.beginTransaction();
+ boolean success = queryDelegate.removeRow((RowItem) i);
+ queryDelegate.commit();
+ refresh();
+ if (notificationsEnabled) {
+ CacheFlushNotifier.notifyOfCacheFlush(this);
+ }
+ if (success) {
+ getLogger().log(Level.FINER, "Row removed from DB...");
+ }
+ return success;
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING,
+ "Failed to remove row, rolling back", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ /* Nothing can be done here */
+ getLogger().log(Level.SEVERE,
+ "Failed to rollback row removal", ee);
+ }
+ return false;
+ } catch (OptimisticLockException e) {
+ getLogger().log(Level.WARNING,
+ "Failed to remove row, rolling back", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ /* Nothing can be done here */
+ getLogger().log(Level.SEVERE,
+ "Failed to rollback row removal", ee);
+ }
+ throw e;
+ }
+ } else {
+ removedItems.put((RowId) itemId, (RowItem) getItem(itemId));
+ cachedItems.remove(itemId);
+ refresh();
+ return true;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ if (autoCommit) {
+ /* Remove and commit instantly. */
+ try {
+ queryDelegate.beginTransaction();
+ boolean success = true;
+ for (Object id : getItemIds()) {
+ if (!queryDelegate.removeRow((RowItem) getItem(id))) {
+ success = false;
+ }
+ }
+ if (success) {
+ queryDelegate.commit();
+ getLogger().log(Level.FINER, "All rows removed from DB...");
+ refresh();
+ if (notificationsEnabled) {
+ CacheFlushNotifier.notifyOfCacheFlush(this);
+ }
+ } else {
+ queryDelegate.rollback();
+ }
+ return success;
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING,
+ "removeAllItems() failed, rolling back", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ /* Nothing can be done here */
+ getLogger().log(Level.SEVERE, "Failed to roll back", ee);
+ }
+ return false;
+ } catch (OptimisticLockException e) {
+ getLogger().log(Level.WARNING,
+ "removeAllItems() failed, rolling back", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ /* Nothing can be done here */
+ getLogger().log(Level.SEVERE, "Failed to roll back", ee);
+ }
+ throw e;
+ }
+ } else {
+ for (Object id : getItemIds()) {
+ removedItems.put((RowId) id, (RowItem) getItem(id));
+ cachedItems.remove(id);
+ }
+ refresh();
+ return true;
+ }
+ }
+
+ /*************************************************/
+ /** Methods from interface Container.Filterable **/
+ /*************************************************/
+
+ /**
+ * {@inheritDoc}
+ */
+
+ @Override
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException {
+ // filter.setCaseSensitive(!ignoreCase);
+
+ filters.add(filter);
+ refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+
+ @Override
+ public void removeContainerFilter(Filter filter) {
+ filters.remove(filter);
+ refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ if (propertyId == null || !propertyIds.contains(propertyId)) {
+ return;
+ }
+
+ /* Generate Filter -object */
+ String likeStr = onlyMatchPrefix ? filterString + "%"
+ : "%" + filterString + "%";
+ Like like = new Like(propertyId.toString(), likeStr);
+ like.setCaseSensitive(!ignoreCase);
+ filters.add(like);
+ refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void removeContainerFilters(Object propertyId) {
+ ArrayList<Filter> toRemove = new ArrayList<Filter>();
+ for (Filter f : filters) {
+ if (f.appliesToProperty(propertyId)) {
+ toRemove.add(f);
+ }
+ }
+ filters.removeAll(toRemove);
+ refresh();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void removeAllContainerFilters() {
+ filters.clear();
+ refresh();
+ }
+
+ /**
+ * Returns true if any filters have been applied to the container.
+ *
+ * @return true if the container has filters applied, false otherwise
+ * @since 7.1
+ */
+ public boolean hasContainerFilters() {
+ return !getContainerFilters().isEmpty();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#getContainerFilters()
+ */
+ @Override
+ public Collection<Filter> getContainerFilters() {
+ return Collections.unmodifiableCollection(filters);
+ }
+
+ /**********************************************/
+ /** Methods from interface Container.Indexed **/
+ /**********************************************/
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#indexOfId(java.lang.Object)
+ */
+
+ @Override
+ public int indexOfId(Object itemId) {
+ // First check if the id is in the added items
+ for (int ix = 0; ix < addedItems.size(); ix++) {
+ RowItem item = addedItems.get(ix);
+ if (item.getId().equals(itemId)) {
+ if (itemPassesFilters(item)) {
+ updateCount();
+ return size + ix;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ if (!containsId(itemId)) {
+ return -1;
+ }
+ if (cachedItems.isEmpty()) {
+ getPage();
+ }
+ // this protects against infinite looping
+ int counter = 0;
+ int oldIndex;
+ while (counter < size) {
+ if (itemIndexes.containsValue(itemId)) {
+ for (Integer idx : itemIndexes.keySet()) {
+ if (itemIndexes.get(idx).equals(itemId)) {
+ return idx;
+ }
+ }
+ }
+ oldIndex = currentOffset;
+ // load in the next page.
+ int nextIndex = currentOffset + pageLength * CACHE_RATIO
+ + cacheOverlap;
+ if (nextIndex >= size) {
+ // Container wrapped around, start from index 0.
+ nextIndex = 0;
+ }
+ updateOffsetAndCache(nextIndex);
+
+ // Update counter
+ if (currentOffset > oldIndex) {
+ counter += currentOffset - oldIndex;
+ } else {
+ counter += size - oldIndex;
+ }
+ }
+ // safeguard in case item not found
+ return -1;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#getIdByIndex(int)
+ */
+
+ @Override
+ public Object getIdByIndex(int index) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException(
+ "Index is negative! index=" + index);
+ }
+ // make sure the size field is valid
+ updateCount();
+ if (index < size) {
+ if (itemIndexes.keySet().contains(index)) {
+ return itemIndexes.get(index);
+ }
+ updateOffsetAndCache(index);
+ return itemIndexes.get(index);
+ } else {
+ // The index is in the added items
+ int offset = index - size;
+ // TODO this is very inefficient if looping - should improve
+ // getItemIds(int, int)
+ return getFilteredAddedItems().get(offset).getId();
+ }
+ }
+
+ @Override
+ public List<Object> getItemIds(int startIndex, int numberOfIds) {
+ // TODO create a better implementation
+ return (List<Object>) ContainerHelpers
+ .getItemIdsUsingGetIdByIndex(startIndex, numberOfIds, this);
+ }
+
+ /**********************************************/
+ /** Methods from interface Container.Ordered **/
+ /**********************************************/
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
+ */
+
+ @Override
+ public Object nextItemId(Object itemId) {
+ int index = indexOfId(itemId) + 1;
+ try {
+ return getIdByIndex(index);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object)
+ */
+
+ @Override
+ public Object prevItemId(Object itemId) {
+ int prevIndex = indexOfId(itemId) - 1;
+ try {
+ return getIdByIndex(prevIndex);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#firstItemId()
+ */
+
+ @Override
+ public Object firstItemId() {
+ updateCount();
+ if (size == 0) {
+ if (addedItems.isEmpty()) {
+ return null;
+ } else {
+ int ix = -1;
+ do {
+ ix++;
+ } while (!itemPassesFilters(addedItems.get(ix))
+ && ix < addedItems.size());
+ if (ix < addedItems.size()) {
+ return addedItems.get(ix).getId();
+ }
+ }
+ }
+ if (!itemIndexes.containsKey(0)) {
+ updateOffsetAndCache(0);
+ }
+ return itemIndexes.get(0);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#lastItemId()
+ */
+
+ @Override
+ public Object lastItemId() {
+ if (addedItems.isEmpty()) {
+ int lastIx = size() - 1;
+ if (!itemIndexes.containsKey(lastIx)) {
+ updateOffsetAndCache(size - 1);
+ }
+ return itemIndexes.get(lastIx);
+ } else {
+ int ix = addedItems.size();
+ do {
+ ix--;
+ } while (!itemPassesFilters(addedItems.get(ix)) && ix >= 0);
+ if (ix >= 0) {
+ return addedItems.get(ix).getId();
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object)
+ */
+
+ @Override
+ public boolean isFirstId(Object itemId) {
+ return firstItemId().equals(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object)
+ */
+
+ @Override
+ public boolean isLastId(Object itemId) {
+ return lastItemId().equals(itemId);
+ }
+
+ /***********************************************/
+ /** Methods from interface Container.Sortable **/
+ /***********************************************/
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ sorters.clear();
+ if (propertyId == null || propertyId.length == 0) {
+ refresh();
+ return;
+ }
+ /* Generate OrderBy -objects */
+ boolean asc = true;
+ for (int i = 0; i < propertyId.length; i++) {
+ /* Check that the property id is valid */
+ if (propertyId[i] instanceof String
+ && propertyIds.contains(propertyId[i])) {
+ try {
+ asc = ascending[i];
+ } catch (Exception e) {
+ getLogger().log(Level.WARNING, "", e);
+ }
+ sorters.add(new OrderBy((String) propertyId[i], asc));
+ }
+ }
+ refresh();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ return getContainerPropertyIds();
+ }
+
+ /**************************************/
+ /** Methods specific to SQLContainer **/
+ /**************************************/
+
+ /**
+ * Refreshes the container - clears all caches and resets size and offset.
+ * Does NOT remove sorting or filtering rules!
+ */
+ public void refresh() {
+ refresh(true);
+ }
+
+ /**
+ * Refreshes the container. If <code>setSizeDirty</code> is
+ * <code>false</code>, assumes that the current size is up to date. This is
+ * used in {@link #updateCount()} to refresh the contents when we know the
+ * size was just updated.
+ *
+ * @param setSizeDirty
+ */
+ private void refresh(boolean setSizeDirty) {
+ if (setSizeDirty) {
+ sizeDirty = true;
+ }
+ currentOffset = 0;
+ cachedItems.clear();
+ itemIndexes.clear();
+ fireContentsChange();
+ }
+
+ /**
+ * Returns modify state of the container.
+ *
+ * @return true if contents of this container have been modified
+ */
+ public boolean isModified() {
+ return !removedItems.isEmpty() || !addedItems.isEmpty()
+ || !modifiedItems.isEmpty();
+ }
+
+ /**
+ * Set auto commit mode enabled or disabled. Auto commit mode means that all
+ * changes made to items of this container will be immediately written to
+ * the underlying data source.
+ *
+ * @param autoCommitEnabled
+ * true to enable auto commit mode
+ */
+ public void setAutoCommit(boolean autoCommitEnabled) {
+ autoCommit = autoCommitEnabled;
+ }
+
+ /**
+ * Returns status of the auto commit mode.
+ *
+ * @return true if auto commit mode is enabled
+ */
+ public boolean isAutoCommit() {
+ return autoCommit;
+ }
+
+ /**
+ * Returns the currently set page length.
+ *
+ * @return current page length
+ */
+ public int getPageLength() {
+ return pageLength;
+ }
+
+ /**
+ * Sets the page length used in lazy fetching of items from the data source.
+ * Also resets the cache size to match the new page length.
+ *
+ * As a side effect the container will be refreshed.
+ *
+ * @param pageLength
+ * new page length
+ */
+ public void setPageLength(int pageLength) {
+ setPageLengthInternal(pageLength);
+ refresh();
+ }
+
+ /**
+ * Sets the page length internally, without refreshing the container.
+ *
+ * @param pageLength
+ * the new page length
+ */
+ private void setPageLengthInternal(int pageLength) {
+ this.pageLength = pageLength > 0 ? pageLength : DEFAULT_PAGE_LENGTH;
+ cacheOverlap = getPageLength();
+ cachedItems.setCacheLimit(CACHE_RATIO * getPageLength() + cacheOverlap);
+ }
+
+ /**
+ * Adds the given OrderBy to this container and refreshes the container
+ * contents with the new sorting rules.
+ *
+ * Note that orderBy.getColumn() must return a column name that exists in
+ * this container.
+ *
+ * @param orderBy
+ * OrderBy to be added to the container sorting rules
+ */
+ public void addOrderBy(OrderBy orderBy) {
+ if (orderBy == null) {
+ return;
+ }
+ if (!propertyIds.contains(orderBy.getColumn())) {
+ throw new IllegalArgumentException(
+ "The column given for sorting does not exist in this container.");
+ }
+ sorters.add(orderBy);
+ refresh();
+ }
+
+ /**
+ * Commits all the changes, additions and removals made to the items of this
+ * container.
+ *
+ * @throws UnsupportedOperationException
+ * @throws SQLException
+ */
+ public void commit() throws UnsupportedOperationException, SQLException {
+ try {
+ getLogger().log(Level.FINER,
+ "Commiting changes through delegate...");
+ queryDelegate.beginTransaction();
+ /* Perform buffered deletions */
+ for (RowItem item : removedItems.values()) {
+ try {
+ if (!queryDelegate.removeRow(item)) {
+ throw new SQLException(
+ "Removal failed for row with ID: "
+ + item.getId());
+ }
+ } catch (IllegalArgumentException e) {
+ throw new SQLException(
+ "Removal failed for row with ID: " + item.getId(),
+ e);
+ }
+ }
+ /* Perform buffered modifications */
+ for (RowItem item : modifiedItems) {
+ if (!removedItems.containsKey(item.getId())) {
+ if (queryDelegate.storeRow(item) > 0) {
+ /*
+ * Also reset the modified state in the item in case it
+ * is reused e.g. in a form.
+ */
+ item.commit();
+ } else {
+ queryDelegate.rollback();
+ refresh();
+ throw new ConcurrentModificationException(
+ "Item with the ID '" + item.getId()
+ + "' has been externally modified.");
+ }
+ }
+ }
+ /* Perform buffered additions */
+ for (RowItem item : addedItems) {
+ queryDelegate.storeRow(item);
+ }
+ queryDelegate.commit();
+ removedItems.clear();
+ addedItems.clear();
+ modifiedItems.clear();
+ refresh();
+ if (notificationsEnabled) {
+ CacheFlushNotifier.notifyOfCacheFlush(this);
+ }
+ } catch (SQLException e) {
+ queryDelegate.rollback();
+ throw e;
+ } catch (OptimisticLockException e) {
+ queryDelegate.rollback();
+ throw e;
+ }
+ }
+
+ /**
+ * Rolls back all the changes, additions and removals made to the items of
+ * this container.
+ *
+ * @throws UnsupportedOperationException
+ * @throws SQLException
+ */
+ public void rollback() throws UnsupportedOperationException, SQLException {
+ getLogger().log(Level.FINE, "Rolling back changes...");
+ removedItems.clear();
+ addedItems.clear();
+ modifiedItems.clear();
+ refresh();
+ }
+
+ /**
+ * Notifies this container that a property in the given item has been
+ * modified. The change will be buffered or made instantaneously depending
+ * on auto commit mode.
+ *
+ * @param changedItem
+ * item that has a modified property
+ */
+ void itemChangeNotification(RowItem changedItem) {
+ if (autoCommit) {
+ try {
+ queryDelegate.beginTransaction();
+ if (queryDelegate.storeRow(changedItem) == 0) {
+ queryDelegate.rollback();
+ refresh();
+ throw new ConcurrentModificationException(
+ "Item with the ID '" + changedItem.getId()
+ + "' has been externally modified.");
+ }
+ queryDelegate.commit();
+ if (notificationsEnabled) {
+ CacheFlushNotifier.notifyOfCacheFlush(this);
+ }
+ getLogger().log(Level.FINER, "Row updated to DB...");
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING,
+ "itemChangeNotification failed, rolling back...", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException ee) {
+ /* Nothing can be done here */
+ getLogger().log(Level.SEVERE, "Rollback failed", e);
+ }
+ throw new RuntimeException(e);
+ }
+ } else {
+ if (!(changedItem.getId() instanceof TemporaryRowId)
+ && !modifiedItems.contains(changedItem)) {
+ modifiedItems.add(changedItem);
+ }
+ }
+ }
+
+ /**
+ * Determines a new offset for updating the row cache. The offset is
+ * calculated from the given index, and will be fixed to match the start of
+ * a page, based on the value of pageLength.
+ *
+ * @param index
+ * Index of the item that was requested, but not found in cache
+ */
+ private void updateOffsetAndCache(int index) {
+
+ int oldOffset = currentOffset;
+
+ currentOffset = (index / pageLength) * pageLength - cacheOverlap;
+
+ if (currentOffset < 0) {
+ currentOffset = 0;
+ }
+
+ if (oldOffset == currentOffset && !cachedItems.isEmpty()) {
+ return;
+ }
+
+ getPage();
+ }
+
+ /**
+ * Fetches new count of rows from the data source, if needed.
+ */
+ private void updateCount() {
+ if (!sizeDirty && new Date().getTime() < sizeUpdated.getTime()
+ + sizeValidMilliSeconds) {
+ return;
+ }
+ try {
+ try {
+ queryDelegate.setFilters(filters);
+ } catch (UnsupportedOperationException e) {
+ getLogger().log(Level.FINE,
+ "The query delegate doesn't support filtering", e);
+ }
+ try {
+ queryDelegate.setOrderBy(sorters);
+ } catch (UnsupportedOperationException e) {
+ getLogger().log(Level.FINE,
+ "The query delegate doesn't support sorting", e);
+ }
+ int newSize = queryDelegate.getCount();
+ sizeUpdated = new Date();
+ sizeDirty = false;
+ if (newSize != size) {
+ size = newSize;
+ // Size is up to date so don't set it back to dirty in refresh()
+ refresh(false);
+ }
+ getLogger().log(Level.FINER, "Updated row count. New count is: {0}",
+ size);
+ } catch (SQLException e) {
+ throw new RuntimeException("Failed to update item set size.", e);
+ }
+ }
+
+ /**
+ * Fetches property id's (column names and their types) from the data
+ * source.
+ *
+ * @throws SQLException
+ */
+ private void getPropertyIds() throws SQLException {
+ propertyIds.clear();
+ propertyTypes.clear();
+ queryDelegate.setFilters(null);
+ queryDelegate.setOrderBy(null);
+ ResultSet rs = null;
+ ResultSetMetaData rsmd = null;
+ try {
+ queryDelegate.beginTransaction();
+ rs = queryDelegate.getResults(0, 1);
+ rsmd = rs.getMetaData();
+ boolean resultExists = rs.next();
+ Class<?> type = null;
+ for (int i = 1; i <= rsmd.getColumnCount(); i++) {
+ if (!isColumnIdentifierValid(rsmd.getColumnLabel(i))) {
+ continue;
+ }
+ String colName = rsmd.getColumnLabel(i);
+ /*
+ * Make sure not to add the same colName twice. This can easily
+ * happen if the SQL query joins many tables with an ID column.
+ */
+ if (!propertyIds.contains(colName)) {
+ propertyIds.add(colName);
+ }
+ /* Try to determine the column's JDBC class by all means. */
+ if (resultExists && rs.getObject(i) != null) {
+ type = rs.getObject(i).getClass();
+ } else {
+ try {
+ type = Class.forName(rsmd.getColumnClassName(i));
+ } catch (Exception e) {
+ getLogger().log(Level.WARNING, "Class not found", e);
+ /* On failure revert to Object and hope for the best. */
+ type = Object.class;
+ }
+ }
+ /*
+ * Determine read only and nullability status of the column. A
+ * column is read only if it is reported as either read only or
+ * auto increment by the database, and also it is set as the
+ * version column in a TableQuery delegate.
+ */
+ boolean readOnly = rsmd.isAutoIncrement(i)
+ || rsmd.isReadOnly(i);
+
+ boolean persistable = !rsmd.isReadOnly(i);
+
+ if (queryDelegate instanceof TableQuery) {
+ if (rsmd.getColumnLabel(i).equals(
+ ((TableQuery) queryDelegate).getVersionColumn())) {
+ readOnly = true;
+ }
+ }
+
+ propertyReadOnly.put(colName, readOnly);
+ propertyPersistable.put(colName, persistable);
+ propertyNullable.put(colName,
+ rsmd.isNullable(i) == ResultSetMetaData.columnNullable);
+ propertyPrimaryKey.put(colName,
+ queryDelegate.getPrimaryKeyColumns()
+ .contains(rsmd.getColumnLabel(i)));
+ propertyTypes.put(colName, type);
+ }
+ rs.getStatement().close();
+ rs.close();
+ queryDelegate.commit();
+ getLogger().log(Level.FINER, "Property IDs fetched.");
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING,
+ "Failed to fetch property ids, rolling back", e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException e1) {
+ getLogger().log(Level.SEVERE, "Failed to roll back", e1);
+ }
+ try {
+ if (rs != null) {
+ if (rs.getStatement() != null) {
+ rs.getStatement().close();
+ }
+ rs.close();
+ }
+ } catch (SQLException e1) {
+ getLogger().log(Level.WARNING, "Failed to close session", e1);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Fetches a page from the data source based on the values of pageLength and
+ * currentOffset. Also updates the set of primary keys, used in
+ * identification of RowItems.
+ */
+ private void getPage() {
+ updateCount();
+ ResultSet rs = null;
+ ResultSetMetaData rsmd = null;
+ cachedItems.clear();
+ itemIndexes.clear();
+ try {
+ try {
+ queryDelegate.setOrderBy(sorters);
+ } catch (UnsupportedOperationException e) {
+ /* The query delegate doesn't support sorting. */
+ /* No need to do anything. */
+ getLogger().log(Level.FINE,
+ "The query delegate doesn't support sorting", e);
+ }
+ queryDelegate.beginTransaction();
+ int fetchedRows = pageLength * CACHE_RATIO + cacheOverlap;
+ rs = queryDelegate.getResults(currentOffset, fetchedRows);
+ rsmd = rs.getMetaData();
+ List<String> pKeys = queryDelegate.getPrimaryKeyColumns();
+ // }
+ /* Create new items and column properties */
+ ColumnProperty cp = null;
+ int rowCount = currentOffset;
+ if (!queryDelegate.implementationRespectsPagingLimits()) {
+ rowCount = currentOffset = 0;
+ setPageLengthInternal(size);
+ }
+ while (rs.next()) {
+ List<ColumnProperty> itemProperties = new ArrayList<ColumnProperty>();
+ /* Generate row itemId based on primary key(s) */
+ Object[] itemId = new Object[pKeys.size()];
+ for (int i = 0; i < pKeys.size(); i++) {
+ itemId[i] = rs.getObject(pKeys.get(i));
+ }
+ RowId id = null;
+ if (pKeys.isEmpty()) {
+ id = new ReadOnlyRowId(rs.getRow());
+ } else {
+ id = new RowId(itemId);
+ }
+ List<String> propertiesToAdd = new ArrayList<String>(
+ propertyIds);
+ if (!removedItems.containsKey(id)) {
+ for (int i = 1; i <= rsmd.getColumnCount(); i++) {
+ if (!isColumnIdentifierValid(rsmd.getColumnLabel(i))) {
+ continue;
+ }
+ String colName = rsmd.getColumnLabel(i);
+ Object value = rs.getObject(i);
+ Class<?> type = value != null ? value.getClass()
+ : Object.class;
+ if (value == null) {
+ for (String propName : propertyTypes.keySet()) {
+ if (propName.equals(rsmd.getColumnLabel(i))) {
+ type = propertyTypes.get(propName);
+ break;
+ }
+ }
+ }
+ /*
+ * In case there are more than one column with the same
+ * name, add only the first one. This can easily happen
+ * if you join many tables where each table has an ID
+ * column.
+ */
+ if (propertiesToAdd.contains(colName)) {
+
+ cp = new ColumnProperty(colName,
+ propertyReadOnly.get(colName),
+ propertyPersistable.get(colName),
+ propertyNullable.get(colName),
+ propertyPrimaryKey.get(colName), value,
+ type);
+ itemProperties.add(cp);
+ propertiesToAdd.remove(colName);
+ }
+ }
+ /* Cache item */
+ itemIndexes.put(rowCount, id);
+
+ // if an item with the id is contained in the modified
+ // cache, then use this record and add it to the cached
+ // items. Otherwise create a new item
+ int modifiedIndex = indexInModifiedCache(id);
+ if (modifiedIndex != -1) {
+ cachedItems.put(id, modifiedItems.get(modifiedIndex));
+ } else {
+ cachedItems.put(id,
+ new RowItem(this, id, itemProperties));
+ }
+
+ rowCount++;
+ }
+ }
+ rs.getStatement().close();
+ rs.close();
+ queryDelegate.commit();
+ getLogger().log(Level.FINER, "Fetched {0} rows starting from {1}",
+ new Object[] { fetchedRows, currentOffset });
+ } catch (SQLException e) {
+ getLogger().log(Level.WARNING, "Failed to fetch rows, rolling back",
+ e);
+ try {
+ queryDelegate.rollback();
+ } catch (SQLException e1) {
+ getLogger().log(Level.SEVERE, "Failed to roll back", e1);
+ }
+ try {
+ if (rs != null) {
+ if (rs.getStatement() != null) {
+ rs.getStatement().close();
+ rs.close();
+ }
+ }
+ } catch (SQLException e1) {
+ getLogger().log(Level.WARNING, "Failed to close session", e1);
+ }
+ throw new RuntimeException("Failed to fetch page.", e);
+ }
+ }
+
+ /**
+ * Returns the index of the item with the given itemId for the modified
+ * cache.
+ *
+ * @param itemId
+ * @return the index of the item with the itemId in the modified cache. Or
+ * -1 if not found.
+ */
+ private int indexInModifiedCache(Object itemId) {
+ for (int ix = 0; ix < modifiedItems.size(); ix++) {
+ RowItem item = modifiedItems.get(ix);
+ if (item.getId().equals(itemId)) {
+ return ix;
+ }
+ }
+ return -1;
+ }
+
+ private int sizeOfAddedItems() {
+ return getFilteredAddedItems().size();
+ }
+
+ private List<RowItem> getFilteredAddedItems() {
+ ArrayList<RowItem> filtered = new ArrayList<RowItem>(addedItems);
+ if (filters != null && !filters.isEmpty()) {
+ for (RowItem item : addedItems) {
+ if (!itemPassesFilters(item)) {
+ filtered.remove(item);
+ }
+ }
+ }
+ return filtered;
+ }
+
+ private boolean itemPassesFilters(RowItem item) {
+ for (Filter filter : filters) {
+ if (!filter.passesFilter(item.getId(), item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Checks is the given column identifier valid to be used with SQLContainer.
+ * Currently the only non-valid identifier is "rownum" when MSSQL or Oracle
+ * is used. This is due to the way the SELECT queries are constructed in
+ * order to implement paging in these databases.
+ *
+ * @param identifier
+ * Column identifier
+ * @return true if the identifier is valid
+ */
+ private boolean isColumnIdentifierValid(String identifier) {
+ if (identifier.equalsIgnoreCase("rownum")
+ && queryDelegate instanceof TableQuery) {
+ TableQuery tq = (TableQuery) queryDelegate;
+ if (tq.getSqlGenerator() instanceof MSSQLGenerator
+ || tq.getSqlGenerator() instanceof OracleGenerator) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns the QueryDelegate set for this SQLContainer.
+ *
+ * @return current querydelegate
+ */
+ protected QueryDelegate getQueryDelegate() {
+ return queryDelegate;
+ }
+
+ /************************************/
+ /** UNSUPPORTED CONTAINER FEATURES **/
+ /************************************/
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object)
+ */
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+
+ @Override
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int, java.lang.Object)
+ */
+
+ @Override
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int)
+ */
+
+ @Override
+ public Object addItemAt(int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
+ */
+
+ @Override
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /******************************************/
+ /** ITEMSETCHANGENOTIFIER IMPLEMENTATION **/
+ /******************************************/
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin
+ * .data.Container.ItemSetChangeListener)
+ */
+
+ @Override
+ public void addItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (itemSetChangeListeners == null) {
+ itemSetChangeListeners = new LinkedList<Container.ItemSetChangeListener>();
+ }
+ itemSetChangeListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Container.ItemSetChangeListener listener) {
+ addItemSetChangeListener(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin
+ * .data.Container.ItemSetChangeListener)
+ */
+
+ @Override
+ public void removeItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (itemSetChangeListeners != null) {
+ itemSetChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ removeItemSetChangeListener(listener);
+ }
+
+ protected void fireContentsChange() {
+ if (itemSetChangeListeners != null) {
+ final Object[] l = itemSetChangeListeners.toArray();
+ final Container.ItemSetChangeEvent event = new SQLContainer.ItemSetChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Container.ItemSetChangeListener) l[i])
+ .containerItemSetChange(event);
+ }
+ }
+ }
+
+ /**
+ * Simple ItemSetChangeEvent implementation.
+ */
+ @SuppressWarnings("serial")
+ public static class ItemSetChangeEvent extends EventObject
+ implements Container.ItemSetChangeEvent {
+
+ private ItemSetChangeEvent(SQLContainer source) {
+ super(source);
+ }
+
+ @Override
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+ }
+
+ /**************************************************/
+ /** ROWIDCHANGELISTENER PASSING TO QUERYDELEGATE **/
+ /**************************************************/
+
+ /**
+ * Adds a RowIdChangeListener to the QueryDelegate
+ *
+ * @param listener
+ */
+ public void addRowIdChangeListener(RowIdChangeListener listener) {
+ if (queryDelegate instanceof QueryDelegate.RowIdChangeNotifier) {
+ ((QueryDelegate.RowIdChangeNotifier) queryDelegate)
+ .addListener(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addRowIdChangeListener(RowIdChangeListener)}
+ **/
+ @Deprecated
+ public void addListener(RowIdChangeListener listener) {
+ addRowIdChangeListener(listener);
+ }
+
+ /**
+ * Removes a RowIdChangeListener from the QueryDelegate
+ *
+ * @param listener
+ */
+ public void removeRowIdChangeListener(RowIdChangeListener listener) {
+ if (queryDelegate instanceof QueryDelegate.RowIdChangeNotifier) {
+ ((QueryDelegate.RowIdChangeNotifier) queryDelegate)
+ .removeListener(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeRowIdChangeListener(RowIdChangeListener)}
+ **/
+ @Deprecated
+ public void removeListener(RowIdChangeListener listener) {
+ removeRowIdChangeListener(listener);
+ }
+
+ /**
+ * Calling this will enable this SQLContainer to send and receive cache
+ * flush notifications for its lifetime.
+ */
+ public void enableCacheFlushNotifications() {
+ if (!notificationsEnabled) {
+ notificationsEnabled = true;
+ CacheFlushNotifier.addInstance(this);
+ }
+ }
+
+ /******************************************/
+ /** Referencing mechanism implementation **/
+ /******************************************/
+
+ /**
+ * Adds a new reference to the given SQLContainer. In addition to the
+ * container you must provide the column (property) names used for the
+ * reference in both this and the referenced SQLContainer.
+ *
+ * Note that multiple references pointing to the same SQLContainer are not
+ * supported.
+ *
+ * @param refdCont
+ * Target SQLContainer of the new reference
+ * @param refingCol
+ * Column (property) name in this container storing the (foreign
+ * key) reference
+ * @param refdCol
+ * Column (property) name in the referenced container storing the
+ * referenced key
+ */
+ public void addReference(SQLContainer refdCont, String refingCol,
+ String refdCol) {
+ if (refdCont == null) {
+ throw new IllegalArgumentException(
+ "Referenced SQLContainer can not be null.");
+ }
+ if (!getContainerPropertyIds().contains(refingCol)) {
+ throw new IllegalArgumentException(
+ "Given referencing column name is invalid."
+ + " Please ensure that this container"
+ + " contains a property ID named: " + refingCol);
+ }
+ if (!refdCont.getContainerPropertyIds().contains(refdCol)) {
+ throw new IllegalArgumentException(
+ "Given referenced column name is invalid."
+ + " Please ensure that the referenced container"
+ + " contains a property ID named: " + refdCol);
+ }
+ if (references.keySet().contains(refdCont)) {
+ throw new IllegalArgumentException(
+ "An SQLContainer instance can only be referenced once.");
+ }
+ references.put(refdCont, new Reference(refdCont, refingCol, refdCol));
+ }
+
+ /**
+ * Removes the reference pointing to the given SQLContainer.
+ *
+ * @param refdCont
+ * Target SQLContainer of the reference
+ * @return true if successful, false if the reference did not exist
+ */
+ public boolean removeReference(SQLContainer refdCont) {
+ if (refdCont == null) {
+ throw new IllegalArgumentException(
+ "Referenced SQLContainer can not be null.");
+ }
+ return references.remove(refdCont) == null ? false : true;
+ }
+
+ /**
+ * Sets the referenced item. The referencing column of the item in this
+ * container is updated accordingly.
+ *
+ * @param itemId
+ * Item Id of the reference source (from this container)
+ * @param refdItemId
+ * Item Id of the reference target (from referenced container)
+ * @param refdCont
+ * Target SQLContainer of the reference
+ * @return true if the referenced item was successfully set, false on
+ * failure
+ */
+ public boolean setReferencedItem(Object itemId, Object refdItemId,
+ SQLContainer refdCont) {
+ if (refdCont == null) {
+ throw new IllegalArgumentException(
+ "Referenced SQLContainer can not be null.");
+ }
+ Reference r = references.get(refdCont);
+ if (r == null) {
+ throw new IllegalArgumentException(
+ "Reference to the given SQLContainer not defined.");
+ }
+ try {
+ getContainerProperty(itemId, r.getReferencingColumn())
+ .setValue(refdCont.getContainerProperty(refdItemId,
+ r.getReferencedColumn()));
+ return true;
+ } catch (Exception e) {
+ getLogger().log(Level.WARNING, "Setting referenced item failed.",
+ e);
+ return false;
+ }
+ }
+
+ /**
+ * Fetches the Item Id of the referenced item from the target SQLContainer.
+ *
+ * @param itemId
+ * Item Id of the reference source (from this container)
+ * @param refdCont
+ * Target SQLContainer of the reference
+ * @return Item Id of the referenced item, or null if not found
+ */
+ public Object getReferencedItemId(Object itemId, SQLContainer refdCont) {
+ if (refdCont == null) {
+ throw new IllegalArgumentException(
+ "Referenced SQLContainer can not be null.");
+ }
+ Reference r = references.get(refdCont);
+ if (r == null) {
+ throw new IllegalArgumentException(
+ "Reference to the given SQLContainer not defined.");
+ }
+ Object refKey = getContainerProperty(itemId, r.getReferencingColumn())
+ .getValue();
+
+ refdCont.removeAllContainerFilters();
+ refdCont.addContainerFilter(new Equal(r.getReferencedColumn(), refKey));
+ Object toReturn = refdCont.firstItemId();
+ refdCont.removeAllContainerFilters();
+ return toReturn;
+ }
+
+ /**
+ * Fetches the referenced item from the target SQLContainer.
+ *
+ * @param itemId
+ * Item Id of the reference source (from this container)
+ * @param refdCont
+ * Target SQLContainer of the reference
+ * @return The referenced item, or null if not found
+ */
+ public Item getReferencedItem(Object itemId, SQLContainer refdCont) {
+ return refdCont.getItem(getReferencedItemId(itemId, refdCont));
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ out.defaultWriteObject();
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws IOException, ClassNotFoundException {
+ in.defaultReadObject();
+ if (notificationsEnabled) {
+ /*
+ * Register instance with CacheFlushNotifier after de-serialization
+ * if notifications are enabled
+ */
+ CacheFlushNotifier.addInstance(this);
+ }
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(SQLContainer.class.getName());
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLUtil.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLUtil.java
new file mode 100644
index 0000000000..2abe45d5fc
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/SQLUtil.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+import java.io.Serializable;
+
+public class SQLUtil implements Serializable {
+ /**
+ * Escapes different special characters in strings that are passed to SQL.
+ * Replaces the following:
+ *
+ * <list>
+ * <li>' is replaced with ''</li>
+ * <li>\x00 is removed</li>
+ * <li>\ is replaced with \\</li>
+ * <li>" is replaced with \"</li>
+ * <li>\x1a is removed</li> </list>
+ *
+ * Also note! The escaping done here may or may not be enough to prevent any
+ * and all SQL injections so it is recommended to check user input before
+ * giving it to the SQLContainer/TableQuery.
+ *
+ * @param constant
+ * @return \\\'\'
+ */
+ public static String escapeSQL(String constant) {
+ if (constant == null) {
+ return null;
+ }
+ String fixedConstant = constant;
+ fixedConstant = fixedConstant.replaceAll("\\\\x00", "");
+ fixedConstant = fixedConstant.replaceAll("\\\\x1a", "");
+ fixedConstant = fixedConstant.replaceAll("'", "''");
+ fixedConstant = fixedConstant.replaceAll("\\\\", "\\\\\\\\");
+ fixedConstant = fixedConstant.replaceAll("\\\"", "\\\\\"");
+ return fixedConstant;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/TemporaryRowId.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/TemporaryRowId.java
new file mode 100644
index 0000000000..5cd3e6fac8
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/TemporaryRowId.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer;
+
+public class TemporaryRowId extends RowId {
+ private static final long serialVersionUID = -641983830469018329L;
+
+ public TemporaryRowId(Object... id) {
+ super(id);
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !(TemporaryRowId.class.equals(obj.getClass()))) {
+ return false;
+ }
+ Object[] compId = ((TemporaryRowId) obj).getId();
+ return id.equals(compId);
+ }
+
+ @Override
+ public String toString() {
+ return "Temporary row id";
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java
new file mode 100644
index 0000000000..cf50a641de
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/J2EEConnectionPool.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.connection;
+
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.sql.DataSource;
+
+public class J2EEConnectionPool implements JDBCConnectionPool {
+
+ private String dataSourceJndiName;
+
+ private DataSource dataSource = null;
+
+ public J2EEConnectionPool(DataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public J2EEConnectionPool(String dataSourceJndiName) {
+ this.dataSourceJndiName = dataSourceJndiName;
+ }
+
+ @Override
+ public Connection reserveConnection() throws SQLException {
+ Connection conn = getDataSource().getConnection();
+ conn.setAutoCommit(false);
+
+ return conn;
+ }
+
+ private DataSource getDataSource() throws SQLException {
+ if (dataSource == null) {
+ dataSource = lookupDataSource();
+ }
+ return dataSource;
+ }
+
+ private DataSource lookupDataSource() throws SQLException {
+ try {
+ InitialContext ic = new InitialContext();
+ return (DataSource) ic.lookup(dataSourceJndiName);
+ } catch (NamingException e) {
+ throw new SQLException(
+ "NamingException - Cannot connect to the database. Cause: "
+ + e.getMessage());
+ }
+ }
+
+ @Override
+ public void releaseConnection(Connection conn) {
+ if (conn != null) {
+ try {
+ conn.close();
+ } catch (SQLException e) {
+ Logger.getLogger(J2EEConnectionPool.class.getName())
+ .log(Level.FINE, "Could not release SQL connection", e);
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ dataSource = null;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/JDBCConnectionPool.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/JDBCConnectionPool.java
new file mode 100644
index 0000000000..842a264caa
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/JDBCConnectionPool.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.connection;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.SQLException;
+
+/**
+ * Interface for implementing connection pools to be used with SQLContainer.
+ */
+public interface JDBCConnectionPool extends Serializable {
+ /**
+ * Retrieves a connection.
+ *
+ * @return a usable connection to the database
+ * @throws SQLException
+ */
+ public Connection reserveConnection() throws SQLException;
+
+ /**
+ * Releases a connection that was retrieved earlier.
+ *
+ * Note that depending on implementation, the transaction possibly open in
+ * the connection may or may not be rolled back.
+ *
+ * @param conn
+ * Connection to be released
+ */
+ public void releaseConnection(Connection conn);
+
+ /**
+ * Destroys the connection pool: close() is called an all the connections in
+ * the pool, whether available or reserved.
+ *
+ * This method was added to fix PostgreSQL -related issues with connections
+ * that were left hanging 'idle'.
+ */
+ public void destroy();
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/SimpleJDBCConnectionPool.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/SimpleJDBCConnectionPool.java
new file mode 100644
index 0000000000..9cf02a9e68
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/connection/SimpleJDBCConnectionPool.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.connection;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Simple implementation of the JDBCConnectionPool interface. Handles loading
+ * the JDBC driver, setting up the connections and ensuring they are still
+ * usable upon release.
+ */
+@SuppressWarnings("serial")
+public class SimpleJDBCConnectionPool implements JDBCConnectionPool {
+
+ private int initialConnections = 5;
+ private int maxConnections = 20;
+
+ private String driverName;
+ private String connectionUri;
+ private String userName;
+ private String password;
+
+ private transient Set<Connection> availableConnections;
+ private transient Set<Connection> reservedConnections;
+
+ private boolean initialized;
+
+ public SimpleJDBCConnectionPool(String driverName, String connectionUri,
+ String userName, String password) throws SQLException {
+ if (driverName == null) {
+ throw new IllegalArgumentException(
+ "JDBC driver class name must be given.");
+ }
+ if (connectionUri == null) {
+ throw new IllegalArgumentException(
+ "Database connection URI must be given.");
+ }
+ if (userName == null) {
+ throw new IllegalArgumentException(
+ "Database username must be given.");
+ }
+ if (password == null) {
+ throw new IllegalArgumentException(
+ "Database password must be given.");
+ }
+ this.driverName = driverName;
+ this.connectionUri = connectionUri;
+ this.userName = userName;
+ this.password = password;
+
+ /* Initialize JDBC driver */
+ try {
+ Class.forName(driverName).newInstance();
+ } catch (Exception ex) {
+ throw new RuntimeException("Specified JDBC Driver: " + driverName
+ + " - initialization failed.", ex);
+ }
+ }
+
+ public SimpleJDBCConnectionPool(String driverName, String connectionUri,
+ String userName, String password, int initialConnections,
+ int maxConnections) throws SQLException {
+ this(driverName, connectionUri, userName, password);
+ this.initialConnections = initialConnections;
+ this.maxConnections = maxConnections;
+ }
+
+ private void initializeConnections() throws SQLException {
+ availableConnections = new HashSet<Connection>(initialConnections);
+ reservedConnections = new HashSet<Connection>(initialConnections);
+ for (int i = 0; i < initialConnections; i++) {
+ availableConnections.add(createConnection());
+ }
+ initialized = true;
+ }
+
+ @Override
+ public synchronized Connection reserveConnection() throws SQLException {
+ if (!initialized) {
+ initializeConnections();
+ }
+ if (availableConnections.isEmpty()) {
+ if (reservedConnections.size() < maxConnections) {
+ availableConnections.add(createConnection());
+ } else {
+ throw new SQLException("Connection limit has been reached.");
+ }
+ }
+
+ Connection c = availableConnections.iterator().next();
+ availableConnections.remove(c);
+ reservedConnections.add(c);
+
+ return c;
+ }
+
+ @Override
+ public synchronized void releaseConnection(Connection conn) {
+ if (conn == null || !initialized) {
+ return;
+ }
+ /* Try to roll back if necessary */
+ try {
+ if (!conn.getAutoCommit()) {
+ conn.rollback();
+ }
+ } catch (SQLException e) {
+ /* Roll back failed, close and discard connection */
+ try {
+ conn.close();
+ } catch (SQLException e1) {
+ /* Nothing needs to be done */
+ }
+ reservedConnections.remove(conn);
+ return;
+ }
+ reservedConnections.remove(conn);
+ availableConnections.add(conn);
+ }
+
+ private Connection createConnection() throws SQLException {
+ Connection c = DriverManager.getConnection(connectionUri, userName,
+ password);
+ c.setAutoCommit(false);
+ if (driverName.toLowerCase().contains("mysql")) {
+ try {
+ Statement s = c.createStatement();
+ s.execute("SET SESSION sql_mode = 'ANSI'");
+ s.close();
+ } catch (Exception e) {
+ // Failed to set ansi mode; continue
+ }
+ }
+ return c;
+ }
+
+ @Override
+ public void destroy() {
+ for (Connection c : availableConnections) {
+ try {
+ c.close();
+ } catch (SQLException e) {
+ // No need to do anything
+ }
+ }
+ for (Connection c : reservedConnections) {
+ try {
+ c.close();
+ } catch (SQLException e) {
+ // No need to do anything
+ }
+ }
+
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ initialized = false;
+ out.defaultWriteObject();
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/AbstractTransactionalQuery.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/AbstractTransactionalQuery.java
new file mode 100644
index 0000000000..586fe28171
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/AbstractTransactionalQuery.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import com.vaadin.data.util.sqlcontainer.connection.JDBCConnectionPool;
+
+/**
+ * Common base class for database query classes that handle connections and
+ * transactions.
+ *
+ * @author Vaadin Ltd
+ * @since 6.8.9
+ */
+public abstract class AbstractTransactionalQuery implements Serializable {
+
+ private JDBCConnectionPool connectionPool;
+ private transient Connection activeConnection;
+
+ AbstractTransactionalQuery() {
+ }
+
+ AbstractTransactionalQuery(JDBCConnectionPool connectionPool) {
+ this.connectionPool = connectionPool;
+ }
+
+ /**
+ * Reserves a connection with auto-commit off if no transaction is in
+ * progress.
+ *
+ * @throws IllegalStateException
+ * if a transaction is already open
+ * @throws SQLException
+ * if a connection could not be obtained or configured
+ */
+ public void beginTransaction()
+ throws UnsupportedOperationException, SQLException {
+ if (isInTransaction()) {
+ throw new IllegalStateException("A transaction is already active!");
+ }
+ activeConnection = connectionPool.reserveConnection();
+ activeConnection.setAutoCommit(false);
+ }
+
+ /**
+ * Commits (if not in auto-commit mode) and releases the active connection.
+ *
+ * @throws SQLException
+ * if not in a transaction managed by this query
+ */
+ public void commit() throws UnsupportedOperationException, SQLException {
+ if (!isInTransaction()) {
+ throw new SQLException("No active transaction");
+ }
+ if (!activeConnection.getAutoCommit()) {
+ activeConnection.commit();
+ }
+ connectionPool.releaseConnection(activeConnection);
+ activeConnection = null;
+ }
+
+ /**
+ * Rolls back and releases the active connection.
+ *
+ * @throws SQLException
+ * if not in a transaction managed by this query
+ */
+ public void rollback() throws UnsupportedOperationException, SQLException {
+ if (!isInTransaction()) {
+ throw new SQLException("No active transaction");
+ }
+ activeConnection.rollback();
+ connectionPool.releaseConnection(activeConnection);
+ activeConnection = null;
+ }
+
+ /**
+ * Check that a transaction is active.
+ *
+ * @throws SQLException
+ * if no active transaction
+ */
+ protected void ensureTransaction() throws SQLException {
+ if (!isInTransaction()) {
+ throw new SQLException("No active transaction!");
+ }
+ }
+
+ /**
+ * Closes a statement and a resultset, then releases the connection if it is
+ * not part of an active transaction. A failure in closing one of the
+ * parameters does not prevent closing the rest.
+ *
+ * If the statement is a {@link PreparedStatement}, its parameters are
+ * cleared prior to closing the statement.
+ *
+ * Although JDBC specification does state that closing a statement closes
+ * its result set and closing a connection closes statements and result
+ * sets, this method does try to close the result set and statement
+ * explicitly whenever not null. This can guard against bugs in certain JDBC
+ * drivers and reduce leaks in case e.g. closing the result set succeeds but
+ * closing the statement or connection fails.
+ *
+ * @param conn
+ * the connection to release
+ * @param statement
+ * the statement to close, may be null to skip closing
+ * @param rs
+ * the result set to close, may be null to skip closing
+ * @throws SQLException
+ * if closing the result set or the statement fails
+ */
+ protected void releaseConnection(Connection conn, Statement statement,
+ ResultSet rs) throws SQLException {
+ try {
+ try {
+ if (null != rs) {
+ rs.close();
+ }
+ } finally {
+ if (null != statement) {
+ if (statement instanceof PreparedStatement) {
+ try {
+ ((PreparedStatement) statement).clearParameters();
+ } catch (Exception e) {
+ // will be closed below anyway
+ }
+ }
+ statement.close();
+ }
+ }
+ } finally {
+ releaseConnection(conn);
+ }
+ }
+
+ /**
+ * Returns the currently active connection, reserves and returns a new
+ * connection if no active connection.
+ *
+ * @return previously active or newly reserved connection
+ * @throws SQLException
+ */
+ protected Connection getConnection() throws SQLException {
+ if (activeConnection != null) {
+ return activeConnection;
+ }
+ return connectionPool.reserveConnection();
+ }
+
+ protected boolean isInTransaction() {
+ return activeConnection != null;
+ }
+
+ /**
+ * Releases the connection if it is not part of an active transaction.
+ *
+ * @param conn
+ * the connection to release
+ */
+ private void releaseConnection(Connection conn) {
+ if (conn != activeConnection && conn != null) {
+ connectionPool.releaseConnection(conn);
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java
new file mode 100644
index 0000000000..8066d563a4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQuery.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+import com.vaadin.data.util.sqlcontainer.SQLContainer;
+import com.vaadin.data.util.sqlcontainer.connection.JDBCConnectionPool;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
+
+@SuppressWarnings("serial")
+public class FreeformQuery extends AbstractTransactionalQuery
+ implements QueryDelegate {
+
+ FreeformQueryDelegate delegate = null;
+ private String queryString;
+ private List<String> primaryKeyColumns;
+
+ /**
+ * Prevent no-parameters instantiation of FreeformQuery
+ */
+ @SuppressWarnings("unused")
+ private FreeformQuery() {
+ }
+
+ /**
+ * Creates a new freeform query delegate to be used with the
+ * {@link SQLContainer}.
+ *
+ * @param queryString
+ * The actual query to perform.
+ * @param primaryKeyColumns
+ * The primary key columns. Read-only mode is forced if this
+ * parameter is null or empty.
+ * @param connectionPool
+ * the JDBCConnectionPool to use to open connections to the SQL
+ * database.
+ * @deprecated As of 6.7, @see
+ * {@link FreeformQuery#FreeformQuery(String, JDBCConnectionPool, String...)}
+ */
+ @Deprecated
+ public FreeformQuery(String queryString, List<String> primaryKeyColumns,
+ JDBCConnectionPool connectionPool) {
+ super(connectionPool);
+ if (primaryKeyColumns == null) {
+ primaryKeyColumns = new ArrayList<String>();
+ }
+ if (primaryKeyColumns.contains("")) {
+ throw new IllegalArgumentException(
+ "The primary key columns contain an empty string!");
+ } else if (queryString == null || "".equals(queryString)) {
+ throw new IllegalArgumentException(
+ "The query string may not be empty or null!");
+ } else if (connectionPool == null) {
+ throw new IllegalArgumentException(
+ "The connectionPool may not be null!");
+ }
+ this.queryString = queryString;
+ this.primaryKeyColumns = Collections
+ .unmodifiableList(primaryKeyColumns);
+ }
+
+ /**
+ * Creates a new freeform query delegate to be used with the
+ * {@link SQLContainer}.
+ *
+ * @param queryString
+ * The actual query to perform.
+ * @param connectionPool
+ * the JDBCConnectionPool to use to open connections to the SQL
+ * database.
+ * @param primaryKeyColumns
+ * The primary key columns. Read-only mode is forced if none are
+ * provided. (optional)
+ */
+ public FreeformQuery(String queryString, JDBCConnectionPool connectionPool,
+ String... primaryKeyColumns) {
+ this(queryString, Arrays.asList(primaryKeyColumns), connectionPool);
+ }
+
+ /**
+ * This implementation of getCount() actually fetches all records from the
+ * database, which might be a performance issue. Override this method with a
+ * SELECT COUNT(*) ... query if this is too slow for your needs.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCount() throws SQLException {
+ // First try the delegate
+ int count = countByDelegate();
+ if (count < 0) {
+ // Couldn't use the delegate, use the bad way.
+ Statement statement = null;
+ ResultSet rs = null;
+ Connection conn = getConnection();
+ try {
+ statement = conn.createStatement(
+ ResultSet.TYPE_SCROLL_INSENSITIVE,
+ ResultSet.CONCUR_READ_ONLY);
+
+ rs = statement.executeQuery(queryString);
+ if (rs.last()) {
+ count = rs.getRow();
+ } else {
+ count = 0;
+ }
+ } finally {
+ releaseConnection(conn, statement, rs);
+ }
+ }
+ return count;
+ }
+
+ @SuppressWarnings("deprecation")
+ private int countByDelegate() throws SQLException {
+ int count = -1;
+ if (delegate == null) {
+ return count;
+ }
+ /* First try using prepared statement */
+ if (delegate instanceof FreeformStatementDelegate) {
+ try {
+ StatementHelper sh = ((FreeformStatementDelegate) delegate)
+ .getCountStatement();
+ PreparedStatement pstmt = null;
+ ResultSet rs = null;
+ Connection c = getConnection();
+ try {
+ pstmt = c.prepareStatement(sh.getQueryString());
+ sh.setParameterValuesToStatement(pstmt);
+ rs = pstmt.executeQuery();
+ if (rs.next()) {
+ count = rs.getInt(1);
+ } else {
+ // The result can be empty when using group by and there
+ // are no matches (#18043)
+ count = 0;
+ }
+ } finally {
+ releaseConnection(c, pstmt, rs);
+ }
+ return count;
+ } catch (UnsupportedOperationException e) {
+ // Count statement generation not supported
+ }
+ }
+ /* Try using regular statement */
+ try {
+ String countQuery = delegate.getCountQuery();
+ if (countQuery != null) {
+ Statement statement = null;
+ ResultSet rs = null;
+ Connection conn = getConnection();
+ try {
+ statement = conn.createStatement();
+ rs = statement.executeQuery(countQuery);
+ if (rs.next()) {
+ count = rs.getInt(1);
+ } else {
+ // The result can be empty when using group by and there
+ // are no matches (#18043)
+ count = 0;
+ }
+ return count;
+ } finally {
+ releaseConnection(conn, statement, rs);
+ }
+ }
+ } catch (UnsupportedOperationException e) {
+ // Count query generation not supported
+ }
+ return count;
+ }
+
+ /**
+ * Fetches the results for the query. This implementation always fetches the
+ * entire record set, ignoring the offset and page length parameters. In
+ * order to support lazy loading of records, you must supply a
+ * FreeformQueryDelegate that implements the
+ * FreeformQueryDelegate.getQueryString(int,int) method.
+ *
+ * @throws SQLException
+ *
+ * @see FreeformQueryDelegate#getQueryString(int, int)
+ */
+ @Override
+ @SuppressWarnings({ "deprecation", "finally" })
+ public ResultSet getResults(int offset, int pagelength)
+ throws SQLException {
+ ensureTransaction();
+ String query = queryString;
+ if (delegate != null) {
+ /* First try using prepared statement */
+ if (delegate instanceof FreeformStatementDelegate) {
+ try {
+ StatementHelper sh = ((FreeformStatementDelegate) delegate)
+ .getQueryStatement(offset, pagelength);
+ PreparedStatement pstmt = getConnection()
+ .prepareStatement(sh.getQueryString());
+ sh.setParameterValuesToStatement(pstmt);
+ return pstmt.executeQuery();
+ } catch (UnsupportedOperationException e) {
+ // Statement generation not supported, continue...
+ }
+ }
+ try {
+ query = delegate.getQueryString(offset, pagelength);
+ } catch (UnsupportedOperationException e) {
+ // This is fine, we'll just use the default queryString.
+ }
+ }
+ Statement statement = getConnection().createStatement();
+ ResultSet rs;
+ try {
+ rs = statement.executeQuery(query);
+ } catch (SQLException e) {
+ try {
+ statement.close();
+ } finally {
+ // throw the original exception even if closing the statement
+ // fails
+ throw e;
+ }
+ }
+ return rs;
+ }
+
+ @Override
+ @SuppressWarnings("deprecation")
+ public boolean implementationRespectsPagingLimits() {
+ if (delegate == null) {
+ return false;
+ }
+ /* First try using prepared statement */
+ if (delegate instanceof FreeformStatementDelegate) {
+ try {
+ StatementHelper sh = ((FreeformStatementDelegate) delegate)
+ .getCountStatement();
+ if (sh != null && sh.getQueryString() != null
+ && sh.getQueryString().length() > 0) {
+ return true;
+ }
+ } catch (UnsupportedOperationException e) {
+ // Statement generation not supported, continue...
+ }
+ }
+ try {
+ String queryString = delegate.getQueryString(0, 50);
+ return queryString != null && queryString.length() > 0;
+ } catch (UnsupportedOperationException e) {
+ return false;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setFilters(java
+ * .util.List)
+ */
+ @Override
+ public void setFilters(List<Filter> filters)
+ throws UnsupportedOperationException {
+ if (delegate != null) {
+ delegate.setFilters(filters);
+ } else if (filters != null) {
+ throw new UnsupportedOperationException(
+ "FreeFormQueryDelegate not set!");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#setOrderBy(java
+ * .util.List)
+ */
+ @Override
+ public void setOrderBy(List<OrderBy> orderBys)
+ throws UnsupportedOperationException {
+ if (delegate != null) {
+ delegate.setOrderBy(orderBys);
+ } else if (orderBys != null) {
+ throw new UnsupportedOperationException(
+ "FreeFormQueryDelegate not set!");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin
+ * .data.util.sqlcontainer.RowItem)
+ */
+ @Override
+ public int storeRow(RowItem row) throws SQLException {
+ if (!isInTransaction()) {
+ throw new IllegalStateException("No transaction is active!");
+ } else if (primaryKeyColumns.isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Cannot store items fetched with a read-only freeform query!");
+ }
+ if (delegate != null) {
+ return delegate.storeRow(getConnection(), row);
+ } else {
+ throw new UnsupportedOperationException(
+ "FreeFormQueryDelegate not set!");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#removeRow(com.
+ * vaadin .data.util.sqlcontainer.RowItem)
+ */
+ @Override
+ public boolean removeRow(RowItem row) throws SQLException {
+ if (!isInTransaction()) {
+ throw new IllegalStateException("No transaction is active!");
+ } else if (primaryKeyColumns.isEmpty()) {
+ throw new UnsupportedOperationException(
+ "Cannot remove items fetched with a read-only freeform query!");
+ }
+ if (delegate != null) {
+ return delegate.removeRow(getConnection(), row);
+ } else {
+ throw new UnsupportedOperationException(
+ "FreeFormQueryDelegate not set!");
+ }
+ }
+
+ @Override
+ public synchronized void beginTransaction()
+ throws UnsupportedOperationException, SQLException {
+ super.beginTransaction();
+ }
+
+ @Override
+ public synchronized void commit()
+ throws UnsupportedOperationException, SQLException {
+ super.commit();
+ }
+
+ @Override
+ public synchronized void rollback()
+ throws UnsupportedOperationException, SQLException {
+ super.rollback();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.sqlcontainer.query.QueryDelegate#
+ * getPrimaryKeyColumns ()
+ */
+ @Override
+ public List<String> getPrimaryKeyColumns() {
+ return primaryKeyColumns;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+
+ public FreeformQueryDelegate getDelegate() {
+ return delegate;
+ }
+
+ public void setDelegate(FreeformQueryDelegate delegate) {
+ this.delegate = delegate;
+ }
+
+ /**
+ * This implementation of the containsRowWithKey method rewrites existing
+ * WHERE clauses in the query string. The logic is, however, not very
+ * complex and some times can do the Wrong Thing<sup>TM</sup>. For the
+ * situations where this logic is not enough, you can implement the
+ * getContainsRowQueryString method in FreeformQueryDelegate and this will
+ * be used instead of the logic.
+ *
+ * @see FreeformQueryDelegate#getContainsRowQueryString(Object...)
+ *
+ */
+ @Override
+ @SuppressWarnings("deprecation")
+ public boolean containsRowWithKey(Object... keys) throws SQLException {
+ String query = null;
+ boolean contains = false;
+ if (delegate != null) {
+ if (delegate instanceof FreeformStatementDelegate) {
+ try {
+ StatementHelper sh = ((FreeformStatementDelegate) delegate)
+ .getContainsRowQueryStatement(keys);
+
+ PreparedStatement pstmt = null;
+ ResultSet rs = null;
+ Connection c = getConnection();
+ try {
+ pstmt = c.prepareStatement(sh.getQueryString());
+ sh.setParameterValuesToStatement(pstmt);
+ rs = pstmt.executeQuery();
+ contains = rs.next();
+ return contains;
+ } finally {
+ releaseConnection(c, pstmt, rs);
+ }
+ } catch (UnsupportedOperationException e) {
+ // Statement generation not supported, continue...
+ }
+ }
+ try {
+ query = delegate.getContainsRowQueryString(keys);
+ } catch (UnsupportedOperationException e) {
+ query = modifyWhereClause(keys);
+ }
+ } else {
+ query = modifyWhereClause(keys);
+ }
+ Statement statement = null;
+ ResultSet rs = null;
+ Connection conn = getConnection();
+ try {
+ statement = conn.createStatement();
+ rs = statement.executeQuery(query);
+ contains = rs.next();
+ } finally {
+ releaseConnection(conn, statement, rs);
+ }
+ return contains;
+ }
+
+ private String modifyWhereClause(Object... keys) {
+ // Build the where rules for the provided keys
+ StringBuffer where = new StringBuffer();
+ for (int ix = 0; ix < primaryKeyColumns.size(); ix++) {
+ where.append(QueryBuilder.quote(primaryKeyColumns.get(ix)));
+ if (keys[ix] == null) {
+ where.append(" IS NULL");
+ } else {
+ where.append(" = '").append(keys[ix]).append("'");
+ }
+ if (ix < primaryKeyColumns.size() - 1) {
+ where.append(" AND ");
+ }
+ }
+ // Is there already a WHERE clause in the query string?
+ int index = queryString.toLowerCase().indexOf("where ");
+ if (index > -1) {
+ // Rewrite the where clause
+ return queryString.substring(0, index) + "WHERE " + where + " AND "
+ + queryString.substring(index + 6);
+ }
+ // Append a where clause
+ return queryString + " WHERE " + where;
+ }
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ try {
+ rollback();
+ } catch (SQLException ignored) {
+ }
+ out.defaultWriteObject();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQueryDelegate.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQueryDelegate.java
new file mode 100644
index 0000000000..fac0b1ab45
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformQueryDelegate.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+
+public interface FreeformQueryDelegate extends Serializable {
+ /**
+ * Should return the SQL query string to be performed. This method is
+ * responsible for gluing together the select query from the filters and the
+ * order by conditions if these are supported.
+ *
+ * @param offset
+ * the first record (row) to fetch.
+ * @param pagelength
+ * the number of records (rows) to fetch. 0 means all records
+ * starting from offset.
+ * @deprecated As of 6.7. Implement {@link FreeformStatementDelegate}
+ * instead of {@link FreeformQueryDelegate}
+ */
+ @Deprecated
+ public String getQueryString(int offset, int limit)
+ throws UnsupportedOperationException;
+
+ /**
+ * Generates and executes a query to determine the current row count from
+ * the DB. Row count will be fetched using filters that are currently set to
+ * the QueryDelegate.
+ *
+ * @return row count
+ * @throws SQLException
+ * @deprecated As of 6.7. Implement {@link FreeformStatementDelegate}
+ * instead of {@link FreeformQueryDelegate}
+ */
+ @Deprecated
+ public String getCountQuery() throws UnsupportedOperationException;
+
+ /**
+ * Sets the filters to apply when performing the SQL query. These are
+ * translated into a WHERE clause. Default filtering mode will be used.
+ *
+ * @param filters
+ * The filters to apply.
+ * @throws UnsupportedOperationException
+ * if the implementation doesn't support filtering.
+ */
+ public void setFilters(List<Filter> filters)
+ throws UnsupportedOperationException;
+
+ /**
+ * Sets the order in which to retrieve rows from the database. The result
+ * can be ordered by zero or more columns and each column can be in
+ * ascending or descending order. These are translated into an ORDER BY
+ * clause in the SQL query.
+ *
+ * @param orderBys
+ * A list of the OrderBy conditions.
+ * @throws UnsupportedOperationException
+ * if the implementation doesn't support ordering.
+ */
+ public void setOrderBy(List<OrderBy> orderBys)
+ throws UnsupportedOperationException;
+
+ /**
+ * Stores a row in the database. The implementation of this interface
+ * decides how to identify whether to store a new row or update an existing
+ * one.
+ *
+ * @param conn
+ * the JDBC connection to use
+ * @param row
+ * RowItem to be stored or updated.
+ * @throws UnsupportedOperationException
+ * if the implementation is read only.
+ * @throws SQLException
+ */
+ public int storeRow(Connection conn, RowItem row)
+ throws UnsupportedOperationException, SQLException;
+
+ /**
+ * Removes the given RowItem from the database.
+ *
+ * @param conn
+ * the JDBC connection to use
+ * @param row
+ * RowItem to be removed
+ * @return true on success
+ * @throws UnsupportedOperationException
+ * @throws SQLException
+ */
+ public boolean removeRow(Connection conn, RowItem row)
+ throws UnsupportedOperationException, SQLException;
+
+ /**
+ * Generates an SQL Query string that allows the user of the FreeformQuery
+ * class to customize the query string used by the
+ * FreeformQuery.containsRowWithKeys() method. This is useful for cases when
+ * the logic in the containsRowWithKeys method is not enough to support more
+ * complex free form queries.
+ *
+ * @param keys
+ * the values of the primary keys
+ * @throws UnsupportedOperationException
+ * to use the default logic in FreeformQuery
+ * @deprecated As of 6.7. Implement {@link FreeformStatementDelegate}
+ * instead of {@link FreeformQueryDelegate}
+ */
+ @Deprecated
+ public String getContainsRowQueryString(Object... keys)
+ throws UnsupportedOperationException;
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformStatementDelegate.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformStatementDelegate.java
new file mode 100644
index 0000000000..884a303684
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/FreeformStatementDelegate.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+/**
+ * FreeformStatementDelegate is an extension to FreeformQueryDelegate that
+ * provides definitions for methods that produce StatementHelper objects instead
+ * of basic query strings. This allows the FreeformQuery query delegate to use
+ * PreparedStatements instead of regular Statement when accessing the database.
+ *
+ * Due to the injection protection and other benefits of prepared statements, it
+ * is advisable to implement this interface instead of the FreeformQueryDelegate
+ * whenever possible.
+ */
+public interface FreeformStatementDelegate extends FreeformQueryDelegate {
+ /**
+ * Should return a new instance of StatementHelper that contains the query
+ * string and parameter values required to create a PreparedStatement. This
+ * method is responsible for gluing together the select query from the
+ * filters and the order by conditions if these are supported.
+ *
+ * @param offset
+ * the first record (row) to fetch.
+ * @param pagelength
+ * the number of records (rows) to fetch. 0 means all records
+ * starting from offset.
+ */
+ public StatementHelper getQueryStatement(int offset, int limit)
+ throws UnsupportedOperationException;
+
+ /**
+ * Should return a new instance of StatementHelper that contains the query
+ * string and parameter values required to create a PreparedStatement that
+ * will fetch the row count from the DB. Row count should be fetched using
+ * filters that are currently set to the QueryDelegate.
+ */
+ public StatementHelper getCountStatement()
+ throws UnsupportedOperationException;
+
+ /**
+ * Should return a new instance of StatementHelper that contains the query
+ * string and parameter values required to create a PreparedStatement used
+ * by the FreeformQuery.containsRowWithKeys() method. This is useful for
+ * cases when the default logic in said method is not enough to support more
+ * complex free form queries.
+ *
+ * @param keys
+ * the values of the primary keys
+ * @throws UnsupportedOperationException
+ * to use the default logic in FreeformQuery
+ */
+ public StatementHelper getContainsRowQueryStatement(Object... keys)
+ throws UnsupportedOperationException;
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/OrderBy.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/OrderBy.java
new file mode 100644
index 0000000000..ed57967772
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/OrderBy.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.Serializable;
+
+/**
+ * OrderBy represents a sorting rule to be applied to a query made by the
+ * SQLContainer's QueryDelegate.
+ *
+ * The sorting rule is simple and contains only the affected column's name and
+ * the direction of the sort.
+ */
+public class OrderBy implements Serializable {
+ private String column;
+ private boolean isAscending;
+
+ /**
+ * Prevent instantiation without required parameters.
+ */
+ @SuppressWarnings("unused")
+ private OrderBy() {
+ }
+
+ public OrderBy(String column, boolean isAscending) {
+ setColumn(column);
+ setAscending(isAscending);
+ }
+
+ public void setColumn(String column) {
+ this.column = column;
+ }
+
+ public String getColumn() {
+ return column;
+ }
+
+ public void setAscending(boolean isAscending) {
+ this.isAscending = isAscending;
+ }
+
+ public boolean isAscending() {
+ return isAscending;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/QueryDelegate.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/QueryDelegate.java
new file mode 100644
index 0000000000..16e04a7da0
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/QueryDelegate.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.Serializable;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.RowId;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+
+public interface QueryDelegate extends Serializable {
+ /**
+ * Generates and executes a query to determine the current row count from
+ * the DB. Row count will be fetched using filters that are currently set to
+ * the QueryDelegate.
+ *
+ * @return row count
+ * @throws SQLException
+ */
+ public int getCount() throws SQLException;
+
+ /**
+ * Executes a paged SQL query and returns the ResultSet. The query is
+ * defined through implementations of this QueryDelegate interface.
+ *
+ * @param offset
+ * the first item of the page to load
+ * @param pagelength
+ * the length of the page to load
+ * @return a ResultSet containing the rows of the page
+ * @throws SQLException
+ * if the database access fails.
+ */
+ public ResultSet getResults(int offset, int pagelength) throws SQLException;
+
+ /**
+ * Allows the SQLContainer implementation to check whether the QueryDelegate
+ * implementation implements paging in the getResults method.
+ *
+ * @see QueryDelegate#getResults(int, int)
+ *
+ * @return true if the delegate implements paging
+ */
+ public boolean implementationRespectsPagingLimits();
+
+ /**
+ * Sets the filters to apply when performing the SQL query. These are
+ * translated into a WHERE clause. Default filtering mode will be used.
+ *
+ * @param filters
+ * The filters to apply.
+ * @throws UnsupportedOperationException
+ * if the implementation doesn't support filtering.
+ */
+ public void setFilters(List<Filter> filters)
+ throws UnsupportedOperationException;
+
+ /**
+ * Sets the order in which to retrieve rows from the database. The result
+ * can be ordered by zero or more columns and each column can be in
+ * ascending or descending order. These are translated into an ORDER BY
+ * clause in the SQL query.
+ *
+ * @param orderBys
+ * A list of the OrderBy conditions.
+ * @throws UnsupportedOperationException
+ * if the implementation doesn't support ordering.
+ */
+ public void setOrderBy(List<OrderBy> orderBys)
+ throws UnsupportedOperationException;
+
+ /**
+ * Stores a row in the database. The implementation of this interface
+ * decides how to identify whether to store a new row or update an existing
+ * one.
+ *
+ * @param columnToValueMap
+ * A map containing the values for all columns to be stored or
+ * updated.
+ * @return the number of affected rows in the database table
+ * @throws UnsupportedOperationException
+ * if the implementation is read only.
+ */
+ public int storeRow(RowItem row)
+ throws UnsupportedOperationException, SQLException;
+
+ /**
+ * Removes the given RowItem from the database.
+ *
+ * @param row
+ * RowItem to be removed
+ * @return true on success
+ * @throws UnsupportedOperationException
+ * @throws SQLException
+ */
+ public boolean removeRow(RowItem row)
+ throws UnsupportedOperationException, SQLException;
+
+ /**
+ * Starts a new database transaction. Used when storing multiple changes.
+ *
+ * Note that if a transaction is already open, it will be rolled back when a
+ * new transaction is started.
+ *
+ * @throws SQLException
+ * if the database access fails.
+ */
+ public void beginTransaction() throws SQLException;
+
+ /**
+ * Commits a transaction. If a transaction is not open nothing should
+ * happen.
+ *
+ * @throws SQLException
+ * if the database access fails.
+ */
+ public void commit() throws SQLException;
+
+ /**
+ * Rolls a transaction back. If a transaction is not open nothing should
+ * happen.
+ *
+ * @throws SQLException
+ * if the database access fails.
+ */
+ public void rollback() throws SQLException;
+
+ /**
+ * Returns a list of primary key column names. The list is either fetched
+ * from the database (TableQuery) or given as an argument depending on
+ * implementation.
+ *
+ * @return
+ */
+ public List<String> getPrimaryKeyColumns();
+
+ /**
+ * Performs a query to find out whether the SQL table contains a row with
+ * the given set of primary keys.
+ *
+ * @param keys
+ * the primary keys
+ * @return true if the SQL table contains a row with the provided keys
+ * @throws SQLException
+ */
+ public boolean containsRowWithKey(Object... keys) throws SQLException;
+
+ /************************/
+ /** ROWID CHANGE EVENT **/
+ /************************/
+
+ /**
+ * An <code>Event</code> object specifying the old and new RowId of an added
+ * item after the addition has been successfully committed.
+ */
+ public interface RowIdChangeEvent extends Serializable {
+ /**
+ * Gets the old (temporary) RowId of the added row that raised this
+ * event.
+ *
+ * @return old RowId
+ */
+ public RowId getOldRowId();
+
+ /**
+ * Gets the new, possibly database assigned RowId of the added row that
+ * raised this event.
+ *
+ * @return new RowId
+ */
+ public RowId getNewRowId();
+ }
+
+ /** RowId change listener interface. */
+ public interface RowIdChangeListener extends Serializable {
+ /**
+ * Lets the listener know that a RowId has been changed.
+ *
+ * @param event
+ */
+ public void rowIdChange(QueryDelegate.RowIdChangeEvent event);
+ }
+
+ /**
+ * The interface for adding and removing <code>RowIdChangeEvent</code>
+ * listeners. By implementing this interface a class explicitly announces
+ * that it will generate a <code>RowIdChangeEvent</code> when it performs a
+ * database commit that may change the RowId.
+ */
+ public interface RowIdChangeNotifier extends Serializable {
+ /**
+ * Adds a RowIdChangeListener for the object.
+ *
+ * @param listener
+ * listener to be added
+ */
+ public void addRowIdChangeListener(
+ QueryDelegate.RowIdChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addRowIdChangeListener(RowIdChangeListener)}
+ **/
+ @Deprecated
+ public void addListener(QueryDelegate.RowIdChangeListener listener);
+
+ /**
+ * Removes the specified RowIdChangeListener from the object.
+ *
+ * @param listener
+ * listener to be removed
+ */
+ public void removeRowIdChangeListener(
+ QueryDelegate.RowIdChangeListener listener);
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeRowIdChangeListener(RowIdChangeListener)}
+ **/
+ @Deprecated
+ public void removeListener(QueryDelegate.RowIdChangeListener listener);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/TableQuery.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/TableQuery.java
new file mode 100644
index 0000000000..959c494634
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/TableQuery.java
@@ -0,0 +1,869 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query;
+
+import java.io.IOException;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Compare.Equal;
+import com.vaadin.data.util.sqlcontainer.ColumnProperty;
+import com.vaadin.data.util.sqlcontainer.OptimisticLockException;
+import com.vaadin.data.util.sqlcontainer.RowId;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+import com.vaadin.data.util.sqlcontainer.SQLUtil;
+import com.vaadin.data.util.sqlcontainer.TemporaryRowId;
+import com.vaadin.data.util.sqlcontainer.connection.JDBCConnectionPool;
+import com.vaadin.data.util.sqlcontainer.query.generator.DefaultSQLGenerator;
+import com.vaadin.data.util.sqlcontainer.query.generator.MSSQLGenerator;
+import com.vaadin.data.util.sqlcontainer.query.generator.SQLGenerator;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+@SuppressWarnings("serial")
+public class TableQuery extends AbstractTransactionalQuery
+ implements QueryDelegate, QueryDelegate.RowIdChangeNotifier {
+
+ /**
+ * Table name (without catalog or schema information).
+ */
+ private String tableName;
+ private String catalogName;
+ private String schemaName;
+ /**
+ * Cached concatenated version of the table name.
+ */
+ private String fullTableName;
+ /**
+ * Primary key column name(s) in the table.
+ */
+ private List<String> primaryKeyColumns;
+ /**
+ * Version column name in the table.
+ */
+ private String versionColumn;
+
+ /** Currently set Filters and OrderBys */
+ private List<Filter> filters;
+ private List<OrderBy> orderBys;
+
+ /** SQLGenerator instance to use for generating queries */
+ private SQLGenerator sqlGenerator;
+
+ /** Row ID change listeners */
+ private LinkedList<RowIdChangeListener> rowIdChangeListeners;
+ /** Row ID change events, stored until commit() is called */
+ private final List<RowIdChangeEvent> bufferedEvents = new ArrayList<RowIdChangeEvent>();
+
+ /** Set to true to output generated SQL Queries to System.out */
+ private final boolean debug = false;
+
+ /**
+ * Creates a new TableQuery using the given connection pool, SQL generator
+ * and table name to fetch the data from. All parameters must be non-null.
+ *
+ * The table name must be a simple name with no catalog or schema
+ * information. If those are needed, use
+ * {@link #TableQuery(String, String, String, JDBCConnectionPool, SQLGenerator)}
+ * .
+ *
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ * @param sqlGenerator
+ * SQL query generator implementation
+ */
+ public TableQuery(String tableName, JDBCConnectionPool connectionPool,
+ SQLGenerator sqlGenerator) {
+ this(null, null, tableName, connectionPool, sqlGenerator);
+ }
+
+ /**
+ * Creates a new TableQuery using the given connection pool, SQL generator
+ * and table name to fetch the data from. Catalog and schema names can be
+ * null, all other parameters must be non-null.
+ *
+ * @param catalogName
+ * Name of the database catalog (can be null)
+ * @param schemaName
+ * Name of the database schema (can be null)
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ * @param sqlGenerator
+ * SQL query generator implementation
+ * @since 7.1
+ */
+ public TableQuery(String catalogName, String schemaName, String tableName,
+ JDBCConnectionPool connectionPool, SQLGenerator sqlGenerator) {
+ this(catalogName, schemaName, tableName, connectionPool, sqlGenerator,
+ true);
+ }
+
+ /**
+ * Creates a new TableQuery using the given connection pool and table name
+ * to fetch the data from. All parameters must be non-null. The default SQL
+ * generator will be used for queries.
+ *
+ * The table name must be a simple name with no catalog or schema
+ * information. If those are needed, use
+ * {@link #TableQuery(String, String, String, JDBCConnectionPool, SQLGenerator)}
+ * .
+ *
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ */
+ public TableQuery(String tableName, JDBCConnectionPool connectionPool) {
+ this(tableName, connectionPool, new DefaultSQLGenerator());
+ }
+
+ /**
+ * Creates a new TableQuery using the given connection pool, SQL generator
+ * and table name to fetch the data from. Catalog and schema names can be
+ * null, all other parameters must be non-null.
+ *
+ * @param catalogName
+ * Name of the database catalog (can be null)
+ * @param schemaName
+ * Name of the database schema (can be null)
+ * @param tableName
+ * Name of the database table to connect to
+ * @param connectionPool
+ * Connection pool for accessing the database
+ * @param sqlGenerator
+ * SQL query generator implementation
+ * @param escapeNames
+ * true to escape special characters in catalog, schema and table
+ * names, false to use the names as-is
+ * @since 7.1
+ */
+ protected TableQuery(String catalogName, String schemaName,
+ String tableName, JDBCConnectionPool connectionPool,
+ SQLGenerator sqlGenerator, boolean escapeNames) {
+ super(connectionPool);
+ if (tableName == null || tableName.trim().length() < 1
+ || connectionPool == null || sqlGenerator == null) {
+ throw new IllegalArgumentException(
+ "Table name, connection pool and SQL generator parameters must be non-null and non-empty.");
+ }
+ if (escapeNames) {
+ this.catalogName = SQLUtil.escapeSQL(catalogName);
+ this.schemaName = SQLUtil.escapeSQL(schemaName);
+ this.tableName = SQLUtil.escapeSQL(tableName);
+ } else {
+ this.catalogName = catalogName;
+ this.schemaName = schemaName;
+ this.tableName = tableName;
+ }
+ this.sqlGenerator = sqlGenerator;
+ fetchMetaData();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getCount()
+ */
+ @Override
+ public int getCount() throws SQLException {
+ getLogger().log(Level.FINE, "Fetching count...");
+ StatementHelper sh = sqlGenerator.generateSelectQuery(
+ getFullTableName(), filters, null, 0, 0, "COUNT(*)");
+ boolean shouldCloseTransaction = false;
+ if (!isInTransaction()) {
+ shouldCloseTransaction = true;
+ beginTransaction();
+ }
+ ResultSet r = null;
+ int count = -1;
+ try {
+ r = executeQuery(sh);
+ r.next();
+ count = r.getInt(1);
+ } finally {
+ try {
+ if (r != null) {
+ // Do not release connection, it is done in commit()
+ releaseConnection(null, r.getStatement(), r);
+ }
+ } finally {
+ if (shouldCloseTransaction) {
+ commit();
+ }
+ }
+ }
+ return count;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#getResults(int,
+ * int)
+ */
+ @Override
+ public ResultSet getResults(int offset, int pagelength)
+ throws SQLException {
+ StatementHelper sh;
+ /*
+ * If no ordering is explicitly set, results will be ordered by the
+ * first primary key column.
+ */
+ if (orderBys == null || orderBys.isEmpty()) {
+ List<OrderBy> ob = new ArrayList<OrderBy>();
+ for (int i = 0; i < primaryKeyColumns.size(); i++) {
+ ob.add(new OrderBy(primaryKeyColumns.get(i), true));
+ }
+ sh = sqlGenerator.generateSelectQuery(getFullTableName(), filters,
+ ob, offset, pagelength, null);
+ } else {
+ sh = sqlGenerator.generateSelectQuery(getFullTableName(), filters,
+ orderBys, offset, pagelength, null);
+ }
+ return executeQuery(sh);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#
+ * implementationRespectsPagingLimits()
+ */
+ @Override
+ public boolean implementationRespectsPagingLimits() {
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#storeRow(com.vaadin
+ * .addon.sqlcontainer.RowItem)
+ */
+ @Override
+ public int storeRow(RowItem row)
+ throws UnsupportedOperationException, SQLException {
+ if (row == null) {
+ throw new IllegalArgumentException(
+ "Row argument must be non-null.");
+ }
+ StatementHelper sh;
+ int result = 0;
+ if (row.getId() instanceof TemporaryRowId) {
+ setVersionColumnFlagInProperty(row);
+ sh = sqlGenerator.generateInsertQuery(getFullTableName(), row);
+ result = executeUpdateReturnKeys(sh, row);
+ } else {
+ setVersionColumnFlagInProperty(row);
+ sh = sqlGenerator.generateUpdateQuery(getFullTableName(), row);
+ result = executeUpdate(sh);
+ }
+ if (versionColumn != null && result == 0) {
+ throw new OptimisticLockException(
+ "Someone else changed the row that was being updated.",
+ row.getId());
+ }
+ return result;
+ }
+
+ private void setVersionColumnFlagInProperty(RowItem row) {
+ ColumnProperty versionProperty = (ColumnProperty) row
+ .getItemProperty(versionColumn);
+ if (versionProperty != null) {
+ versionProperty.setVersionColumn(true);
+ }
+ }
+
+ /**
+ * Inserts the given row in the database table immediately. Begins and
+ * commits the transaction needed. This method was added specifically to
+ * solve the problem of returning the final RowId immediately on the
+ * SQLContainer.addItem() call when auto commit mode is enabled in the
+ * SQLContainer.
+ *
+ * @param row
+ * RowItem to add to the database
+ * @return Final RowId of the added row
+ * @throws SQLException
+ */
+ public RowId storeRowImmediately(RowItem row) throws SQLException {
+ beginTransaction();
+ /* Set version column, if one is provided */
+ setVersionColumnFlagInProperty(row);
+ /* Generate query */
+ StatementHelper sh = sqlGenerator
+ .generateInsertQuery(getFullTableName(), row);
+ Connection connection = null;
+ PreparedStatement pstmt = null;
+ ResultSet generatedKeys = null;
+ connection = getConnection();
+ try {
+ pstmt = connection.prepareStatement(sh.getQueryString(),
+ primaryKeyColumns.toArray(new String[0]));
+ sh.setParameterValuesToStatement(pstmt);
+ getLogger().log(Level.FINE, "DB -> {0}", sh.getQueryString());
+ int result = pstmt.executeUpdate();
+ RowId newId = null;
+ if (result > 0) {
+ /*
+ * If affected rows exist, we'll get the new RowId, commit the
+ * transaction and return the new RowId.
+ */
+ generatedKeys = pstmt.getGeneratedKeys();
+ newId = getNewRowId(row, generatedKeys);
+ }
+ // transaction has to be closed in any case
+ commit();
+ return newId;
+ } finally {
+ releaseConnection(connection, pstmt, generatedKeys);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#setFilters(java.util
+ * .List)
+ */
+ @Override
+ public void setFilters(List<Filter> filters)
+ throws UnsupportedOperationException {
+ if (filters == null) {
+ this.filters = null;
+ return;
+ }
+ this.filters = Collections.unmodifiableList(filters);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#setOrderBy(java.util
+ * .List)
+ */
+ @Override
+ public void setOrderBy(List<OrderBy> orderBys)
+ throws UnsupportedOperationException {
+ if (orderBys == null) {
+ this.orderBys = null;
+ return;
+ }
+ this.orderBys = Collections.unmodifiableList(orderBys);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#beginTransaction()
+ */
+ @Override
+ public void beginTransaction()
+ throws UnsupportedOperationException, SQLException {
+ getLogger().log(Level.FINE, "DB -> begin transaction");
+ super.beginTransaction();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#commit()
+ */
+ @Override
+ public void commit() throws UnsupportedOperationException, SQLException {
+ getLogger().log(Level.FINE, "DB -> commit");
+ super.commit();
+
+ /* Handle firing row ID change events */
+ RowIdChangeEvent[] unFiredEvents = bufferedEvents
+ .toArray(new RowIdChangeEvent[] {});
+ bufferedEvents.clear();
+ if (rowIdChangeListeners != null && !rowIdChangeListeners.isEmpty()) {
+ for (RowIdChangeListener r : rowIdChangeListeners) {
+ for (RowIdChangeEvent e : unFiredEvents) {
+ r.rowIdChange(e);
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.QueryDelegate#rollback()
+ */
+ @Override
+ public void rollback() throws UnsupportedOperationException, SQLException {
+ getLogger().log(Level.FINE, "DB -> rollback");
+ super.rollback();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#getPrimaryKeyColumns()
+ */
+ @Override
+ public List<String> getPrimaryKeyColumns() {
+ return Collections.unmodifiableList(primaryKeyColumns);
+ }
+
+ public String getVersionColumn() {
+ return versionColumn;
+ }
+
+ public void setVersionColumn(String column) {
+ versionColumn = column;
+ }
+
+ /**
+ * Returns the table name for the query without catalog and schema
+ * information.
+ *
+ * @return table name, not null
+ */
+ public String getTableName() {
+ return tableName;
+ }
+
+ /**
+ * Returns the catalog name for the query.
+ *
+ * @return catalog name, can be null
+ * @since 7.1
+ */
+ public String getCatalogName() {
+ return catalogName;
+ }
+
+ /**
+ * Returns the catalog name for the query.
+ *
+ * @return catalog name, can be null
+ * @since 7.1
+ */
+ public String getSchemaName() {
+ return schemaName;
+ }
+
+ /**
+ * Returns the complete table name obtained by concatenation of the catalog
+ * and schema names (if any) and the table name.
+ *
+ * This method can be overridden if customization is needed.
+ *
+ * @return table name in the form it should be used in query and update
+ * statements
+ * @since 7.1
+ */
+ protected String getFullTableName() {
+ if (fullTableName == null) {
+ StringBuilder sb = new StringBuilder();
+ if (catalogName != null) {
+ sb.append(catalogName).append(".");
+ }
+ if (schemaName != null) {
+ sb.append(schemaName).append(".");
+ }
+ sb.append(tableName);
+ fullTableName = sb.toString();
+ }
+ return fullTableName;
+ }
+
+ public SQLGenerator getSqlGenerator() {
+ return sqlGenerator;
+ }
+
+ /**
+ * Executes the given query string using either the active connection if a
+ * transaction is already open, or a new connection from this query's
+ * connection pool.
+ *
+ * @param sh
+ * an instance of StatementHelper, containing the query string
+ * and parameter values.
+ * @return ResultSet of the query
+ * @throws SQLException
+ */
+ private ResultSet executeQuery(StatementHelper sh) throws SQLException {
+ ensureTransaction();
+ Connection connection = getConnection();
+ PreparedStatement pstmt = null;
+ try {
+ pstmt = connection.prepareStatement(sh.getQueryString());
+ sh.setParameterValuesToStatement(pstmt);
+ getLogger().log(Level.FINE, "DB -> {0}", sh.getQueryString());
+ return pstmt.executeQuery();
+ } catch (SQLException e) {
+ releaseConnection(null, pstmt, null);
+ throw e;
+ }
+ }
+
+ /**
+ * Executes the given update query string using either the active connection
+ * if a transaction is already open, or a new connection from this query's
+ * connection pool.
+ *
+ * @param sh
+ * an instance of StatementHelper, containing the query string
+ * and parameter values.
+ * @return Number of affected rows
+ * @throws SQLException
+ */
+ private int executeUpdate(StatementHelper sh) throws SQLException {
+ PreparedStatement pstmt = null;
+ Connection connection = null;
+ try {
+ connection = getConnection();
+ pstmt = connection.prepareStatement(sh.getQueryString());
+ sh.setParameterValuesToStatement(pstmt);
+ getLogger().log(Level.FINE, "DB -> {0}", sh.getQueryString());
+ int retval = pstmt.executeUpdate();
+ return retval;
+ } finally {
+ releaseConnection(connection, pstmt, null);
+ }
+ }
+
+ /**
+ * Executes the given update query string using either the active connection
+ * if a transaction is already open, or a new connection from this query's
+ * connection pool.
+ *
+ * Additionally adds a new RowIdChangeEvent to the event buffer.
+ *
+ * @param sh
+ * an instance of StatementHelper, containing the query string
+ * and parameter values.
+ * @param row
+ * the row item to update
+ * @return Number of affected rows
+ * @throws SQLException
+ */
+ private int executeUpdateReturnKeys(StatementHelper sh, RowItem row)
+ throws SQLException {
+ PreparedStatement pstmt = null;
+ ResultSet genKeys = null;
+ Connection connection = null;
+ try {
+ connection = getConnection();
+ pstmt = connection.prepareStatement(sh.getQueryString(),
+ primaryKeyColumns.toArray(new String[0]));
+ sh.setParameterValuesToStatement(pstmt);
+ getLogger().log(Level.FINE, "DB -> {0}", sh.getQueryString());
+ int result = pstmt.executeUpdate();
+ genKeys = pstmt.getGeneratedKeys();
+ RowId newId = getNewRowId(row, genKeys);
+ bufferedEvents.add(new RowIdChangeEvent(row.getId(), newId));
+ return result;
+ } finally {
+ releaseConnection(connection, pstmt, genKeys);
+ }
+ }
+
+ /**
+ * Fetches name(s) of primary key column(s) from DB metadata.
+ *
+ * Also tries to get the escape string to be used in search strings.
+ */
+ private void fetchMetaData() {
+ Connection connection = null;
+ ResultSet rs = null;
+ ResultSet tables = null;
+ try {
+ connection = getConnection();
+ DatabaseMetaData dbmd = connection.getMetaData();
+ if (dbmd != null) {
+ tables = dbmd.getTables(catalogName, schemaName, tableName,
+ null);
+ if (!tables.next()) {
+ String catalog = (catalogName != null)
+ ? catalogName.toUpperCase() : null;
+ String schema = (schemaName != null)
+ ? schemaName.toUpperCase() : null;
+ tables = dbmd.getTables(catalog, schema,
+ tableName.toUpperCase(), null);
+ if (!tables.next()) {
+ throw new IllegalArgumentException(
+ "Table with the name \"" + getFullTableName()
+ + "\" was not found. Check your database contents.");
+ } else {
+ catalogName = catalog;
+ schemaName = schema;
+ tableName = tableName.toUpperCase();
+ }
+ }
+ tables.close();
+ rs = dbmd.getPrimaryKeys(catalogName, schemaName, tableName);
+ List<String> names = new ArrayList<String>();
+ while (rs.next()) {
+ names.add(rs.getString("COLUMN_NAME"));
+ }
+ rs.close();
+ if (!names.isEmpty()) {
+ primaryKeyColumns = names;
+ }
+ if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Primary key constraints have not been defined for the table \""
+ + getFullTableName()
+ + "\". Use FreeFormQuery to access this table.");
+ }
+ for (String colName : primaryKeyColumns) {
+ if (colName.equalsIgnoreCase("rownum")) {
+ if (getSqlGenerator() instanceof MSSQLGenerator
+ || getSqlGenerator() instanceof MSSQLGenerator) {
+ throw new IllegalArgumentException(
+ "When using Oracle or MSSQL, a primary key column"
+ + " named \'rownum\' is not allowed!");
+ }
+ }
+ }
+ }
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ } finally {
+ try {
+ releaseConnection(connection, null, rs);
+ } catch (SQLException ignore) {
+ } finally {
+ try {
+ if (tables != null) {
+ tables.close();
+ }
+ } catch (SQLException ignore) {
+ }
+ }
+ }
+ }
+
+ private RowId getNewRowId(RowItem row, ResultSet genKeys) {
+ try {
+ /* Fetch primary key values and generate a map out of them. */
+ Map<String, Object> values = new HashMap<String, Object>();
+ ResultSetMetaData rsmd = genKeys.getMetaData();
+ int colCount = rsmd.getColumnCount();
+ if (genKeys.next()) {
+ for (int i = 1; i <= colCount; i++) {
+ values.put(rsmd.getColumnName(i), genKeys.getObject(i));
+ }
+ }
+ /* Generate new RowId */
+ List<Object> newRowId = new ArrayList<Object>();
+ if (values.size() == 1) {
+ if (primaryKeyColumns.size() == 1) {
+ newRowId.add(values.get(values.keySet().iterator().next()));
+ } else {
+ for (String s : primaryKeyColumns) {
+ if (!((ColumnProperty) row.getItemProperty(s))
+ .isReadOnlyChangeAllowed()) {
+ newRowId.add(values
+ .get(values.keySet().iterator().next()));
+ } else {
+ newRowId.add(values.get(s));
+ }
+ }
+ }
+ } else {
+ for (String s : primaryKeyColumns) {
+ newRowId.add(values.get(s));
+ }
+ }
+ return new RowId(newRowId.toArray());
+ } catch (Exception e) {
+ getLogger().log(Level.FINE,
+ "Failed to fetch key values on insert: {0}",
+ e.getMessage());
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#removeRow(com.vaadin
+ * .addon.sqlcontainer.RowItem)
+ */
+ @Override
+ public boolean removeRow(RowItem row)
+ throws UnsupportedOperationException, SQLException {
+ if (getLogger().isLoggable(Level.FINE)) {
+ getLogger().log(Level.FINE, "Removing row with id: {0}",
+ row.getId().getId()[0]);
+ }
+ if (executeUpdate(sqlGenerator.generateDeleteQuery(getFullTableName(),
+ primaryKeyColumns, versionColumn, row)) == 1) {
+ return true;
+ }
+ if (versionColumn != null) {
+ throw new OptimisticLockException(
+ "Someone else changed the row that was being deleted.",
+ row.getId());
+ }
+ return false;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.sqlcontainer.query.QueryDelegate#containsRowWithKey(
+ * java.lang.Object[])
+ */
+ @Override
+ public boolean containsRowWithKey(Object... keys) throws SQLException {
+ ArrayList<Filter> filtersAndKeys = new ArrayList<Filter>();
+ if (filters != null) {
+ filtersAndKeys.addAll(filters);
+ }
+ int ix = 0;
+ for (String colName : primaryKeyColumns) {
+ filtersAndKeys.add(new Equal(colName, keys[ix]));
+ ix++;
+ }
+ StatementHelper sh = sqlGenerator.generateSelectQuery(
+ getFullTableName(), filtersAndKeys, orderBys, 0, 0, "*");
+
+ boolean shouldCloseTransaction = false;
+ if (!isInTransaction()) {
+ shouldCloseTransaction = true;
+ beginTransaction();
+ }
+ ResultSet rs = null;
+ try {
+ rs = executeQuery(sh);
+ boolean contains = rs.next();
+ return contains;
+ } finally {
+ try {
+ if (rs != null) {
+ // Do not release connection, it is done in commit()
+ releaseConnection(null, rs.getStatement(), rs);
+ }
+ } finally {
+ if (shouldCloseTransaction) {
+ commit();
+ }
+ }
+ }
+ }
+
+ /**
+ * Custom writeObject to call rollback() if object is serialized.
+ */
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws IOException {
+ try {
+ rollback();
+ } catch (SQLException ignored) {
+ }
+ out.defaultWriteObject();
+ }
+
+ /**
+ * Simple RowIdChangeEvent implementation.
+ */
+ public static class RowIdChangeEvent extends EventObject
+ implements QueryDelegate.RowIdChangeEvent {
+ private final RowId oldId;
+ private final RowId newId;
+
+ private RowIdChangeEvent(RowId oldId, RowId newId) {
+ super(oldId);
+ this.oldId = oldId;
+ this.newId = newId;
+ }
+
+ @Override
+ public RowId getNewRowId() {
+ return newId;
+ }
+
+ @Override
+ public RowId getOldRowId() {
+ return oldId;
+ }
+ }
+
+ /**
+ * Adds RowIdChangeListener to this query
+ */
+ @Override
+ public void addRowIdChangeListener(RowIdChangeListener listener) {
+ if (rowIdChangeListeners == null) {
+ rowIdChangeListeners = new LinkedList<QueryDelegate.RowIdChangeListener>();
+ }
+ rowIdChangeListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addRowIdChangeListener(com.vaadin.data.util.sqlcontainer.query.QueryDelegate.RowIdChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(RowIdChangeListener listener) {
+ addRowIdChangeListener(listener);
+ }
+
+ /**
+ * Removes the given RowIdChangeListener from this query
+ */
+ @Override
+ public void removeRowIdChangeListener(RowIdChangeListener listener) {
+ if (rowIdChangeListeners != null) {
+ rowIdChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeRowIdChangeListener(com.vaadin.data.util.sqlcontainer.query.QueryDelegate.RowIdChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(RowIdChangeListener listener) {
+ removeRowIdChangeListener(listener);
+ }
+
+ private static final Logger getLogger() {
+ return Logger.getLogger(TableQuery.class.getName());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/DefaultSQLGenerator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/DefaultSQLGenerator.java
new file mode 100644
index 0000000000..9c05545e41
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/DefaultSQLGenerator.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.ColumnProperty;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+import com.vaadin.data.util.sqlcontainer.SQLUtil;
+import com.vaadin.data.util.sqlcontainer.TemporaryRowId;
+import com.vaadin.data.util.sqlcontainer.query.OrderBy;
+import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
+import com.vaadin.data.util.sqlcontainer.query.generator.filter.StringDecorator;
+
+/**
+ * Generates generic SQL that is supported by HSQLDB, MySQL and PostgreSQL.
+ *
+ * @author Jonatan Kronqvist / Vaadin Ltd
+ */
+@SuppressWarnings("serial")
+public class DefaultSQLGenerator implements SQLGenerator {
+
+ private Class<? extends StatementHelper> statementHelperClass = null;
+
+ public DefaultSQLGenerator() {
+
+ }
+
+ /**
+ * Create a new DefaultSqlGenerator instance that uses the given
+ * implementation of {@link StatementHelper}
+ *
+ * @param statementHelper
+ */
+ public DefaultSQLGenerator(
+ Class<? extends StatementHelper> statementHelperClazz) {
+ this();
+ statementHelperClass = statementHelperClazz;
+ }
+
+ /**
+ * Construct a DefaultSQLGenerator with the specified identifiers for start
+ * and end of quoted strings. The identifiers may be different depending on
+ * the database engine and it's settings.
+ *
+ * @param quoteStart
+ * the identifier (character) denoting the start of a quoted
+ * string
+ * @param quoteEnd
+ * the identifier (character) denoting the end of a quoted string
+ */
+ public DefaultSQLGenerator(String quoteStart, String quoteEnd) {
+ QueryBuilder
+ .setStringDecorator(new StringDecorator(quoteStart, quoteEnd));
+ }
+
+ /**
+ * Same as {@link #DefaultSQLGenerator(String, String)} but with support for
+ * custom {@link StatementHelper} implementation.
+ *
+ * @param quoteStart
+ * @param quoteEnd
+ * @param statementHelperClazz
+ */
+ public DefaultSQLGenerator(String quoteStart, String quoteEnd,
+ Class<? extends StatementHelper> statementHelperClazz) {
+ this(quoteStart, quoteEnd);
+ statementHelperClass = statementHelperClazz;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator#
+ * generateSelectQuery(java.lang.String, java.util.List, java.util.List,
+ * int, int, java.lang.String)
+ */
+ @Override
+ public StatementHelper generateSelectQuery(String tableName,
+ List<Filter> filters, List<OrderBy> orderBys, int offset,
+ int pagelength, String toSelect) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ toSelect = toSelect == null ? "*" : toSelect;
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+ query.append("SELECT " + toSelect + " FROM ")
+ .append(SQLUtil.escapeSQL(tableName));
+ if (filters != null) {
+ query.append(QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ if (orderBys != null) {
+ for (OrderBy o : orderBys) {
+ generateOrderBy(query, o, orderBys.indexOf(o) == 0);
+ }
+ }
+ if (pagelength != 0) {
+ generateLimits(query, offset, pagelength);
+ }
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator#
+ * generateUpdateQuery(java.lang.String,
+ * com.vaadin.addon.sqlcontainer.RowItem)
+ */
+ @Override
+ public StatementHelper generateUpdateQuery(String tableName, RowItem item) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ if (item == null) {
+ throw new IllegalArgumentException("Updated item must be given.");
+ }
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+ query.append("UPDATE ").append(tableName).append(" SET");
+
+ /* Generate column<->value and rowidentifiers map */
+ Map<String, Object> columnToValueMap = generateColumnToValueMap(item);
+ Map<String, Object> rowIdentifiers = generateRowIdentifiers(item);
+ /* Generate columns and values to update */
+ boolean first = true;
+ for (String column : columnToValueMap.keySet()) {
+ if (first) {
+ query.append(" " + QueryBuilder.quote(column) + " = ?");
+ } else {
+ query.append(", " + QueryBuilder.quote(column) + " = ?");
+ }
+ sh.addParameterValue(columnToValueMap.get(column),
+ item.getItemProperty(column).getType());
+ first = false;
+ }
+ /* Generate identifiers for the row to be updated */
+ first = true;
+ for (String column : rowIdentifiers.keySet()) {
+ if (first) {
+ query.append(" WHERE " + QueryBuilder.quote(column) + " = ?");
+ } else {
+ query.append(" AND " + QueryBuilder.quote(column) + " = ?");
+ }
+ sh.addParameterValue(rowIdentifiers.get(column),
+ item.getItemProperty(column).getType());
+ first = false;
+ }
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator#
+ * generateInsertQuery(java.lang.String,
+ * com.vaadin.addon.sqlcontainer.RowItem)
+ */
+ @Override
+ public StatementHelper generateInsertQuery(String tableName, RowItem item) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ if (item == null) {
+ throw new IllegalArgumentException("New item must be given.");
+ }
+ if (!(item.getId() instanceof TemporaryRowId)) {
+ throw new IllegalArgumentException(
+ "Cannot generate an insert query for item already in database.");
+ }
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+ query.append("INSERT INTO ").append(tableName).append(" (");
+
+ /* Generate column<->value map */
+ Map<String, Object> columnToValueMap = generateColumnToValueMap(item);
+ /* Generate column names for insert query */
+ boolean first = true;
+ for (String column : columnToValueMap.keySet()) {
+ if (!first) {
+ query.append(", ");
+ }
+ query.append(QueryBuilder.quote(column));
+ first = false;
+ }
+
+ /* Generate values for insert query */
+ query.append(") VALUES (");
+ first = true;
+ for (String column : columnToValueMap.keySet()) {
+ if (!first) {
+ query.append(", ");
+ }
+ query.append("?");
+ sh.addParameterValue(columnToValueMap.get(column),
+ item.getItemProperty(column).getType());
+ first = false;
+ }
+ query.append(")");
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.SQLGenerator#
+ * generateDeleteQuery(java.lang.String,
+ * com.vaadin.addon.sqlcontainer.RowItem)
+ */
+ @Override
+ public StatementHelper generateDeleteQuery(String tableName,
+ List<String> primaryKeyColumns, String versionColumn,
+ RowItem item) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ if (item == null) {
+ throw new IllegalArgumentException(
+ "Item to be deleted must be given.");
+ }
+ if (primaryKeyColumns == null || primaryKeyColumns.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Valid keyColumnNames must be provided.");
+ }
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+ query.append("DELETE FROM ").append(tableName).append(" WHERE ");
+ int count = 1;
+ for (String keyColName : primaryKeyColumns) {
+ if ((this instanceof MSSQLGenerator
+ || this instanceof OracleGenerator)
+ && keyColName.equalsIgnoreCase("rownum")) {
+ count++;
+ continue;
+ }
+ if (count > 1) {
+ query.append(" AND ");
+ }
+ if (item.getItemProperty(keyColName).getValue() != null) {
+ query.append(QueryBuilder.quote(keyColName) + " = ?");
+ sh.addParameterValue(
+ item.getItemProperty(keyColName).getValue(),
+ item.getItemProperty(keyColName).getType());
+ }
+ count++;
+ }
+ if (versionColumn != null) {
+ if (!item.getItemPropertyIds().contains(versionColumn)) {
+ throw new IllegalArgumentException(String.format(
+ "Table '%s' does not contain version column '%s'.",
+ tableName, versionColumn));
+ }
+
+ query.append(String.format(" AND %s = ?",
+ QueryBuilder.quote(versionColumn)));
+ sh.addParameterValue(item.getItemProperty(versionColumn).getValue(),
+ item.getItemProperty(versionColumn).getType());
+ }
+
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /**
+ * Generates sorting rules as an ORDER BY -clause
+ *
+ * @param sb
+ * StringBuffer to which the clause is appended.
+ * @param o
+ * OrderBy object to be added into the sb.
+ * @param firstOrderBy
+ * If true, this is the first OrderBy.
+ * @return
+ */
+ protected StringBuffer generateOrderBy(StringBuffer sb, OrderBy o,
+ boolean firstOrderBy) {
+ if (firstOrderBy) {
+ sb.append(" ORDER BY ");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(QueryBuilder.quote(o.getColumn()));
+ if (o.isAscending()) {
+ sb.append(" ASC");
+ } else {
+ sb.append(" DESC");
+ }
+ return sb;
+ }
+
+ /**
+ * Generates the LIMIT and OFFSET clause.
+ *
+ * @param sb
+ * StringBuffer to which the clause is appended.
+ * @param offset
+ * Value for offset.
+ * @param pagelength
+ * Value for pagelength.
+ * @return StringBuffer with LIMIT and OFFSET clause added.
+ */
+ protected StringBuffer generateLimits(StringBuffer sb, int offset,
+ int pagelength) {
+ sb.append(" LIMIT ").append(pagelength).append(" OFFSET ")
+ .append(offset);
+ return sb;
+ }
+
+ protected Map<String, Object> generateColumnToValueMap(RowItem item) {
+ Map<String, Object> columnToValueMap = new HashMap<String, Object>();
+ for (Object id : item.getItemPropertyIds()) {
+ ColumnProperty cp = (ColumnProperty) item.getItemProperty(id);
+ /* Prevent "rownum" usage as a column name if MSSQL or ORACLE */
+ if ((this instanceof MSSQLGenerator
+ || this instanceof OracleGenerator)
+ && cp.getPropertyId().equalsIgnoreCase("rownum")) {
+ continue;
+ }
+ if (cp.isPersistent()) {
+ columnToValueMap.put(cp.getPropertyId(), cp.getValue());
+ }
+ }
+ return columnToValueMap;
+ }
+
+ protected Map<String, Object> generateRowIdentifiers(RowItem item) {
+ Map<String, Object> rowIdentifiers = new HashMap<String, Object>();
+ for (Object id : item.getItemPropertyIds()) {
+ ColumnProperty cp = (ColumnProperty) item.getItemProperty(id);
+ /* Prevent "rownum" usage as a column name if MSSQL or ORACLE */
+ if ((this instanceof MSSQLGenerator
+ || this instanceof OracleGenerator)
+ && cp.getPropertyId().equalsIgnoreCase("rownum")) {
+ continue;
+ }
+
+ if (cp.isRowIdentifier()) {
+ Object value;
+ if (cp.isPrimaryKey()) {
+ // If the value of a primary key has changed, its old value
+ // should be used to identify the row (#9145)
+ value = cp.getOldValue();
+ } else {
+ value = cp.getValue();
+ }
+ rowIdentifiers.put(cp.getPropertyId(), value);
+ }
+ }
+ return rowIdentifiers;
+ }
+
+ /**
+ * Returns the statement helper for the generator. Override this to handle
+ * platform specific data types.
+ *
+ * @see http://dev.vaadin.com/ticket/9148
+ * @return a new instance of the statement helper
+ */
+ protected StatementHelper getStatementHelper() {
+ if (statementHelperClass == null) {
+ return new StatementHelper();
+ }
+
+ try {
+ return statementHelperClass.newInstance();
+ } catch (InstantiationException e) {
+ throw new RuntimeException(
+ "Unable to instantiate custom StatementHelper", e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(
+ "Unable to instantiate custom StatementHelper", e);
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/MSSQLGenerator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/MSSQLGenerator.java
new file mode 100644
index 0000000000..baddc60e3f
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/MSSQLGenerator.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator;
+
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.query.OrderBy;
+import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
+
+@SuppressWarnings("serial")
+public class MSSQLGenerator extends DefaultSQLGenerator {
+
+ public MSSQLGenerator() {
+
+ }
+
+ /**
+ * Construct a MSSQLGenerator with the specified identifiers for start and
+ * end of quoted strings. The identifiers may be different depending on the
+ * database engine and it's settings.
+ *
+ * @param quoteStart
+ * the identifier (character) denoting the start of a quoted
+ * string
+ * @param quoteEnd
+ * the identifier (character) denoting the end of a quoted string
+ */
+ public MSSQLGenerator(String quoteStart, String quoteEnd) {
+ super(quoteStart, quoteEnd);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.DefaultSQLGenerator#
+ * generateSelectQuery(java.lang.String, java.util.List,
+ * com.vaadin.addon.sqlcontainer.query.FilteringMode, java.util.List, int,
+ * int, java.lang.String)
+ */
+ @Override
+ public StatementHelper generateSelectQuery(String tableName,
+ List<Filter> filters, List<OrderBy> orderBys, int offset,
+ int pagelength, String toSelect) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ /* Adjust offset and page length parameters to match "row numbers" */
+ offset = pagelength > 1 ? ++offset : offset;
+ pagelength = pagelength > 1 ? --pagelength : pagelength;
+ toSelect = toSelect == null ? "*" : toSelect;
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+
+ /* Row count request is handled here */
+ if ("COUNT(*)".equalsIgnoreCase(toSelect)) {
+ query.append(String.format(
+ "SELECT COUNT(*) AS %s FROM (SELECT * FROM %s",
+ QueryBuilder.quote("rowcount"), tableName));
+ if (filters != null && !filters.isEmpty()) {
+ query.append(
+ QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ query.append(") AS t");
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /* SELECT without row number constraints */
+ if (offset == 0 && pagelength == 0) {
+ query.append("SELECT ").append(toSelect).append(" FROM ")
+ .append(tableName);
+ if (filters != null) {
+ query.append(
+ QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ if (orderBys != null) {
+ for (OrderBy o : orderBys) {
+ generateOrderBy(query, o, orderBys.indexOf(o) == 0);
+ }
+ }
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /* Remaining SELECT cases are handled here */
+ query.append("SELECT * FROM (SELECT row_number() OVER (");
+ if (orderBys != null) {
+ for (OrderBy o : orderBys) {
+ generateOrderBy(query, o, orderBys.indexOf(o) == 0);
+ }
+ }
+ query.append(") AS rownum, " + toSelect + " FROM ").append(tableName);
+ if (filters != null) {
+ query.append(QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ query.append(") AS a WHERE a.rownum BETWEEN ").append(offset)
+ .append(" AND ").append(Integer.toString(offset + pagelength));
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/OracleGenerator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/OracleGenerator.java
new file mode 100644
index 0000000000..0097b5017c
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/OracleGenerator.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator;
+
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.query.OrderBy;
+import com.vaadin.data.util.sqlcontainer.query.generator.filter.QueryBuilder;
+
+@SuppressWarnings("serial")
+public class OracleGenerator extends DefaultSQLGenerator {
+
+ public OracleGenerator() {
+
+ }
+
+ public OracleGenerator(
+ Class<? extends StatementHelper> statementHelperClazz) {
+ super(statementHelperClazz);
+ }
+
+ /**
+ * Construct an OracleSQLGenerator with the specified identifiers for start
+ * and end of quoted strings. The identifiers may be different depending on
+ * the database engine and it's settings.
+ *
+ * @param quoteStart
+ * the identifier (character) denoting the start of a quoted
+ * string
+ * @param quoteEnd
+ * the identifier (character) denoting the end of a quoted string
+ */
+ public OracleGenerator(String quoteStart, String quoteEnd) {
+ super(quoteStart, quoteEnd);
+ }
+
+ public OracleGenerator(String quoteStart, String quoteEnd,
+ Class<? extends StatementHelper> statementHelperClazz) {
+ super(quoteStart, quoteEnd, statementHelperClazz);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.sqlcontainer.query.generator.DefaultSQLGenerator#
+ * generateSelectQuery(java.lang.String, java.util.List,
+ * com.vaadin.addon.sqlcontainer.query.FilteringMode, java.util.List, int,
+ * int, java.lang.String)
+ */
+ @Override
+ public StatementHelper generateSelectQuery(String tableName,
+ List<Filter> filters, List<OrderBy> orderBys, int offset,
+ int pagelength, String toSelect) {
+ if (tableName == null || tableName.trim().equals("")) {
+ throw new IllegalArgumentException("Table name must be given.");
+ }
+ /* Adjust offset and page length parameters to match "row numbers" */
+ offset = pagelength > 1 ? ++offset : offset;
+ pagelength = pagelength > 1 ? --pagelength : pagelength;
+ toSelect = toSelect == null ? "*" : toSelect;
+ StatementHelper sh = getStatementHelper();
+ StringBuffer query = new StringBuffer();
+
+ /* Row count request is handled here */
+ if ("COUNT(*)".equalsIgnoreCase(toSelect)) {
+ query.append(String.format(
+ "SELECT COUNT(*) AS %s FROM (SELECT * FROM %s",
+ QueryBuilder.quote("rowcount"), tableName));
+ if (filters != null && !filters.isEmpty()) {
+ query.append(
+ QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ query.append(")");
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /* SELECT without row number constraints */
+ if (offset == 0 && pagelength == 0) {
+ query.append("SELECT ").append(toSelect).append(" FROM ")
+ .append(tableName);
+ if (filters != null) {
+ query.append(
+ QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ if (orderBys != null) {
+ for (OrderBy o : orderBys) {
+ generateOrderBy(query, o, orderBys.indexOf(o) == 0);
+ }
+ }
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+ /* Remaining SELECT cases are handled here */
+ query.append(String.format(
+ "SELECT * FROM (SELECT x.*, ROWNUM AS %s FROM (SELECT %s FROM %s",
+ QueryBuilder.quote("rownum"), toSelect, tableName));
+ if (filters != null) {
+ query.append(QueryBuilder.getWhereStringForFilters(filters, sh));
+ }
+ if (orderBys != null) {
+ for (OrderBy o : orderBys) {
+ generateOrderBy(query, o, orderBys.indexOf(o) == 0);
+ }
+ }
+ query.append(String.format(") x) WHERE %s BETWEEN %d AND %d",
+ QueryBuilder.quote("rownum"), offset, offset + pagelength));
+ sh.setQueryString(query.toString());
+ return sh;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/SQLGenerator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/SQLGenerator.java
new file mode 100644
index 0000000000..6011346b78
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/SQLGenerator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator;
+
+import java.io.Serializable;
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.RowItem;
+import com.vaadin.data.util.sqlcontainer.query.OrderBy;
+
+/**
+ * The SQLGenerator interface is meant to be implemented for each different SQL
+ * syntax that is to be supported. By default there are implementations for
+ * HSQLDB, MySQL, PostgreSQL, MSSQL and Oracle syntaxes.
+ *
+ * @author Jonatan Kronqvist / Vaadin Ltd
+ */
+public interface SQLGenerator extends Serializable {
+ /**
+ * Generates a SELECT query with the provided parameters. Uses default
+ * filtering mode (INCLUSIVE).
+ *
+ * @param tableName
+ * Name of the table queried
+ * @param filters
+ * The filters, converted into a WHERE clause
+ * @param orderBys
+ * The the ordering conditions, converted into an ORDER BY clause
+ * @param offset
+ * The offset of the first row to be included
+ * @param pagelength
+ * The number of rows to be returned when the query executes
+ * @param toSelect
+ * String containing what to select, e.g. "*", "COUNT(*)"
+ * @return StatementHelper instance containing the query string for a
+ * PreparedStatement and the values required for the parameters
+ */
+ public StatementHelper generateSelectQuery(String tableName,
+ List<Filter> filters, List<OrderBy> orderBys, int offset,
+ int pagelength, String toSelect);
+
+ /**
+ * Generates an UPDATE query with the provided parameters.
+ *
+ * @param tableName
+ * Name of the table queried
+ * @param item
+ * RowItem containing the updated values update.
+ * @return StatementHelper instance containing the query string for a
+ * PreparedStatement and the values required for the parameters
+ */
+ public StatementHelper generateUpdateQuery(String tableName, RowItem item);
+
+ /**
+ * Generates an INSERT query for inserting a new row with the provided
+ * values.
+ *
+ * @param tableName
+ * Name of the table queried
+ * @param item
+ * New RowItem to be inserted into the database.
+ * @return StatementHelper instance containing the query string for a
+ * PreparedStatement and the values required for the parameters
+ */
+ public StatementHelper generateInsertQuery(String tableName, RowItem item);
+
+ /**
+ * Generates a DELETE query for deleting data related to the given RowItem
+ * from the database.
+ *
+ * @param tableName
+ * Name of the table queried
+ * @param primaryKeyColumns
+ * the names of the columns holding the primary key. Usually just
+ * one column, but might be several.
+ * @param versionColumn
+ * the column containing the version number of the row, null if
+ * versioning (optimistic locking) not enabled.
+ * @param item
+ * Item to be deleted from the database
+ * @return StatementHelper instance containing the query string for a
+ * PreparedStatement and the values required for the parameters
+ */
+ public StatementHelper generateDeleteQuery(String tableName,
+ List<String> primaryKeyColumns, String versionColumn, RowItem item);
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/StatementHelper.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/StatementHelper.java
new file mode 100644
index 0000000000..43cfb597bb
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/StatementHelper.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Time;
+import java.sql.Timestamp;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * StatementHelper is a simple helper class that assists TableQuery and the
+ * query generators in filling a PreparedStatement. The actual statement is
+ * generated by the query generator methods, but the resulting statement and all
+ * the parameter values are stored in an instance of StatementHelper.
+ *
+ * This class will also fill the values with correct setters into the
+ * PreparedStatement on request.
+ */
+public class StatementHelper implements Serializable {
+
+ private String queryString;
+
+ private List<Object> parameters = new ArrayList<Object>();
+ private Map<Integer, Class<?>> dataTypes = new HashMap<Integer, Class<?>>();
+
+ public StatementHelper() {
+ }
+
+ public void setQueryString(String queryString) {
+ this.queryString = queryString;
+ }
+
+ public String getQueryString() {
+ return queryString;
+ }
+
+ public void addParameterValue(Object parameter) {
+ if (parameter != null) {
+ parameters.add(parameter);
+ dataTypes.put(parameters.size() - 1, parameter.getClass());
+ } else {
+ throw new IllegalArgumentException(
+ "You cannot add null parameters using addParamaters(Object). "
+ + "Use addParameters(Object,Class) instead");
+ }
+ }
+
+ public void addParameterValue(Object parameter, Class<?> type) {
+ parameters.add(parameter);
+ dataTypes.put(parameters.size() - 1, type);
+ }
+
+ public void setParameterValuesToStatement(PreparedStatement pstmt)
+ throws SQLException {
+ for (int i = 0; i < parameters.size(); i++) {
+ if (parameters.get(i) == null) {
+ handleNullValue(i, pstmt);
+ } else {
+ pstmt.setObject(i + 1, parameters.get(i));
+ }
+ }
+
+ /*
+ * The following list contains the data types supported by
+ * PreparedStatement but not supported by SQLContainer:
+ *
+ * [The list is provided as PreparedStatement method signatures]
+ *
+ * setNCharacterStream(int parameterIndex, Reader value)
+ *
+ * setNClob(int parameterIndex, NClob value)
+ *
+ * setNString(int parameterIndex, String value)
+ *
+ * setRef(int parameterIndex, Ref x)
+ *
+ * setRowId(int parameterIndex, RowId x)
+ *
+ * setSQLXML(int parameterIndex, SQLXML xmlObject)
+ *
+ * setBytes(int parameterIndex, byte[] x)
+ *
+ * setCharacterStream(int parameterIndex, Reader reader)
+ *
+ * setClob(int parameterIndex, Clob x)
+ *
+ * setURL(int parameterIndex, URL x)
+ *
+ * setArray(int parameterIndex, Array x)
+ *
+ * setAsciiStream(int parameterIndex, InputStream x)
+ *
+ * setBinaryStream(int parameterIndex, InputStream x)
+ *
+ * setBlob(int parameterIndex, Blob x)
+ */
+ }
+
+ private void handleNullValue(int i, PreparedStatement pstmt)
+ throws SQLException {
+ Class<?> dataType = dataTypes.get(i);
+ int index = i + 1;
+ if (BigDecimal.class.equals(dataType)) {
+ pstmt.setBigDecimal(index, null);
+ } else if (Boolean.class.equals(dataType)) {
+ pstmt.setNull(index, Types.BOOLEAN);
+ } else if (Byte.class.equals(dataType)) {
+ pstmt.setNull(index, Types.SMALLINT);
+ } else if (Date.class.equals(dataType)) {
+ pstmt.setDate(index, null);
+ } else if (Double.class.equals(dataType)) {
+ pstmt.setNull(index, Types.DOUBLE);
+ } else if (Float.class.equals(dataType)) {
+ pstmt.setNull(index, Types.FLOAT);
+ } else if (Integer.class.equals(dataType)) {
+ pstmt.setNull(index, Types.INTEGER);
+ } else if (Long.class.equals(dataType)) {
+ pstmt.setNull(index, Types.BIGINT);
+ } else if (Short.class.equals(dataType)) {
+ pstmt.setNull(index, Types.SMALLINT);
+ } else if (String.class.equals(dataType)) {
+ pstmt.setString(index, null);
+ } else if (Time.class.equals(dataType)) {
+ pstmt.setTime(index, null);
+ } else if (Timestamp.class.equals(dataType)) {
+ pstmt.setTimestamp(index, null);
+ } else if (byte[].class.equals(dataType)) {
+ pstmt.setBytes(index, null);
+ } else {
+
+ if (handleUnrecognizedTypeNullValue(i, pstmt, dataTypes)) {
+ return;
+ }
+
+ throw new SQLException("Data type for parameter " + i
+ + " not supported by SQLContainer: " + dataType.getName());
+ }
+ }
+
+ /**
+ * Handle unrecognized null values. Override this to handle null values for
+ * platform specific data types that are not handled by the default
+ * implementation of the {@link StatementHelper}.
+ *
+ * @param i
+ * @param pstmt
+ * @param dataTypes2
+ *
+ * @return true if handled, false otherwise
+ *
+ * @see {@link http://dev.vaadin.com/ticket/9148}
+ */
+ protected boolean handleUnrecognizedTypeNullValue(int i,
+ PreparedStatement pstmt, Map<Integer, Class<?>> dataTypes)
+ throws SQLException {
+ return false;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/AndTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/AndTranslator.java
new file mode 100644
index 0000000000..6eab9f5596
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/AndTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.And;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class AndTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof And;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ return QueryBuilder.group(QueryBuilder
+ .getJoinedFilterString(((And) filter).getFilters(), "AND", sh));
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/BetweenTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/BetweenTranslator.java
new file mode 100644
index 0000000000..2cdecd1e6d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/BetweenTranslator.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Between;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class BetweenTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof Between;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ Between between = (Between) filter;
+ sh.addParameterValue(between.getStartValue());
+ sh.addParameterValue(between.getEndValue());
+ return QueryBuilder.quote(between.getPropertyId()) + " BETWEEN ? AND ?";
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/CompareTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/CompareTranslator.java
new file mode 100644
index 0000000000..bcb348dc8a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/CompareTranslator.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Compare;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class CompareTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof Compare;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ Compare compare = (Compare) filter;
+ sh.addParameterValue(compare.getValue());
+ String prop = QueryBuilder.quote(compare.getPropertyId());
+ switch (compare.getOperation()) {
+ case EQUAL:
+ return prop + " = ?";
+ case GREATER:
+ return prop + " > ?";
+ case GREATER_OR_EQUAL:
+ return prop + " >= ?";
+ case LESS:
+ return prop + " < ?";
+ case LESS_OR_EQUAL:
+ return prop + " <= ?";
+ default:
+ return "";
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/FilterTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/FilterTranslator.java
new file mode 100644
index 0000000000..e593146550
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/FilterTranslator.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public interface FilterTranslator extends Serializable {
+ public boolean translatesFilter(Filter filter);
+
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh);
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/IsNullTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/IsNullTranslator.java
new file mode 100644
index 0000000000..dd7a90828a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/IsNullTranslator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.IsNull;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class IsNullTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof IsNull;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ IsNull in = (IsNull) filter;
+ return QueryBuilder.quote(in.getPropertyId()) + " IS NULL";
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/LikeTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/LikeTranslator.java
new file mode 100644
index 0000000000..3c27240e9e
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/LikeTranslator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Like;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class LikeTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof Like;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ Like like = (Like) filter;
+ if (like.isCaseSensitive()) {
+ sh.addParameterValue(like.getValue());
+ return QueryBuilder.quote(like.getPropertyId()) + " LIKE ?";
+ } else {
+ sh.addParameterValue(like.getValue().toUpperCase());
+ return "UPPER(" + QueryBuilder.quote(like.getPropertyId())
+ + ") LIKE ?";
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/NotTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/NotTranslator.java
new file mode 100644
index 0000000000..fe98ca24b6
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/NotTranslator.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.IsNull;
+import com.vaadin.data.util.filter.Not;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class NotTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof Not;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ Not not = (Not) filter;
+ if (not.getFilter() instanceof IsNull) {
+ IsNull in = (IsNull) not.getFilter();
+ return QueryBuilder.quote(in.getPropertyId()) + " IS NOT NULL";
+ }
+ return "NOT "
+ + QueryBuilder.getWhereStringForFilter(not.getFilter(), sh);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/OrTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/OrTranslator.java
new file mode 100644
index 0000000000..2f30acc89f
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/OrTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Or;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class OrTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof Or;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ return QueryBuilder.group(QueryBuilder
+ .getJoinedFilterString(((Or) filter).getFilters(), "OR", sh));
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/QueryBuilder.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/QueryBuilder.java
new file mode 100644
index 0000000000..b8fb306076
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/QueryBuilder.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class QueryBuilder implements Serializable {
+
+ private static ArrayList<FilterTranslator> filterTranslators = new ArrayList<FilterTranslator>();
+ private static StringDecorator stringDecorator = new StringDecorator("\"",
+ "\"");
+
+ static {
+ /* Register all default filter translators */
+ addFilterTranslator(new AndTranslator());
+ addFilterTranslator(new OrTranslator());
+ addFilterTranslator(new LikeTranslator());
+ addFilterTranslator(new BetweenTranslator());
+ addFilterTranslator(new CompareTranslator());
+ addFilterTranslator(new NotTranslator());
+ addFilterTranslator(new IsNullTranslator());
+ addFilterTranslator(new SimpleStringTranslator());
+ }
+
+ public synchronized static void addFilterTranslator(
+ FilterTranslator translator) {
+ filterTranslators.add(translator);
+ }
+
+ /**
+ * Allows specification of a custom ColumnQuoter instance that handles
+ * quoting of column names for the current DB dialect.
+ *
+ * @param decorator
+ * the ColumnQuoter instance to use.
+ */
+ public static void setStringDecorator(StringDecorator decorator) {
+ stringDecorator = decorator;
+ }
+
+ public static String quote(Object str) {
+ return stringDecorator.quote(str);
+ }
+
+ public static String group(String str) {
+ return stringDecorator.group(str);
+ }
+
+ /**
+ * Constructs and returns a string representing the filter that can be used
+ * in a WHERE clause.
+ *
+ * @param filter
+ * the filter to translate
+ * @param sh
+ * the statement helper to update with the value(s) of the filter
+ * @return a string representing the filter.
+ */
+ public synchronized static String getWhereStringForFilter(Filter filter,
+ StatementHelper sh) {
+ for (FilterTranslator ft : filterTranslators) {
+ if (ft.translatesFilter(filter)) {
+ return ft.getWhereStringForFilter(filter, sh);
+ }
+ }
+ return "";
+ }
+
+ public static String getJoinedFilterString(Collection<Filter> filters,
+ String joinString, StatementHelper sh) {
+ StringBuilder result = new StringBuilder();
+ for (Filter f : filters) {
+ result.append(getWhereStringForFilter(f, sh));
+ result.append(" ").append(joinString).append(" ");
+ }
+ // Remove the last instance of joinString
+ result.delete(result.length() - joinString.length() - 2,
+ result.length());
+ return result.toString();
+ }
+
+ public static String getWhereStringForFilters(List<Filter> filters,
+ StatementHelper sh) {
+ if (filters == null || filters.isEmpty()) {
+ return "";
+ }
+ StringBuilder where = new StringBuilder(" WHERE ");
+ where.append(getJoinedFilterString(filters, "AND", sh));
+ return where.toString();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/SimpleStringTranslator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/SimpleStringTranslator.java
new file mode 100644
index 0000000000..312adc5ed7
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/SimpleStringTranslator.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.util.filter.Like;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.sqlcontainer.query.generator.StatementHelper;
+
+public class SimpleStringTranslator implements FilterTranslator {
+
+ @Override
+ public boolean translatesFilter(Filter filter) {
+ return filter instanceof SimpleStringFilter;
+ }
+
+ @Override
+ public String getWhereStringForFilter(Filter filter, StatementHelper sh) {
+ SimpleStringFilter ssf = (SimpleStringFilter) filter;
+ // Create a Like filter based on the SimpleStringFilter and execute the
+ // LikeTranslator
+ String likeStr = ssf.isOnlyMatchPrefix() ? ssf.getFilterString() + "%"
+ : "%" + ssf.getFilterString() + "%";
+ Like like = new Like(ssf.getPropertyId().toString(), likeStr);
+ like.setCaseSensitive(!ssf.isIgnoreCase());
+ return new LikeTranslator().getWhereStringForFilter(like, sh);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/StringDecorator.java b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/StringDecorator.java
new file mode 100644
index 0000000000..f8005f4290
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/data/util/sqlcontainer/query/generator/filter/StringDecorator.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.data.util.sqlcontainer.query.generator.filter;
+
+import java.io.Serializable;
+
+/**
+ * The StringDecorator knows how to produce a quoted string using the specified
+ * quote start and quote end characters. It also handles grouping of a string
+ * (surrounding it in parenthesis).
+ *
+ * Extend this class if you need to support special characters for grouping
+ * (parenthesis).
+ *
+ * @author Vaadin Ltd
+ */
+public class StringDecorator implements Serializable {
+
+ private final String quoteStart;
+ private final String quoteEnd;
+
+ /**
+ * Constructs a StringDecorator that uses the quoteStart and quoteEnd
+ * characters to create quoted strings.
+ *
+ * @param quoteStart
+ * the character denoting the start of a quote.
+ * @param quoteEnd
+ * the character denoting the end of a quote.
+ */
+ public StringDecorator(String quoteStart, String quoteEnd) {
+ this.quoteStart = quoteStart;
+ this.quoteEnd = quoteEnd;
+ }
+
+ /**
+ * Surround a string with quote characters.
+ *
+ * @param str
+ * the string to quote
+ * @return the quoted string
+ */
+ public String quote(Object str) {
+ return quoteStart + str + quoteEnd;
+ }
+
+ /**
+ * Groups a string by surrounding it in parenthesis
+ *
+ * @param str
+ * the string to group
+ * @return the grouped string
+ */
+ public String group(String str) {
+ return "(" + str + ")";
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/AbstractColorPicker.java b/compatibility-server/src/main/java/com/vaadin/ui/AbstractColorPicker.java
new file mode 100644
index 0000000000..d7d12c6d03
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/AbstractColorPicker.java
@@ -0,0 +1,588 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Collection;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerState;
+import com.vaadin.ui.Window.CloseEvent;
+import com.vaadin.ui.Window.CloseListener;
+import com.vaadin.ui.components.colorpicker.ColorChangeEvent;
+import com.vaadin.ui.components.colorpicker.ColorChangeListener;
+import com.vaadin.ui.components.colorpicker.ColorPickerPopup;
+import com.vaadin.ui.components.colorpicker.ColorSelector;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+
+/**
+ * An abstract class that defines default implementation for a color picker
+ * component.
+ *
+ * @since 7.0.0
+ */
+public abstract class AbstractColorPicker extends AbstractComponent
+ implements CloseListener, ColorSelector {
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ /**
+ * Interface for converting 2d-coordinates to a Color
+ */
+ public interface Coordinates2Color extends Serializable {
+
+ /**
+ * Calculate color from coordinates
+ *
+ * @param x
+ * the x-coordinate
+ * @param y
+ * the y-coordinate
+ *
+ * @return the color
+ */
+ public Color calculate(int x, int y);
+
+ /**
+ * Calculate coordinates from color
+ *
+ * @param c
+ * the c
+ *
+ * @return the integer array with the coordinates
+ */
+ public int[] calculate(Color c);
+ }
+
+ public enum PopupStyle {
+ POPUP_NORMAL("normal"), POPUP_SIMPLE("simple");
+
+ private String style;
+
+ PopupStyle(String styleName) {
+ style = styleName;
+ }
+
+ @Override
+ public String toString() {
+ return style;
+ }
+ }
+
+ private ColorPickerServerRpc rpc = new ColorPickerServerRpc() {
+
+ @Override
+ public void openPopup(boolean open) {
+ showPopup(open);
+ }
+ };
+
+ protected static final String STYLENAME_DEFAULT = "v-colorpicker";
+ protected static final String STYLENAME_BUTTON = "v-button";
+ protected static final String STYLENAME_AREA = "v-colorpicker-area";
+
+ protected PopupStyle popupStyle = PopupStyle.POPUP_NORMAL;
+
+ /** The popup window. */
+ private ColorPickerPopup window;
+
+ /** The color. */
+ protected Color color;
+
+ /** The UI. */
+ private UI parent;
+
+ protected String popupCaption = null;
+ private int positionX = 0;
+ private int positionY = 0;
+
+ protected boolean rgbVisible = true;
+ protected boolean hsvVisible = true;
+ protected boolean swatchesVisible = true;
+ protected boolean historyVisible = true;
+ protected boolean textfieldVisible = true;
+
+ /**
+ * Instantiates a new color picker.
+ */
+ public AbstractColorPicker() {
+ this("Colors", Color.WHITE);
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * the caption of the popup window
+ */
+ public AbstractColorPicker(String popupCaption) {
+ this(popupCaption, Color.WHITE);
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * the caption of the popup window
+ * @param initialColor
+ * the initial color
+ */
+ public AbstractColorPicker(String popupCaption, Color initialColor) {
+ super();
+ registerRpc(rpc);
+ setColor(initialColor);
+ this.popupCaption = popupCaption;
+ setDefaultStyles();
+ setCaption("");
+ }
+
+ @Override
+ public void setColor(Color color) {
+ this.color = color;
+
+ if (window != null) {
+ window.setColor(color);
+ }
+ getState().color = color.getCSS();
+ }
+
+ @Override
+ public Color getColor() {
+ return color;
+ }
+
+ /**
+ * Set true if the component should show a default caption (css-code for the
+ * currently selected color, e.g. #ffffff) when no other caption is
+ * available.
+ *
+ * @param enabled
+ */
+ public void setDefaultCaptionEnabled(boolean enabled) {
+ getState().showDefaultCaption = enabled;
+ }
+
+ /**
+ * Returns true if the component shows the default caption (css-code for the
+ * currently selected color, e.g. #ffffff) if no other caption is available.
+ */
+ public boolean isDefaultCaptionEnabled() {
+ return getState(false).showDefaultCaption;
+ }
+
+ /**
+ * Sets the position of the popup window
+ *
+ * @param x
+ * the x-coordinate
+ * @param y
+ * the y-coordinate
+ */
+ public void setPosition(int x, int y) {
+ positionX = x;
+ positionY = y;
+
+ if (window != null) {
+ window.setPositionX(x);
+ window.setPositionY(y);
+ }
+ }
+
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ @Override
+ public void windowClose(CloseEvent e) {
+ if (e.getWindow() == window) {
+ getState().popupVisible = false;
+ }
+ }
+
+ /**
+ * Fired when a color change event occurs
+ *
+ * @param event
+ * The color change event
+ */
+ protected void colorChanged(ColorChangeEvent event) {
+ setColor(event.getColor());
+ fireColorChanged();
+ }
+
+ /**
+ * Notifies the listeners that the selected color has changed
+ */
+ public void fireColorChanged() {
+ fireEvent(new ColorChangeEvent(this, color));
+ }
+
+ /**
+ * The style for the popup window
+ *
+ * @param style
+ * The style
+ */
+ public void setPopupStyle(PopupStyle style) {
+ popupStyle = style;
+
+ switch (style) {
+ case POPUP_NORMAL: {
+ setRGBVisibility(true);
+ setHSVVisibility(true);
+ setSwatchesVisibility(true);
+ setHistoryVisibility(true);
+ setTextfieldVisibility(true);
+ break;
+ }
+
+ case POPUP_SIMPLE: {
+ setRGBVisibility(false);
+ setHSVVisibility(false);
+ setSwatchesVisibility(true);
+ setHistoryVisibility(false);
+ setTextfieldVisibility(false);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Gets the style for the popup window
+ *
+ * @since 7.5.0
+ * @return popup window style
+ */
+ public PopupStyle getPopupStyle() {
+ return popupStyle;
+ }
+
+ /**
+ * Set the visibility of the RGB Tab
+ *
+ * @param visible
+ * The visibility
+ */
+ public void setRGBVisibility(boolean visible) {
+
+ if (!visible && !hsvVisible && !swatchesVisible) {
+ throw new IllegalArgumentException("Cannot hide all tabs.");
+ }
+
+ rgbVisible = visible;
+ if (window != null) {
+ window.setRGBTabVisible(visible);
+ }
+ }
+
+ /**
+ * Gets the visibility of the RGB Tab
+ *
+ * @since 7.5.0
+ * @return visibility of the RGB tab
+ */
+ public boolean getRGBVisibility() {
+ return rgbVisible;
+ }
+
+ /**
+ * Set the visibility of the HSV Tab
+ *
+ * @param visible
+ * The visibility
+ */
+ public void setHSVVisibility(boolean visible) {
+ if (!visible && !rgbVisible && !swatchesVisible) {
+ throw new IllegalArgumentException("Cannot hide all tabs.");
+ }
+
+ hsvVisible = visible;
+ if (window != null) {
+ window.setHSVTabVisible(visible);
+ }
+ }
+
+ /**
+ * Gets the visibility of the HSV Tab
+ *
+ * @since 7.5.0
+ * @return visibility of the HSV tab
+ */
+ public boolean getHSVVisibility() {
+ return hsvVisible;
+ }
+
+ /**
+ * Set the visibility of the Swatches Tab
+ *
+ * @param visible
+ * The visibility
+ */
+ public void setSwatchesVisibility(boolean visible) {
+ if (!visible && !hsvVisible && !rgbVisible) {
+ throw new IllegalArgumentException("Cannot hide all tabs.");
+ }
+
+ swatchesVisible = visible;
+ if (window != null) {
+ window.setSwatchesTabVisible(visible);
+ }
+ }
+
+ /**
+ * Gets the visibility of the Swatches Tab
+ *
+ * @since 7.5.0
+ * @return visibility of the swatches tab
+ */
+ public boolean getSwatchesVisibility() {
+ return swatchesVisible;
+ }
+
+ /**
+ * Sets the visibility of the Color History
+ *
+ * @param visible
+ * The visibility
+ */
+ public void setHistoryVisibility(boolean visible) {
+ historyVisible = visible;
+ if (window != null) {
+ window.setHistoryVisible(visible);
+ }
+ }
+
+ /**
+ * Gets the visibility of the Color History
+ *
+ * @since 7.5.0
+ * @return visibility of color history
+ */
+ public boolean getHistoryVisibility() {
+ return historyVisible;
+ }
+
+ /**
+ * Sets the visibility of the CSS color code text field
+ *
+ * @param visible
+ * The visibility
+ */
+ public void setTextfieldVisibility(boolean visible) {
+ textfieldVisible = visible;
+ if (window != null) {
+ window.setPreviewVisible(visible);
+ }
+ }
+
+ /**
+ * Gets the visibility of CSS color code text field
+ *
+ * @since 7.5.0
+ * @return visibility of css color code text field
+ */
+ public boolean getTextfieldVisibility() {
+ return textfieldVisible;
+ }
+
+ @Override
+ protected ColorPickerState getState() {
+ return (ColorPickerState) super.getState();
+ }
+
+ @Override
+ protected ColorPickerState getState(boolean markAsDirty) {
+ return (ColorPickerState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Sets the default styles of the component
+ *
+ */
+ abstract protected void setDefaultStyles();
+
+ /**
+ * Shows a popup-window for color selection.
+ */
+ public void showPopup() {
+ showPopup(true);
+ }
+
+ /**
+ * Hides a popup-window for color selection.
+ */
+ public void hidePopup() {
+ showPopup(false);
+ }
+
+ /**
+ * Shows or hides popup-window depending on the given parameter. If there is
+ * no such window yet, one is created.
+ *
+ * @param open
+ */
+ protected void showPopup(boolean open) {
+ if (open && !isReadOnly()) {
+ if (parent == null) {
+ parent = getUI();
+ }
+
+ if (window == null) {
+
+ // Create the popup
+ window = new ColorPickerPopup(color);
+ window.setCaption(popupCaption);
+
+ window.setRGBTabVisible(rgbVisible);
+ window.setHSVTabVisible(hsvVisible);
+ window.setSwatchesTabVisible(swatchesVisible);
+ window.setHistoryVisible(historyVisible);
+ window.setPreviewVisible(textfieldVisible);
+
+ window.setImmediate(true);
+ window.addCloseListener(this);
+ window.addColorChangeListener(new ColorChangeListener() {
+ @Override
+ public void colorChanged(ColorChangeEvent event) {
+ AbstractColorPicker.this.colorChanged(event);
+ }
+ });
+
+ window.getHistory().setColor(color);
+ parent.addWindow(window);
+ window.setVisible(true);
+ window.setPositionX(positionX);
+ window.setPositionY(positionY);
+
+ } else if (!parent.equals(window.getParent())) {
+
+ window.setRGBTabVisible(rgbVisible);
+ window.setHSVTabVisible(hsvVisible);
+ window.setSwatchesTabVisible(swatchesVisible);
+ window.setHistoryVisible(historyVisible);
+ window.setPreviewVisible(textfieldVisible);
+
+ window.setColor(color);
+ window.getHistory().setColor(color);
+ window.setVisible(true);
+ parent.addWindow(window);
+ }
+
+ } else if (window != null) {
+ window.setVisible(false);
+ parent.removeWindow(window);
+ }
+ getState().popupVisible = open;
+ }
+
+ /**
+ * Set whether the caption text is rendered as HTML or not. You might need
+ * to re-theme component to allow higher content than the original text
+ * style.
+ *
+ * If set to true, the captions are passed to the browser as html and the
+ * developer is responsible for ensuring no harmful html is used. If set to
+ * false, the content is passed to the browser as plain text.
+ *
+ * @param htmlContentAllowed
+ * <code>true</code> if caption is rendered as HTML,
+ * <code>false</code> otherwise
+ * @deprecated as of , use {@link #setCaptionAsHtml(boolean)} instead
+ */
+ @Deprecated
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ setCaptionAsHtml(htmlContentAllowed);
+ }
+
+ /**
+ * Return HTML rendering setting
+ *
+ * @return <code>true</code> if the caption text is to be rendered as HTML,
+ * <code>false</code> otherwise
+ * @deprecated as of , use {@link #isCaptionAsHtml()} instead
+ */
+ @Deprecated
+ public boolean isHtmlContentAllowed() {
+ return isCaptionAsHtml();
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+
+ Attributes attributes = design.attributes();
+ if (design.hasAttr("color")) {
+ // Ignore the # character
+ String hexColor = DesignAttributeHandler
+ .readAttribute("color", attributes, String.class)
+ .substring(1);
+ setColor(new Color(Integer.parseInt(hexColor, 16)));
+ }
+ if (design.hasAttr("popup-style")) {
+ setPopupStyle(PopupStyle.valueOf(
+ "POPUP_" + attributes.get("popup-style").toUpperCase()));
+ }
+ if (design.hasAttr("position")) {
+ String[] position = attributes.get("position").split(",");
+ setPosition(Integer.parseInt(position[0]),
+ Integer.parseInt(position[1]));
+ }
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+
+ Attributes attribute = design.attributes();
+ DesignAttributeHandler.writeAttribute("color", attribute,
+ color.getCSS(), Color.WHITE.getCSS(), String.class);
+ DesignAttributeHandler.writeAttribute("popup-style", attribute,
+ (popupStyle == PopupStyle.POPUP_NORMAL ? "normal" : "simple"),
+ "normal", String.class);
+ DesignAttributeHandler.writeAttribute("position", attribute,
+ positionX + "," + positionY, "0,0", String.class);
+ }
+
+ @Override
+ protected Collection<String> getCustomAttributes() {
+ Collection<String> result = super.getCustomAttributes();
+ result.add("color");
+ result.add("position");
+ result.add("popup-style");
+ return result;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/AbstractSelect.java b/compatibility-server/src/main/java/com/vaadin/ui/AbstractSelect.java
new file mode 100644
index 0000000000..57c2fc1046
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/AbstractSelect.java
@@ -0,0 +1,2353 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.jsoup.nodes.Element;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.event.DataBoundTransferable;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetailsImpl;
+import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.ContainsDataFlavor;
+import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.server.Resource;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.shared.ui.combobox.FilteringMode;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.select.AbstractSelectState;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.ui.declarative.DesignFormatter;
+import com.vaadin.v7.data.Validator.InvalidValueException;
+import com.vaadin.v7.data.util.converter.LegacyConverter;
+import com.vaadin.v7.data.util.converter.LegacyConverterUtil;
+import com.vaadin.v7.data.util.converter.LegacyConverter.ConversionException;
+import com.vaadin.v7.ui.LegacyAbstractField;
+
+/**
+ * <p>
+ * A class representing a selection of items the user has selected in a UI. The
+ * set of choices is presented as a set of {@link com.vaadin.data.Item}s in a
+ * {@link com.vaadin.data.Container}.
+ * </p>
+ *
+ * <p>
+ * A <code>Select</code> component may be in single- or multiselect mode.
+ * Multiselect mode means that more than one item can be selected
+ * simultaneously.
+ * </p>
+ *
+ * @author Vaadin Ltd.
+ * @since 5.0
+ */
+@SuppressWarnings("serial")
+// TODO currently cannot specify type more precisely in case of multi-select
+public abstract class AbstractSelect extends LegacyAbstractField<Object>
+ implements Container, Container.Viewer,
+ Container.PropertySetChangeListener,
+ Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier,
+ Container.ItemSetChangeListener, LegacyComponent {
+
+ public enum ItemCaptionMode {
+ /**
+ * Item caption mode: Item's ID converted to a String using
+ * {@link VaadinSession#getConverterFactory()} is used as caption.
+ */
+ ID,
+ /**
+ * Item caption mode: Item's ID's <code>String</code> representation is
+ * used as caption.
+ *
+ * @since 7.5.6
+ */
+ ID_TOSTRING,
+ /**
+ * Item caption mode: Item's <code>String</code> representation is used
+ * as caption.
+ */
+ ITEM,
+ /**
+ * Item caption mode: Index of the item is used as caption. The index
+ * mode can only be used with the containers implementing the
+ * {@link com.vaadin.data.Container.Indexed} interface.
+ */
+ INDEX,
+ /**
+ * Item caption mode: If an Item has a caption it's used, if not, Item's
+ * ID converted to a String using
+ * {@link VaadinSession#getConverterFactory()} is used as caption.
+ * <b>This is the default</b>.
+ */
+ EXPLICIT_DEFAULTS_ID,
+ /**
+ * Item caption mode: Captions must be explicitly specified.
+ */
+ EXPLICIT,
+ /**
+ * Item caption mode: Only icons are shown, captions are hidden.
+ */
+ ICON_ONLY,
+ /**
+ * Item caption mode: Item captions are read from property specified
+ * with <code>setItemCaptionPropertyId</code>.
+ */
+ PROPERTY;
+ }
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#ID} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ID = ItemCaptionMode.ID;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#ITEM} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ITEM = ItemCaptionMode.ITEM;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#INDEX} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_INDEX = ItemCaptionMode.INDEX;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID}
+ * instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#EXPLICIT} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_EXPLICIT = ItemCaptionMode.EXPLICIT;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#ICON_ONLY} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_ICON_ONLY = ItemCaptionMode.ICON_ONLY;
+
+ /**
+ * @deprecated As of 7.0, use {@link ItemCaptionMode#PROPERTY} instead
+ */
+ @Deprecated
+ public static final ItemCaptionMode ITEM_CAPTION_MODE_PROPERTY = ItemCaptionMode.PROPERTY;
+
+ /**
+ * Interface for option filtering, used to filter options based on user
+ * entered value. The value is matched to the item caption.
+ * <code>FilteringMode.OFF</code> (0) turns the filtering off.
+ * <code>FilteringMode.STARTSWITH</code> (1) matches from the start of the
+ * caption. <code>FilteringMode.CONTAINS</code> (1) matches anywhere in the
+ * caption.
+ */
+ public interface Filtering extends Serializable {
+
+ /**
+ * @deprecated As of 7.0, use {@link FilteringMode#OFF} instead
+ */
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_OFF = FilteringMode.OFF;
+ /**
+ * @deprecated As of 7.0, use {@link FilteringMode#STARTSWITH} instead
+ */
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_STARTSWITH = FilteringMode.STARTSWITH;
+ /**
+ * @deprecated As of 7.0, use {@link FilteringMode#CONTAINS} instead
+ */
+ @Deprecated
+ public static final FilteringMode FILTERINGMODE_CONTAINS = FilteringMode.CONTAINS;
+
+ /**
+ * Sets the option filtering mode.
+ *
+ * @param filteringMode
+ * the filtering mode to use
+ */
+ public void setFilteringMode(FilteringMode filteringMode);
+
+ /**
+ * Gets the current filtering mode.
+ *
+ * @return the filtering mode in use
+ */
+ public FilteringMode getFilteringMode();
+
+ }
+
+ /**
+ * Select options.
+ */
+ protected Container items;
+
+ /**
+ * Is the user allowed to add new options?
+ */
+ private boolean allowNewOptions;
+
+ /**
+ * Keymapper used to map key values.
+ */
+ protected KeyMapper<Object> itemIdMapper = new KeyMapper<Object>();
+
+ /**
+ * Item icons.
+ */
+ private final HashMap<Object, Resource> itemIcons = new HashMap<Object, Resource>();
+
+ /**
+ * Item captions.
+ */
+ private final HashMap<Object, String> itemCaptions = new HashMap<Object, String>();
+
+ /**
+ * Item caption mode.
+ */
+ private ItemCaptionMode itemCaptionMode = ItemCaptionMode.EXPLICIT_DEFAULTS_ID;
+
+ /**
+ * Item caption source property id.
+ */
+ private Object itemCaptionPropertyId = null;
+
+ /**
+ * Item icon source property id.
+ */
+ private Object itemIconPropertyId = null;
+
+ /**
+ * List of property set change event listeners.
+ */
+ private Set<Container.PropertySetChangeListener> propertySetEventListeners = null;
+
+ /**
+ * List of item set change event listeners.
+ */
+ private Set<Container.ItemSetChangeListener> itemSetEventListeners = null;
+
+ /**
+ * Item id that represents null selection of this select.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ */
+ private Object nullSelectionItemId = null;
+
+ // Null (empty) selection is enabled by default
+ private boolean nullSelectionAllowed = true;
+ private NewItemHandler newItemHandler;
+
+ // Caption (Item / Property) change listeners
+ CaptionChangeListener captionChangeListener;
+
+ /* Constructors */
+
+ /**
+ * Creates an empty Select. The caption is not used.
+ */
+ public AbstractSelect() {
+ setContainerDataSource(new IndexedContainer());
+ }
+
+ /**
+ * Creates an empty Select with caption.
+ */
+ public AbstractSelect(String caption) {
+ setContainerDataSource(new IndexedContainer());
+ setCaption(caption);
+ }
+
+ /**
+ * Creates a new select that is connected to a data-source.
+ *
+ * @param caption
+ * the Caption of the component.
+ * @param dataSource
+ * the Container datasource to be selected from by this select.
+ */
+ public AbstractSelect(String caption, Container dataSource) {
+ setCaption(caption);
+ setContainerDataSource(dataSource);
+ }
+
+ /**
+ * Creates a new select that is filled from a collection of option values.
+ *
+ * @param caption
+ * the Caption of this field.
+ * @param options
+ * the Collection containing the options.
+ */
+ public AbstractSelect(String caption, Collection<?> options) {
+
+ // Creates the options container and add given options to it
+ final Container c = new IndexedContainer();
+ if (options != null) {
+ for (final Iterator<?> i = options.iterator(); i.hasNext();) {
+ c.addItem(i.next());
+ }
+ }
+
+ setCaption(caption);
+ setContainerDataSource(c);
+ }
+
+ /* Component methods */
+
+ /**
+ * Paints the content of this component.
+ *
+ * @param target
+ * the Paint Event.
+ * @throws PaintException
+ * if the paint operation failed.
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+
+ // Paints select attributes
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ if (getNullSelectionItemId() != null) {
+ target.addAttribute("nullselectitem", true);
+ }
+ }
+
+ // Constructs selected keys array
+ String[] selectedKeys;
+ if (isMultiSelect()) {
+ selectedKeys = new String[((Set<?>) getValue()).size()];
+ } else {
+ selectedKeys = new String[(getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+ }
+
+ // ==
+ // first remove all previous item/property listeners
+ getCaptionChangeListener().clear();
+ // Paints the options and create array of selected id keys
+
+ target.startTag("options");
+ int keyIndex = 0;
+ // Support for external null selection item id
+ final Collection<?> ids = getItemIds();
+ if (isNullSelectionAllowed() && getNullSelectionItemId() != null
+ && !ids.contains(getNullSelectionItemId())) {
+ final Object id = getNullSelectionItemId();
+ // Paints option
+ target.startTag("so");
+ paintItem(target, id);
+ if (isSelected(id)) {
+ selectedKeys[keyIndex++] = itemIdMapper.key(id);
+ }
+ target.endTag("so");
+ }
+
+ final Iterator<?> i = getItemIds().iterator();
+ // Paints the available selection options from data source
+ while (i.hasNext()) {
+ // Gets the option attribute values
+ final Object id = i.next();
+ if (!isNullSelectionAllowed() && id != null
+ && id.equals(getNullSelectionItemId())) {
+ // Remove item if it's the null selection item but null
+ // selection is not allowed
+ continue;
+ }
+ final String key = itemIdMapper.key(id);
+ // add listener for each item, to cause repaint if an item changes
+ getCaptionChangeListener().addNotifierForItem(id);
+ target.startTag("so");
+ paintItem(target, id);
+ if (isSelected(id) && keyIndex < selectedKeys.length) {
+ selectedKeys[keyIndex++] = key;
+ }
+ target.endTag("so");
+ }
+ target.endTag("options");
+ // ==
+
+ // Paint variables
+ target.addVariable(this, "selected", selectedKeys);
+ if (isNewItemsAllowed()) {
+ target.addVariable(this, "newitem", "");
+ }
+
+ }
+
+ protected void paintItem(PaintTarget target, Object itemId)
+ throws PaintException {
+ final String key = itemIdMapper.key(itemId);
+ final String caption = getItemCaption(itemId);
+ final Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ target.addAttribute("icon", icon);
+ }
+ target.addAttribute("caption", caption);
+ if (itemId != null && itemId.equals(getNullSelectionItemId())) {
+ target.addAttribute("nullselection", true);
+ }
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ }
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ // New option entered (and it is allowed)
+ if (isNewItemsAllowed()) {
+ final String newitem = (String) variables.get("newitem");
+ if (newitem != null && newitem.length() > 0) {
+ getNewItemHandler().addNewItem(newitem);
+ }
+ }
+
+ // Selection change
+ if (variables.containsKey("selected")) {
+ final String[] clientSideSelectedKeys = (String[]) variables
+ .get("selected");
+
+ // Multiselect mode
+ if (isMultiSelect()) {
+
+ // TODO Optimize by adding repaintNotNeeded when applicable
+
+ // Converts the key-array to id-set
+ final LinkedList<Object> acceptedSelections = new LinkedList<Object>();
+ for (int i = 0; i < clientSideSelectedKeys.length; i++) {
+ final Object id = itemIdMapper
+ .get(clientSideSelectedKeys[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ markAsDirty();
+ } else if (id != null && containsId(id)) {
+ acceptedSelections.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed()
+ && acceptedSelections.size() < 1) {
+ // empty selection not allowed, keep old value
+ markAsDirty();
+ return;
+ }
+
+ // Limits the deselection to the set of visible items
+ // (non-visible items can not be deselected)
+ Collection<?> visibleNotSelected = getVisibleItemIds();
+ if (visibleNotSelected != null) {
+ visibleNotSelected = new HashSet<Object>(
+ visibleNotSelected);
+ // Don't remove those that will be added to preserve order
+ visibleNotSelected.removeAll(acceptedSelections);
+
+ @SuppressWarnings("unchecked")
+ Set<Object> newsel = (Set<Object>) getValue();
+ if (newsel == null) {
+ newsel = new LinkedHashSet<Object>();
+ } else {
+ newsel = new LinkedHashSet<Object>(newsel);
+ }
+ newsel.removeAll(visibleNotSelected);
+ newsel.addAll(acceptedSelections);
+ setValue(newsel, true);
+ }
+ } else {
+ // Single select mode
+ if (!isNullSelectionAllowed()
+ && (clientSideSelectedKeys.length == 0
+ || clientSideSelectedKeys[0] == null
+ || clientSideSelectedKeys[0] == getNullSelectionItemId())) {
+ markAsDirty();
+ return;
+ }
+ if (clientSideSelectedKeys.length == 0) {
+ // Allows deselection only if the deselected item is
+ // visible
+ final Object current = getValue();
+ final Collection<?> visible = getVisibleItemIds();
+ if (visible != null && visible.contains(current)) {
+ setValue(null, true);
+ }
+ } else {
+ String clientSelectedKey = clientSideSelectedKeys[0];
+ if ("null".equals(clientSelectedKey)
+ || itemIdMapper.containsKey(clientSelectedKey)) {
+ // Happens to work for nullselection
+ // (get ("null") -> null))
+ final Object id = itemIdMapper.get(clientSelectedKey);
+
+ if (!isNullSelectionAllowed() && id == null) {
+ markAsDirty();
+ } else if (id != null
+ && id.equals(getNullSelectionItemId())) {
+ setValue(null, true);
+ } else {
+ setValue(id, true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * TODO refine doc Setter for new item handler that is called when user adds
+ * new item in newItemAllowed mode.
+ *
+ * @param newItemHandler
+ */
+ public void setNewItemHandler(NewItemHandler newItemHandler) {
+ this.newItemHandler = newItemHandler;
+ }
+
+ /**
+ * TODO refine doc
+ *
+ * @return
+ */
+ public NewItemHandler getNewItemHandler() {
+ if (newItemHandler == null) {
+ newItemHandler = new DefaultNewItemHandler();
+ }
+ return newItemHandler;
+ }
+
+ public interface NewItemHandler extends Serializable {
+ void addNewItem(String newItemCaption);
+ }
+
+ /**
+ * TODO refine doc
+ *
+ * This is a default class that handles adding new items that are typed by
+ * user to selects container.
+ *
+ * By extending this class one may implement some logic on new item addition
+ * like database inserts.
+ *
+ */
+ public class DefaultNewItemHandler implements NewItemHandler {
+ @Override
+ public void addNewItem(String newItemCaption) {
+ // Checks for readonly
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ // Adds new option
+ if (addItem(newItemCaption) != null) {
+
+ // Sets the caption property, if used
+ if (getItemCaptionPropertyId() != null) {
+ getContainerProperty(newItemCaption,
+ getItemCaptionPropertyId())
+ .setValue(newItemCaption);
+ }
+ if (isMultiSelect()) {
+ Set values = new HashSet((Collection) getValue());
+ values.add(newItemCaption);
+ setValue(values);
+ } else {
+ setValue(newItemCaption);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the visible item ids. In Select, this returns list of all item ids,
+ * but can be overriden in subclasses if they paint only part of the items
+ * to the terminal or null if no items is visible.
+ */
+ public Collection<?> getVisibleItemIds() {
+ return getItemIds();
+ }
+
+ /* Property methods */
+
+ /**
+ * Returns the type of the property. <code>getValue</code> and
+ * <code>setValue</code> methods must be compatible with this type: one can
+ * safely cast <code>getValue</code> to given type and pass any variable
+ * assignable to this type as a parameter to <code>setValue</code>.
+ *
+ * @return the Type of the property.
+ */
+ @Override
+ public Class<?> getType() {
+ if (isMultiSelect()) {
+ return Set.class;
+ } else {
+ return Object.class;
+ }
+ }
+
+ /**
+ * Gets the selected item id or in multiselect mode a set of selected ids.
+ *
+ * @see com.vaadin.v7.ui.LegacyAbstractField#getValue()
+ */
+ @Override
+ public Object getValue() {
+ final Object retValue = super.getValue();
+
+ if (isMultiSelect()) {
+
+ // If the return value is not a set
+ if (retValue == null) {
+ return new HashSet<Object>();
+ }
+ if (retValue instanceof Set) {
+ return Collections.unmodifiableSet((Set<?>) retValue);
+ } else if (retValue instanceof Collection) {
+ return new HashSet<Object>((Collection<?>) retValue);
+ } else {
+ final Set<Object> s = new HashSet<Object>();
+ if (items.containsId(retValue)) {
+ s.add(retValue);
+ }
+ return s;
+ }
+
+ } else {
+ return retValue;
+ }
+ }
+
+ /**
+ * Sets the visible value of the property.
+ *
+ * <p>
+ * The value of the select is the selected item id. If the select is in
+ * multiselect-mode, the value is a set of selected item keys. In
+ * multiselect mode all collections of id:s can be assigned.
+ * </p>
+ *
+ * @param newValue
+ * the New selected item or collection of selected items.
+ * @see com.vaadin.v7.ui.LegacyAbstractField#setValue(java.lang.Object)
+ */
+ @Override
+ public void setValue(Object newValue) throws Property.ReadOnlyException {
+ if (newValue == getNullSelectionItemId()) {
+ newValue = null;
+ }
+
+ setValue(newValue, false);
+ }
+
+ /**
+ * Sets the visible value of the property.
+ *
+ * <p>
+ * The value of the select is the selected item id. If the select is in
+ * multiselect-mode, the value is a set of selected item keys. In
+ * multiselect mode all collections of id:s can be assigned.
+ * </p>
+ *
+ * @since 7.5.7
+ * @param newValue
+ * the New selected item or collection of selected items.
+ * @param repaintIsNotNeeded
+ * True if caller is sure that repaint is not needed.
+ * @param ignoreReadOnly
+ * True if read-only check should be omitted.
+ * @see com.vaadin.v7.ui.LegacyAbstractField#setValue(java.lang.Object,
+ * java.lang.Boolean)
+ */
+ @Override
+ protected void setValue(Object newFieldValue, boolean repaintIsNotNeeded,
+ boolean ignoreReadOnly)
+ throws com.vaadin.data.Property.ReadOnlyException,
+ ConversionException, InvalidValueException {
+ if (isMultiSelect()) {
+ if (newFieldValue == null) {
+ super.setValue(new LinkedHashSet<Object>(), repaintIsNotNeeded,
+ ignoreReadOnly);
+ } else if (Collection.class
+ .isAssignableFrom(newFieldValue.getClass())) {
+ super.setValue(
+ new LinkedHashSet<Object>(
+ (Collection<?>) newFieldValue),
+ repaintIsNotNeeded, ignoreReadOnly);
+ }
+ } else if (newFieldValue == null || items.containsId(newFieldValue)) {
+ super.setValue(newFieldValue, repaintIsNotNeeded, ignoreReadOnly);
+ }
+ }
+
+ /* Container methods */
+
+ /**
+ * Gets the item from the container with given id. If the container does not
+ * contain the requested item, null is returned.
+ *
+ * @param itemId
+ * the item id.
+ * @return the item from the container.
+ */
+ @Override
+ public Item getItem(Object itemId) {
+ return items.getItem(itemId);
+ }
+
+ /**
+ * Gets the item Id collection from the container.
+ *
+ * @return the Collection of item ids.
+ */
+ @Override
+ public Collection<?> getItemIds() {
+ return items.getItemIds();
+ }
+
+ /**
+ * Gets the property Id collection from the container.
+ *
+ * @return the Collection of property ids.
+ */
+ @Override
+ public Collection<?> getContainerPropertyIds() {
+ return items.getContainerPropertyIds();
+ }
+
+ /**
+ * Gets the property type.
+ *
+ * @param propertyId
+ * the Id identifying the property.
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return items.getType(propertyId);
+ }
+
+ /*
+ * Gets the number of items in the container.
+ *
+ * @return the Number of items in the container.
+ *
+ * @see com.vaadin.data.Container#size()
+ */
+ @Override
+ public int size() {
+ int size = items.size();
+ assert size >= 0;
+ return size;
+ }
+
+ /**
+ * Tests, if the collection contains an item with given id.
+ *
+ * @param itemId
+ * the Id the of item to be tested.
+ */
+ @Override
+ public boolean containsId(Object itemId) {
+ if (itemId != null) {
+ return items.containsId(itemId);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Gets the Property identified by the given itemId and propertyId from the
+ * Container
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(Object, Object)
+ */
+ @Override
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ return items.getContainerProperty(itemId, propertyId);
+ }
+
+ /**
+ * Adds the new property to all items. Adds a property with given id, type
+ * and default value to all items in the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ final boolean retval = items.addContainerProperty(propertyId, type,
+ defaultValue);
+ if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
+ firePropertySetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Removes all items from the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() throws UnsupportedOperationException {
+
+ final boolean retval = items.removeAllItems();
+ itemIdMapper.removeAll();
+ if (retval) {
+ setValue(null);
+ if (!(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ }
+ return retval;
+ }
+
+ /**
+ * Creates a new item into container with container managed id. The id of
+ * the created new item is returned. The item can be fetched with getItem()
+ * method. if the creation fails, null is returned.
+ *
+ * @return the Id of the created item or null in case of failure.
+ * @see com.vaadin.data.Container#addItem()
+ */
+ @Override
+ public Object addItem() throws UnsupportedOperationException {
+
+ final Object retval = items.addItem();
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Create a new item into container. The created new item is returned and
+ * ready for setting property values. if the creation fails, null is
+ * returned. In case the container already contains the item, null is
+ * returned.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns null.
+ *
+ * @param itemId
+ * the Identification of the item to be created.
+ * @return the Created item with the given id, or null in case of failure.
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+
+ final Item retval = items.addItem(itemId);
+ if (retval != null
+ && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Adds given items with given item ids to container.
+ *
+ * @since 7.2
+ * @param itemId
+ * item identifiers to be added to underlying container
+ * @throws UnsupportedOperationException
+ * if the underlying container don't support adding items with
+ * identifiers
+ */
+ public void addItems(Object... itemId)
+ throws UnsupportedOperationException {
+ for (Object id : itemId) {
+ addItem(id);
+ }
+ }
+
+ /**
+ * Adds given items with given item ids to container.
+ *
+ * @since 7.2
+ * @param itemIds
+ * item identifiers to be added to underlying container
+ * @throws UnsupportedOperationException
+ * if the underlying container don't support adding items with
+ * identifiers
+ */
+ public void addItems(Collection<?> itemIds)
+ throws UnsupportedOperationException {
+ addItems(itemIds.toArray());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ unselect(itemId);
+ final boolean retval = items.removeItem(itemId);
+ itemIdMapper.remove(itemId);
+ if (retval && !(items instanceof Container.ItemSetChangeNotifier)) {
+ fireItemSetChange();
+ }
+ return retval;
+ }
+
+ /**
+ * Checks that the current selection is valid, i.e. the selected item ids
+ * exist in the container. Updates the selection if one or several selected
+ * item ids are no longer available in the container.
+ */
+ @SuppressWarnings("unchecked")
+ public void sanitizeSelection() {
+ Object value = getValue();
+ if (value == null) {
+ return;
+ }
+
+ boolean changed = false;
+
+ if (isMultiSelect()) {
+ Collection<Object> valueAsCollection = (Collection<Object>) value;
+ List<Object> newSelection = new ArrayList<Object>(
+ valueAsCollection.size());
+ for (Object subValue : valueAsCollection) {
+ if (containsId(subValue)) {
+ newSelection.add(subValue);
+ } else {
+ changed = true;
+ }
+ }
+ if (changed) {
+ setValue(newSelection);
+ }
+ } else {
+ if (!containsId(value)) {
+ setValue(null);
+ }
+ }
+
+ }
+
+ /**
+ * Removes the property from all items. Removes a property with given id
+ * from all the items in the container.
+ *
+ * This functionality is optional. If the function is unsupported, it always
+ * returns false.
+ *
+ * @return True if the operation succeeded.
+ * @see com.vaadin.data.Container#removeContainerProperty(java.lang.Object)
+ */
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+
+ final boolean retval = items.removeContainerProperty(propertyId);
+ if (retval && !(items instanceof Container.PropertySetChangeNotifier)) {
+ firePropertySetChange();
+ }
+ return retval;
+ }
+
+ /* Container.Viewer methods */
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * As a side-effect the fields value (selection) is set to null due old
+ * selection not necessary exists in new Container.
+ *
+ * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
+ *
+ * @param newDataSource
+ * the new data source.
+ */
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+ if (newDataSource == null) {
+ newDataSource = new IndexedContainer();
+ }
+
+ getCaptionChangeListener().clear();
+
+ if (items != newDataSource) {
+
+ // Removes listeners from the old datasource
+ if (items != null) {
+ if (items instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) items)
+ .removeItemSetChangeListener(this);
+ }
+ if (items instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) items)
+ .removePropertySetChangeListener(this);
+ }
+ }
+
+ // Assigns new data source
+ items = newDataSource;
+
+ // Clears itemIdMapper also
+ itemIdMapper.removeAll();
+
+ // Adds listeners
+ if (items != null) {
+ if (items instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) items)
+ .addItemSetChangeListener(this);
+ }
+ if (items instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) items)
+ .addPropertySetChangeListener(this);
+ }
+ }
+
+ /*
+ * We expect changing the data source should also clean value. See
+ * #810, #4607, #5281
+ */
+ setValue(null);
+
+ markAsDirty();
+
+ }
+ }
+
+ /**
+ * Gets the viewing data-source container.
+ *
+ * @see com.vaadin.data.Container.Viewer#getContainerDataSource()
+ */
+ @Override
+ public Container getContainerDataSource() {
+ return items;
+ }
+
+ /* Select attributes */
+
+ /**
+ * Is the select in multiselect mode? In multiselect mode
+ *
+ * @return the Value of property multiSelect.
+ */
+ public boolean isMultiSelect() {
+ return getState(false).multiSelect;
+ }
+
+ /**
+ * Sets the multiselect mode. Setting multiselect mode false may lose
+ * selection information: if selected items set contains one or more
+ * selected items, only one of the selected items is kept as selected.
+ *
+ * Subclasses of AbstractSelect can choose not to support changing the
+ * multiselect mode, and may throw {@link UnsupportedOperationException}.
+ *
+ * @param multiSelect
+ * the New value of property multiSelect.
+ */
+ public void setMultiSelect(boolean multiSelect) {
+ if (multiSelect && getNullSelectionItemId() != null) {
+ throw new IllegalStateException(
+ "Multiselect and NullSelectionItemId can not be set at the same time.");
+ }
+ if (multiSelect != getState(false).multiSelect) {
+
+ // Selection before mode change
+ final Object oldValue = getValue();
+
+ getState().multiSelect = multiSelect;
+
+ // Convert the value type
+ if (multiSelect) {
+ final Set<Object> s = new HashSet<Object>();
+ if (oldValue != null) {
+ s.add(oldValue);
+ }
+ setValue(s);
+ } else {
+ final Set<?> s = (Set<?>) oldValue;
+ if (s == null || s.isEmpty()) {
+ setValue(null);
+ } else {
+ // Set the single select to contain only the first
+ // selected value in the multiselect
+ setValue(s.iterator().next());
+ }
+ }
+
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Does the select allow adding new options by the user. If true, the new
+ * options can be added to the Container. The text entered by the user is
+ * used as id. Note that data-source must allow adding new items.
+ *
+ * @return True if additions are allowed.
+ */
+ public boolean isNewItemsAllowed() {
+ return allowNewOptions;
+ }
+
+ /**
+ * Enables or disables possibility to add new options by the user.
+ *
+ * @param allowNewOptions
+ * the New value of property allowNewOptions.
+ */
+ public void setNewItemsAllowed(boolean allowNewOptions) {
+
+ // Only handle change requests
+ if (this.allowNewOptions != allowNewOptions) {
+
+ this.allowNewOptions = allowNewOptions;
+
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Override the caption of an item. Setting caption explicitly overrides id,
+ * item and index captions.
+ *
+ * @param itemId
+ * the id of the item to be recaptioned.
+ * @param caption
+ * the New caption.
+ */
+ public void setItemCaption(Object itemId, String caption) {
+ if (itemId != null) {
+ itemCaptions.put(itemId, caption);
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the caption of an item. The caption is generated as specified by the
+ * item caption mode. See <code>setItemCaptionMode()</code> for more
+ * details.
+ *
+ * @param itemId
+ * the id of the item to be queried.
+ * @return the caption for specified item.
+ */
+ public String getItemCaption(Object itemId) {
+
+ // Null items can not be found
+ if (itemId == null) {
+ return null;
+ }
+
+ String caption = null;
+
+ switch (getItemCaptionMode()) {
+
+ case ID:
+ caption = idToCaption(itemId);
+ break;
+ case ID_TOSTRING:
+ caption = itemId.toString();
+ break;
+ case INDEX:
+ if (items instanceof Container.Indexed) {
+ caption = String
+ .valueOf(((Container.Indexed) items).indexOfId(itemId));
+ } else {
+ caption = "ERROR: Container is not indexed";
+ }
+ break;
+
+ case ITEM:
+ final Item i = getItem(itemId);
+ if (i != null) {
+ caption = i.toString();
+ }
+ break;
+
+ case EXPLICIT:
+ caption = itemCaptions.get(itemId);
+ break;
+
+ case EXPLICIT_DEFAULTS_ID:
+ caption = itemCaptions.get(itemId);
+ if (caption == null) {
+ caption = idToCaption(itemId);
+ }
+ break;
+
+ case PROPERTY:
+ final Property<?> p = getContainerProperty(itemId,
+ getItemCaptionPropertyId());
+ if (p != null) {
+ Object value = p.getValue();
+ if (value != null) {
+ caption = value.toString();
+ }
+ }
+ break;
+ }
+
+ // All items must have some captions
+ return caption != null ? caption : "";
+ }
+
+ private String idToCaption(Object itemId) {
+ try {
+ LegacyConverter<String, Object> c = (LegacyConverter<String, Object>) LegacyConverterUtil
+ .getConverter(String.class, itemId.getClass(),
+ getSession());
+ return LegacyConverterUtil.convertFromModel(itemId, String.class, c,
+ getLocale());
+ } catch (Exception e) {
+ return itemId.toString();
+ }
+ }
+
+ /**
+ * Sets the icon for an item.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param icon
+ * the icon to use or null.
+ */
+ public void setItemIcon(Object itemId, Resource icon) {
+ if (itemId != null) {
+ if (icon == null) {
+ itemIcons.remove(itemId);
+ } else {
+ itemIcons.put(itemId, icon);
+ }
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the item icon.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @return the icon for the item or null, if not specified.
+ */
+ public Resource getItemIcon(Object itemId) {
+ final Resource explicit = itemIcons.get(itemId);
+ if (explicit != null) {
+ return explicit;
+ }
+
+ if (getItemIconPropertyId() == null) {
+ return null;
+ }
+
+ final Property<?> ip = getContainerProperty(itemId,
+ getItemIconPropertyId());
+ if (ip == null) {
+ return null;
+ }
+ final Object icon = ip.getValue();
+ if (icon instanceof Resource) {
+ return (Resource) icon;
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the item caption mode.
+ *
+ * See {@link ItemCaptionMode} for a description of the modes.
+ * <p>
+ * {@link ItemCaptionMode#EXPLICIT_DEFAULTS_ID} is the default mode.
+ * </p>
+ *
+ * @param mode
+ * the One of the modes listed above.
+ */
+ public void setItemCaptionMode(ItemCaptionMode mode) {
+ if (mode != null) {
+ itemCaptionMode = mode;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the item caption mode.
+ *
+ * <p>
+ * The mode can be one of the following ones:
+ * <ul>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> : Items
+ * Id-objects <code>toString</code> is used as item caption. If caption is
+ * explicitly specified, it overrides the id-caption.
+ * <li><code>ITEM_CAPTION_MODE_ID</code> : Items Id-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_ITEM</code> : Item-objects
+ * <code>toString</code> is used as item caption.</li>
+ * <li><code>ITEM_CAPTION_MODE_INDEX</code> : The index of the item is used
+ * as item caption. The index mode can only be used with the containers
+ * implementing <code>Container.Indexed</code> interface.</li>
+ * <li><code>ITEM_CAPTION_MODE_EXPLICIT</code> : The item captions must be
+ * explicitly specified.</li>
+ * <li><code>ITEM_CAPTION_MODE_PROPERTY</code> : The item captions are read
+ * from property, that must be specified with
+ * <code>setItemCaptionPropertyId</code>.</li>
+ * </ul>
+ * The <code>ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID</code> is the default
+ * mode.
+ * </p>
+ *
+ * @return the One of the modes listed above.
+ */
+ public ItemCaptionMode getItemCaptionMode() {
+ return itemCaptionMode;
+ }
+
+ /**
+ * Sets the item caption property.
+ *
+ * <p>
+ * Setting the id to a existing property implicitly sets the item caption
+ * mode to <code>ITEM_CAPTION_MODE_PROPERTY</code>. If the object is in
+ * <code>ITEM_CAPTION_MODE_PROPERTY</code> mode, setting caption property id
+ * null resets the item caption mode to
+ * <code>ITEM_CAPTION_EXPLICIT_DEFAULTS_ID</code>.
+ * </p>
+ * <p>
+ * Note that the type of the property used for caption must be String
+ * </p>
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @param propertyId
+ * the id of the property.
+ *
+ */
+ public void setItemCaptionPropertyId(Object propertyId) {
+ if (propertyId != null) {
+ itemCaptionPropertyId = propertyId;
+ setItemCaptionMode(ITEM_CAPTION_MODE_PROPERTY);
+ markAsDirty();
+ } else {
+ itemCaptionPropertyId = null;
+ if (getItemCaptionMode() == ITEM_CAPTION_MODE_PROPERTY) {
+ setItemCaptionMode(ITEM_CAPTION_MODE_EXPLICIT_DEFAULTS_ID);
+ }
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the item caption property.
+ *
+ * @return the Id of the property used as item caption source.
+ */
+ public Object getItemCaptionPropertyId() {
+ return itemCaptionPropertyId;
+ }
+
+ /**
+ * Sets the item icon property.
+ *
+ * <p>
+ * If the property id is set to a valid value, each item is given an icon
+ * got from the given property of the items. The type of the property must
+ * be assignable to Resource.
+ * </p>
+ *
+ * <p>
+ * Note : The icons set with <code>setItemIcon</code> function override the
+ * icons from the property.
+ * </p>
+ *
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @param propertyId
+ * the id of the property that specifies icons for items or null
+ * @throws IllegalArgumentException
+ * If the propertyId is not in the container or is not of a
+ * valid type
+ */
+ public void setItemIconPropertyId(Object propertyId)
+ throws IllegalArgumentException {
+ if (propertyId == null) {
+ itemIconPropertyId = null;
+ } else if (!getContainerPropertyIds().contains(propertyId)) {
+ throw new IllegalArgumentException(
+ "Property id not found in the container");
+ } else if (Resource.class.isAssignableFrom(getType(propertyId))) {
+ itemIconPropertyId = propertyId;
+ } else {
+ throw new IllegalArgumentException(
+ "Property type must be assignable to Resource");
+ }
+ markAsDirty();
+ }
+
+ /**
+ * Gets the item icon property.
+ *
+ * <p>
+ * If the property id is set to a valid value, each item is given an icon
+ * got from the given property of the items. The type of the property must
+ * be assignable to Icon.
+ * </p>
+ *
+ * <p>
+ * Note : The icons set with <code>setItemIcon</code> function override the
+ * icons from the property.
+ * </p>
+ *
+ * <p>
+ * Setting the property id to null disables this feature. The id is null by
+ * default
+ * </p>
+ * .
+ *
+ * @return the Id of the property containing the item icons.
+ */
+ public Object getItemIconPropertyId() {
+ return itemIconPropertyId;
+ }
+
+ /**
+ * Tests if an item is selected.
+ *
+ * <p>
+ * In single select mode testing selection status of the item identified by
+ * {@link #getNullSelectionItemId()} returns true if the value of the
+ * property is null.
+ * </p>
+ *
+ * @param itemId
+ * the Id the of the item to be tested.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public boolean isSelected(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+ if (isMultiSelect()) {
+ return ((Set<?>) getValue()).contains(itemId);
+ } else {
+ final Object value = getValue();
+ return itemId
+ .equals(value == null ? getNullSelectionItemId() : value);
+ }
+ }
+
+ /**
+ * Selects an item.
+ *
+ * <p>
+ * In single select mode selecting item identified by
+ * {@link #getNullSelectionItemId()} sets the value of the property to null.
+ * </p>
+ *
+ * @param itemId
+ * the identifier of Item to be selected.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public void select(Object itemId) {
+ if (!isMultiSelect()) {
+ setValue(itemId);
+ } else if (!isSelected(itemId) && itemId != null
+ && items.containsId(itemId)) {
+ final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
+ s.add(itemId);
+ setValue(s);
+ }
+ }
+
+ /**
+ * Unselects an item.
+ *
+ * @param itemId
+ * the identifier of the Item to be unselected.
+ * @see #getNullSelectionItemId()
+ * @see #setNullSelectionItemId(Object)
+ *
+ */
+ public void unselect(Object itemId) {
+ if (isSelected(itemId)) {
+ if (isMultiSelect()) {
+ final Set<Object> s = new HashSet<Object>((Set<?>) getValue());
+ s.remove(itemId);
+ setValue(s);
+ } else {
+ setValue(null);
+ }
+ }
+ }
+
+ /**
+ * Notifies this listener that the Containers contents has changed.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeListener#containerPropertySetChange(com.vaadin.data.Container.PropertySetChangeEvent)
+ */
+ @Override
+ public void containerPropertySetChange(
+ Container.PropertySetChangeEvent event) {
+ firePropertySetChange();
+ }
+
+ /**
+ * Adds a new Property set change listener for this Container.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeNotifier#addListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ @Override
+ public void addPropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners == null) {
+ propertySetEventListeners = new LinkedHashSet<Container.PropertySetChangeListener>();
+ }
+ propertySetEventListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addPropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Container.PropertySetChangeListener listener) {
+ addPropertySetChangeListener(listener);
+ }
+
+ /**
+ * Removes a previously registered Property set change listener.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeNotifier#removeListener(com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ @Override
+ public void removePropertySetChangeListener(
+ Container.PropertySetChangeListener listener) {
+ if (propertySetEventListeners != null) {
+ propertySetEventListeners.remove(listener);
+ if (propertySetEventListeners.isEmpty()) {
+ propertySetEventListeners = null;
+ }
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ removePropertySetChangeListener(listener);
+ }
+
+ /**
+ * Adds an Item set change listener for the object.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ @Override
+ public void addItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners == null) {
+ itemSetEventListeners = new LinkedHashSet<Container.ItemSetChangeListener>();
+ }
+ itemSetEventListeners.add(listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(Container.ItemSetChangeListener listener) {
+ addItemSetChangeListener(listener);
+ }
+
+ /**
+ * Removes the Item set change listener from the object.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeNotifier#removeListener(com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ @Override
+ public void removeItemSetChangeListener(
+ Container.ItemSetChangeListener listener) {
+ if (itemSetEventListeners != null) {
+ itemSetEventListeners.remove(listener);
+ if (itemSetEventListeners.isEmpty()) {
+ itemSetEventListeners = null;
+ }
+ }
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeItemSetChangeListener(com.vaadin.data.Container.ItemSetChangeListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ removeItemSetChangeListener(listener);
+ }
+
+ @Override
+ public Collection<?> getListeners(Class<?> eventType) {
+ if (Container.ItemSetChangeEvent.class.isAssignableFrom(eventType)) {
+ if (itemSetEventListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(itemSetEventListeners);
+ }
+ } else if (Container.PropertySetChangeEvent.class
+ .isAssignableFrom(eventType)) {
+ if (propertySetEventListeners == null) {
+ return Collections.EMPTY_LIST;
+ } else {
+ return Collections
+ .unmodifiableCollection(propertySetEventListeners);
+ }
+ }
+
+ return super.getListeners(eventType);
+ }
+
+ /**
+ * Lets the listener know a Containers Item set has changed.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange(com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ // Clears the item id mapping table
+ itemIdMapper.removeAll();
+
+ // Notify all listeners
+ fireItemSetChange();
+ }
+
+ /**
+ * Fires the property set change event.
+ */
+ protected void firePropertySetChange() {
+ if (propertySetEventListeners != null
+ && !propertySetEventListeners.isEmpty()) {
+ final Container.PropertySetChangeEvent event = new PropertySetChangeEvent(
+ this);
+ final Object[] listeners = propertySetEventListeners.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ ((Container.PropertySetChangeListener) listeners[i])
+ .containerPropertySetChange(event);
+ }
+ }
+ markAsDirty();
+ }
+
+ /**
+ * Fires the item set change event.
+ */
+ protected void fireItemSetChange() {
+ if (itemSetEventListeners != null && !itemSetEventListeners.isEmpty()) {
+ final Container.ItemSetChangeEvent event = new ItemSetChangeEvent(
+ this);
+ final Object[] listeners = itemSetEventListeners.toArray();
+ for (int i = 0; i < listeners.length; i++) {
+ ((Container.ItemSetChangeListener) listeners[i])
+ .containerItemSetChange(event);
+ }
+ }
+ markAsDirty();
+ }
+
+ /**
+ * Implementation of item set change event.
+ */
+ private static class ItemSetChangeEvent extends EventObject
+ implements Serializable, Container.ItemSetChangeEvent {
+
+ private ItemSetChangeEvent(Container source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property where the event occurred.
+ *
+ * @see com.vaadin.data.Container.ItemSetChangeEvent#getContainer()
+ */
+ @Override
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ }
+
+ /**
+ * Implementation of property set change event.
+ */
+ private static class PropertySetChangeEvent extends EventObject
+ implements Container.PropertySetChangeEvent, Serializable {
+
+ private PropertySetChangeEvent(Container source) {
+ super(source);
+ }
+
+ /**
+ * Retrieves the Container whose contents have been modified.
+ *
+ * @see com.vaadin.data.Container.PropertySetChangeEvent#getContainer()
+ */
+ @Override
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ }
+
+ /**
+ * For multi-selectable fields, also an empty collection of values is
+ * considered to be an empty field.
+ *
+ * @see LegacyAbstractField#isEmpty().
+ */
+ @Override
+ public boolean isEmpty() {
+ if (!isMultiSelect()) {
+ return super.isEmpty();
+ } else {
+ Object value = getValue();
+ return super.isEmpty() || (value instanceof Collection
+ && ((Collection<?>) value).isEmpty());
+ }
+ }
+
+ /**
+ * Allow or disallow empty selection by the user. If the select is in
+ * single-select mode, you can make an item represent the empty selection by
+ * calling <code>setNullSelectionItemId()</code>. This way you can for
+ * instance set an icon and caption for the null selection item.
+ *
+ * @param nullSelectionAllowed
+ * whether or not to allow empty selection
+ * @see #setNullSelectionItemId(Object)
+ * @see #isNullSelectionAllowed()
+ */
+ public void setNullSelectionAllowed(boolean nullSelectionAllowed) {
+ if (nullSelectionAllowed != this.nullSelectionAllowed) {
+ this.nullSelectionAllowed = nullSelectionAllowed;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Checks if null empty selection is allowed by the user.
+ *
+ * @return whether or not empty selection is allowed
+ * @see #setNullSelectionAllowed(boolean)
+ */
+ public boolean isNullSelectionAllowed() {
+ return nullSelectionAllowed;
+ }
+
+ /**
+ * Returns the item id that represents null value of this select in single
+ * select mode.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ *
+ * @return the Object Null value item id.
+ * @see #setNullSelectionItemId(Object)
+ * @see #isSelected(Object)
+ * @see #select(Object)
+ */
+ public Object getNullSelectionItemId() {
+ return nullSelectionItemId;
+ }
+
+ /**
+ * Sets the item id that represents null value of this select.
+ *
+ * <p>
+ * Data interface does not support nulls as item ids. Selecting the item
+ * identified by this id is the same as selecting no items at all. This
+ * setting only affects the single select mode.
+ * </p>
+ *
+ * @param nullSelectionItemId
+ * the nullSelectionItemId to set.
+ * @see #getNullSelectionItemId()
+ * @see #isSelected(Object)
+ * @see #select(Object)
+ */
+ public void setNullSelectionItemId(Object nullSelectionItemId) {
+ if (nullSelectionItemId != null && isMultiSelect()) {
+ throw new IllegalStateException(
+ "Multiselect and NullSelectionItemId can not be set at the same time.");
+ }
+ this.nullSelectionItemId = nullSelectionItemId;
+ }
+
+ /**
+ * Notifies the component that it is connected to an application.
+ *
+ * @see com.vaadin.v7.ui.LegacyAbstractField#attach()
+ */
+ @Override
+ public void attach() {
+ super.attach();
+ }
+
+ /**
+ * Detaches the component from application.
+ *
+ * @see com.vaadin.ui.AbstractComponent#detach()
+ */
+ @Override
+ public void detach() {
+ getCaptionChangeListener().clear();
+ super.detach();
+ }
+
+ // Caption change listener
+ protected CaptionChangeListener getCaptionChangeListener() {
+ if (captionChangeListener == null) {
+ captionChangeListener = new CaptionChangeListener();
+ }
+ return captionChangeListener;
+ }
+
+ /**
+ * This is a listener helper for Item and Property changes that should cause
+ * a repaint. It should be attached to all items that are displayed, and the
+ * default implementation does this in paintContent(). Especially
+ * "lazyloading" components should take care to add and remove listeners as
+ * appropriate. Call addNotifierForItem() for each painted item (and
+ * remember to clear).
+ *
+ * NOTE: singleton, use getCaptionChangeListener().
+ *
+ */
+ protected class CaptionChangeListener implements
+ Item.PropertySetChangeListener, Property.ValueChangeListener {
+
+ // TODO clean this up - type is either Item.PropertySetChangeNotifier or
+ // Property.ValueChangeNotifier
+ HashSet<Object> captionChangeNotifiers = new HashSet<Object>();
+
+ public void addNotifierForItem(Object itemId) {
+ switch (getItemCaptionMode()) {
+ case ITEM:
+ final Item i = getItem(itemId);
+ if (i == null) {
+ return;
+ }
+ if (i instanceof Item.PropertySetChangeNotifier) {
+ ((Item.PropertySetChangeNotifier) i)
+ .addPropertySetChangeListener(
+ getCaptionChangeListener());
+ captionChangeNotifiers.add(i);
+ }
+ Collection<?> pids = i.getItemPropertyIds();
+ if (pids != null) {
+ for (Iterator<?> it = pids.iterator(); it.hasNext();) {
+ Property<?> p = i.getItemProperty(it.next());
+ if (p != null
+ && p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addValueChangeListener(
+ getCaptionChangeListener());
+ captionChangeNotifiers.add(p);
+ }
+ }
+
+ }
+ break;
+ case PROPERTY:
+ final Property<?> p = getContainerProperty(itemId,
+ getItemCaptionPropertyId());
+ if (p != null && p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addValueChangeListener(getCaptionChangeListener());
+ captionChangeNotifiers.add(p);
+ }
+ break;
+
+ }
+ if (getItemIconPropertyId() != null) {
+ final Property p = getContainerProperty(itemId,
+ getItemIconPropertyId());
+ if (p != null && p instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) p)
+ .addValueChangeListener(getCaptionChangeListener());
+ captionChangeNotifiers.add(p);
+ }
+ }
+ }
+
+ public void clear() {
+ for (Iterator<Object> it = captionChangeNotifiers.iterator(); it
+ .hasNext();) {
+ Object notifier = it.next();
+ if (notifier instanceof Item.PropertySetChangeNotifier) {
+ ((Item.PropertySetChangeNotifier) notifier)
+ .removePropertySetChangeListener(
+ getCaptionChangeListener());
+ } else {
+ ((Property.ValueChangeNotifier) notifier)
+ .removeValueChangeListener(
+ getCaptionChangeListener());
+ }
+ }
+ captionChangeNotifiers.clear();
+ }
+
+ @Override
+ public void valueChange(
+ com.vaadin.data.Property.ValueChangeEvent event) {
+ markAsDirty();
+ }
+
+ @Override
+ public void itemPropertySetChange(
+ com.vaadin.data.Item.PropertySetChangeEvent event) {
+ markAsDirty();
+ }
+
+ }
+
+ /**
+ * Criterion which accepts a drop only if the drop target is (one of) the
+ * given Item identifier(s). Criterion can be used only on a drop targets
+ * that extends AbstractSelect like {@link Table} and {@link Tree}. The
+ * target and identifiers of valid Items are given in constructor.
+ *
+ * @since 6.3
+ */
+ public static class TargetItemIs extends AbstractItemSetCriterion {
+
+ /**
+ * @param select
+ * the select implementation that is used as a drop target
+ * @param itemId
+ * the identifier(s) that are valid drop locations
+ */
+ public TargetItemIs(AbstractSelect select, Object... itemId) {
+ super(select, itemId);
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
+ .getTargetDetails();
+ if (dropTargetData.getTarget() != select) {
+ return false;
+ }
+ return itemIds.contains(dropTargetData.getItemIdOver());
+ }
+
+ }
+
+ /**
+ * Abstract helper class to implement item id based criterion.
+ *
+ * Note, inner class used not to open itemIdMapper for public access.
+ *
+ * @since 6.3
+ *
+ */
+ private static abstract class AbstractItemSetCriterion
+ extends ClientSideCriterion {
+ protected final Collection<Object> itemIds = new HashSet<Object>();
+ protected AbstractSelect select;
+
+ public AbstractItemSetCriterion(AbstractSelect select,
+ Object... itemId) {
+ if (itemIds == null || select == null) {
+ throw new IllegalArgumentException(
+ "Accepted item identifiers must be accepted.");
+ }
+ Collections.addAll(itemIds, itemId);
+ this.select = select;
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ String[] keys = new String[itemIds.size()];
+ int i = 0;
+ for (Object itemId : itemIds) {
+ String key = select.itemIdMapper.key(itemId);
+ keys[i++] = key;
+ }
+ target.addAttribute("keys", keys);
+ target.addAttribute("s", select);
+ }
+
+ }
+
+ /**
+ * This criterion accepts a only a {@link Transferable} that contains given
+ * Item (practically its identifier) from a specific AbstractSelect.
+ *
+ * @since 6.3
+ */
+ public static class AcceptItem extends AbstractItemSetCriterion {
+
+ /**
+ * @param select
+ * the select from which the item id's are checked
+ * @param itemId
+ * the item identifier(s) of the select that are accepted
+ */
+ public AcceptItem(AbstractSelect select, Object... itemId) {
+ super(select, itemId);
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ DataBoundTransferable transferable = (DataBoundTransferable) dragEvent
+ .getTransferable();
+ if (transferable.getSourceComponent() != select) {
+ return false;
+ }
+ return itemIds.contains(transferable.getItemId());
+ }
+
+ /**
+ * A simple accept criterion which ensures that {@link Transferable}
+ * contains an {@link Item} (or actually its identifier). In other words
+ * the criterion check that drag is coming from a {@link Container} like
+ * {@link Tree} or {@link Table}.
+ */
+ public static final ClientSideCriterion ALL = new ContainsDataFlavor(
+ "itemId");
+
+ }
+
+ /**
+ * TargetDetails implementation for subclasses of {@link AbstractSelect}
+ * that implement {@link DropTarget}.
+ *
+ * @since 6.3
+ */
+ public class AbstractSelectTargetDetails extends TargetDetailsImpl {
+
+ /**
+ * The item id over which the drag event happened.
+ */
+ protected Object idOver;
+
+ /**
+ * Constructor that automatically converts itemIdOver key to
+ * corresponding item Id
+ *
+ */
+ protected AbstractSelectTargetDetails(
+ Map<String, Object> rawVariables) {
+ super(rawVariables, (DropTarget) AbstractSelect.this);
+ // eagar fetch itemid, mapper may be emptied
+ String keyover = (String) getData("itemIdOver");
+ if (keyover != null) {
+ idOver = itemIdMapper.get(keyover);
+ }
+ }
+
+ /**
+ * If the drag operation is currently over an {@link Item}, this method
+ * returns the identifier of that {@link Item}.
+ *
+ */
+ public Object getItemIdOver() {
+ return idOver;
+ }
+
+ /**
+ * Returns a detailed vertical location where the drop happened on Item.
+ */
+ public VerticalDropLocation getDropLocation() {
+ String detail = (String) getData("detail");
+ if (detail == null) {
+ return null;
+ }
+ return VerticalDropLocation.valueOf(detail);
+ }
+
+ }
+
+ /**
+ * An accept criterion to accept drops only on a specific vertical location
+ * of an item.
+ * <p>
+ * This accept criterion is currently usable in Tree and Table
+ * implementations.
+ */
+ public static class VerticalLocationIs extends TargetDetailIs {
+ public static VerticalLocationIs TOP = new VerticalLocationIs(
+ VerticalDropLocation.TOP);
+ public static VerticalLocationIs BOTTOM = new VerticalLocationIs(
+ VerticalDropLocation.BOTTOM);
+ public static VerticalLocationIs MIDDLE = new VerticalLocationIs(
+ VerticalDropLocation.MIDDLE);
+
+ private VerticalLocationIs(VerticalDropLocation l) {
+ super("detail", l.name());
+ }
+ }
+
+ /**
+ * Implement this interface and pass it to Tree.setItemDescriptionGenerator
+ * or Table.setItemDescriptionGenerator to generate mouse over descriptions
+ * ("tooltips") for the rows and cells in Table or for the items in Tree.
+ */
+ public interface ItemDescriptionGenerator extends Serializable {
+
+ /**
+ * Called by Table when a cell (and row) is painted or a item is painted
+ * in Tree
+ *
+ * @param source
+ * The source of the generator, the Tree or Table the
+ * generator is attached to
+ * @param itemId
+ * The itemId of the painted cell
+ * @param propertyId
+ * The propertyId of the cell, null when getting row
+ * description
+ * @return The description or "tooltip" of the item.
+ */
+ public String generateDescription(Component source, Object itemId,
+ Object propertyId);
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext context) {
+ // handle default attributes
+ super.readDesign(design, context);
+ // handle children specifying selectable items (<option>)
+ readItems(design, context);
+ }
+
+ protected void readItems(Element design, DesignContext context) {
+ Set<String> selected = new HashSet<String>();
+ for (Element child : design.children()) {
+ readItem(child, selected, context);
+ }
+ if (!selected.isEmpty()) {
+ if (isMultiSelect()) {
+ setValue(selected, false, true);
+ } else if (selected.size() == 1) {
+ setValue(selected.iterator().next(), false, true);
+ } else {
+ throw new DesignException(
+ "Multiple values selected for a single select component");
+ }
+ }
+ }
+
+ /**
+ * Reads an Item from a design and inserts it into the data source.
+ * Hierarchical select components should override this method to recursively
+ * recursively read any child items as well.
+ *
+ * @since 7.5.0
+ * @param child
+ * a child element representing the item
+ * @param selected
+ * A set accumulating selected items. If the item that is read is
+ * marked as selected, its item id should be added to this set.
+ * @param context
+ * the DesignContext instance used in parsing
+ * @return the item id of the new item
+ *
+ * @throws DesignException
+ * if the tag name of the {@code child} element is not
+ * {@code option}.
+ */
+ protected Object readItem(Element child, Set<String> selected,
+ DesignContext context) {
+ if (!"option".equals(child.tagName())) {
+ throw new DesignException("Unrecognized child element in "
+ + getClass().getSimpleName() + ": " + child.tagName());
+ }
+
+ String itemId;
+ String caption = DesignFormatter.decodeFromTextNode(child.html());
+ if (child.hasAttr("item-id")) {
+ itemId = child.attr("item-id");
+ addItem(itemId);
+ setItemCaption(itemId, caption);
+ } else {
+ addItem(itemId = caption);
+ }
+
+ if (child.hasAttr("icon")) {
+ setItemIcon(itemId, DesignAttributeHandler.readAttribute("icon",
+ child.attributes(), Resource.class));
+ }
+
+ if (child.hasAttr("selected")) {
+ selected.add(itemId);
+ }
+
+ return itemId;
+ }
+
+ @Override
+ public void writeDesign(Element design, DesignContext context) {
+ // Write default attributes
+ super.writeDesign(design, context);
+
+ // Write options if warranted
+ if (context.shouldWriteData(this)) {
+ writeItems(design, context);
+ }
+ }
+
+ /**
+ * Writes the data source items to a design. Hierarchical select components
+ * should override this method to only write the root items.
+ *
+ * @since 7.5.0
+ * @param design
+ * the element into which to insert the items
+ * @param context
+ * the DesignContext instance used in writing
+ */
+ protected void writeItems(Element design, DesignContext context) {
+ for (Object itemId : getItemIds()) {
+ writeItem(design, itemId, context);
+ }
+ }
+
+ /**
+ * Writes a data source Item to a design. Hierarchical select components
+ * should override this method to recursively write any child items as well.
+ *
+ * @since 7.5.0
+ * @param design
+ * the element into which to insert the item
+ * @param itemId
+ * the id of the item to write
+ * @param context
+ * the DesignContext instance used in writing
+ * @return
+ */
+ protected Element writeItem(Element design, Object itemId,
+ DesignContext context) {
+ Element element = design.appendElement("option");
+
+ String caption = getItemCaption(itemId);
+ if (caption != null && !caption.equals(itemId.toString())) {
+ element.html(DesignFormatter.encodeForTextNode(caption));
+ element.attr("item-id", itemId.toString());
+ } else {
+ element.html(DesignFormatter.encodeForTextNode(itemId.toString()));
+ }
+
+ Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ DesignAttributeHandler.writeAttribute("icon", element.attributes(),
+ icon, null, Resource.class);
+ }
+
+ if (isSelected(itemId)) {
+ element.attr("selected", "");
+ }
+
+ return element;
+ }
+
+ @Override
+ protected AbstractSelectState getState() {
+ return (AbstractSelectState) super.getState();
+ }
+
+ @Override
+ protected AbstractSelectState getState(boolean markAsDirty) {
+ return (AbstractSelectState) super.getState(markAsDirty);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/Calendar.java b/compatibility-server/src/main/java/com/vaadin/ui/Calendar.java
new file mode 100644
index 0000000000..1a2c7ef716
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/Calendar.java
@@ -0,0 +1,2029 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import java.lang.reflect.Method;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Date;
+import java.util.EventListener;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.jsoup.nodes.Attributes;
+import org.jsoup.nodes.Element;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.util.BeanItemContainer;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.shared.ui.calendar.CalendarServerRpc;
+import com.vaadin.shared.ui.calendar.CalendarState;
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventClickHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.RangeSelectEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.RangeSelectHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClickHandler;
+import com.vaadin.ui.components.calendar.CalendarDateRange;
+import com.vaadin.ui.components.calendar.CalendarTargetDetails;
+import com.vaadin.ui.components.calendar.ContainerEventProvider;
+import com.vaadin.ui.components.calendar.event.BasicEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEditableEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeListener;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider;
+import com.vaadin.ui.components.calendar.handler.BasicBackwardHandler;
+import com.vaadin.ui.components.calendar.handler.BasicDateClickHandler;
+import com.vaadin.ui.components.calendar.handler.BasicEventMoveHandler;
+import com.vaadin.ui.components.calendar.handler.BasicEventResizeHandler;
+import com.vaadin.ui.components.calendar.handler.BasicForwardHandler;
+import com.vaadin.ui.components.calendar.handler.BasicWeekClickHandler;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+
+/**
+ * <p>
+ * Vaadin Calendar is for visualizing events in a calendar. Calendar events can
+ * be visualized in the variable length view depending on the start and end
+ * dates.
+ * </p>
+ *
+ * <li>You can set the viewable date range with the {@link #setStartDate(Date)}
+ * and {@link #setEndDate(Date)} methods. Calendar has a default date range of
+ * one week</li>
+ *
+ * <li>Calendar has two kind of views: monthly and weekly view</li>
+ *
+ * <li>If date range is seven days or shorter, the weekly view is used.</li>
+ *
+ * <li>Calendar queries its events by using a
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider}. By default, a
+ * {@link com.vaadin.addon.calendar.event.BasicEventProvider BasicEventProvider}
+ * is used.</li>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class Calendar extends AbstractComponent
+ implements CalendarComponentEvents.NavigationNotifier,
+ CalendarComponentEvents.EventMoveNotifier,
+ CalendarComponentEvents.RangeSelectNotifier,
+ CalendarComponentEvents.EventResizeNotifier,
+ CalendarEventProvider.EventSetChangeListener, DropTarget,
+ CalendarEditableEventProvider, Action.Container, LegacyComponent {
+
+ /**
+ * Calendar can use either 12 hours clock or 24 hours clock.
+ */
+ public enum TimeFormat {
+
+ Format12H(), Format24H();
+ }
+
+ /** Defines currently active format for time. 12H/24H. */
+ protected TimeFormat currentTimeFormat;
+
+ /** Internal calendar data source. */
+ protected java.util.Calendar currentCalendar = java.util.Calendar
+ .getInstance();
+
+ /** Defines the component's active time zone. */
+ protected TimeZone timezone;
+
+ /** Defines the calendar's date range starting point. */
+ protected Date startDate = null;
+
+ /** Defines the calendar's date range ending point. */
+ protected Date endDate = null;
+
+ /** Event provider. */
+ private CalendarEventProvider calendarEventProvider;
+
+ /**
+ * Internal buffer for the events that are retrieved from the event
+ * provider.
+ */
+ protected List<CalendarEvent> events;
+
+ /** Date format that will be used in the UIDL for dates. */
+ protected DateFormat df_date = new SimpleDateFormat("yyyy-MM-dd");
+
+ /** Time format that will be used in the UIDL for time. */
+ protected DateFormat df_time = new SimpleDateFormat("HH:mm:ss");
+
+ /** Date format that will be used in the UIDL for both date and time. */
+ protected DateFormat df_date_time = new SimpleDateFormat(
+ DateConstants.CLIENT_DATE_FORMAT + "-"
+ + DateConstants.CLIENT_TIME_FORMAT);
+
+ /**
+ * Week view's scroll position. Client sends updates to this value so that
+ * scroll position wont reset all the time.
+ */
+ private int scrollTop = 0;
+
+ /** Caption format for the weekly view */
+ private String weeklyCaptionFormat = null;
+
+ /** Map from event ids to event handlers */
+ private final Map<String, EventListener> handlers;
+
+ /**
+ * Drop Handler for Vaadin DD. By default null.
+ */
+ private DropHandler dropHandler;
+
+ /**
+ * First day to show for a week
+ */
+ private int firstDay = 1;
+
+ /**
+ * Last day to show for a week
+ */
+ private int lastDay = 7;
+
+ /**
+ * First hour to show for a day
+ */
+ private int firstHour = 0;
+
+ /**
+ * Last hour to show for a day
+ */
+ private int lastHour = 23;
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Action.Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper<Action> actionMapper = null;
+
+ /**
+ *
+ */
+ private CalendarServerRpcImpl rpc = new CalendarServerRpcImpl();
+
+ private Integer customFirstDayOfWeek;
+
+ /**
+ * Returns the logger for the calendar
+ */
+ protected Logger getLogger() {
+ return Logger.getLogger(Calendar.class.getName());
+ }
+
+ /**
+ * Construct a Vaadin Calendar with a BasicEventProvider and no caption.
+ * Default date range is one week.
+ */
+ public Calendar() {
+ this(null, new BasicEventProvider());
+ }
+
+ /**
+ * Construct a Vaadin Calendar with a BasicEventProvider and the provided
+ * caption. Default date range is one week.
+ *
+ * @param caption
+ */
+ public Calendar(String caption) {
+ this(caption, new BasicEventProvider());
+ }
+
+ /**
+ * <p>
+ * Construct a Vaadin Calendar with event provider. Event provider is
+ * obligatory, because calendar component will query active events through
+ * it.
+ * </p>
+ *
+ * <p>
+ * By default, Vaadin Calendar will show dates from the start of the current
+ * week to the end of the current week. Use {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)} to change this.
+ * </p>
+ *
+ * @param eventProvider
+ * Event provider, cannot be null.
+ */
+ public Calendar(CalendarEventProvider eventProvider) {
+ this(null, eventProvider);
+ }
+
+ /**
+ * <p>
+ * Construct a Vaadin Calendar with event provider and a caption. Event
+ * provider is obligatory, because calendar component will query active
+ * events through it.
+ * </p>
+ *
+ * <p>
+ * By default, Vaadin Calendar will show dates from the start of the current
+ * week to the end of the current week. Use {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)} to change this.
+ * </p>
+ *
+ * @param eventProvider
+ * Event provider, cannot be null.
+ */
+ // this is the constructor every other constructor calls
+ public Calendar(String caption, CalendarEventProvider eventProvider) {
+ registerRpc(rpc);
+ setCaption(caption);
+ handlers = new HashMap<String, EventListener>();
+ setDefaultHandlers();
+ currentCalendar.setTime(new Date());
+ setEventProvider(eventProvider);
+ getState().firstDayOfWeek = firstDay;
+ getState().lastVisibleDayOfWeek = lastDay;
+ getState().firstHourOfDay = firstHour;
+ getState().lastHourOfDay = lastHour;
+ setTimeFormat(null);
+
+ }
+
+ @Override
+ public CalendarState getState() {
+ return (CalendarState) super.getState();
+ }
+
+ @Override
+ protected CalendarState getState(boolean markAsDirty) {
+ return (CalendarState) super.getState(markAsDirty);
+ }
+
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ super.beforeClientResponse(initial);
+
+ initCalendarWithLocale();
+
+ getState().format24H = TimeFormat.Format24H == getTimeFormat();
+ setupDaysAndActions();
+ setupCalendarEvents();
+ rpc.scroll(scrollTop);
+ }
+
+ /**
+ * Set all the wanted default handlers here. This is always called after
+ * constructing this object. All other events have default handlers except
+ * range and event click.
+ */
+ protected void setDefaultHandlers() {
+ setHandler(new BasicBackwardHandler());
+ setHandler(new BasicForwardHandler());
+ setHandler(new BasicWeekClickHandler());
+ setHandler(new BasicDateClickHandler());
+ setHandler(new BasicEventMoveHandler());
+ setHandler(new BasicEventResizeHandler());
+ }
+
+ /**
+ * Gets the calendar's start date.
+ *
+ * @return First visible date.
+ */
+ public Date getStartDate() {
+ if (startDate == null) {
+ currentCalendar.set(java.util.Calendar.MILLISECOND, 0);
+ currentCalendar.set(java.util.Calendar.SECOND, 0);
+ currentCalendar.set(java.util.Calendar.MINUTE, 0);
+ currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
+ currentCalendar.set(java.util.Calendar.DAY_OF_WEEK,
+ currentCalendar.getFirstDayOfWeek());
+ return currentCalendar.getTime();
+ }
+ return startDate;
+ }
+
+ /**
+ * Sets start date for the calendar. This and {@link #setEndDate(Date)}
+ * control the range of dates visible on the component. The default range is
+ * one week.
+ *
+ * @param date
+ * First visible date to show.
+ */
+ public void setStartDate(Date date) {
+ if (!date.equals(startDate)) {
+ startDate = date;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Gets the calendar's end date.
+ *
+ * @return Last visible date.
+ */
+ public Date getEndDate() {
+ if (endDate == null) {
+ currentCalendar.set(java.util.Calendar.MILLISECOND, 0);
+ currentCalendar.set(java.util.Calendar.SECOND, 59);
+ currentCalendar.set(java.util.Calendar.MINUTE, 59);
+ currentCalendar.set(java.util.Calendar.HOUR_OF_DAY, 23);
+ currentCalendar.set(java.util.Calendar.DAY_OF_WEEK,
+ currentCalendar.getFirstDayOfWeek() + 6);
+ return currentCalendar.getTime();
+ }
+ return endDate;
+ }
+
+ /**
+ * Sets end date for the calendar. Starting from startDate, only six weeks
+ * will be shown if duration to endDate is longer than six weeks.
+ *
+ * This and {@link #setStartDate(Date)} control the range of dates visible
+ * on the component. The default range is one week.
+ *
+ * @param date
+ * Last visible date to show.
+ */
+ public void setEndDate(Date date) {
+ if (startDate != null && startDate.after(date)) {
+ startDate = (Date) date.clone();
+ markAsDirty();
+ } else if (!date.equals(endDate)) {
+ endDate = date;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Sets the locale to be used in the Calendar component.
+ *
+ * @see com.vaadin.ui.AbstractComponent#setLocale(java.util.Locale)
+ */
+ @Override
+ public void setLocale(Locale newLocale) {
+ super.setLocale(newLocale);
+ initCalendarWithLocale();
+ }
+
+ /**
+ * Initialize the java calendar instance with the current locale and
+ * timezone.
+ */
+ private void initCalendarWithLocale() {
+ if (timezone != null) {
+ currentCalendar = java.util.Calendar.getInstance(timezone,
+ getLocale());
+
+ } else {
+ currentCalendar = java.util.Calendar.getInstance(getLocale());
+ }
+
+ if (customFirstDayOfWeek != null) {
+ currentCalendar.setFirstDayOfWeek(customFirstDayOfWeek);
+ }
+ }
+
+ private void setupCalendarEvents() {
+ int durationInDays = (int) (((endDate.getTime()) - startDate.getTime())
+ / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ if (durationInDays > 60) {
+ throw new RuntimeException(
+ "Daterange is too big (max 60) = " + durationInDays);
+ }
+
+ Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
+ Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);
+
+ currentCalendar.setTime(firstDateToShow);
+ events = getEventProvider().getEvents(firstDateToShow, lastDateToShow);
+
+ List<CalendarState.Event> calendarStateEvents = new ArrayList<CalendarState.Event>();
+ if (events != null) {
+ for (int i = 0; i < events.size(); i++) {
+ CalendarEvent e = events.get(i);
+ CalendarState.Event event = new CalendarState.Event();
+ event.index = i;
+ event.caption = e.getCaption() == null ? "" : e.getCaption();
+ event.dateFrom = df_date.format(e.getStart());
+ event.dateTo = df_date.format(e.getEnd());
+ event.timeFrom = df_time.format(e.getStart());
+ event.timeTo = df_time.format(e.getEnd());
+ event.description = e.getDescription() == null ? ""
+ : e.getDescription();
+ event.styleName = e.getStyleName() == null ? ""
+ : e.getStyleName();
+ event.allDay = e.isAllDay();
+ calendarStateEvents.add(event);
+ }
+ }
+ getState().events = calendarStateEvents;
+ }
+
+ private void setupDaysAndActions() {
+ // Make sure we have a up-to-date locale
+ initCalendarWithLocale();
+
+ CalendarState state = getState();
+
+ state.firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+
+ // If only one is null, throw exception
+ // If both are null, set defaults
+ if (startDate == null ^ endDate == null) {
+ String message = "Schedule cannot be painted without a proper date range.\n";
+ if (startDate == null) {
+ throw new IllegalStateException(message
+ + "You must set a start date using setStartDate(Date).");
+
+ } else {
+ throw new IllegalStateException(message
+ + "You must set an end date using setEndDate(Date).");
+ }
+
+ } else if (startDate == null && endDate == null) {
+ // set defaults
+ startDate = getStartDate();
+ endDate = getEndDate();
+ }
+
+ int durationInDays = (int) (((endDate.getTime()) - startDate.getTime())
+ / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ if (durationInDays > 60) {
+ throw new RuntimeException(
+ "Daterange is too big (max 60) = " + durationInDays);
+ }
+
+ state.dayNames = getDayNamesShort();
+ state.monthNames = getMonthNamesShort();
+
+ // Use same timezone in all dates this component handles.
+ // Show "now"-marker in browser within given timezone.
+ Date now = new Date();
+ currentCalendar.setTime(now);
+ now = currentCalendar.getTime();
+
+ // Reset time zones for custom date formats
+ df_date.setTimeZone(currentCalendar.getTimeZone());
+ df_time.setTimeZone(currentCalendar.getTimeZone());
+
+ state.now = (df_date.format(now) + " " + df_time.format(now));
+
+ Date firstDateToShow = expandStartDate(startDate, durationInDays > 7);
+ Date lastDateToShow = expandEndDate(endDate, durationInDays > 7);
+
+ currentCalendar.setTime(firstDateToShow);
+
+ DateFormat weeklyCaptionFormatter = getWeeklyCaptionFormatter();
+ weeklyCaptionFormatter.setTimeZone(currentCalendar.getTimeZone());
+
+ Map<CalendarDateRange, Set<Action>> actionMap = new HashMap<CalendarDateRange, Set<Action>>();
+
+ List<CalendarState.Day> days = new ArrayList<CalendarState.Day>();
+
+ // Send all dates to client from server. This
+ // approach was taken because gwt doesn't
+ // support date localization properly.
+ while (currentCalendar.getTime().compareTo(lastDateToShow) < 1) {
+ final Date date = currentCalendar.getTime();
+ final CalendarState.Day day = new CalendarState.Day();
+ day.date = df_date.format(date);
+ day.localizedDateFormat = weeklyCaptionFormatter.format(date);
+ day.dayOfWeek = getDowByLocale(currentCalendar);
+ day.week = getWeek(currentCalendar);
+ day.yearOfWeek = getYearOfWeek(currentCalendar);
+
+ days.add(day);
+
+ // Get actions for a specific date
+ if (actionHandlers != null) {
+ for (Action.Handler actionHandler : actionHandlers) {
+
+ // Create calendar which omits time
+ GregorianCalendar cal = new GregorianCalendar(getTimeZone(),
+ getLocale());
+ cal.clear();
+ cal.set(currentCalendar.get(java.util.Calendar.YEAR),
+ currentCalendar.get(java.util.Calendar.MONTH),
+ currentCalendar.get(java.util.Calendar.DATE));
+
+ // Get day start and end times
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.DATE, 1);
+ cal.add(java.util.Calendar.SECOND, -1);
+ Date end = cal.getTime();
+
+ boolean monthView = (durationInDays > 7);
+
+ /**
+ * If in day or week view add actions for each half-an-hour.
+ * If in month view add actions for each day
+ */
+ if (monthView) {
+ setActionsForDay(actionMap, start, end, actionHandler);
+ } else {
+ setActionsForEachHalfHour(actionMap, start, end,
+ actionHandler);
+ }
+
+ }
+ }
+
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ }
+ state.days = days;
+ state.actions = createActionsList(actionMap);
+ }
+
+ private int getWeek(java.util.Calendar calendar) {
+ return calendar.get(java.util.Calendar.WEEK_OF_YEAR);
+ }
+
+ private int getYearOfWeek(java.util.Calendar calendar) {
+ // Would use calendar.getWeekYear() but it's only available since 1.7.
+ int week = getWeek(calendar);
+ int month = calendar.get(java.util.Calendar.MONTH);
+ int year = calendar.get(java.util.Calendar.YEAR);
+
+ if (week == 1 && month == java.util.Calendar.DECEMBER) {
+ return year + 1;
+ }
+
+ return year;
+ }
+
+ private void setActionsForEachHalfHour(
+ Map<CalendarDateRange, Set<Action>> actionMap, Date start, Date end,
+ Action.Handler actionHandler) {
+ GregorianCalendar cal = new GregorianCalendar(getTimeZone(),
+ getLocale());
+ cal.setTime(start);
+ while (cal.getTime().before(end)) {
+ Date s = cal.getTime();
+ cal.add(java.util.Calendar.MINUTE, 30);
+ Date e = cal.getTime();
+ CalendarDateRange range = new CalendarDateRange(s, e,
+ getTimeZone());
+ Action[] actions = actionHandler.getActions(range, this);
+ if (actions != null) {
+ Set<Action> actionSet = new LinkedHashSet<Action>(
+ Arrays.asList(actions));
+ actionMap.put(range, actionSet);
+ }
+ }
+ }
+
+ private void setActionsForDay(Map<CalendarDateRange, Set<Action>> actionMap,
+ Date start, Date end, Action.Handler actionHandler) {
+ CalendarDateRange range = new CalendarDateRange(start, end,
+ getTimeZone());
+ Action[] actions = actionHandler.getActions(range, this);
+ if (actions != null) {
+ Set<Action> actionSet = new LinkedHashSet<Action>(
+ Arrays.asList(actions));
+ actionMap.put(range, actionSet);
+ }
+ }
+
+ private List<CalendarState.Action> createActionsList(
+ Map<CalendarDateRange, Set<Action>> actionMap) {
+ if (actionMap.isEmpty()) {
+ return null;
+ }
+
+ List<CalendarState.Action> calendarActions = new ArrayList<CalendarState.Action>();
+
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+
+ for (Entry<CalendarDateRange, Set<Action>> entry : actionMap
+ .entrySet()) {
+ CalendarDateRange range = entry.getKey();
+ Set<Action> actions = entry.getValue();
+ for (Action action : actions) {
+ String key = actionMapper.key(action);
+ CalendarState.Action calendarAction = new CalendarState.Action();
+ calendarAction.actionKey = key;
+ calendarAction.caption = action.getCaption();
+ setResource(key, action.getIcon());
+ calendarAction.iconKey = key;
+ calendarAction.startDate = formatter.format(range.getStart());
+ calendarAction.endDate = formatter.format(range.getEnd());
+ calendarActions.add(calendarAction);
+ }
+ }
+
+ return calendarActions;
+ }
+
+ /**
+ * Gets currently active time format. Value is either TimeFormat.Format12H
+ * or TimeFormat.Format24H.
+ *
+ * @return TimeFormat Format for the time.
+ */
+ public TimeFormat getTimeFormat() {
+ if (currentTimeFormat == null) {
+ SimpleDateFormat f;
+ if (getLocale() == null) {
+ f = (SimpleDateFormat) SimpleDateFormat
+ .getTimeInstance(SimpleDateFormat.SHORT);
+ } else {
+ f = (SimpleDateFormat) SimpleDateFormat
+ .getTimeInstance(SimpleDateFormat.SHORT, getLocale());
+ }
+ String p = f.toPattern();
+ if (p.indexOf("HH") != -1 || p.indexOf("H") != -1) {
+ return TimeFormat.Format24H;
+ }
+ return TimeFormat.Format12H;
+ }
+ return currentTimeFormat;
+ }
+
+ /**
+ * Example: <code>setTimeFormat(TimeFormat.Format12H);</code></br>
+ * Set to null, if you want the format being defined by the locale.
+ *
+ * @param format
+ * Set 12h or 24h format. Default is defined by the locale.
+ */
+ public void setTimeFormat(TimeFormat format) {
+ currentTimeFormat = format;
+ markAsDirty();
+ }
+
+ /**
+ * Returns a time zone that is currently used by this component.
+ *
+ * @return Component's Time zone
+ */
+ public TimeZone getTimeZone() {
+ if (timezone == null) {
+ return currentCalendar.getTimeZone();
+ }
+ return timezone;
+ }
+
+ /**
+ * Set time zone that this component will use. Null value sets the default
+ * time zone.
+ *
+ * @param zone
+ * Time zone to use
+ */
+ public void setTimeZone(TimeZone zone) {
+ timezone = zone;
+ if (!currentCalendar.getTimeZone().equals(zone)) {
+ if (zone == null) {
+ zone = TimeZone.getDefault();
+ }
+ currentCalendar.setTimeZone(zone);
+ df_date_time.setTimeZone(zone);
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Get the internally used Calendar instance. This is the currently used
+ * instance of {@link java.util.Calendar} but is bound to change during the
+ * lifetime of the component.
+ *
+ * @return the currently used java calendar
+ */
+ public java.util.Calendar getInternalCalendar() {
+ return currentCalendar;
+ }
+
+ /**
+ * <p>
+ * This method restricts the weekdays that are shown. This affects both the
+ * monthly and the weekly view. The general contract is that <b>firstDay <
+ * lastDay</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param firstDay
+ * the first day of the week to show, between 1 and 7
+ */
+ public void setFirstVisibleDayOfWeek(int firstDay) {
+ if (this.firstDay != firstDay && firstDay >= 1 && firstDay <= 7
+ && getLastVisibleDayOfWeek() >= firstDay) {
+ this.firstDay = firstDay;
+ getState().firstVisibleDayOfWeek = firstDay;
+ }
+ }
+
+ /**
+ * Get the first visible day of the week. Returns the weekdays as integers
+ * represented by {@link java.util.Calendar#DAY_OF_WEEK}
+ *
+ * @return An integer representing the week day according to
+ * {@link java.util.Calendar#DAY_OF_WEEK}
+ */
+ public int getFirstVisibleDayOfWeek() {
+ return firstDay;
+ }
+
+ /**
+ * <p>
+ * This method restricts the weekdays that are shown. This affects both the
+ * monthly and the weekly view. The general contract is that <b>firstDay <
+ * lastDay</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param lastDay
+ * the first day of the week to show, between 1 and 7
+ */
+ public void setLastVisibleDayOfWeek(int lastDay) {
+ if (this.lastDay != lastDay && lastDay >= 1 && lastDay <= 7
+ && getFirstVisibleDayOfWeek() <= lastDay) {
+ this.lastDay = lastDay;
+ getState().lastVisibleDayOfWeek = lastDay;
+ }
+ }
+
+ /**
+ * Get the last visible day of the week. Returns the weekdays as integers
+ * represented by {@link java.util.Calendar#DAY_OF_WEEK}
+ *
+ * @return An integer representing the week day according to
+ * {@link java.util.Calendar#DAY_OF_WEEK}
+ */
+ public int getLastVisibleDayOfWeek() {
+ return lastDay;
+ }
+
+ /**
+ * <p>
+ * This method restricts the hours that are shown per day. This affects the
+ * weekly view. The general contract is that <b>firstHour < lastHour</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param firstHour
+ * the first hour of the day to show, between 0 and 23
+ */
+ public void setFirstVisibleHourOfDay(int firstHour) {
+ if (this.firstHour != firstHour && firstHour >= 0 && firstHour <= 23
+ && firstHour <= getLastVisibleHourOfDay()) {
+ this.firstHour = firstHour;
+ getState().firstHourOfDay = firstHour;
+ }
+ }
+
+ /**
+ * Returns the first visible hour in the week view. Returns the hour using a
+ * 24h time format
+ *
+ */
+ public int getFirstVisibleHourOfDay() {
+ return firstHour;
+ }
+
+ /**
+ * <p>
+ * This method restricts the hours that are shown per day. This affects the
+ * weekly view. The general contract is that <b>firstHour < lastHour</b>.
+ * </p>
+ *
+ * <p>
+ * Note that this only affects the rendering process. Events are still
+ * requested by the dates set by {@link #setStartDate(Date)} and
+ * {@link #setEndDate(Date)}.
+ * </p>
+ *
+ * @param lastHour
+ * the first hour of the day to show, between 0 and 23
+ */
+ public void setLastVisibleHourOfDay(int lastHour) {
+ if (this.lastHour != lastHour && lastHour >= 0 && lastHour <= 23
+ && lastHour >= getFirstVisibleHourOfDay()) {
+ this.lastHour = lastHour;
+ getState().lastHourOfDay = lastHour;
+ }
+ }
+
+ /**
+ * Returns the last visible hour in the week view. Returns the hour using a
+ * 24h time format
+ *
+ */
+ public int getLastVisibleHourOfDay() {
+ return lastHour;
+ }
+
+ /**
+ * Gets the date caption format for the weekly view.
+ *
+ * @return The pattern used in caption of dates in weekly view.
+ */
+ public String getWeeklyCaptionFormat() {
+ return weeklyCaptionFormat;
+ }
+
+ /**
+ * Sets custom date format for the weekly view. This is the caption of the
+ * date. Format could be like "mmm MM/dd".
+ *
+ * @param dateFormatPattern
+ * The date caption pattern.
+ */
+ public void setWeeklyCaptionFormat(String dateFormatPattern) {
+ if ((weeklyCaptionFormat == null && dateFormatPattern != null)
+ || (weeklyCaptionFormat != null
+ && !weeklyCaptionFormat.equals(dateFormatPattern))) {
+ weeklyCaptionFormat = dateFormatPattern;
+ markAsDirty();
+ }
+ }
+
+ private DateFormat getWeeklyCaptionFormatter() {
+ if (weeklyCaptionFormat != null) {
+ return new SimpleDateFormat(weeklyCaptionFormat, getLocale());
+ } else {
+ return SimpleDateFormat.getDateInstance(SimpleDateFormat.SHORT,
+ getLocale());
+ }
+ }
+
+ /**
+ * Get the day of week by the given calendar and its locale
+ *
+ * @param calendar
+ * The calendar to use
+ * @return
+ */
+ private static int getDowByLocale(java.util.Calendar calendar) {
+ int fow = calendar.get(java.util.Calendar.DAY_OF_WEEK);
+
+ // monday first
+ if (calendar.getFirstDayOfWeek() == java.util.Calendar.MONDAY) {
+ fow = (fow == java.util.Calendar.SUNDAY) ? 7 : fow - 1;
+ }
+
+ return fow;
+ }
+
+ /**
+ * Is the user allowed to trigger events which alters the events
+ *
+ * @return true if the client is allowed to send changes to server
+ * @see #isEventClickAllowed()
+ */
+ protected boolean isClientChangeAllowed() {
+ return !isReadOnly();
+ }
+
+ /**
+ * Is the user allowed to trigger click events. Returns {@code true} by
+ * default. Subclass can override this method to disallow firing event
+ * clicks got from the client side.
+ *
+ * @return true if the client is allowed to click events
+ * @see #isClientChangeAllowed()
+ * @deprecated As of 7.4, override {@link #fireEventClick(Integer)} instead.
+ */
+ @Deprecated
+ protected boolean isEventClickAllowed() {
+ return true;
+ }
+
+ /**
+ * Fires an event when the user selecing moving forward/backward in the
+ * calendar.
+ *
+ * @param forward
+ * True if the calendar moved forward else backward is assumed.
+ */
+ protected void fireNavigationEvent(boolean forward) {
+ if (forward) {
+ fireEvent(new ForwardEvent(this));
+ } else {
+ fireEvent(new BackwardEvent(this));
+ }
+ }
+
+ /**
+ * Fires an event move event to all server side move listerners
+ *
+ * @param index
+ * The index of the event in the events list
+ * @param newFromDatetime
+ * The changed from date time
+ */
+ protected void fireEventMove(int index, Date newFromDatetime) {
+ MoveEvent event = new MoveEvent(this, events.get(index),
+ newFromDatetime);
+
+ if (calendarEventProvider instanceof EventMoveHandler) {
+ // Notify event provider if it is an event move handler
+ ((EventMoveHandler) calendarEventProvider).eventMove(event);
+ }
+
+ // Notify event move handler attached by using the
+ // setHandler(EventMoveHandler) method
+ fireEvent(event);
+ }
+
+ /**
+ * Fires event when a week was clicked in the calendar.
+ *
+ * @param week
+ * The week that was clicked
+ * @param year
+ * The year of the week
+ */
+ protected void fireWeekClick(int week, int year) {
+ fireEvent(new WeekClick(this, week, year));
+ }
+
+ /**
+ * Fires event when a date was clicked in the calendar. Uses an existing
+ * event from the event cache.
+ *
+ * @param index
+ * The index of the event in the event cache.
+ */
+ protected void fireEventClick(Integer index) {
+ fireEvent(new EventClick(this, events.get(index)));
+ }
+
+ /**
+ * Fires event when a date was clicked in the calendar. Creates a new event
+ * for the date and passes it to the listener.
+ *
+ * @param date
+ * The date and time that was clicked
+ */
+ protected void fireDateClick(Date date) {
+ fireEvent(new DateClickEvent(this, date));
+ }
+
+ /**
+ * Fires an event range selected event. The event is fired when a user
+ * highlights an area in the calendar. The highlighted areas start and end
+ * dates are returned as arguments.
+ *
+ * @param from
+ * The start date and time of the highlighted area
+ * @param to
+ * The end date and time of the highlighted area
+ * @param monthlyMode
+ * Is the calendar in monthly mode
+ */
+ protected void fireRangeSelect(Date from, Date to, boolean monthlyMode) {
+ fireEvent(new RangeSelectEvent(this, from, to, monthlyMode));
+ }
+
+ /**
+ * Fires an event resize event. The event is fired when a user resizes the
+ * event in the calendar causing the time range of the event to increase or
+ * decrease. The new start and end times are returned as arguments to this
+ * method.
+ *
+ * @param index
+ * The index of the event in the event cache
+ * @param startTime
+ * The new start date and time of the event
+ * @param endTime
+ * The new end date and time of the event
+ */
+ protected void fireEventResize(int index, Date startTime, Date endTime) {
+ EventResize event = new EventResize(this, events.get(index), startTime,
+ endTime);
+
+ if (calendarEventProvider instanceof EventResizeHandler) {
+ // Notify event provider if it is an event resize handler
+ ((EventResizeHandler) calendarEventProvider).eventResize(event);
+ }
+
+ // Notify event resize handler attached by using the
+ // setHandler(EventMoveHandler) method
+ fireEvent(event);
+ }
+
+ /**
+ * Localized display names for week days starting from sunday. Returned
+ * array's length is always 7.
+ *
+ * @return Array of localized weekday names.
+ */
+ protected String[] getDayNamesShort() {
+ DateFormatSymbols s = new DateFormatSymbols(getLocale());
+ return Arrays.copyOfRange(s.getWeekdays(), 1, 8);
+ }
+
+ /**
+ * Localized display names for months starting from January. Returned
+ * array's length is always 12.
+ *
+ * @return Array of localized month names.
+ */
+ protected String[] getMonthNamesShort() {
+ DateFormatSymbols s = new DateFormatSymbols(getLocale());
+ return Arrays.copyOf(s.getShortMonths(), 12);
+ }
+
+ /**
+ * Gets a date that is first day in the week that target given date belongs
+ * to.
+ *
+ * @param date
+ * Target date
+ * @return Date that is first date in same week that given date is.
+ */
+ protected Date getFirstDateForWeek(Date date) {
+ int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+ currentCalendar.setTime(date);
+ while (firstDayOfWeek != currentCalendar
+ .get(java.util.Calendar.DAY_OF_WEEK)) {
+ currentCalendar.add(java.util.Calendar.DATE, -1);
+ }
+ return currentCalendar.getTime();
+ }
+
+ /**
+ * Gets a date that is last day in the week that target given date belongs
+ * to.
+ *
+ * @param date
+ * Target date
+ * @return Date that is last date in same week that given date is.
+ */
+ protected Date getLastDateForWeek(Date date) {
+ currentCalendar.setTime(date);
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ int firstDayOfWeek = currentCalendar.getFirstDayOfWeek();
+ // Roll to weeks last day using firstdayofweek. Roll until FDofW is
+ // found and then roll back one day.
+ while (firstDayOfWeek != currentCalendar
+ .get(java.util.Calendar.DAY_OF_WEEK)) {
+ currentCalendar.add(java.util.Calendar.DATE, 1);
+ }
+ currentCalendar.add(java.util.Calendar.DATE, -1);
+ return currentCalendar.getTime();
+ }
+
+ /**
+ * Calculates the end time of the day using the given calendar and date
+ *
+ * @param date
+ * @param calendar
+ * the calendar instance to be used in the calculation. The given
+ * instance is unchanged in this operation.
+ * @return the given date, with time set to the end of the day
+ */
+ private static Date getEndOfDay(java.util.Calendar calendar, Date date) {
+ java.util.Calendar calendarClone = (java.util.Calendar) calendar
+ .clone();
+
+ calendarClone.setTime(date);
+ calendarClone.set(java.util.Calendar.MILLISECOND,
+ calendarClone.getActualMaximum(java.util.Calendar.MILLISECOND));
+ calendarClone.set(java.util.Calendar.SECOND,
+ calendarClone.getActualMaximum(java.util.Calendar.SECOND));
+ calendarClone.set(java.util.Calendar.MINUTE,
+ calendarClone.getActualMaximum(java.util.Calendar.MINUTE));
+ calendarClone.set(java.util.Calendar.HOUR,
+ calendarClone.getActualMaximum(java.util.Calendar.HOUR));
+ calendarClone.set(java.util.Calendar.HOUR_OF_DAY,
+ calendarClone.getActualMaximum(java.util.Calendar.HOUR_OF_DAY));
+
+ return calendarClone.getTime();
+ }
+
+ /**
+ * Calculates the end time of the day using the given calendar and date
+ *
+ * @param date
+ * @param calendar
+ * the calendar instance to be used in the calculation. The given
+ * instance is unchanged in this operation.
+ * @return the given date, with time set to the end of the day
+ */
+ private static Date getStartOfDay(java.util.Calendar calendar, Date date) {
+ java.util.Calendar calendarClone = (java.util.Calendar) calendar
+ .clone();
+
+ calendarClone.setTime(date);
+ calendarClone.set(java.util.Calendar.MILLISECOND, 0);
+ calendarClone.set(java.util.Calendar.SECOND, 0);
+ calendarClone.set(java.util.Calendar.MINUTE, 0);
+ calendarClone.set(java.util.Calendar.HOUR, 0);
+ calendarClone.set(java.util.Calendar.HOUR_OF_DAY, 0);
+
+ return calendarClone.getTime();
+ }
+
+ /**
+ * Finds the first day of the week and returns a day representing the start
+ * of that day
+ *
+ * @param start
+ * The actual date
+ * @param expandToFullWeek
+ * Should the returned date be moved to the start of the week
+ * @return If expandToFullWeek is set then it returns the first day of the
+ * week, else it returns a clone of the actual date with the time
+ * set to the start of the day
+ */
+ protected Date expandStartDate(Date start, boolean expandToFullWeek) {
+ // If the duration is more than week, use monthly view and get startweek
+ // and endweek. Example if views daterange is from tuesday to next weeks
+ // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
+ // monday
+ if (expandToFullWeek) {
+ start = getFirstDateForWeek(start);
+
+ } else {
+ start = (Date) start.clone();
+ }
+
+ // Always expand to the start of the first day to the end of the last
+ // day
+ start = getStartOfDay(currentCalendar, start);
+
+ return start;
+ }
+
+ /**
+ * Finds the last day of the week and returns a day representing the end of
+ * that day
+ *
+ * @param end
+ * The actual date
+ * @param expandToFullWeek
+ * Should the returned date be moved to the end of the week
+ * @return If expandToFullWeek is set then it returns the last day of the
+ * week, else it returns a clone of the actual date with the time
+ * set to the end of the day
+ */
+ protected Date expandEndDate(Date end, boolean expandToFullWeek) {
+ // If the duration is more than week, use monthly view and get startweek
+ // and endweek. Example if views daterange is from tuesday to next weeks
+ // wednesday->expand to monday to nextweeks sunday. If firstdayofweek =
+ // monday
+ if (expandToFullWeek) {
+ end = getLastDateForWeek(end);
+
+ } else {
+ end = (Date) end.clone();
+ }
+
+ // Always expand to the start of the first day to the end of the last
+ // day
+ end = getEndOfDay(currentCalendar, end);
+
+ return end;
+ }
+
+ /**
+ * Set the {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} to be used with this calendar. The EventProvider
+ * is used to query for events to show, and must be non-null. By default a
+ * {@link com.vaadin.addon.calendar.event.BasicEventProvider
+ * BasicEventProvider} is used.
+ *
+ * @param calendarEventProvider
+ * the calendarEventProvider to set. Cannot be null.
+ */
+ public void setEventProvider(CalendarEventProvider calendarEventProvider) {
+ if (calendarEventProvider == null) {
+ throw new IllegalArgumentException(
+ "Calendar event provider cannot be null");
+ }
+
+ // remove old listener
+ if (getEventProvider() instanceof EventSetChangeNotifier) {
+ ((EventSetChangeNotifier) getEventProvider())
+ .removeEventSetChangeListener(this);
+ }
+
+ this.calendarEventProvider = calendarEventProvider;
+
+ // add new listener
+ if (calendarEventProvider instanceof EventSetChangeNotifier) {
+ ((EventSetChangeNotifier) calendarEventProvider)
+ .addEventSetChangeListener(this);
+ }
+ }
+
+ /**
+ * @return the {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} currently used
+ */
+ public CalendarEventProvider getEventProvider() {
+ return calendarEventProvider;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.ui.CalendarEvents.EventChangeListener#
+ * eventChange (com.vaadin.addon.calendar.ui.CalendarEvents.EventChange)
+ */
+ @Override
+ public void eventSetChange(EventSetChangeEvent changeEvent) {
+ // sanity check
+ if (calendarEventProvider == changeEvent.getProvider()) {
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Set the handler for the given type information. Mirrors
+ * {@link #addListener(String, Class, Object, Method) addListener} from
+ * AbstractComponent
+ *
+ * @param eventId
+ * A unique id for the event. Usually one of
+ * {@link CalendarEventId}
+ * @param eventType
+ * The class of the event, most likely a subclass of
+ * {@link CalendarComponentEvent}
+ * @param listener
+ * A listener that listens to the given event
+ * @param listenerMethod
+ * The method on the lister to call when the event is triggered
+ */
+ protected void setHandler(String eventId, Class<?> eventType,
+ EventListener listener, Method listenerMethod) {
+ if (handlers.get(eventId) != null) {
+ removeListener(eventId, eventType, handlers.get(eventId));
+ handlers.remove(eventId);
+ }
+
+ if (listener != null) {
+ addListener(eventId, eventType, listener, listenerMethod);
+ handlers.put(eventId, listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardHandler)
+ */
+ @Override
+ public void setHandler(ForwardHandler listener) {
+ setHandler(ForwardEvent.EVENT_ID, ForwardEvent.class, listener,
+ ForwardHandler.forwardMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardHandler)
+ */
+ @Override
+ public void setHandler(BackwardHandler listener) {
+ setHandler(BackwardEvent.EVENT_ID, BackwardEvent.class, listener,
+ BackwardHandler.backwardMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickHandler)
+ */
+ @Override
+ public void setHandler(DateClickHandler listener) {
+ setHandler(DateClickEvent.EVENT_ID, DateClickEvent.class, listener,
+ DateClickHandler.dateClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventClickHandler)
+ */
+ @Override
+ public void setHandler(EventClickHandler listener) {
+ setHandler(EventClick.EVENT_ID, EventClick.class, listener,
+ EventClickHandler.eventClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.NavigationNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClickHandler)
+ */
+ @Override
+ public void setHandler(WeekClickHandler listener) {
+ setHandler(WeekClick.EVENT_ID, WeekClick.class, listener,
+ WeekClickHandler.weekClickMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * )
+ */
+ @Override
+ public void setHandler(EventResizeHandler listener) {
+ setHandler(EventResize.EVENT_ID, EventResize.class, listener,
+ EventResizeHandler.eventResizeMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.RangeSelectNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.RangeSelectHandler
+ * )
+ */
+ @Override
+ public void setHandler(RangeSelectHandler listener) {
+ setHandler(RangeSelectEvent.EVENT_ID, RangeSelectEvent.class, listener,
+ RangeSelectHandler.rangeSelectMethod);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler)
+ */
+ @Override
+ public void setHandler(EventMoveHandler listener) {
+ setHandler(MoveEvent.EVENT_ID, MoveEvent.class, listener,
+ EventMoveHandler.eventMoveMethod);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.ui.CalendarComponentEvents.
+ * CalendarEventNotifier #getHandler(java.lang.String)
+ */
+ @Override
+ public EventListener getHandler(String eventId) {
+ return handlers.get(eventId);
+ }
+
+ /**
+ * Get the currently active drop handler
+ */
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ /**
+ * Set the drop handler for the calendar See {@link DropHandler} for
+ * implementation details.
+ *
+ * @param dropHandler
+ * The drop handler to set
+ */
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map)
+ */
+ @Override
+ public TargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ Map<String, Object> serverVariables = new HashMap<String, Object>();
+
+ if (clientVariables.containsKey("dropSlotIndex")) {
+ int slotIndex = (Integer) clientVariables.get("dropSlotIndex");
+ int dayIndex = (Integer) clientVariables.get("dropDayIndex");
+
+ currentCalendar.setTime(getStartOfDay(currentCalendar, startDate));
+ currentCalendar.add(java.util.Calendar.DATE, dayIndex);
+
+ // change this if slot length is modified
+ currentCalendar.add(java.util.Calendar.MINUTE, slotIndex * 30);
+
+ serverVariables.put("dropTime", currentCalendar.getTime());
+
+ } else {
+ int dayIndex = (Integer) clientVariables.get("dropDayIndex");
+ currentCalendar.setTime(expandStartDate(startDate, true));
+ currentCalendar.add(java.util.Calendar.DATE, dayIndex);
+ serverVariables.put("dropDay", currentCalendar.getTime());
+ }
+ serverVariables.put("mouseEvent", clientVariables.get("mouseEvent"));
+
+ CalendarTargetDetails td = new CalendarTargetDetails(serverVariables,
+ this);
+ td.setHasDropTime(clientVariables.containsKey("dropSlotIndex"));
+
+ return td;
+ }
+
+ /**
+ * Sets a container as a data source for the events in the calendar.
+ * Equivalent for doing
+ * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
+ *
+ * Use this method if you are adding a container which uses the default
+ * property ids like {@link BeanItemContainer} for instance. If you are
+ * using custom properties instead use
+ * {@link Calendar#setContainerDataSource(com.vaadin.data.Container.Indexed, Object, Object, Object, Object, Object)}
+ *
+ * Please note that the container must be sorted by date!
+ *
+ * @param container
+ * The container to use as a datasource
+ */
+ public void setContainerDataSource(Container.Indexed container) {
+ ContainerEventProvider provider = new ContainerEventProvider(container);
+ provider.addEventSetChangeListener(
+ new CalendarEventProvider.EventSetChangeListener() {
+ @Override
+ public void eventSetChange(
+ EventSetChangeEvent changeEvent) {
+ // Repaint if events change
+ markAsDirty();
+ }
+ });
+ provider.addEventChangeListener(new EventChangeListener() {
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // Repaint if event changes
+ markAsDirty();
+ }
+ });
+ setEventProvider(provider);
+ }
+
+ /**
+ * Sets a container as a data source for the events in the calendar.
+ * Equivalent for doing
+ * <code>Calendar.setEventProvider(new ContainerEventProvider(container))</code>
+ *
+ * Please note that the container must be sorted by date!
+ *
+ * @param container
+ * The container to use as a data source
+ * @param captionProperty
+ * The property that has the caption, null if no caption property
+ * is present
+ * @param descriptionProperty
+ * The property that has the description, null if no description
+ * property is present
+ * @param startDateProperty
+ * The property that has the starting date
+ * @param endDateProperty
+ * The property that has the ending date
+ * @param styleNameProperty
+ * The property that has the stylename, null if no stylname
+ * property is present
+ */
+ public void setContainerDataSource(Container.Indexed container,
+ Object captionProperty, Object descriptionProperty,
+ Object startDateProperty, Object endDateProperty,
+ Object styleNameProperty) {
+ ContainerEventProvider provider = new ContainerEventProvider(container);
+ provider.setCaptionProperty(captionProperty);
+ provider.setDescriptionProperty(descriptionProperty);
+ provider.setStartDateProperty(startDateProperty);
+ provider.setEndDateProperty(endDateProperty);
+ provider.setStyleNameProperty(styleNameProperty);
+ provider.addEventSetChangeListener(
+ new CalendarEventProvider.EventSetChangeListener() {
+ @Override
+ public void eventSetChange(
+ EventSetChangeEvent changeEvent) {
+ // Repaint if events change
+ markAsDirty();
+ }
+ });
+ provider.addEventChangeListener(new EventChangeListener() {
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // Repaint if event changes
+ markAsDirty();
+ }
+ });
+ setEventProvider(provider);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ return getEventProvider().getEvents(startDate, endDate);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ if (getEventProvider() instanceof CalendarEditableEventProvider) {
+ CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
+ provider.addEvent(event);
+ markAsDirty();
+ } else {
+ throw new UnsupportedOperationException(
+ "Event provider does not support adding events");
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ if (getEventProvider() instanceof CalendarEditableEventProvider) {
+ CalendarEditableEventProvider provider = (CalendarEditableEventProvider) getEventProvider();
+ provider.removeEvent(event);
+ markAsDirty();
+ } else {
+ throw new UnsupportedOperationException(
+ "Event provider does not support removing events");
+ }
+ }
+
+ /**
+ * Adds an action handler to the calender that handles event produced by the
+ * context menu.
+ *
+ * <p>
+ * The {@link Handler#getActions(Object, Object)} parameters depend on what
+ * view the Calendar is in:
+ * <ul>
+ * <li>If the Calendar is in <i>Day or Week View</i> then the target
+ * parameter will be a {@link CalendarDateRange} with a range of
+ * half-an-hour. The {@link Handler#getActions(Object, Object)} method will
+ * be called once per half-hour slot.</li>
+ * <li>If the Calendar is in <i>Month View</i> then the target parameter
+ * will be a {@link CalendarDateRange} with a range of one day. The
+ * {@link Handler#getActions(Object, Object)} will be called once for each
+ * day.
+ * </ul>
+ * The Dates passed into the {@link CalendarDateRange} are in the same
+ * timezone as the calendar is.
+ * </p>
+ *
+ * <p>
+ * The {@link Handler#handleAction(Action, Object, Object)} parameters
+ * depend on what the context menu is called upon:
+ * <ul>
+ * <li>If the context menu is called upon an event then the target parameter
+ * is the event, i.e. instanceof {@link CalendarEvent}</li>
+ * <li>If the context menu is called upon an empty slot then the target is a
+ * {@link Date} representing that slot
+ * </ul>
+ * </p>
+ */
+ @Override
+ public void addActionHandler(Handler actionHandler) {
+ if (actionHandler != null) {
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Action.Handler>();
+ actionMapper = new KeyMapper<Action>();
+ }
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ markAsDirty();
+ }
+ }
+ }
+
+ /**
+ * Is the calendar in a mode where all days of the month is shown
+ *
+ * @return Returns true if calendar is in monthly mode and false if it is in
+ * weekly mode
+ */
+ public boolean isMonthlyMode() {
+ CalendarState state = getState(false);
+ if (state.days != null) {
+ return state.days.size() > 7;
+ } else {
+ // Default mode
+ return true;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.Action.Container#removeActionHandler(com.vaadin.event
+ * .Action.Handler)
+ */
+ @Override
+ public void removeActionHandler(Handler actionHandler) {
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+ actionHandlers.remove(actionHandler);
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+ markAsDirty();
+ }
+ }
+
+ private class CalendarServerRpcImpl implements CalendarServerRpc {
+
+ @Override
+ public void eventMove(int eventIndex, String newDate) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (newDate != null) {
+ try {
+ Date d = df_date_time.parse(newDate);
+ if (eventIndex >= 0 && eventIndex < events.size()
+ && events.get(eventIndex) != null) {
+ fireEventMove(eventIndex, d);
+ }
+ } catch (ParseException e) {
+ getLogger().log(Level.WARNING, e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public void rangeSelect(String range) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+
+ if (range != null && range.length() > 14 && range.contains("TO")) {
+ String[] dates = range.split("TO");
+ try {
+ Date d1 = df_date.parse(dates[0]);
+ Date d2 = df_date.parse(dates[1]);
+
+ fireRangeSelect(d1, d2, true);
+
+ } catch (ParseException e) {
+ // NOP
+ }
+ } else if (range != null && range.length() > 12
+ && range.contains(":")) {
+ String[] dates = range.split(":");
+ if (dates.length == 3) {
+ try {
+ Date d = df_date.parse(dates[0]);
+ currentCalendar.setTime(d);
+ int startMinutes = Integer.parseInt(dates[1]);
+ int endMinutes = Integer.parseInt(dates[2]);
+ currentCalendar.add(java.util.Calendar.MINUTE,
+ startMinutes);
+ Date start = currentCalendar.getTime();
+ currentCalendar.add(java.util.Calendar.MINUTE,
+ endMinutes - startMinutes);
+ Date end = currentCalendar.getTime();
+ fireRangeSelect(start, end, false);
+ } catch (ParseException e) {
+ // NOP
+ } catch (NumberFormatException e) {
+ // NOP
+ }
+ }
+ }
+ }
+
+ @Override
+ public void forward() {
+ fireEvent(new ForwardEvent(Calendar.this));
+ }
+
+ @Override
+ public void backward() {
+ fireEvent(new BackwardEvent(Calendar.this));
+ }
+
+ @Override
+ public void dateClick(String date) {
+ if (date != null && date.length() > 6) {
+ try {
+ Date d = df_date.parse(date);
+ fireDateClick(d);
+ } catch (ParseException e) {
+ }
+ }
+ }
+
+ @Override
+ public void weekClick(String event) {
+ if (event.length() > 0 && event.contains("w")) {
+ String[] splitted = event.split("w");
+ if (splitted.length == 2) {
+ try {
+ int yr = Integer.parseInt(splitted[0]);
+ int week = Integer.parseInt(splitted[1]);
+ fireWeekClick(week, yr);
+ } catch (NumberFormatException e) {
+ // NOP
+ }
+ }
+ }
+ }
+
+ @Override
+ public void eventClick(int eventIndex) {
+ if (!isEventClickAllowed()) {
+ return;
+ }
+ if (eventIndex >= 0 && eventIndex < events.size()
+ && events.get(eventIndex) != null) {
+ fireEventClick(eventIndex);
+ }
+ }
+
+ @Override
+ public void eventResize(int eventIndex, String newStartDate,
+ String newEndDate) {
+ if (!isClientChangeAllowed()) {
+ return;
+ }
+ if (newStartDate != null && !"".equals(newStartDate)
+ && newEndDate != null && !"".equals(newEndDate)) {
+ try {
+ Date newStartTime = df_date_time.parse(newStartDate);
+ Date newEndTime = df_date_time.parse(newEndDate);
+
+ fireEventResize(eventIndex, newStartTime, newEndTime);
+ } catch (ParseException e) {
+ // NOOP
+ }
+ }
+ }
+
+ @Override
+ public void scroll(int scrollPosition) {
+ scrollTop = scrollPosition;
+ markAsDirty();
+ }
+
+ @Override
+ public void actionOnEmptyCell(String actionKey, String startDate,
+ String endDate) {
+ Action action = actionMapper.get(actionKey);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+ try {
+ Date start = formatter.parse(startDate);
+ for (Action.Handler ah : actionHandlers) {
+ ah.handleAction(action, Calendar.this, start);
+ }
+
+ } catch (ParseException e) {
+ getLogger().log(Level.WARNING,
+ "Could not parse action date string");
+ }
+
+ }
+
+ @Override
+ public void actionOnEvent(String actionKey, String startDate,
+ String endDate, int eventIndex) {
+ Action action = actionMapper.get(actionKey);
+ SimpleDateFormat formatter = new SimpleDateFormat(
+ DateConstants.ACTION_DATE_FORMAT_PATTERN);
+ formatter.setTimeZone(getTimeZone());
+ for (Action.Handler ah : actionHandlers) {
+ ah.handleAction(action, Calendar.this, events.get(eventIndex));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.server.VariableOwner#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ /*
+ * Only defined to fulfill the LegacyComponent interface used for
+ * calendar drag & drop. No implementation required.
+ */
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.ui.LegacyComponent#paintContent(com.vaadin.server.PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ if (dropHandler != null) {
+ dropHandler.getAcceptCriterion().paint(target);
+ }
+ }
+
+ /**
+ * Sets whether the event captions are rendered as HTML.
+ * <p>
+ * If set to true, the captions are rendered in the browser as HTML and the
+ * developer is responsible for ensuring no harmful HTML is used. If set to
+ * false, the caption is rendered in the browser as plain text.
+ * <p>
+ * The default is false, i.e. to render that caption as plain text.
+ *
+ * @param captionAsHtml
+ * true if the captions are rendered as HTML, false if rendered
+ * as plain text
+ */
+ public void setEventCaptionAsHtml(boolean eventCaptionAsHtml) {
+ getState().eventCaptionAsHtml = eventCaptionAsHtml;
+ }
+
+ /**
+ * Checks whether event captions are rendered as HTML
+ * <p>
+ * The default is false, i.e. to render that caption as plain text.
+ *
+ * @return true if the captions are rendered as HTML, false if rendered as
+ * plain text
+ */
+ public boolean isEventCaptionAsHtml() {
+ return getState(false).eventCaptionAsHtml;
+ }
+
+ @Override
+ public void readDesign(Element design, DesignContext designContext) {
+ super.readDesign(design, designContext);
+
+ Attributes attr = design.attributes();
+ if (design.hasAttr("time-format")) {
+ setTimeFormat(TimeFormat.valueOf(
+ "Format" + design.attr("time-format").toUpperCase()));
+ }
+
+ if (design.hasAttr("start-date")) {
+ setStartDate(DesignAttributeHandler.readAttribute("start-date",
+ attr, Date.class));
+ }
+ if (design.hasAttr("end-date")) {
+ setEndDate(DesignAttributeHandler.readAttribute("end-date", attr,
+ Date.class));
+ }
+ };
+
+ @Override
+ public void writeDesign(Element design, DesignContext designContext) {
+ super.writeDesign(design, designContext);
+
+ if (currentTimeFormat != null) {
+ design.attr("time-format",
+ (currentTimeFormat == TimeFormat.Format12H ? "12h"
+ : "24h"));
+ }
+ if (startDate != null) {
+ design.attr("start-date", df_date.format(getStartDate()));
+ }
+ if (endDate != null) {
+ design.attr("end-date", df_date.format(getEndDate()));
+ }
+ if (!getTimeZone().equals(TimeZone.getDefault())) {
+ design.attr("time-zone", getTimeZone().getID());
+ }
+ }
+
+ @Override
+ protected Collection<String> getCustomAttributes() {
+ Collection<String> customAttributes = super.getCustomAttributes();
+ customAttributes.add("time-format");
+ customAttributes.add("start-date");
+ customAttributes.add("end-date");
+ return customAttributes;
+ }
+
+ /**
+ * Allow setting first day of week independent of Locale. Set to null if you
+ * want first day of week being defined by the locale
+ *
+ * @since 7.6
+ * @param dayOfWeek
+ * any of java.util.Calendar.SUNDAY..java.util.Calendar.SATURDAY
+ * or null to revert to default first day of week by locale
+ */
+ public void setFirstDayOfWeek(Integer dayOfWeek) {
+ int minimalSupported = java.util.Calendar.SUNDAY;
+ int maximalSupported = java.util.Calendar.SATURDAY;
+ if (dayOfWeek != null && (dayOfWeek < minimalSupported
+ || dayOfWeek > maximalSupported)) {
+ throw new IllegalArgumentException(String.format(
+ "Day of week must be between %s and %s. Actually received: %s",
+ minimalSupported, maximalSupported, dayOfWeek));
+ }
+ customFirstDayOfWeek = dayOfWeek;
+ markAsDirty();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/ColorPicker.java b/compatibility-server/src/main/java/com/vaadin/ui/ColorPicker.java
new file mode 100644
index 0000000000..67002373d0
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/ColorPicker.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * A class that defines default (button-like) implementation for a color picker
+ * component.
+ *
+ * @since 7.0.0
+ *
+ * @see ColorPickerArea
+ *
+ */
+public class ColorPicker extends AbstractColorPicker {
+
+ /**
+ * Instantiates a new color picker.
+ */
+ public ColorPicker() {
+ super();
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * caption of the color select popup
+ */
+ public ColorPicker(String popupCaption) {
+ super(popupCaption);
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * caption of the color select popup
+ * @param initialColor
+ * the initial color
+ */
+ public ColorPicker(String popupCaption, Color initialColor) {
+ super(popupCaption, initialColor);
+ setDefaultCaptionEnabled(true);
+ }
+
+ @Override
+ protected void setDefaultStyles() {
+ setPrimaryStyleName(STYLENAME_BUTTON);
+ addStyleName(STYLENAME_DEFAULT);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/ColorPickerArea.java b/compatibility-server/src/main/java/com/vaadin/ui/ColorPickerArea.java
new file mode 100644
index 0000000000..c4f3971259
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/ColorPickerArea.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * A class that defines area-like implementation for a color picker component.
+ *
+ * @since 7.0.0
+ *
+ * @see ColorPicker
+ *
+ */
+public class ColorPickerArea extends AbstractColorPicker {
+
+ /**
+ * Instantiates a new color picker.
+ */
+ public ColorPickerArea() {
+ super();
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * caption of the color select popup
+ */
+ public ColorPickerArea(String popupCaption) {
+ super(popupCaption);
+ }
+
+ /**
+ * Instantiates a new color picker.
+ *
+ * @param popupCaption
+ * caption of the color select popup
+ * @param initialColor
+ * the initial color
+ */
+ public ColorPickerArea(String popupCaption, Color initialColor) {
+ super(popupCaption, initialColor);
+ setDefaultCaptionEnabled(false);
+ }
+
+ @Override
+ protected void setDefaultStyles() {
+ // state already has correct default
+ }
+
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ super.beforeClientResponse(initial);
+
+ if ("".equals(getState().height)) {
+ getState().height = "30px";
+ }
+ if ("".equals(getState().width)) {
+ getState().width = "30px";
+ }
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/ComboBox.java b/compatibility-server/src/main/java/com/vaadin/ui/ComboBox.java
new file mode 100644
index 0000000000..a78823d9a3
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/ComboBox.java
@@ -0,0 +1,925 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.event.FieldEvents;
+import com.vaadin.event.FieldEvents.BlurEvent;
+import com.vaadin.event.FieldEvents.BlurListener;
+import com.vaadin.event.FieldEvents.FocusAndBlurServerRpcImpl;
+import com.vaadin.event.FieldEvents.FocusEvent;
+import com.vaadin.event.FieldEvents.FocusListener;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.server.Resource;
+import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
+import com.vaadin.shared.ui.combobox.ComboBoxState;
+import com.vaadin.shared.ui.combobox.FilteringMode;
+
+/**
+ * A filtering dropdown single-select. Suitable for newItemsAllowed, but it's
+ * turned of by default to avoid mistakes. Items are filtered based on user
+ * input, and loaded dynamically ("lazy-loading") from the server. You can turn
+ * on newItemsAllowed and change filtering mode (and also turn it off), but you
+ * can not turn on multi-select mode.
+ *
+ */
+@SuppressWarnings("serial")
+public class ComboBox extends AbstractSelect
+ implements AbstractSelect.Filtering, FieldEvents.BlurNotifier,
+ FieldEvents.FocusNotifier {
+
+ /**
+ * ItemStyleGenerator can be used to add custom styles to combo box items
+ * shown in the popup. The CSS class name that will be added to the item
+ * style names is <tt>v-filterselect-item-[style name]</tt>.
+ *
+ * @since 7.5.6
+ * @see ComboBox#setItemStyleGenerator(ItemStyleGenerator)
+ */
+ public interface ItemStyleGenerator extends Serializable {
+
+ /**
+ * Called by ComboBox when an item is painted.
+ *
+ * @param source
+ * the source combo box
+ * @param itemId
+ * The itemId of the item to be painted. Can be
+ * <code>null</code> if null selection is allowed.
+ * @return The style name to add to this item. (the CSS class name will
+ * be v-filterselect-item-[style name]
+ */
+ public String getStyle(ComboBox source, Object itemId);
+ }
+
+ private ComboBoxServerRpc rpc = new ComboBoxServerRpc() {
+ @Override
+ public void createNewItem(String itemValue) {
+ if (isNewItemsAllowed()) {
+ // New option entered (and it is allowed)
+ if (itemValue != null && itemValue.length() > 0) {
+ getNewItemHandler().addNewItem(itemValue);
+ // rebuild list
+ filterstring = null;
+ prevfilterstring = null;
+ }
+ }
+ }
+
+ @Override
+ public void setSelectedItem(String item) {
+ if (item == null) {
+ setValue(null, true);
+ } else {
+ final Object id = itemIdMapper.get(item);
+ if (id != null && id.equals(getNullSelectionItemId())) {
+ setValue(null, true);
+ } else {
+ setValue(id, true);
+ }
+ }
+ }
+
+ @Override
+ public void requestPage(String filter, int page) {
+ filterstring = filter;
+ if (filterstring != null) {
+ filterstring = filterstring.toLowerCase(getLocale());
+ }
+ currentPage = page;
+
+ // TODO this should trigger a data-only update instead of a full
+ // repaint
+ requestRepaint();
+ }
+ };
+
+ FocusAndBlurServerRpcImpl focusBlurRpc = new FocusAndBlurServerRpcImpl(
+ this) {
+ @Override
+ protected void fireEvent(Component.Event event) {
+ ComboBox.this.fireEvent(event);
+ }
+ };
+
+ // Current page when the user is 'paging' trough options
+ private int currentPage = -1;
+
+ private String filterstring;
+ private String prevfilterstring;
+
+ /**
+ * Number of options that pass the filter, excluding the null item if any.
+ */
+ private int filteredSize;
+
+ /**
+ * Cache of filtered options, used only by the in-memory filtering system.
+ */
+ private List<Object> filteredOptions;
+
+ /**
+ * Flag to indicate that request repaint is called by filter request only
+ */
+ private boolean optionRequest;
+
+ /**
+ * True while painting to suppress item set change notifications that could
+ * be caused by temporary filtering.
+ */
+ private boolean isPainting;
+
+ /**
+ * Flag to indicate whether to scroll the selected item visible (select the
+ * page on which it is) when opening the popup or not. Only applies to
+ * single select mode.
+ *
+ * This requires finding the index of the item, which can be expensive in
+ * many large lazy loading containers.
+ */
+ private boolean scrollToSelectedItem = true;
+
+ private ItemStyleGenerator itemStyleGenerator = null;
+
+ public ComboBox() {
+ init();
+ }
+
+ public ComboBox(String caption, Collection<?> options) {
+ super(caption, options);
+ init();
+ }
+
+ public ComboBox(String caption, Container dataSource) {
+ super(caption, dataSource);
+ init();
+ }
+
+ public ComboBox(String caption) {
+ super(caption);
+ init();
+ }
+
+ /**
+ * Initialize the ComboBox with default settings and register client to
+ * server RPC implementation.
+ */
+ private void init() {
+ registerRpc(rpc);
+ registerRpc(focusBlurRpc);
+
+ setNewItemsAllowed(false);
+ setImmediate(true);
+ }
+
+ /**
+ * Gets the current input prompt.
+ *
+ * @see #setInputPrompt(String)
+ * @return the current input prompt, or null if not enabled
+ */
+ public String getInputPrompt() {
+ return getState(false).inputPrompt;
+ }
+
+ /**
+ * Sets the input prompt - a textual prompt that is displayed when the
+ * select would otherwise be empty, to prompt the user for input.
+ *
+ * @param inputPrompt
+ * the desired input prompt, or null to disable
+ */
+ public void setInputPrompt(String inputPrompt) {
+ getState().inputPrompt = inputPrompt;
+ }
+
+ private boolean isFilteringNeeded() {
+ return filterstring != null && filterstring.length() > 0
+ && getFilteringMode() != FilteringMode.OFF;
+ }
+
+ /**
+ * A class representing an item in a ComboBox for server to client
+ * communication. This class is for internal use only and subject to change.
+ *
+ * @since
+ */
+ private static class ComboBoxItem implements Serializable {
+ String key = "";
+ String caption = "";
+ String style = null;
+ Resource icon = null;
+
+ // constructor for a null item
+ public ComboBoxItem() {
+ }
+
+ public ComboBoxItem(String key, String caption, String style,
+ Resource icon) {
+ this.key = key;
+ this.caption = caption;
+ this.style = style;
+ this.icon = icon;
+ }
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ isPainting = true;
+ try {
+ // clear caption change listeners
+ getCaptionChangeListener().clear();
+
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+
+ boolean needNullSelectOption = false;
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ needNullSelectOption = (getNullSelectionItemId() == null);
+ if (!needNullSelectOption) {
+ target.addAttribute("nullselectitem", true);
+ }
+ }
+
+ // Constructs selected keys array
+ String[] selectedKeys = new String[(getValue() == null
+ && getNullSelectionItemId() == null ? 0 : 1)];
+
+ // Paints the options and create array of selected id keys
+ int keyIndex = 0;
+
+ if (currentPage < 0) {
+ optionRequest = false;
+ currentPage = 0;
+ filterstring = "";
+ }
+
+ boolean nullFilteredOut = isFilteringNeeded();
+ // null option is needed and not filtered out, even if not on
+ // current page
+ boolean nullOptionVisible = needNullSelectOption
+ && !nullFilteredOut;
+
+ // first try if using container filters is possible
+ List<?> options = getOptionsWithFilter(nullOptionVisible);
+ if (null == options) {
+ // not able to use container filters, perform explicit in-memory
+ // filtering
+ options = getFilteredOptions();
+ filteredSize = options.size();
+ options = sanitetizeList(options, nullOptionVisible);
+ }
+
+ final boolean paintNullSelection = needNullSelectOption
+ && currentPage == 0 && !nullFilteredOut;
+
+ List<ComboBoxItem> items = new ArrayList<ComboBoxItem>();
+
+ if (paintNullSelection) {
+ ComboBoxItem item = new ComboBoxItem();
+ item.style = getItemStyle(null);
+ items.add(item);
+ }
+
+ final Iterator<?> i = options.iterator();
+ // Paints the available selection options from data source
+
+ while (i.hasNext()) {
+
+ final Object id = i.next();
+
+ if (!isNullSelectionAllowed() && id != null
+ && id.equals(getNullSelectionItemId())
+ && !isSelected(id)) {
+ continue;
+ }
+
+ // Gets the option attribute values
+ final String key = itemIdMapper.key(id);
+ final String caption = getItemCaption(id);
+ final Resource icon = getItemIcon(id);
+
+ getCaptionChangeListener().addNotifierForItem(id);
+
+ // Prepare to paint the option
+ ComboBoxItem item = new ComboBoxItem(key, caption,
+ getItemStyle(id), icon);
+ items.add(item);
+ if (keyIndex < selectedKeys.length && isSelected(id)) {
+ // at most one item can be selected at a time
+ selectedKeys[keyIndex++] = key;
+ }
+ }
+
+ // paint the items
+ target.startTag("options");
+ for (ComboBoxItem item : items) {
+ target.startTag("so");
+ if (item.icon != null) {
+ target.addAttribute("icon", item.icon);
+ }
+ target.addAttribute("caption", item.caption);
+ target.addAttribute("key", item.key);
+ if (item.style != null) {
+ target.addAttribute("style", item.style);
+ }
+
+ target.endTag("so");
+ }
+ target.endTag("options");
+
+ target.addAttribute("totalitems",
+ size() + (needNullSelectOption ? 1 : 0));
+ if (filteredSize > 0 || nullOptionVisible) {
+ target.addAttribute("totalMatches",
+ filteredSize + (nullOptionVisible ? 1 : 0));
+ }
+
+ // Paint variables
+ target.addVariable(this, "selected", selectedKeys);
+ if (getValue() != null && selectedKeys[0] == null) {
+ // not always available, e.g. scrollToSelectedIndex=false
+ // Give the caption for selected item still, not to make it look
+ // like there is no selection at all
+ target.addAttribute("selectedCaption",
+ getItemCaption(getValue()));
+ }
+ if (isNewItemsAllowed()) {
+ target.addVariable(this, "newitem", "");
+ }
+
+ target.addVariable(this, "filter", filterstring);
+ target.addVariable(this, "page", currentPage);
+
+ currentPage = -1; // current page is always set by client
+
+ optionRequest = true;
+ } finally {
+ isPainting = false;
+ }
+
+ }
+
+ private String getItemStyle(Object itemId) throws PaintException {
+ if (itemStyleGenerator != null) {
+ return itemStyleGenerator.getStyle(this, itemId);
+ }
+ return null;
+ }
+
+ /**
+ * Sets whether it is possible to input text into the field or whether the
+ * field area of the component is just used to show what is selected. By
+ * disabling text input, the comboBox will work in the same way as a
+ * {@link NativeSelect}
+ *
+ * @see #isTextInputAllowed()
+ *
+ * @param textInputAllowed
+ * true to allow entering text, false to just show the current
+ * selection
+ */
+ public void setTextInputAllowed(boolean textInputAllowed) {
+ getState().textInputAllowed = textInputAllowed;
+ }
+
+ /**
+ * Returns true if the user can enter text into the field to either filter
+ * the selections or enter a new value if {@link #isNewItemsAllowed()}
+ * returns true. If text input is disabled, the comboBox will work in the
+ * same way as a {@link NativeSelect}
+ *
+ * @return
+ */
+ public boolean isTextInputAllowed() {
+ return getState(false).textInputAllowed;
+ }
+
+ @Override
+ protected ComboBoxState getState() {
+ return (ComboBoxState) super.getState();
+ }
+
+ @Override
+ protected ComboBoxState getState(boolean markAsDirty) {
+ return (ComboBoxState) super.getState(markAsDirty);
+ }
+
+ /**
+ * Returns the filtered options for the current page using a container
+ * filter.
+ *
+ * As a size effect, {@link #filteredSize} is set to the total number of
+ * items passing the filter.
+ *
+ * The current container must be {@link Filterable} and {@link Indexed}, and
+ * the filtering mode must be suitable for container filtering (tested with
+ * {@link #canUseContainerFilter()}).
+ *
+ * Use {@link #getFilteredOptions()} and
+ * {@link #sanitetizeList(List, boolean)} if this is not the case.
+ *
+ * @param needNullSelectOption
+ * @return filtered list of options (may be empty) or null if cannot use
+ * container filters
+ */
+ protected List<?> getOptionsWithFilter(boolean needNullSelectOption) {
+ Container container = getContainerDataSource();
+
+ if (getPageLength() == 0 && !isFilteringNeeded()) {
+ // no paging or filtering: return all items
+ filteredSize = container.size();
+ assert filteredSize >= 0;
+ return new ArrayList<Object>(container.getItemIds());
+ }
+
+ if (!(container instanceof Filterable)
+ || !(container instanceof Indexed)
+ || getItemCaptionMode() != ITEM_CAPTION_MODE_PROPERTY) {
+ return null;
+ }
+
+ Filterable filterable = (Filterable) container;
+
+ Filter filter = buildFilter(filterstring, getFilteringMode());
+
+ // adding and removing filters leads to extraneous item set
+ // change events from the underlying container, but the ComboBox does
+ // not process or propagate them based on the flag filteringContainer
+ if (filter != null) {
+ filterable.addContainerFilter(filter);
+ }
+
+ // try-finally to ensure that the filter is removed from container even
+ // if a exception is thrown...
+ try {
+ Indexed indexed = (Indexed) container;
+
+ int indexToEnsureInView = -1;
+
+ // if not an option request (item list when user changes page), go
+ // to page with the selected item after filtering if accepted by
+ // filter
+ Object selection = getValue();
+ if (isScrollToSelectedItem() && !optionRequest
+ && selection != null) {
+ // ensure proper page
+ indexToEnsureInView = indexed.indexOfId(selection);
+ }
+
+ filteredSize = container.size();
+ assert filteredSize >= 0;
+ currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
+ indexToEnsureInView, filteredSize);
+ int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
+ filteredSize);
+ int last = getLastItemIndexOnCurrentPage(needNullSelectOption,
+ filteredSize, first);
+
+ // Compute the number of items to fetch from the indexes given or
+ // based on the filtered size of the container
+ int lastItemToFetch = Math.min(last, filteredSize - 1);
+ int nrOfItemsToFetch = (lastItemToFetch + 1) - first;
+
+ List<?> options = indexed.getItemIds(first, nrOfItemsToFetch);
+
+ return options;
+ } finally {
+ // to the outside, filtering should not be visible
+ if (filter != null) {
+ filterable.removeContainerFilter(filter);
+ }
+ }
+ }
+
+ /**
+ * Constructs a filter instance to use when using a Filterable container in
+ * the <code>ITEM_CAPTION_MODE_PROPERTY</code> mode.
+ *
+ * Note that the client side implementation expects the filter string to
+ * apply to the item caption string it sees, so changing the behavior of
+ * this method can cause problems.
+ *
+ * @param filterString
+ * @param filteringMode
+ * @return
+ */
+ protected Filter buildFilter(String filterString,
+ FilteringMode filteringMode) {
+ Filter filter = null;
+
+ if (null != filterString && !"".equals(filterString)) {
+ switch (filteringMode) {
+ case OFF:
+ break;
+ case STARTSWITH:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, true);
+ break;
+ case CONTAINS:
+ filter = new SimpleStringFilter(getItemCaptionPropertyId(),
+ filterString, true, false);
+ break;
+ }
+ }
+ return filter;
+ }
+
+ @Override
+ public void containerItemSetChange(Container.ItemSetChangeEvent event) {
+ if (!isPainting) {
+ super.containerItemSetChange(event);
+ }
+ }
+
+ /**
+ * Makes correct sublist of given list of options.
+ *
+ * If paint is not an option request (affected by page or filter change),
+ * page will be the one where possible selection exists.
+ *
+ * Detects proper first and last item in list to return right page of
+ * options. Also, if the current page is beyond the end of the list, it will
+ * be adjusted.
+ *
+ * @param options
+ * @param needNullSelectOption
+ * flag to indicate if nullselect option needs to be taken into
+ * consideration
+ */
+ private List<?> sanitetizeList(List<?> options,
+ boolean needNullSelectOption) {
+
+ if (getPageLength() != 0 && options.size() > getPageLength()) {
+
+ int indexToEnsureInView = -1;
+
+ // if not an option request (item list when user changes page), go
+ // to page with the selected item after filtering if accepted by
+ // filter
+ Object selection = getValue();
+ if (isScrollToSelectedItem() && !optionRequest
+ && selection != null) {
+ // ensure proper page
+ indexToEnsureInView = options.indexOf(selection);
+ }
+
+ int size = options.size();
+ currentPage = adjustCurrentPage(currentPage, needNullSelectOption,
+ indexToEnsureInView, size);
+ int first = getFirstItemIndexOnCurrentPage(needNullSelectOption,
+ size);
+ int last = getLastItemIndexOnCurrentPage(needNullSelectOption, size,
+ first);
+ return options.subList(first, last + 1);
+ } else {
+ return options;
+ }
+ }
+
+ /**
+ * Returns the index of the first item on the current page. The index is to
+ * the underlying (possibly filtered) contents. The null item, if any, does
+ * not have an index but takes up a slot on the first page.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @return first item to show on the UI (index to the filtered list of
+ * options, not taking the null item into consideration if any)
+ */
+ private int getFirstItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size) {
+ // Not all options are visible, find out which ones are on the
+ // current "page".
+ int first = currentPage * getPageLength();
+ if (needNullSelectOption && currentPage > 0) {
+ first--;
+ }
+ return first;
+ }
+
+ /**
+ * Returns the index of the last item on the current page. The index is to
+ * the underlying (possibly filtered) contents. If needNullSelectOption is
+ * true, the null item takes up the first slot on the first page,
+ * effectively reducing the first page size by one.
+ *
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ * @param first
+ * index in the filtered view of the first item of the page
+ * @return index in the filtered view of the last item on the page
+ */
+ private int getLastItemIndexOnCurrentPage(boolean needNullSelectOption,
+ int size, int first) {
+ // page length usable for non-null items
+ int effectivePageLength = getPageLength()
+ - (needNullSelectOption && (currentPage == 0) ? 1 : 0);
+ return Math.min(size - 1, first + effectivePageLength - 1);
+ }
+
+ /**
+ * Adjusts the index of the current page if necessary: make sure the current
+ * page is not after the end of the contents, and optionally go to the page
+ * containg a specific item. There are no side effects but the adjusted page
+ * index is returned.
+ *
+ * @param page
+ * page number to use as the starting point
+ * @param needNullSelectOption
+ * true if a null option should be shown before any other options
+ * (takes up the first slot on the first page, not counted in
+ * index)
+ * @param indexToEnsureInView
+ * index of an item that should be included on the page (in the
+ * data set, not counting the null item if any), -1 for none
+ * @param size
+ * number of items after filtering (not including the null item,
+ * if any)
+ */
+ private int adjustCurrentPage(int page, boolean needNullSelectOption,
+ int indexToEnsureInView, int size) {
+ if (indexToEnsureInView != -1) {
+ int newPage = (indexToEnsureInView + (needNullSelectOption ? 1 : 0))
+ / getPageLength();
+ page = newPage;
+ }
+ // adjust the current page if beyond the end of the list
+ if (page * getPageLength() > size) {
+ page = (size + (needNullSelectOption ? 1 : 0)) / getPageLength();
+ }
+ return page;
+ }
+
+ /**
+ * Filters the options in memory and returns the full filtered list.
+ *
+ * This can be less efficient than using container filters, so use
+ * {@link #getOptionsWithFilter(boolean)} if possible (filterable container
+ * and suitable item caption mode etc.).
+ *
+ * @return
+ */
+ protected List<?> getFilteredOptions() {
+ if (!isFilteringNeeded()) {
+ prevfilterstring = null;
+ filteredOptions = new LinkedList<Object>(getItemIds());
+ return filteredOptions;
+ }
+
+ if (filterstring.equals(prevfilterstring)) {
+ return filteredOptions;
+ }
+
+ Collection<?> items;
+ if (prevfilterstring != null
+ && filterstring.startsWith(prevfilterstring)) {
+ items = filteredOptions;
+ } else {
+ items = getItemIds();
+ }
+ prevfilterstring = filterstring;
+
+ filteredOptions = new LinkedList<Object>();
+ for (final Iterator<?> it = items.iterator(); it.hasNext();) {
+ final Object itemId = it.next();
+ String caption = getItemCaption(itemId);
+ if (caption == null || caption.equals("")) {
+ continue;
+ } else {
+ caption = caption.toLowerCase(getLocale());
+ }
+ switch (getFilteringMode()) {
+ case CONTAINS:
+ if (caption.indexOf(filterstring) > -1) {
+ filteredOptions.add(itemId);
+ }
+ break;
+ case STARTSWITH:
+ default:
+ if (caption.startsWith(filterstring)) {
+ filteredOptions.add(itemId);
+ }
+ break;
+ }
+ }
+
+ return filteredOptions;
+ }
+
+ /**
+ * Invoked when the value of a variable has changed.
+ *
+ * @see com.vaadin.ui.AbstractComponent#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+ // Not calling super.changeVariables due the history of select
+ // component hierarchy
+
+ // all the client to server requests are now handled by RPC
+ }
+
+ @Override
+ public void setFilteringMode(FilteringMode filteringMode) {
+ getState().filteringMode = filteringMode;
+ }
+
+ @Override
+ public FilteringMode getFilteringMode() {
+ return getState(false).filteringMode;
+ }
+
+ @Override
+ public void addBlurListener(BlurListener listener) {
+ addListener(BlurEvent.EVENT_ID, BlurEvent.class, listener,
+ BlurListener.blurMethod);
+ }
+
+ @Override
+ public void removeBlurListener(BlurListener listener) {
+ removeListener(BlurEvent.EVENT_ID, BlurEvent.class, listener);
+ }
+
+ @Override
+ public void addFocusListener(FocusListener listener) {
+ addListener(FocusEvent.EVENT_ID, FocusEvent.class, listener,
+ FocusListener.focusMethod);
+ }
+
+ @Override
+ public void removeFocusListener(FocusListener listener) {
+ removeListener(FocusEvent.EVENT_ID, FocusEvent.class, listener);
+ }
+
+ /**
+ * ComboBox does not support multi select mode.
+ *
+ * @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
+ * {@link TwinColSelect} instead
+ * @see com.vaadin.ui.AbstractSelect#setMultiSelect(boolean)
+ * @throws UnsupportedOperationException
+ * if trying to activate multiselect mode
+ */
+ @Deprecated
+ @Override
+ public void setMultiSelect(boolean multiSelect) {
+ if (multiSelect) {
+ throw new UnsupportedOperationException(
+ "Multiselect not supported");
+ }
+ }
+
+ /**
+ * ComboBox does not support multi select mode.
+ *
+ * @deprecated As of 7.0, use {@link ListSelect}, {@link OptionGroup} or
+ * {@link TwinColSelect} instead
+ *
+ * @see com.vaadin.ui.AbstractSelect#isMultiSelect()
+ *
+ * @return false
+ */
+ @Deprecated
+ @Override
+ public boolean isMultiSelect() {
+ return false;
+ }
+
+ /**
+ * Returns the page length of the suggestion popup.
+ *
+ * @return the pageLength
+ */
+ public int getPageLength() {
+ return getState(false).pageLength;
+ }
+
+ /**
+ * Returns the suggestion pop-up's width as a CSS string.
+ *
+ * @see #setPopupWidth
+ * @since 7.7
+ */
+ public String getPopupWidth() {
+ return getState(false).suggestionPopupWidth;
+ }
+
+ /**
+ * Sets the page length for the suggestion popup. Setting the page length to
+ * 0 will disable suggestion popup paging (all items visible).
+ *
+ * @param pageLength
+ * the pageLength to set
+ */
+ public void setPageLength(int pageLength) {
+ getState().pageLength = pageLength;
+ }
+
+ /**
+ * Sets the suggestion pop-up's width as a CSS string. By using relative
+ * units (e.g. "50%") it's possible to set the popup's width relative to the
+ * ComboBox itself.
+ *
+ * @see #getPopupWidth()
+ * @since 7.7
+ * @param width
+ * the width
+ */
+ public void setPopupWidth(String width) {
+ getState().suggestionPopupWidth = width;
+ }
+
+ /**
+ * Sets whether to scroll the selected item visible (directly open the page
+ * on which it is) when opening the combo box popup or not. Only applies to
+ * single select mode.
+ *
+ * This requires finding the index of the item, which can be expensive in
+ * many large lazy loading containers.
+ *
+ * @param scrollToSelectedItem
+ * true to find the page with the selected item when opening the
+ * selection popup
+ */
+ public void setScrollToSelectedItem(boolean scrollToSelectedItem) {
+ this.scrollToSelectedItem = scrollToSelectedItem;
+ }
+
+ /**
+ * Returns true if the select should find the page with the selected item
+ * when opening the popup (single select combo box only).
+ *
+ * @see #setScrollToSelectedItem(boolean)
+ *
+ * @return true if the page with the selected item will be shown when
+ * opening the popup
+ */
+ public boolean isScrollToSelectedItem() {
+ return scrollToSelectedItem;
+ }
+
+ /**
+ * Sets the item style generator that is used to produce custom styles for
+ * showing items in the popup. The CSS class name that will be added to the
+ * item style names is <tt>v-filterselect-item-[style name]</tt>.
+ *
+ * @param itemStyleGenerator
+ * the item style generator to set, or <code>null</code> to not
+ * use any custom item styles
+ * @since 7.5.6
+ */
+ public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
+ this.itemStyleGenerator = itemStyleGenerator;
+ markAsDirty();
+ }
+
+ /**
+ * Gets the currently used item style generator.
+ *
+ * @return the itemStyleGenerator the currently used item style generator,
+ * or <code>null</code> if no generator is used
+ * @since 7.5.6
+ */
+ public ItemStyleGenerator getItemStyleGenerator() {
+ return itemStyleGenerator;
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/Tree.java b/compatibility-server/src/main/java/com/vaadin/ui/Tree.java
new file mode 100644
index 0000000000..5da499f94e
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/Tree.java
@@ -0,0 +1,1984 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+import java.util.StringTokenizer;
+
+import org.jsoup.nodes.Element;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ContainerHierarchicalWrapper;
+import com.vaadin.data.util.HierarchicalContainer;
+import com.vaadin.event.Action;
+import com.vaadin.event.Action.Handler;
+import com.vaadin.event.ContextClickEvent;
+import com.vaadin.event.DataBoundTransferable;
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.event.ItemClickEvent.ItemClickNotifier;
+import com.vaadin.event.Transferable;
+import com.vaadin.event.dd.DragAndDropEvent;
+import com.vaadin.event.dd.DragSource;
+import com.vaadin.event.dd.DropHandler;
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetails;
+import com.vaadin.event.dd.acceptcriteria.ClientSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.ServerSideCriterion;
+import com.vaadin.event.dd.acceptcriteria.TargetDetailIs;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.server.Resource;
+import com.vaadin.shared.MouseEventDetails;
+import com.vaadin.shared.ui.MultiSelectMode;
+import com.vaadin.shared.ui.dd.VerticalDropLocation;
+import com.vaadin.shared.ui.tree.TreeConstants;
+import com.vaadin.shared.ui.tree.TreeServerRpc;
+import com.vaadin.shared.ui.tree.TreeState;
+import com.vaadin.ui.declarative.DesignAttributeHandler;
+import com.vaadin.ui.declarative.DesignContext;
+import com.vaadin.ui.declarative.DesignException;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Tree component. A Tree can be used to select an item (or multiple items) from
+ * a hierarchical set of items.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+@SuppressWarnings({ "serial", "deprecation" })
+public class Tree extends AbstractSelect implements Container.Hierarchical,
+ Action.Container, ItemClickNotifier, DragSource, DropTarget {
+
+ /**
+ * ContextClickEvent for the Tree Component.
+ *
+ * @since 7.6
+ */
+ public static class TreeContextClickEvent extends ContextClickEvent {
+
+ private final Object itemId;
+
+ public TreeContextClickEvent(Tree source, Object itemId,
+ MouseEventDetails mouseEventDetails) {
+ super(source, mouseEventDetails);
+ this.itemId = itemId;
+ }
+
+ @Override
+ public Tree getComponent() {
+ return (Tree) super.getComponent();
+ }
+
+ /**
+ * Returns the item id of context clicked row.
+ *
+ * @return item id of clicked row; <code>null</code> if no row is
+ * present at the location
+ */
+ public Object getItemId() {
+ return itemId;
+ }
+ }
+
+ /* Private members */
+
+ private static final String NULL_ALT_EXCEPTION_MESSAGE = "Parameter 'altText' needs to be non null";
+
+ /**
+ * Item icons alt texts.
+ */
+ private final HashMap<Object, String> itemIconAlts = new HashMap<Object, String>();
+
+ /**
+ * Set of expanded nodes.
+ */
+ private HashSet<Object> expanded = new HashSet<Object>();
+
+ /**
+ * List of action handlers.
+ */
+ private LinkedList<Action.Handler> actionHandlers = null;
+
+ /**
+ * Action mapper.
+ */
+ private KeyMapper<Action> actionMapper = null;
+
+ /**
+ * Is the tree selectable on the client side.
+ */
+ private boolean selectable = true;
+
+ /**
+ * Flag to indicate sub-tree loading
+ */
+ private boolean partialUpdate = false;
+
+ /**
+ * Holds a itemId which was recently expanded
+ */
+ private Object expandedItemId;
+
+ /**
+ * a flag which indicates initial paint. After this flag set true partial
+ * updates are allowed.
+ */
+ private boolean initialPaint = true;
+
+ /**
+ * Item tooltip generator
+ */
+ private ItemDescriptionGenerator itemDescriptionGenerator;
+
+ /**
+ * Supported drag modes for Tree.
+ */
+ public enum TreeDragMode {
+ /**
+ * When drag mode is NONE, dragging from Tree is not supported. Browsers
+ * may still support selecting text/icons from Tree which can initiate
+ * HTML 5 style drag and drop operation.
+ */
+ NONE,
+ /**
+ * When drag mode is NODE, users can initiate drag from Tree nodes that
+ * represent {@link Item}s in from the backed {@link Container}.
+ */
+ NODE
+ // , SUBTREE
+ }
+
+ private TreeDragMode dragMode = TreeDragMode.NONE;
+
+ private MultiSelectMode multiSelectMode = MultiSelectMode.DEFAULT;
+
+ /* Tree constructors */
+
+ /**
+ * Creates a new empty tree.
+ */
+ public Tree() {
+ this(null);
+
+ registerRpc(new TreeServerRpc() {
+ @Override
+ public void contextClick(String rowKey, MouseEventDetails details) {
+ fireEvent(new TreeContextClickEvent(Tree.this,
+ itemIdMapper.get(rowKey), details));
+ }
+ });
+ }
+
+ /**
+ * Creates a new empty tree with caption.
+ *
+ * @param caption
+ */
+ public Tree(String caption) {
+ this(caption, new HierarchicalContainer());
+ }
+
+ /**
+ * Creates a new tree with caption and connect it to a Container.
+ *
+ * @param caption
+ * @param dataSource
+ */
+ public Tree(String caption, Container dataSource) {
+ super(caption, dataSource);
+ }
+
+ @Override
+ public void setItemIcon(Object itemId, Resource icon) {
+ setItemIcon(itemId, icon, "");
+ }
+
+ /**
+ * Sets the icon for an item.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param icon
+ * the icon to use or null.
+ *
+ * @param altText
+ * the alternative text for the icon
+ */
+ public void setItemIcon(Object itemId, Resource icon, String altText) {
+ if (itemId != null) {
+ super.setItemIcon(itemId, icon);
+
+ if (icon == null) {
+ itemIconAlts.remove(itemId);
+ } else if (altText == null) {
+ throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
+ } else {
+ itemIconAlts.put(itemId, altText);
+ }
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Set the alternate text for an item.
+ *
+ * Used when the item has an icon.
+ *
+ * @param itemId
+ * the id of the item to be assigned an icon.
+ * @param altText
+ * the alternative text for the icon
+ */
+ public void setItemIconAlternateText(Object itemId, String altText) {
+ if (itemId != null) {
+ if (altText == null) {
+ throw new IllegalArgumentException(NULL_ALT_EXCEPTION_MESSAGE);
+ } else {
+ itemIconAlts.put(itemId, altText);
+ }
+ }
+ }
+
+ /**
+ * Return the alternate text of an icon in a tree item.
+ *
+ * @param itemId
+ * Object with the ID of the item
+ * @return String with the alternate text of the icon, or null when no icon
+ * was set
+ */
+ public String getItemIconAlternateText(Object itemId) {
+ String storedAlt = itemIconAlts.get(itemId);
+ return storedAlt == null ? "" : storedAlt;
+ }
+
+ /* Expanding and collapsing */
+
+ /**
+ * Check is an item is expanded
+ *
+ * @param itemId
+ * the item id.
+ * @return true iff the item is expanded.
+ */
+ public boolean isExpanded(Object itemId) {
+ return expanded.contains(itemId);
+ }
+
+ /**
+ * Expands an item.
+ *
+ * @param itemId
+ * the item id.
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItem(Object itemId) {
+ boolean success = expandItem(itemId, true);
+ markAsDirty();
+ return success;
+ }
+
+ /**
+ * Expands an item.
+ *
+ * @param itemId
+ * the item id.
+ * @param sendChildTree
+ * flag to indicate if client needs subtree or not (may be
+ * cached)
+ * @return True if the expand operation succeeded
+ */
+ private boolean expandItem(Object itemId, boolean sendChildTree) {
+
+ // Succeeds if the node is already expanded
+ if (isExpanded(itemId)) {
+ return true;
+ }
+
+ // Nodes that can not have children are not expandable
+ if (!areChildrenAllowed(itemId)) {
+ return false;
+ }
+
+ // Expands
+ expanded.add(itemId);
+
+ expandedItemId = itemId;
+ if (initialPaint) {
+ markAsDirty();
+ } else if (sendChildTree) {
+ requestPartialRepaint();
+ }
+ fireExpandEvent(itemId);
+
+ return true;
+ }
+
+ @Override
+ public void markAsDirty() {
+ super.markAsDirty();
+ partialUpdate = false;
+ }
+
+ private void requestPartialRepaint() {
+ super.markAsDirty();
+ partialUpdate = true;
+ }
+
+ /**
+ * Expands the items recursively
+ *
+ * Expands all the children recursively starting from an item. Operation
+ * succeeds only if all expandable items are expanded.
+ *
+ * @param startItemId
+ * @return True iff the expand operation succeeded
+ */
+ public boolean expandItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ final Stack<Object> todo = new Stack<Object>();
+ todo.add(startItemId);
+
+ // Expands recursively
+ while (!todo.isEmpty()) {
+ final Object id = todo.pop();
+ if (areChildrenAllowed(id) && !expandItem(id, false)) {
+ result = false;
+ }
+ if (hasChildren(id)) {
+ todo.addAll(getChildren(id));
+ }
+ }
+ markAsDirty();
+ return result;
+ }
+
+ /**
+ * Collapses an item.
+ *
+ * @param itemId
+ * the item id.
+ * @return True iff the collapse operation succeeded
+ */
+ public boolean collapseItem(Object itemId) {
+
+ // Succeeds if the node is already collapsed
+ if (!isExpanded(itemId)) {
+ return true;
+ }
+
+ // Collapse
+ expanded.remove(itemId);
+ markAsDirty();
+ fireCollapseEvent(itemId);
+
+ return true;
+ }
+
+ /**
+ * Collapses the items recursively.
+ *
+ * Collapse all the children recursively starting from an item. Operation
+ * succeeds only if all expandable items are collapsed.
+ *
+ * @param startItemId
+ * @return True iff the collapse operation succeeded
+ */
+ public boolean collapseItemsRecursively(Object startItemId) {
+
+ boolean result = true;
+
+ // Initial stack
+ final Stack<Object> todo = new Stack<Object>();
+ todo.add(startItemId);
+
+ // Collapse recursively
+ while (!todo.isEmpty()) {
+ final Object id = todo.pop();
+ if (areChildrenAllowed(id) && !collapseItem(id)) {
+ result = false;
+ }
+ if (hasChildren(id)) {
+ todo.addAll(getChildren(id));
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Returns the current selectable state. Selectable determines if the a node
+ * can be selected on the client side. Selectable does not affect
+ * {@link #setValue(Object)} or {@link #select(Object)}.
+ *
+ * <p>
+ * The tree is selectable by default.
+ * </p>
+ *
+ * @return the current selectable state.
+ */
+ public boolean isSelectable() {
+ return selectable;
+ }
+
+ /**
+ * Sets the selectable state. Selectable determines if the a node can be
+ * selected on the client side. Selectable does not affect
+ * {@link #setValue(Object)} or {@link #select(Object)}.
+ *
+ * <p>
+ * The tree is selectable by default.
+ * </p>
+ *
+ * @param selectable
+ * The new selectable state.
+ */
+ public void setSelectable(boolean selectable) {
+ if (this.selectable != selectable) {
+ this.selectable = selectable;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Sets the behavior of the multiselect mode
+ *
+ * @param mode
+ * The mode to set
+ */
+ public void setMultiselectMode(MultiSelectMode mode) {
+ if (multiSelectMode != mode && mode != null) {
+ multiSelectMode = mode;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Returns the mode the multiselect is in. The mode controls how
+ * multiselection can be done.
+ *
+ * @return The mode
+ */
+ public MultiSelectMode getMultiselectMode() {
+ return multiSelectMode;
+ }
+
+ /* Component API */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.AbstractSelect#changeVariables(java.lang.Object,
+ * java.util.Map)
+ */
+ @Override
+ public void changeVariables(Object source, Map<String, Object> variables) {
+
+ if (variables.containsKey("clickedKey")) {
+ String key = (String) variables.get("clickedKey");
+
+ Object id = itemIdMapper.get(key);
+ MouseEventDetails details = MouseEventDetails
+ .deSerialize((String) variables.get("clickEvent"));
+ Item item = getItem(id);
+ if (item != null) {
+ fireEvent(new ItemClickEvent(this, item, id, null, details));
+ }
+ }
+
+ if (!isSelectable() && variables.containsKey("selected")) {
+ // Not-selectable is a special case, AbstractSelect does not support
+ // TODO could be optimized.
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ // Collapses the nodes
+ if (variables.containsKey("collapse")) {
+ final String[] keys = (String[]) variables.get("collapse");
+ for (int i = 0; i < keys.length; i++) {
+ final Object id = itemIdMapper.get(keys[i]);
+ if (id != null && isExpanded(id)) {
+ expanded.remove(id);
+ if (expandedItemId == id) {
+ expandedItemId = null;
+ }
+ fireCollapseEvent(id);
+ }
+ }
+ }
+
+ // Expands the nodes
+ if (variables.containsKey("expand")) {
+ boolean sendChildTree = false;
+ if (variables.containsKey("requestChildTree")) {
+ sendChildTree = true;
+ }
+ final String[] keys = (String[]) variables.get("expand");
+ for (int i = 0; i < keys.length; i++) {
+ final Object id = itemIdMapper.get(keys[i]);
+ if (id != null) {
+ expandItem(id, sendChildTree);
+ }
+ }
+ }
+
+ // AbstractSelect cannot handle multiselection so we handle
+ // it ourself
+ if (variables.containsKey("selected") && isMultiSelect()
+ && multiSelectMode == MultiSelectMode.DEFAULT) {
+ handleSelectedItems(variables);
+ variables = new HashMap<String, Object>(variables);
+ variables.remove("selected");
+ }
+
+ // Selections are handled by the select component
+ super.changeVariables(source, variables);
+
+ // Actions
+ if (variables.containsKey("action")) {
+ final StringTokenizer st = new StringTokenizer(
+ (String) variables.get("action"), ",");
+ if (st.countTokens() == 2) {
+ final Object itemId = itemIdMapper.get(st.nextToken());
+ final Action action = actionMapper.get(st.nextToken());
+ if (action != null && (itemId == null || containsId(itemId))
+ && actionHandlers != null) {
+ for (Handler ah : actionHandlers) {
+ ah.handleAction(action, this, itemId);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles the selection
+ *
+ * @param variables
+ * The variables sent to the server from the client
+ */
+ private void handleSelectedItems(Map<String, Object> variables) {
+ final String[] ka = (String[]) variables.get("selected");
+
+ // Converts the key-array to id-set
+ final LinkedList<Object> s = new LinkedList<Object>();
+ for (int i = 0; i < ka.length; i++) {
+ final Object id = itemIdMapper.get(ka[i]);
+ if (!isNullSelectionAllowed()
+ && (id == null || id == getNullSelectionItemId())) {
+ // skip empty selection if nullselection is not allowed
+ markAsDirty();
+ } else if (id != null && containsId(id)) {
+ s.add(id);
+ }
+ }
+
+ if (!isNullSelectionAllowed() && s.size() < 1) {
+ // empty selection not allowed, keep old value
+ markAsDirty();
+ return;
+ }
+
+ setValue(s, true);
+ }
+
+ /**
+ * Paints any needed component-specific things to the given UIDL stream.
+ *
+ * @see com.vaadin.ui.AbstractComponent#paintContent(PaintTarget)
+ */
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ initialPaint = false;
+
+ if (partialUpdate) {
+ target.addAttribute("partialUpdate", true);
+ target.addAttribute("rootKey", itemIdMapper.key(expandedItemId));
+ } else {
+ getCaptionChangeListener().clear();
+
+ // The tab ordering number
+ if (getTabIndex() > 0) {
+ target.addAttribute("tabindex", getTabIndex());
+ }
+
+ // Paint tree attributes
+ if (isSelectable()) {
+ target.addAttribute("selectmode",
+ (isMultiSelect() ? "multi" : "single"));
+ if (isMultiSelect()) {
+ target.addAttribute("multiselectmode",
+ multiSelectMode.toString());
+ }
+ } else {
+ target.addAttribute("selectmode", "none");
+ }
+ if (isNewItemsAllowed()) {
+ target.addAttribute("allownewitem", true);
+ }
+
+ if (isNullSelectionAllowed()) {
+ target.addAttribute("nullselect", true);
+ }
+
+ if (dragMode != TreeDragMode.NONE) {
+ target.addAttribute("dragMode", dragMode.ordinal());
+ }
+
+ if (isHtmlContentAllowed()) {
+ target.addAttribute(TreeConstants.ATTRIBUTE_HTML_ALLOWED, true);
+ }
+
+ }
+
+ // Initialize variables
+ final Set<Action> actionSet = new LinkedHashSet<Action>();
+
+ // rendered selectedKeys
+ LinkedList<String> selectedKeys = new LinkedList<String>();
+
+ final LinkedList<String> expandedKeys = new LinkedList<String>();
+
+ // Iterates through hierarchical tree using a stack of iterators
+ final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
+ Collection<?> ids;
+ if (partialUpdate) {
+ ids = getChildren(expandedItemId);
+ } else {
+ ids = rootItemIds();
+ }
+
+ if (ids != null) {
+ iteratorStack.push(ids.iterator());
+ }
+
+ /*
+ * Body actions - Actions which has the target null and can be invoked
+ * by right clicking on the Tree body
+ */
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ for (Handler ah : actionHandlers) {
+
+ // Getting action for the null item, which in this case
+ // means the body item
+ final Action[] aa = ah.getActions(null, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String akey = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(akey);
+ }
+ }
+ }
+ target.addAttribute("alb", keys.toArray());
+ }
+
+ while (!iteratorStack.isEmpty()) {
+
+ // Gets the iterator for current tree level
+ final Iterator<?> i = iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Removes used iterator from the stack
+ iteratorStack.pop();
+
+ // Closes node
+ if (!iteratorStack.isEmpty()) {
+ target.endTag("node");
+ }
+ }
+
+ // Adds the item on current level
+ else {
+ final Object itemId = i.next();
+
+ // Starts the item / node
+ final boolean isNode = areChildrenAllowed(itemId);
+ if (isNode) {
+ target.startTag("node");
+ } else {
+ target.startTag("leaf");
+ }
+
+ if (itemStyleGenerator != null) {
+ String stylename = itemStyleGenerator.getStyle(this,
+ itemId);
+ if (stylename != null) {
+ target.addAttribute(TreeConstants.ATTRIBUTE_NODE_STYLE,
+ stylename);
+ }
+ }
+
+ if (itemDescriptionGenerator != null) {
+ String description = itemDescriptionGenerator
+ .generateDescription(this, itemId, null);
+ if (description != null && !description.equals("")) {
+ target.addAttribute("descr", description);
+ }
+ }
+
+ // Adds the attributes
+ target.addAttribute(TreeConstants.ATTRIBUTE_NODE_CAPTION,
+ getItemCaption(itemId));
+ final Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON,
+ getItemIcon(itemId));
+ target.addAttribute(TreeConstants.ATTRIBUTE_NODE_ICON_ALT,
+ getItemIconAlternateText(itemId));
+ }
+ final String key = itemIdMapper.key(itemId);
+ target.addAttribute("key", key);
+ if (isSelected(itemId)) {
+ target.addAttribute("selected", true);
+ selectedKeys.add(key);
+ }
+ if (areChildrenAllowed(itemId) && isExpanded(itemId)) {
+ target.addAttribute("expanded", true);
+ expandedKeys.add(key);
+ }
+
+ // Add caption change listener
+ getCaptionChangeListener().addNotifierForItem(itemId);
+
+ // Actions
+ if (actionHandlers != null) {
+ final ArrayList<String> keys = new ArrayList<String>();
+ final Iterator<Action.Handler> ahi = actionHandlers
+ .iterator();
+ while (ahi.hasNext()) {
+ final Action[] aa = ahi.next().getActions(itemId, this);
+ if (aa != null) {
+ for (int ai = 0; ai < aa.length; ai++) {
+ final String akey = actionMapper.key(aa[ai]);
+ actionSet.add(aa[ai]);
+ keys.add(akey);
+ }
+ }
+ }
+ target.addAttribute("al", keys.toArray());
+ }
+
+ // Adds the children if expanded, or close the tag
+ if (isExpanded(itemId) && hasChildren(itemId)
+ && areChildrenAllowed(itemId)) {
+ iteratorStack.push(getChildren(itemId).iterator());
+ } else {
+ if (isNode) {
+ target.endTag("node");
+ } else {
+ target.endTag("leaf");
+ }
+ }
+ }
+ }
+
+ // Actions
+ if (!actionSet.isEmpty()) {
+ target.addVariable(this, "action", "");
+ target.startTag("actions");
+ final Iterator<Action> i = actionSet.iterator();
+ while (i.hasNext()) {
+ final Action a = i.next();
+ target.startTag("action");
+ if (a.getCaption() != null) {
+ target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_CAPTION,
+ a.getCaption());
+ }
+ if (a.getIcon() != null) {
+ target.addAttribute(TreeConstants.ATTRIBUTE_ACTION_ICON,
+ a.getIcon());
+ }
+ target.addAttribute("key", actionMapper.key(a));
+ target.endTag("action");
+ }
+ target.endTag("actions");
+ }
+
+ if (partialUpdate) {
+ partialUpdate = false;
+ } else {
+ // Selected
+ target.addVariable(this, "selected",
+ selectedKeys.toArray(new String[selectedKeys.size()]));
+
+ // Expand and collapse
+ target.addVariable(this, "expand", new String[] {});
+ target.addVariable(this, "collapse", new String[] {});
+
+ // New items
+ target.addVariable(this, "newitem", new String[] {});
+
+ if (dropHandler != null) {
+ dropHandler.getAcceptCriterion().paint(target);
+ }
+
+ }
+ }
+
+ /* Container.Hierarchical API */
+
+ /**
+ * Tests if the Item with given ID can have any children.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#areChildrenAllowed(Object)
+ */
+ @Override
+ public boolean areChildrenAllowed(Object itemId) {
+ return ((Container.Hierarchical) items).areChildrenAllowed(itemId);
+ }
+
+ /**
+ * Gets the IDs of all Items that are children of the specified Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#getChildren(Object)
+ */
+ @Override
+ public Collection<?> getChildren(Object itemId) {
+ return ((Container.Hierarchical) items).getChildren(itemId);
+ }
+
+ /**
+ * Gets the ID of the parent Item of the specified Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#getParent(Object)
+ */
+ @Override
+ public Object getParent(Object itemId) {
+ return ((Container.Hierarchical) items).getParent(itemId);
+ }
+
+ /**
+ * Tests if the Item specified with <code>itemId</code> has child Items.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#hasChildren(Object)
+ */
+ @Override
+ public boolean hasChildren(Object itemId) {
+ return ((Container.Hierarchical) items).hasChildren(itemId);
+ }
+
+ /**
+ * Tests if the Item specified with <code>itemId</code> is a root Item.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#isRoot(Object)
+ */
+ @Override
+ public boolean isRoot(Object itemId) {
+ return ((Container.Hierarchical) items).isRoot(itemId);
+ }
+
+ /**
+ * Gets the IDs of all Items in the container that don't have a parent.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#rootItemIds()
+ */
+ @Override
+ public Collection<?> rootItemIds() {
+ return ((Container.Hierarchical) items).rootItemIds();
+ }
+
+ /**
+ * Sets the given Item's capability to have children.
+ *
+ * @see com.vaadin.data.Container.Hierarchical#setChildrenAllowed(Object,
+ * boolean)
+ */
+ @Override
+ public boolean setChildrenAllowed(Object itemId,
+ boolean areChildrenAllowed) {
+ final boolean success = ((Container.Hierarchical) items)
+ .setChildrenAllowed(itemId, areChildrenAllowed);
+ if (success) {
+ markAsDirty();
+ }
+ return success;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Hierarchical#setParent(java.lang.Object ,
+ * java.lang.Object)
+ */
+ @Override
+ public boolean setParent(Object itemId, Object newParentId) {
+ final boolean success = ((Container.Hierarchical) items)
+ .setParent(itemId, newParentId);
+ if (success) {
+ markAsDirty();
+ }
+ return success;
+ }
+
+ /* Overriding select behavior */
+
+ /**
+ * Sets the Container that serves as the data source of the viewer.
+ *
+ * @see com.vaadin.data.Container.Viewer#setContainerDataSource(Container)
+ */
+ @Override
+ public void setContainerDataSource(Container newDataSource) {
+ if (newDataSource == null) {
+ newDataSource = new HierarchicalContainer();
+ }
+
+ // Assure that the data source is ordered by making unordered
+ // containers ordered by wrapping them
+ if (Container.Hierarchical.class
+ .isAssignableFrom(newDataSource.getClass())) {
+ super.setContainerDataSource(newDataSource);
+ } else {
+ super.setContainerDataSource(
+ new ContainerHierarchicalWrapper(newDataSource));
+ }
+
+ /*
+ * Ensure previous expanded items are cleaned up if they don't exist in
+ * the new container
+ */
+ if (expanded != null) {
+ /*
+ * We need to check that the expanded-field is not null since
+ * setContainerDataSource() is called from the parent constructor
+ * (AbstractSelect()) and at that time the expanded field is not yet
+ * initialized.
+ */
+ cleanupExpandedItems();
+ }
+
+ }
+
+ @Override
+ public void containerItemSetChange(
+ com.vaadin.data.Container.ItemSetChangeEvent event) {
+ super.containerItemSetChange(event);
+ if (getContainerDataSource() instanceof Filterable) {
+ boolean hasFilters = !((Filterable) getContainerDataSource())
+ .getContainerFilters().isEmpty();
+ if (!hasFilters) {
+ /*
+ * If Container is not filtered then the itemsetchange is caused
+ * by either adding or removing items to the container. To
+ * prevent a memory leak we should cleanup the expanded list
+ * from items which was removed.
+ *
+ * However, there will still be a leak if the container is
+ * filtered to show only a subset of the items in the tree and
+ * later unfiltered items are removed from the container. In
+ * that case references to the unfiltered item ids will remain
+ * in the expanded list until the Tree instance is removed and
+ * the list is destroyed, or the container data source is
+ * replaced/updated. To force the removal of the removed items
+ * the application developer needs to a) remove the container
+ * filters temporarly or b) re-apply the container datasource
+ * using setContainerDataSource(getContainerDataSource())
+ */
+ cleanupExpandedItems();
+ }
+ }
+
+ }
+
+ /* Expand event and listener */
+
+ /**
+ * Event to fired when a node is expanded. ExapandEvent is fired when a node
+ * is to be expanded. it can me used to dynamically fill the sub-nodes of
+ * the node.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public static class ExpandEvent extends Component.Event {
+
+ private final Object expandedItemId;
+
+ /**
+ * New instance of options change event
+ *
+ * @param source
+ * the Source of the event.
+ * @param expandedItemId
+ */
+ public ExpandEvent(Component source, Object expandedItemId) {
+ super(source);
+ this.expandedItemId = expandedItemId;
+ }
+
+ /**
+ * Node where the event occurred.
+ *
+ * @return the Source of the event.
+ */
+ public Object getItemId() {
+ return expandedItemId;
+ }
+ }
+
+ /**
+ * Expand event listener.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface ExpandListener extends Serializable {
+
+ public static final Method EXPAND_METHOD = ReflectTools.findMethod(
+ ExpandListener.class, "nodeExpand", ExpandEvent.class);
+
+ /**
+ * A node has been expanded.
+ *
+ * @param event
+ * the Expand event.
+ */
+ public void nodeExpand(ExpandEvent event);
+ }
+
+ /**
+ * Adds the expand listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addExpandListener(ExpandListener listener) {
+ addListener(ExpandEvent.class, listener, ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addExpandListener(ExpandListener)}
+ **/
+ @Deprecated
+ public void addListener(ExpandListener listener) {
+ addExpandListener(listener);
+ }
+
+ /**
+ * Removes the expand listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeExpandListener(ExpandListener listener) {
+ removeListener(ExpandEvent.class, listener,
+ ExpandListener.EXPAND_METHOD);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeExpandListener(ExpandListener)}
+ **/
+ @Deprecated
+ public void removeListener(ExpandListener listener) {
+ removeExpandListener(listener);
+ }
+
+ /**
+ * Emits the expand event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireExpandEvent(Object itemId) {
+ fireEvent(new ExpandEvent(this, itemId));
+ }
+
+ /* Collapse event */
+
+ /**
+ * Collapse event
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public static class CollapseEvent extends Component.Event {
+
+ private final Object collapsedItemId;
+
+ /**
+ * New instance of options change event.
+ *
+ * @param source
+ * the Source of the event.
+ * @param collapsedItemId
+ */
+ public CollapseEvent(Component source, Object collapsedItemId) {
+ super(source);
+ this.collapsedItemId = collapsedItemId;
+ }
+
+ /**
+ * Gets tge Collapsed Item id.
+ *
+ * @return the collapsed item id.
+ */
+ public Object getItemId() {
+ return collapsedItemId;
+ }
+ }
+
+ /**
+ * Collapse event listener.
+ *
+ * @author Vaadin Ltd.
+ * @since 3.0
+ */
+ public interface CollapseListener extends Serializable {
+
+ public static final Method COLLAPSE_METHOD = ReflectTools.findMethod(
+ CollapseListener.class, "nodeCollapse", CollapseEvent.class);
+
+ /**
+ * A node has been collapsed.
+ *
+ * @param event
+ * the Collapse event.
+ */
+ public void nodeCollapse(CollapseEvent event);
+ }
+
+ /**
+ * Adds the collapse listener.
+ *
+ * @param listener
+ * the Listener to be added.
+ */
+ public void addCollapseListener(CollapseListener listener) {
+ addListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addCollapseListener(CollapseListener)}
+ **/
+ @Deprecated
+ public void addListener(CollapseListener listener) {
+ addCollapseListener(listener);
+ }
+
+ /**
+ * Removes the collapse listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeCollapseListener(CollapseListener listener) {
+ removeListener(CollapseEvent.class, listener,
+ CollapseListener.COLLAPSE_METHOD);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeCollapseListener(CollapseListener)}
+ **/
+ @Deprecated
+ public void removeListener(CollapseListener listener) {
+ removeCollapseListener(listener);
+ }
+
+ /**
+ * Emits collapse event.
+ *
+ * @param itemId
+ * the item id.
+ */
+ protected void fireCollapseEvent(Object itemId) {
+ fireEvent(new CollapseEvent(this, itemId));
+ }
+
+ /* Action container */
+
+ /**
+ * Adds an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#addActionHandler(Action.Handler)
+ */
+ @Override
+ public void addActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandler != null) {
+
+ if (actionHandlers == null) {
+ actionHandlers = new LinkedList<Action.Handler>();
+ actionMapper = new KeyMapper<Action>();
+ }
+
+ if (!actionHandlers.contains(actionHandler)) {
+ actionHandlers.add(actionHandler);
+ markAsDirty();
+ }
+ }
+ }
+
+ /**
+ * Removes an action handler.
+ *
+ * @see com.vaadin.event.Action.Container#removeActionHandler(Action.Handler)
+ */
+ @Override
+ public void removeActionHandler(Action.Handler actionHandler) {
+
+ if (actionHandlers != null && actionHandlers.contains(actionHandler)) {
+
+ actionHandlers.remove(actionHandler);
+
+ if (actionHandlers.isEmpty()) {
+ actionHandlers = null;
+ actionMapper = null;
+ }
+
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Removes all action handlers
+ */
+ public void removeAllActionHandlers() {
+ actionHandlers = null;
+ actionMapper = null;
+ markAsDirty();
+ }
+
+ /**
+ * Gets the visible item ids.
+ *
+ * @see com.vaadin.ui.Select#getVisibleItemIds()
+ */
+ @Override
+ public Collection<?> getVisibleItemIds() {
+
+ final LinkedList<Object> visible = new LinkedList<Object>();
+
+ // Iterates trough hierarchical tree using a stack of iterators
+ final Stack<Iterator<?>> iteratorStack = new Stack<Iterator<?>>();
+ final Collection<?> ids = rootItemIds();
+ if (ids != null) {
+ iteratorStack.push(ids.iterator());
+ }
+ while (!iteratorStack.isEmpty()) {
+
+ // Gets the iterator for current tree level
+ final Iterator<?> i = iteratorStack.peek();
+
+ // If the level is finished, back to previous tree level
+ if (!i.hasNext()) {
+
+ // Removes used iterator from the stack
+ iteratorStack.pop();
+ }
+
+ // Adds the item on current level
+ else {
+ final Object itemId = i.next();
+
+ visible.add(itemId);
+
+ // Adds children if expanded, or close the tag
+ if (isExpanded(itemId) && hasChildren(itemId)) {
+ iteratorStack.push(getChildren(itemId).iterator());
+ }
+ }
+ }
+
+ return visible;
+ }
+
+ /**
+ * Tree does not support <code>setNullSelectionItemId</code>.
+ *
+ * @see com.vaadin.ui.AbstractSelect#setNullSelectionItemId(java.lang.Object)
+ */
+ @Override
+ public void setNullSelectionItemId(Object nullSelectionItemId)
+ throws UnsupportedOperationException {
+ if (nullSelectionItemId != null) {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ /**
+ * Adding new items is not supported.
+ *
+ * @throws UnsupportedOperationException
+ * if set to true.
+ * @see com.vaadin.ui.Select#setNewItemsAllowed(boolean)
+ */
+ @Override
+ public void setNewItemsAllowed(boolean allowNewOptions)
+ throws UnsupportedOperationException {
+ if (allowNewOptions) {
+ throw new UnsupportedOperationException();
+ }
+ }
+
+ private ItemStyleGenerator itemStyleGenerator;
+
+ private DropHandler dropHandler;
+
+ private boolean htmlContentAllowed;
+
+ @Override
+ public void addItemClickListener(ItemClickListener listener) {
+ addListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener, ItemClickEvent.ITEM_CLICK_METHOD);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #addItemClickListener(ItemClickListener)}
+ **/
+ @Override
+ @Deprecated
+ public void addListener(ItemClickListener listener) {
+ addItemClickListener(listener);
+ }
+
+ @Override
+ public void removeItemClickListener(ItemClickListener listener) {
+ removeListener(TreeConstants.ITEM_CLICK_EVENT_ID, ItemClickEvent.class,
+ listener);
+ }
+
+ /**
+ * @deprecated As of 7.0, replaced by
+ * {@link #removeItemClickListener(ItemClickListener)}
+ **/
+ @Override
+ @Deprecated
+ public void removeListener(ItemClickListener listener) {
+ removeItemClickListener(listener);
+ }
+
+ /**
+ * Sets the {@link ItemStyleGenerator} to be used with this tree.
+ *
+ * @param itemStyleGenerator
+ * item style generator or null to remove generator
+ */
+ public void setItemStyleGenerator(ItemStyleGenerator itemStyleGenerator) {
+ if (this.itemStyleGenerator != itemStyleGenerator) {
+ this.itemStyleGenerator = itemStyleGenerator;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * @return the current {@link ItemStyleGenerator} for this tree. Null if
+ * {@link ItemStyleGenerator} is not set.
+ */
+ public ItemStyleGenerator getItemStyleGenerator() {
+ return itemStyleGenerator;
+ }
+
+ /**
+ * ItemStyleGenerator can be used to add custom styles to tree items. The
+ * CSS class name that will be added to the item content is
+ * <tt>v-tree-node-[style name]</tt>.
+ */
+ public interface ItemStyleGenerator extends Serializable {
+
+ /**
+ * Called by Tree when an item is painted.
+ *
+ * @param source
+ * the source Tree
+ * @param itemId
+ * The itemId of the item to be painted
+ * @return The style name to add to this item. (the CSS class name will
+ * be v-tree-node-[style name]
+ */
+ public abstract String getStyle(Tree source, Object itemId);
+ }
+
+ // Overriden so javadoc comes from Container.Hierarchical
+ @Override
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ return super.removeItem(itemId);
+ }
+
+ @Override
+ public DropHandler getDropHandler() {
+ return dropHandler;
+ }
+
+ public void setDropHandler(DropHandler dropHandler) {
+ this.dropHandler = dropHandler;
+ }
+
+ /**
+ * A {@link TargetDetails} implementation with Tree specific api.
+ *
+ * @since 6.3
+ */
+ public class TreeTargetDetails extends AbstractSelectTargetDetails {
+
+ TreeTargetDetails(Map<String, Object> rawVariables) {
+ super(rawVariables);
+ }
+
+ @Override
+ public Tree getTarget() {
+ return (Tree) super.getTarget();
+ }
+
+ /**
+ * If the event is on a node that can not have children (see
+ * {@link Tree#areChildrenAllowed(Object)}), this method returns the
+ * parent item id of the target item (see {@link #getItemIdOver()} ).
+ * The identifier of the parent node is also returned if the cursor is
+ * on the top part of node. Else this method returns the same as
+ * {@link #getItemIdOver()}.
+ * <p>
+ * In other words this method returns the identifier of the "folder"
+ * into the drag operation is targeted.
+ * <p>
+ * If the method returns null, the current target is on a root node or
+ * on other undefined area over the tree component.
+ * <p>
+ * The default Tree implementation marks the targetted tree node with
+ * CSS classnames v-tree-node-dragfolder and
+ * v-tree-node-caption-dragfolder (for the caption element).
+ */
+ public Object getItemIdInto() {
+
+ Object itemIdOver = getItemIdOver();
+ if (areChildrenAllowed(itemIdOver)
+ && getDropLocation() == VerticalDropLocation.MIDDLE) {
+ return itemIdOver;
+ }
+ return getParent(itemIdOver);
+ }
+
+ /**
+ * If drop is targeted into "folder node" (see {@link #getItemIdInto()}
+ * ), this method returns the item id of the node after the drag was
+ * targeted. This method is useful when implementing drop into specific
+ * location (between specific nodes) in tree.
+ *
+ * @return the id of the item after the user targets the drop or null if
+ * "target" is a first item in node list (or the first in root
+ * node list)
+ */
+ public Object getItemIdAfter() {
+ Object itemIdOver = getItemIdOver();
+ Object itemIdInto2 = getItemIdInto();
+ if (itemIdOver.equals(itemIdInto2)) {
+ return null;
+ }
+ VerticalDropLocation dropLocation = getDropLocation();
+ if (VerticalDropLocation.TOP == dropLocation) {
+ // if on top of the caption area, add before
+ Collection<?> children;
+ Object itemIdInto = getItemIdInto();
+ if (itemIdInto != null) {
+ // seek the previous from child list
+ children = getChildren(itemIdInto);
+ } else {
+ children = rootItemIds();
+ }
+ Object ref = null;
+ for (Object object : children) {
+ if (object.equals(itemIdOver)) {
+ return ref;
+ }
+ ref = object;
+ }
+ }
+ return itemIdOver;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.DropTarget#translateDropTargetDetails(java.util.Map)
+ */
+ @Override
+ public TreeTargetDetails translateDropTargetDetails(
+ Map<String, Object> clientVariables) {
+ return new TreeTargetDetails(clientVariables);
+ }
+
+ /**
+ * Helper API for {@link TreeDropCriterion}
+ *
+ * @param itemId
+ * @return
+ */
+ private String key(Object itemId) {
+ return itemIdMapper.key(itemId);
+ }
+
+ /**
+ * Sets the drag mode that controls how Tree behaves as a {@link DragSource}
+ * .
+ *
+ * @param dragMode
+ */
+ public void setDragMode(TreeDragMode dragMode) {
+ this.dragMode = dragMode;
+ markAsDirty();
+ }
+
+ /**
+ * @return the drag mode that controls how Tree behaves as a
+ * {@link DragSource}.
+ *
+ * @see TreeDragMode
+ */
+ public TreeDragMode getDragMode() {
+ return dragMode;
+ }
+
+ /**
+ * Concrete implementation of {@link DataBoundTransferable} for data
+ * transferred from a tree.
+ *
+ * @see {@link DataBoundTransferable}.
+ *
+ * @since 6.3
+ */
+ protected class TreeTransferable extends DataBoundTransferable {
+
+ public TreeTransferable(Component sourceComponent,
+ Map<String, Object> rawVariables) {
+ super(sourceComponent, rawVariables);
+ }
+
+ @Override
+ public Object getItemId() {
+ return getData("itemId");
+ }
+
+ @Override
+ public Object getPropertyId() {
+ return getItemCaptionPropertyId();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.event.dd.DragSource#getTransferable(java.util.Map)
+ */
+ @Override
+ public Transferable getTransferable(Map<String, Object> payload) {
+ TreeTransferable transferable = new TreeTransferable(this, payload);
+ // updating drag source variables
+ Object object = payload.get("itemId");
+ if (object != null) {
+ transferable.setData("itemId", itemIdMapper.get((String) object));
+ }
+
+ return transferable;
+ }
+
+ /**
+ * Lazy loading accept criterion for Tree. Accepted target nodes are loaded
+ * from server once per drag and drop operation. Developer must override one
+ * method that decides accepted tree nodes for the whole Tree.
+ *
+ * <p>
+ * Initially pretty much no data is sent to client. On first required
+ * criterion check (per drag request) the client side data structure is
+ * initialized from server and no subsequent requests requests are needed
+ * during that drag and drop operation.
+ */
+ public static abstract class TreeDropCriterion extends ServerSideCriterion {
+
+ private Tree tree;
+
+ private Set<Object> allowedItemIds;
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.ServerSideCriterion#getIdentifier
+ * ()
+ */
+ @Override
+ protected String getIdentifier() {
+ return TreeDropCriterion.class.getCanonicalName();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#accepts(com.vaadin
+ * .event.dd.DragAndDropEvent)
+ */
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ AbstractSelectTargetDetails dropTargetData = (AbstractSelectTargetDetails) dragEvent
+ .getTargetDetails();
+ tree = (Tree) dragEvent.getTargetDetails().getTarget();
+ allowedItemIds = getAllowedItemIds(dragEvent, tree);
+
+ return allowedItemIds.contains(dropTargetData.getItemIdOver());
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.event.dd.acceptCriteria.AcceptCriterion#paintResponse(
+ * com.vaadin.server.PaintTarget)
+ */
+ @Override
+ public void paintResponse(PaintTarget target) throws PaintException {
+ /*
+ * send allowed nodes to client so subsequent requests can be
+ * avoided
+ */
+ Object[] array = allowedItemIds.toArray();
+ for (int i = 0; i < array.length; i++) {
+ String key = tree.key(array[i]);
+ array[i] = key;
+ }
+ target.addAttribute("allowedIds", array);
+ }
+
+ protected abstract Set<Object> getAllowedItemIds(
+ DragAndDropEvent dragEvent, Tree tree);
+
+ }
+
+ /**
+ * A criterion that accepts {@link Transferable} only directly on a tree
+ * node that can have children.
+ * <p>
+ * Class is singleton, use {@link TargetItemAllowsChildren#get()} to get the
+ * instance.
+ *
+ * @see Tree#setChildrenAllowed(Object, boolean)
+ *
+ * @since 6.3
+ */
+ public static class TargetItemAllowsChildren extends TargetDetailIs {
+
+ private static TargetItemAllowsChildren instance = new TargetItemAllowsChildren();
+
+ public static TargetItemAllowsChildren get() {
+ return instance;
+ }
+
+ private TargetItemAllowsChildren() {
+ super("itemIdOverIsNode", Boolean.TRUE);
+ }
+
+ /*
+ * Uses enhanced server side check
+ */
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ try {
+ // must be over tree node and in the middle of it (not top or
+ // bottom
+ // part)
+ TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
+ .getTargetDetails();
+
+ Object itemIdOver = eventDetails.getItemIdOver();
+ if (!eventDetails.getTarget().areChildrenAllowed(itemIdOver)) {
+ return false;
+ }
+ // return true if directly over
+ return eventDetails
+ .getDropLocation() == VerticalDropLocation.MIDDLE;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ }
+
+ /**
+ * An accept criterion that checks the parent node (or parent hierarchy) for
+ * the item identifier given in constructor. If the parent is found, content
+ * is accepted. Criterion can be used to accepts drags on a specific sub
+ * tree only.
+ * <p>
+ * The root items is also consider to be valid target.
+ */
+ public class TargetInSubtree extends ClientSideCriterion {
+
+ private Object rootId;
+ private int depthToCheck = -1;
+
+ /**
+ * Constructs a criteria that accepts the drag if the targeted Item is a
+ * descendant of Item identified by given id
+ *
+ * @param parentItemId
+ * the item identifier of the parent node
+ */
+ public TargetInSubtree(Object parentItemId) {
+ rootId = parentItemId;
+ }
+
+ /**
+ * Constructs a criteria that accepts drops within given level below the
+ * subtree root identified by given id.
+ *
+ * @param rootId
+ * the item identifier to be sought for
+ * @param depthToCheck
+ * the depth that tree is traversed upwards to seek for the
+ * parent, -1 means that the whole structure should be
+ * checked
+ */
+ public TargetInSubtree(Object rootId, int depthToCheck) {
+ this.rootId = rootId;
+ this.depthToCheck = depthToCheck;
+ }
+
+ @Override
+ public boolean accept(DragAndDropEvent dragEvent) {
+ try {
+ TreeTargetDetails eventDetails = (TreeTargetDetails) dragEvent
+ .getTargetDetails();
+
+ if (eventDetails.getItemIdOver() != null) {
+ Object itemId = eventDetails.getItemIdOver();
+ int i = 0;
+ while (itemId != null
+ && (depthToCheck == -1 || i <= depthToCheck)) {
+ if (itemId.equals(rootId)) {
+ return true;
+ }
+ itemId = getParent(itemId);
+ i++;
+ }
+ }
+ return false;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ super.paintContent(target);
+ target.addAttribute("depth", depthToCheck);
+ target.addAttribute("key", key(rootId));
+ }
+ }
+
+ /**
+ * Set the item description generator which generates tooltips for the tree
+ * items
+ *
+ * @param generator
+ * The generator to use or null to disable
+ */
+ public void setItemDescriptionGenerator(
+ ItemDescriptionGenerator generator) {
+ if (generator != itemDescriptionGenerator) {
+ itemDescriptionGenerator = generator;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * Get the item description generator which generates tooltips for tree
+ * items
+ */
+ public ItemDescriptionGenerator getItemDescriptionGenerator() {
+ return itemDescriptionGenerator;
+ }
+
+ private void cleanupExpandedItems() {
+ Set<Object> removedItemIds = new HashSet<Object>();
+ for (Object expandedItemId : expanded) {
+ if (getItem(expandedItemId) == null) {
+ removedItemIds.add(expandedItemId);
+ if (this.expandedItemId == expandedItemId) {
+ this.expandedItemId = null;
+ }
+ }
+ }
+ expanded.removeAll(removedItemIds);
+ }
+
+ /**
+ * Reads an Item from a design and inserts it into the data source.
+ * Recursively handles any children of the item as well.
+ *
+ * @since 7.5.0
+ * @param node
+ * an element representing the item (tree node).
+ * @param selected
+ * A set accumulating selected items. If the item that is read is
+ * marked as selected, its item id should be added to this set.
+ * @param context
+ * the DesignContext instance used in parsing
+ * @return the item id of the new item
+ *
+ * @throws DesignException
+ * if the tag name of the {@code node} element is not
+ * {@code node}.
+ */
+ @Override
+ protected String readItem(Element node, Set<String> selected,
+ DesignContext context) {
+
+ if (!"node".equals(node.tagName())) {
+ throw new DesignException("Unrecognized child element in "
+ + getClass().getSimpleName() + ": " + node.tagName());
+ }
+
+ String itemId = node.attr("text");
+ addItem(itemId);
+ if (node.hasAttr("icon")) {
+ Resource icon = DesignAttributeHandler.readAttribute("icon",
+ node.attributes(), Resource.class);
+ setItemIcon(itemId, icon);
+ }
+ if (node.hasAttr("selected")) {
+ selected.add(itemId);
+ }
+
+ for (Element child : node.children()) {
+ String childItemId = readItem(child, selected, context);
+ setParent(childItemId, itemId);
+ }
+ return itemId;
+ }
+
+ /**
+ * Recursively writes the root items and their children to a design.
+ *
+ * @since 7.5.0
+ * @param design
+ * the element into which to insert the items
+ * @param context
+ * the DesignContext instance used in writing
+ */
+ @Override
+ protected void writeItems(Element design, DesignContext context) {
+ for (Object itemId : rootItemIds()) {
+ writeItem(design, itemId, context);
+ }
+ }
+
+ /**
+ * Recursively writes a data source Item and its children to a design.
+ *
+ * @since 7.5.0
+ * @param design
+ * the element into which to insert the item
+ * @param itemId
+ * the id of the item to write
+ * @param context
+ * the DesignContext instance used in writing
+ * @return
+ */
+ @Override
+ protected Element writeItem(Element design, Object itemId,
+ DesignContext context) {
+ Element element = design.appendElement("node");
+
+ element.attr("text", itemId.toString());
+
+ Resource icon = getItemIcon(itemId);
+ if (icon != null) {
+ DesignAttributeHandler.writeAttribute("icon", element.attributes(),
+ icon, null, Resource.class);
+ }
+
+ if (isSelected(itemId)) {
+ element.attr("selected", "");
+ }
+
+ Collection<?> children = getChildren(itemId);
+ if (children != null) {
+ // Yeah... see #5864
+ for (Object childItemId : children) {
+ writeItem(element, childItemId, context);
+ }
+ }
+
+ return element;
+ }
+
+ /**
+ * Sets whether html is allowed in the item captions. If set to
+ * <code>true</code>, the captions are passed to the browser as html and the
+ * developer is responsible for ensuring no harmful html is used. If set to
+ * <code>false</code>, the content is passed to the browser as plain text.
+ * The default setting is <code>false</code>
+ *
+ * @since 7.6
+ * @param htmlContentAllowed
+ * <code>true</code> if the captions are used as html,
+ * <code>false</code> if used as plain text
+ */
+ public void setHtmlContentAllowed(boolean htmlContentAllowed) {
+ this.htmlContentAllowed = htmlContentAllowed;
+ markAsDirty();
+ }
+
+ /**
+ * Checks whether captions are interpreted as html or plain text.
+ *
+ * @since 7.6
+ * @return <code>true</code> if the captions are displayed as html,
+ * <code>false</code> if displayed as plain text
+ * @see #setHtmlContentAllowed(boolean)
+ */
+ public boolean isHtmlContentAllowed() {
+ return htmlContentAllowed;
+ }
+
+ @Override
+ protected TreeState getState() {
+ return (TreeState) super.getState();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/TwinColSelect.java b/compatibility-server/src/main/java/com/vaadin/ui/TwinColSelect.java
new file mode 100644
index 0000000000..261d813ffa
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/TwinColSelect.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui;
+
+import java.util.Collection;
+
+import com.vaadin.data.Container;
+import com.vaadin.server.PaintException;
+import com.vaadin.server.PaintTarget;
+import com.vaadin.shared.ui.twincolselect.TwinColSelectConstants;
+import com.vaadin.shared.ui.twincolselect.TwinColSelectState;
+
+/**
+ * Multiselect component with two lists: left side for available items and right
+ * side for selected items.
+ */
+@SuppressWarnings("serial")
+public class TwinColSelect extends AbstractSelect {
+
+ private int rows = 0;
+
+ private String leftColumnCaption;
+ private String rightColumnCaption;
+
+ /**
+ *
+ */
+ public TwinColSelect() {
+ super();
+ setMultiSelect(true);
+ }
+
+ /**
+ * @param caption
+ */
+ public TwinColSelect(String caption) {
+ super(caption);
+ setMultiSelect(true);
+ }
+
+ /**
+ * @param caption
+ * @param dataSource
+ */
+ public TwinColSelect(String caption, Container dataSource) {
+ super(caption, dataSource);
+ setMultiSelect(true);
+ }
+
+ public int getRows() {
+ return rows;
+ }
+
+ /**
+ * Sets the number of rows in the editor. If the number of rows is set to 0,
+ * the actual number of displayed rows is determined implicitly by the
+ * adapter.
+ * <p>
+ * If a height if set (using {@link #setHeight(String)} or
+ * {@link #setHeight(float, int)}) it overrides the number of rows. Leave
+ * the height undefined to use this method. This is the opposite of how
+ * {@link #setColumns(int)} work.
+ *
+ *
+ * @param rows
+ * the number of rows to set.
+ */
+ public void setRows(int rows) {
+ if (rows < 0) {
+ rows = 0;
+ }
+ if (this.rows != rows) {
+ this.rows = rows;
+ markAsDirty();
+ }
+ }
+
+ /**
+ * @param caption
+ * @param options
+ */
+ public TwinColSelect(String caption, Collection<?> options) {
+ super(caption, options);
+ setMultiSelect(true);
+ }
+
+ @Override
+ public void paintContent(PaintTarget target) throws PaintException {
+ // Adds the number of columns
+ // Adds the number of rows
+ if (rows != 0) {
+ target.addAttribute("rows", rows);
+ }
+
+ // Right and left column captions and/or icons (if set)
+ String lc = getLeftColumnCaption();
+ String rc = getRightColumnCaption();
+ if (lc != null) {
+ target.addAttribute(TwinColSelectConstants.ATTRIBUTE_LEFT_CAPTION,
+ lc);
+ }
+ if (rc != null) {
+ target.addAttribute(TwinColSelectConstants.ATTRIBUTE_RIGHT_CAPTION,
+ rc);
+ }
+
+ super.paintContent(target);
+ }
+
+ /**
+ * Sets the text shown above the right column.
+ *
+ * @param caption
+ * The text to show
+ */
+ public void setRightColumnCaption(String rightColumnCaption) {
+ this.rightColumnCaption = rightColumnCaption;
+ markAsDirty();
+ }
+
+ /**
+ * Returns the text shown above the right column.
+ *
+ * @return The text shown or null if not set.
+ */
+ public String getRightColumnCaption() {
+ return rightColumnCaption;
+ }
+
+ /**
+ * Sets the text shown above the left column.
+ *
+ * @param caption
+ * The text to show
+ */
+ public void setLeftColumnCaption(String leftColumnCaption) {
+ this.leftColumnCaption = leftColumnCaption;
+ markAsDirty();
+ }
+
+ /**
+ * Returns the text shown above the left column.
+ *
+ * @return The text shown or null if not set.
+ */
+ public String getLeftColumnCaption() {
+ return leftColumnCaption;
+ }
+
+ @Override
+ protected TwinColSelectState getState() {
+ return (TwinColSelectState) super.getState();
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvent.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvent.java
new file mode 100644
index 0000000000..f007f0cf34
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvent.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar;
+
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.Component;
+
+/**
+ * All Calendar events extends this class.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ *
+ */
+@SuppressWarnings("serial")
+public class CalendarComponentEvent extends Component.Event {
+
+ /**
+ * Set the source of the event
+ *
+ * @param source
+ * The source calendar
+ *
+ */
+ public CalendarComponentEvent(Calendar source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.ui.Component.Event#getComponent()
+ */
+ @Override
+ public Calendar getComponent() {
+ return (Calendar) super.getComponent();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvents.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvents.java
new file mode 100644
index 0000000000..2c4fec95d4
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarComponentEvents.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.util.Date;
+import java.util.EventListener;
+
+import com.vaadin.shared.ui.calendar.CalendarEventId;
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.util.ReflectTools;
+
+/**
+ * Interface for all Vaadin Calendar events.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarComponentEvents extends Serializable {
+
+ /**
+ * Notifier interface for notifying listener of calendar events
+ */
+ public interface CalendarEventNotifier extends Serializable {
+ /**
+ * Get the assigned event handler for the given eventId.
+ *
+ * @param eventId
+ * @return the assigned eventHandler, or null if no handler is assigned
+ */
+ public EventListener getHandler(String eventId);
+ }
+
+ /**
+ * Notifier interface for event drag & drops.
+ */
+ public interface EventMoveNotifier extends CalendarEventNotifier {
+
+ /**
+ * Set the EventMoveHandler.
+ *
+ * @param listener
+ * EventMoveHandler to be added
+ */
+ public void setHandler(EventMoveHandler listener);
+
+ }
+
+ /**
+ * MoveEvent is sent when existing event is dragged to a new position.
+ */
+ @SuppressWarnings("serial")
+ public class MoveEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTMOVE;
+
+ /** Index for the moved Schedule.Event. */
+ private CalendarEvent calendarEvent;
+
+ /** New starting date for the moved Calendar.Event. */
+ private Date newStart;
+
+ /**
+ * MoveEvent needs the target event and new start date.
+ *
+ * @param source
+ * Calendar component.
+ * @param calendarEvent
+ * Target event.
+ * @param newStart
+ * Target event's new start date.
+ */
+ public MoveEvent(Calendar source, CalendarEvent calendarEvent,
+ Date newStart) {
+ super(source);
+
+ this.calendarEvent = calendarEvent;
+ this.newStart = newStart;
+ }
+
+ /**
+ * Get target event.
+ *
+ * @return Target event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ /**
+ * Get new start date.
+ *
+ * @return New start date.
+ */
+ public Date getNewStart() {
+ return newStart;
+ }
+ }
+
+ /**
+ * Handler interface for when events are being dragged on the calendar
+ *
+ */
+ public interface EventMoveHandler extends EventListener, Serializable {
+
+ /** Trigger method for the MoveEvent. */
+ public static final Method eventMoveMethod = ReflectTools.findMethod(
+ EventMoveHandler.class, "eventMove", MoveEvent.class);
+
+ /**
+ * This method will be called when event has been moved to a new
+ * position.
+ *
+ * @param event
+ * MoveEvent containing specific information of the new
+ * position and target event.
+ */
+ public void eventMove(MoveEvent event);
+ }
+
+ /**
+ * Handler interface for day or time cell drag-marking with mouse.
+ */
+ public interface RangeSelectNotifier
+ extends Serializable, CalendarEventNotifier {
+
+ /**
+ * Set the RangeSelectHandler that listens for drag-marking.
+ *
+ * @param listener
+ * RangeSelectHandler to be added.
+ */
+ public void setHandler(RangeSelectHandler listener);
+ }
+
+ /**
+ * RangeSelectEvent is sent when day or time cells are drag-marked with
+ * mouse.
+ */
+ @SuppressWarnings("serial")
+ public class RangeSelectEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.RANGESELECT;
+
+ /** Calendar event's start date. */
+ private Date start;
+
+ /** Calendar event's end date. */
+ private Date end;
+
+ /**
+ * Defines the event's view mode.
+ */
+ private boolean monthlyMode;
+
+ /**
+ * RangeSelectEvent needs a start and end date.
+ *
+ * @param source
+ * Calendar component.
+ * @param start
+ * Start date.
+ * @param end
+ * End date.
+ * @param monthlyMode
+ * Calendar view mode.
+ */
+ public RangeSelectEvent(Calendar source, Date start, Date end,
+ boolean monthlyMode) {
+ super(source);
+ this.start = start;
+ this.end = end;
+ this.monthlyMode = monthlyMode;
+ }
+
+ /**
+ * Get start date.
+ *
+ * @return Start date.
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * Get end date.
+ *
+ * @return End date.
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * Gets the event's view mode. Calendar can be be either in monthly or
+ * weekly mode, depending on the active date range.
+ *
+ * @deprecated User {@link Calendar#isMonthlyMode()} instead
+ *
+ * @return Returns true when monthly view is active.
+ */
+ @Deprecated
+ public boolean isMonthlyMode() {
+ return monthlyMode;
+ }
+ }
+
+ /** RangeSelectHandler handles RangeSelectEvent. */
+ public interface RangeSelectHandler extends EventListener, Serializable {
+
+ /** Trigger method for the RangeSelectEvent. */
+ public static final Method rangeSelectMethod = ReflectTools.findMethod(
+ RangeSelectHandler.class, "rangeSelect",
+ RangeSelectEvent.class);
+
+ /**
+ * This method will be called when day or time cells are drag-marked
+ * with mouse.
+ *
+ * @param event
+ * RangeSelectEvent that contains range start and end date.
+ */
+ public void rangeSelect(RangeSelectEvent event);
+ }
+
+ /** Notifier interface for navigation listening. */
+ public interface NavigationNotifier extends Serializable {
+ /**
+ * Add a forward navigation listener.
+ *
+ * @param handler
+ * ForwardHandler to be added.
+ */
+ public void setHandler(ForwardHandler handler);
+
+ /**
+ * Add a backward navigation listener.
+ *
+ * @param handler
+ * BackwardHandler to be added.
+ */
+ public void setHandler(BackwardHandler handler);
+
+ /**
+ * Add a date click listener.
+ *
+ * @param handler
+ * DateClickHandler to be added.
+ */
+ public void setHandler(DateClickHandler handler);
+
+ /**
+ * Add a event click listener.
+ *
+ * @param handler
+ * EventClickHandler to be added.
+ */
+ public void setHandler(EventClickHandler handler);
+
+ /**
+ * Add a week click listener.
+ *
+ * @param handler
+ * WeekClickHandler to be added.
+ */
+ public void setHandler(WeekClickHandler handler);
+ }
+
+ /**
+ * ForwardEvent is sent when forward navigation button is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class ForwardEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.FORWARD;
+
+ /**
+ * ForwardEvent needs only the source component.
+ *
+ * @param source
+ * Calendar component.
+ */
+ public ForwardEvent(Calendar source) {
+ super(source);
+ }
+ }
+
+ /** ForwardHandler handles ForwardEvent. */
+ public interface ForwardHandler extends EventListener, Serializable {
+
+ /** Trigger method for the ForwardEvent. */
+ public static final Method forwardMethod = ReflectTools.findMethod(
+ ForwardHandler.class, "forward", ForwardEvent.class);
+
+ /**
+ * This method will be called when date range is moved forward.
+ *
+ * @param event
+ * ForwardEvent
+ */
+ public void forward(ForwardEvent event);
+ }
+
+ /**
+ * BackwardEvent is sent when backward navigation button is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class BackwardEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.BACKWARD;
+
+ /**
+ * BackwardEvent needs only the source source component.
+ *
+ * @param source
+ * Calendar component.
+ */
+ public BackwardEvent(Calendar source) {
+ super(source);
+ }
+ }
+
+ /** BackwardHandler handles BackwardEvent. */
+ public interface BackwardHandler extends EventListener, Serializable {
+
+ /** Trigger method for the BackwardEvent. */
+ public static final Method backwardMethod = ReflectTools.findMethod(
+ BackwardHandler.class, "backward", BackwardEvent.class);
+
+ /**
+ * This method will be called when date range is moved backwards.
+ *
+ * @param event
+ * BackwardEvent
+ */
+ public void backward(BackwardEvent event);
+ }
+
+ /**
+ * DateClickEvent is sent when a date is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class DateClickEvent extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.DATECLICK;
+
+ /** Date that was clicked. */
+ private Date date;
+
+ /** DateClickEvent needs the target date that was clicked. */
+ public DateClickEvent(Calendar source, Date date) {
+ super(source);
+ this.date = date;
+ }
+
+ /**
+ * Get clicked date.
+ *
+ * @return Clicked date.
+ */
+ public Date getDate() {
+ return date;
+ }
+ }
+
+ /** DateClickHandler handles DateClickEvent. */
+ public interface DateClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the DateClickEvent. */
+ public static final Method dateClickMethod = ReflectTools.findMethod(
+ DateClickHandler.class, "dateClick", DateClickEvent.class);
+
+ /**
+ * This method will be called when a date is clicked.
+ *
+ * @param event
+ * DateClickEvent containing the target date.
+ */
+ public void dateClick(DateClickEvent event);
+ }
+
+ /**
+ * EventClick is sent when an event is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class EventClick extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTCLICK;
+
+ /** Clicked source event. */
+ private CalendarEvent calendarEvent;
+
+ /** Target source event is needed for the EventClick. */
+ public EventClick(Calendar source, CalendarEvent calendarEvent) {
+ super(source);
+ this.calendarEvent = calendarEvent;
+ }
+
+ /**
+ * Get the clicked event.
+ *
+ * @return Clicked event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+ }
+
+ /** EventClickHandler handles EventClick. */
+ public interface EventClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the EventClick. */
+ public static final Method eventClickMethod = ReflectTools.findMethod(
+ EventClickHandler.class, "eventClick", EventClick.class);
+
+ /**
+ * This method will be called when an event is clicked.
+ *
+ * @param event
+ * EventClick containing the target event.
+ */
+ public void eventClick(EventClick event);
+ }
+
+ /**
+ * WeekClick is sent when week is clicked.
+ */
+ @SuppressWarnings("serial")
+ public class WeekClick extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.WEEKCLICK;
+
+ /** Target week. */
+ private int week;
+
+ /** Target year. */
+ private int year;
+
+ /**
+ * WeekClick needs a target year and week.
+ *
+ * @param source
+ * Target source.
+ * @param week
+ * Target week.
+ * @param year
+ * Target year.
+ */
+ public WeekClick(Calendar source, int week, int year) {
+ super(source);
+ this.week = week;
+ this.year = year;
+ }
+
+ /**
+ * Get week as a integer. See {@link java.util.Calendar} for the allowed
+ * values.
+ *
+ * @return Week as a integer.
+ */
+ public int getWeek() {
+ return week;
+ }
+
+ /**
+ * Get year as a integer. See {@link java.util.Calendar} for the allowed
+ * values.
+ *
+ * @return Year as a integer
+ */
+ public int getYear() {
+ return year;
+ }
+ }
+
+ /** WeekClickHandler handles WeekClicks. */
+ public interface WeekClickHandler extends EventListener, Serializable {
+
+ /** Trigger method for the WeekClick. */
+ public static final Method weekClickMethod = ReflectTools.findMethod(
+ WeekClickHandler.class, "weekClick", WeekClick.class);
+
+ /**
+ * This method will be called when a week is clicked.
+ *
+ * @param event
+ * WeekClick containing the target week and year.
+ */
+ public void weekClick(WeekClick event);
+ }
+
+ /**
+ * EventResize is sent when an event is resized
+ */
+ @SuppressWarnings("serial")
+ public class EventResize extends CalendarComponentEvent {
+
+ public static final String EVENT_ID = CalendarEventId.EVENTRESIZE;
+
+ private CalendarEvent calendarEvent;
+
+ private Date startTime;
+
+ private Date endTime;
+
+ public EventResize(Calendar source, CalendarEvent calendarEvent,
+ Date startTime, Date endTime) {
+ super(source);
+ this.calendarEvent = calendarEvent;
+ this.startTime = startTime;
+ this.endTime = endTime;
+ }
+
+ /**
+ * Get target event.
+ *
+ * @return Target event.
+ */
+ public CalendarEvent getCalendarEvent() {
+ return calendarEvent;
+ }
+
+ /**
+ * @deprecated Use {@link #getNewStart()} instead
+ *
+ * @return the new start time
+ */
+ @Deprecated
+ public Date getNewStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Returns the updated start date/time of the event
+ *
+ * @return The new date for the event
+ */
+ public Date getNewStart() {
+ return startTime;
+ }
+
+ /**
+ * @deprecated Use {@link #getNewEnd()} instead
+ *
+ * @return the new end time
+ */
+ @Deprecated
+ public Date getNewEndTime() {
+ return endTime;
+ }
+
+ /**
+ * Returns the updates end date/time of the event
+ *
+ * @return The new date for the event
+ */
+ public Date getNewEnd() {
+ return endTime;
+ }
+ }
+
+ /**
+ * Notifier interface for event resizing.
+ */
+ public interface EventResizeNotifier extends Serializable {
+
+ /**
+ * Set a EventResizeHandler.
+ *
+ * @param handler
+ * EventResizeHandler to be set
+ */
+ public void setHandler(EventResizeHandler handler);
+ }
+
+ /**
+ * Handler for EventResize event.
+ */
+ public interface EventResizeHandler extends EventListener, Serializable {
+
+ /** Trigger method for the EventResize. */
+ public static final Method eventResizeMethod = ReflectTools.findMethod(
+ EventResizeHandler.class, "eventResize", EventResize.class);
+
+ void eventResize(EventResize event);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarDateRange.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarDateRange.java
new file mode 100644
index 0000000000..09d6c80a7f
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarDateRange.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * Class for representing a date range.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+@SuppressWarnings("serial")
+public class CalendarDateRange implements Serializable {
+
+ private Date start;
+
+ private Date end;
+
+ private final transient TimeZone tz;
+
+ /**
+ * Constructor
+ *
+ * @param start
+ * The start date and time of the date range
+ * @param end
+ * The end date and time of the date range
+ */
+ public CalendarDateRange(Date start, Date end, TimeZone tz) {
+ super();
+ this.start = start;
+ this.end = end;
+ this.tz = tz;
+ }
+
+ /**
+ * Get the start date of the date range
+ *
+ * @return the start Date of the range
+ */
+ public Date getStart() {
+ return start;
+ }
+
+ /**
+ * Get the end date of the date range
+ *
+ * @return the end Date of the range
+ */
+ public Date getEnd() {
+ return end;
+ }
+
+ /**
+ * Is a date in the date range
+ *
+ * @param date
+ * The date to check
+ * @return true if the date range contains a date start and end of range
+ * inclusive; false otherwise
+ */
+ public boolean inRange(Date date) {
+ if (date == null) {
+ return false;
+ }
+
+ return date.compareTo(start) >= 0 && date.compareTo(end) <= 0;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "CalendarDateRange [start=" + start + ", end=" + end + "]";
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarTargetDetails.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarTargetDetails.java
new file mode 100644
index 0000000000..3b71ab5a00
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/CalendarTargetDetails.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar;
+
+import java.util.Date;
+import java.util.Map;
+
+import com.vaadin.event.dd.DropTarget;
+import com.vaadin.event.dd.TargetDetailsImpl;
+import com.vaadin.ui.Calendar;
+
+/**
+ * Drop details for {@link com.vaadin.ui.addon.calendar.ui.Calendar Calendar}.
+ * When something is dropped on the Calendar, this class contains the specific
+ * details of the drop point. Specifically, this class gives access to the date
+ * where the drop happened. If the Calendar was in weekly mode, the date also
+ * includes the start time of the slot.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class CalendarTargetDetails extends TargetDetailsImpl {
+
+ private boolean hasDropTime;
+
+ public CalendarTargetDetails(Map<String, Object> rawDropData,
+ DropTarget dropTarget) {
+ super(rawDropData, dropTarget);
+ }
+
+ /**
+ * @return true if {@link #getDropTime()} will return a date object with the
+ * time set to the start of the time slot where the drop happened
+ */
+ public boolean hasDropTime() {
+ return hasDropTime;
+ }
+
+ /**
+ * Does the dropped item have a time associated with it
+ *
+ * @param hasDropTime
+ */
+ public void setHasDropTime(boolean hasDropTime) {
+ this.hasDropTime = hasDropTime;
+ }
+
+ /**
+ * @return the date where the drop happened
+ */
+ public Date getDropTime() {
+ if (hasDropTime) {
+ return (Date) getData("dropTime");
+ } else {
+ return (Date) getData("dropDay");
+ }
+ }
+
+ /**
+ * @return the {@link com.vaadin.ui.addon.calendar.ui.Calendar Calendar}
+ * instance which was the target of the drop
+ */
+ public Calendar getTargetCalendar() {
+ return (Calendar) getTarget();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/ContainerEventProvider.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/ContainerEventProvider.java
new file mode 100644
index 0000000000..b0a6aaa95a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/ContainerEventProvider.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.LinkedList;
+import java.util.List;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.event.BasicEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEditableEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeListener;
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeNotifier;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider.EventSetChangeNotifier;
+
+/**
+ * A event provider which uses a {@link Container} as a datasource. Container
+ * used as data source.
+ *
+ * NOTE: The data source must be sorted by date!
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class ContainerEventProvider
+ implements CalendarEditableEventProvider, EventSetChangeNotifier,
+ EventChangeNotifier, EventMoveHandler, EventResizeHandler,
+ Container.ItemSetChangeListener, Property.ValueChangeListener {
+
+ // Default property ids
+ public static final String CAPTION_PROPERTY = "caption";
+ public static final String DESCRIPTION_PROPERTY = "description";
+ public static final String STARTDATE_PROPERTY = "start";
+ public static final String ENDDATE_PROPERTY = "end";
+ public static final String STYLENAME_PROPERTY = "styleName";
+ public static final String ALL_DAY_PROPERTY = "allDay";
+
+ /**
+ * Internal class to keep the container index which item this event
+ * represents
+ *
+ */
+ private class ContainerCalendarEvent extends BasicEvent {
+ private final int index;
+
+ public ContainerCalendarEvent(int containerIndex) {
+ super();
+ index = containerIndex;
+ }
+
+ public int getContainerIndex() {
+ return index;
+ }
+ }
+
+ /**
+ * Listeners attached to the container
+ */
+ private final List<EventSetChangeListener> eventSetChangeListeners = new LinkedList<CalendarEventProvider.EventSetChangeListener>();
+ private final List<EventChangeListener> eventChangeListeners = new LinkedList<CalendarEvent.EventChangeListener>();
+
+ /**
+ * The event cache contains the events previously created by
+ * {@link #getEvents(Date, Date)}
+ */
+ private final List<CalendarEvent> eventCache = new LinkedList<CalendarEvent>();
+
+ /**
+ * The container used as datasource
+ */
+ private Indexed container;
+
+ /**
+ * Container properties. Defaults based on using the {@link BasicEvent}
+ * helper class.
+ */
+ private Object captionProperty = CAPTION_PROPERTY;
+ private Object descriptionProperty = DESCRIPTION_PROPERTY;
+ private Object startDateProperty = STARTDATE_PROPERTY;
+ private Object endDateProperty = ENDDATE_PROPERTY;
+ private Object styleNameProperty = STYLENAME_PROPERTY;
+ private Object allDayProperty = ALL_DAY_PROPERTY;
+
+ /**
+ * Constructor
+ *
+ * @param container
+ * Container to use as a data source.
+ */
+ public ContainerEventProvider(Container.Indexed container) {
+ this.container = container;
+ listenToContainerEvents();
+ }
+
+ /**
+ * Set the container data source
+ *
+ * @param container
+ * The container to use as datasource
+ *
+ */
+ public void setContainerDataSource(Container.Indexed container) {
+ // Detach the previous container
+ detachContainerDataSource();
+
+ this.container = container;
+ listenToContainerEvents();
+ }
+
+ /**
+ * Returns the container used as data source
+ *
+ */
+ public Container.Indexed getContainerDataSource() {
+ return container;
+ }
+
+ /**
+ * Attaches listeners to the container so container events can be processed
+ */
+ private void listenToContainerEvents() {
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container).addItemSetChangeListener(this);
+ }
+ if (container instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) container).addValueChangeListener(this);
+ }
+ }
+
+ /**
+ * Removes listeners from the container so no events are processed
+ */
+ private void ignoreContainerEvents() {
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container)
+ .removeItemSetChangeListener(this);
+ }
+ if (container instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) container).removeValueChangeListener(this);
+ }
+ }
+
+ /**
+ * Converts an event in the container to an {@link CalendarEvent}
+ *
+ * @param index
+ * The index of the item in the container to get the event for
+ * @return
+ */
+ private CalendarEvent getEvent(int index) {
+
+ // Check the event cache first
+ for (CalendarEvent e : eventCache) {
+ if (e instanceof ContainerCalendarEvent
+ && ((ContainerCalendarEvent) e)
+ .getContainerIndex() == index) {
+ return e;
+ } else if (container.getIdByIndex(index) == e) {
+ return e;
+ }
+ }
+
+ final Object id = container.getIdByIndex(index);
+ Item item = container.getItem(id);
+ CalendarEvent event;
+ if (id instanceof CalendarEvent) {
+ /*
+ * If we are using the BeanItemContainer or another container which
+ * stores the objects as ids then just return the instances
+ */
+ event = (CalendarEvent) id;
+
+ } else {
+ /*
+ * Else we use the properties to create the event
+ */
+ BasicEvent basicEvent = new ContainerCalendarEvent(index);
+
+ // Set values from property values
+ if (captionProperty != null
+ && item.getItemPropertyIds().contains(captionProperty)) {
+ basicEvent.setCaption(String.valueOf(
+ item.getItemProperty(captionProperty).getValue()));
+ }
+ if (descriptionProperty != null && item.getItemPropertyIds()
+ .contains(descriptionProperty)) {
+ basicEvent.setDescription(String.valueOf(
+ item.getItemProperty(descriptionProperty).getValue()));
+ }
+ if (startDateProperty != null
+ && item.getItemPropertyIds().contains(startDateProperty)) {
+ basicEvent.setStart((Date) item
+ .getItemProperty(startDateProperty).getValue());
+ }
+ if (endDateProperty != null
+ && item.getItemPropertyIds().contains(endDateProperty)) {
+ basicEvent.setEnd((Date) item.getItemProperty(endDateProperty)
+ .getValue());
+ }
+ if (styleNameProperty != null
+ && item.getItemPropertyIds().contains(styleNameProperty)) {
+ basicEvent.setStyleName(String.valueOf(
+ item.getItemProperty(styleNameProperty).getValue()));
+ }
+ if (allDayProperty != null
+ && item.getItemPropertyIds().contains(allDayProperty)) {
+ basicEvent.setAllDay((Boolean) item
+ .getItemProperty(allDayProperty).getValue());
+ }
+ event = basicEvent;
+ }
+ return event;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ eventCache.clear();
+ int size = container.size();
+ assert size >= 0;
+
+ for (int i = 0; i < size; i++) {
+ Object id = container.getIdByIndex(i);
+ Item item = container.getItem(id);
+ boolean add = true;
+ if (startDate != null) {
+ Date eventEnd = (Date) item.getItemProperty(endDateProperty)
+ .getValue();
+ if (eventEnd.compareTo(startDate) < 0) {
+ add = false;
+ }
+ }
+ if (add && endDate != null) {
+ Date eventStart = (Date) item.getItemProperty(startDateProperty)
+ .getValue();
+ if (eventStart.compareTo(endDate) >= 0) {
+ break; // because container is sorted, all further events
+ // will be even later
+ }
+ }
+ if (add) {
+ eventCache.add(getEvent(i));
+ }
+ }
+ return Collections.unmodifiableList(eventCache);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeNotifier
+ * #addListener(com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeListener)
+ */
+ @Override
+ public void addEventSetChangeListener(EventSetChangeListener listener) {
+ if (!eventSetChangeListeners.contains(listener)) {
+ eventSetChangeListeners.add(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeNotifier
+ * #removeListener(com.vaadin.addon.calendar.event.CalendarEventProvider.
+ * EventSetChangeListener)
+ */
+ @Override
+ public void removeEventSetChangeListener(EventSetChangeListener listener) {
+ eventSetChangeListeners.remove(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent.EventChangeNotifier#
+ * addListener
+ * (com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener)
+ */
+ @Override
+ public void addEventChangeListener(EventChangeListener listener) {
+ if (eventChangeListeners.contains(listener)) {
+ eventChangeListeners.add(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent.EventChangeNotifier#
+ * removeListener
+ * (com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener)
+ */
+ @Override
+ public void removeEventChangeListener(EventChangeListener listener) {
+ eventChangeListeners.remove(listener);
+ }
+
+ /**
+ * Get the property which provides the caption of the event
+ */
+ public Object getCaptionProperty() {
+ return captionProperty;
+ }
+
+ /**
+ * Set the property which provides the caption of the event
+ */
+ public void setCaptionProperty(Object captionProperty) {
+ this.captionProperty = captionProperty;
+ }
+
+ /**
+ * Get the property which provides the description of the event
+ */
+ public Object getDescriptionProperty() {
+ return descriptionProperty;
+ }
+
+ /**
+ * Set the property which provides the description of the event
+ */
+ public void setDescriptionProperty(Object descriptionProperty) {
+ this.descriptionProperty = descriptionProperty;
+ }
+
+ /**
+ * Get the property which provides the starting date and time of the event
+ */
+ public Object getStartDateProperty() {
+ return startDateProperty;
+ }
+
+ /**
+ * Set the property which provides the starting date and time of the event
+ */
+ public void setStartDateProperty(Object startDateProperty) {
+ this.startDateProperty = startDateProperty;
+ }
+
+ /**
+ * Get the property which provides the ending date and time of the event
+ */
+ public Object getEndDateProperty() {
+ return endDateProperty;
+ }
+
+ /**
+ * Set the property which provides the ending date and time of the event
+ */
+ public void setEndDateProperty(Object endDateProperty) {
+ this.endDateProperty = endDateProperty;
+ }
+
+ /**
+ * Get the property which provides the style name for the event
+ */
+ public Object getStyleNameProperty() {
+ return styleNameProperty;
+ }
+
+ /**
+ * Set the property which provides the style name for the event
+ */
+ public void setStyleNameProperty(Object styleNameProperty) {
+ this.styleNameProperty = styleNameProperty;
+ }
+
+ /**
+ * Set the all day property for the event
+ *
+ * @since 7.3.4
+ */
+ public void setAllDayProperty(Object allDayProperty) {
+ this.allDayProperty = allDayProperty;
+ }
+
+ /**
+ * Get the all day property for the event
+ *
+ * @since 7.3.4
+ */
+ public Object getAllDayProperty() {
+ return allDayProperty;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeListener#containerItemSetChange
+ * (com.vaadin.data.Container.ItemSetChangeEvent)
+ */
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ if (event.getContainer() == container) {
+ // Trigger an eventset change event when the itemset changes
+ for (EventSetChangeListener listener : eventSetChangeListeners) {
+ listener.eventSetChange(new EventSetChangeEvent(this));
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeListener#valueChange(com.vaadin.data
+ * .Property.ValueChangeEvent)
+ */
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ /*
+ * TODO Need to figure out how to get the item which triggered the the
+ * valuechange event and then trigger a EventChange event to the
+ * listeners
+ */
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler
+ * #eventMove
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.MoveEvent)
+ */
+ @Override
+ public void eventMove(MoveEvent event) {
+ CalendarEvent ce = event.getCalendarEvent();
+ if (eventCache.contains(ce)) {
+ int index;
+ if (ce instanceof ContainerCalendarEvent) {
+ index = ((ContainerCalendarEvent) ce).getContainerIndex();
+ } else {
+ index = container.indexOfId(ce);
+ }
+
+ long eventLength = ce.getEnd().getTime() - ce.getStart().getTime();
+ Date newEnd = new Date(event.getNewStart().getTime() + eventLength);
+
+ ignoreContainerEvents();
+ Item item = container.getItem(container.getIdByIndex(index));
+ item.getItemProperty(startDateProperty)
+ .setValue(event.getNewStart());
+ item.getItemProperty(endDateProperty).setValue(newEnd);
+ listenToContainerEvents();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * #eventResize
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResize)
+ */
+ @Override
+ public void eventResize(EventResize event) {
+ CalendarEvent ce = event.getCalendarEvent();
+ if (eventCache.contains(ce)) {
+ int index;
+ if (ce instanceof ContainerCalendarEvent) {
+ index = ((ContainerCalendarEvent) ce).getContainerIndex();
+ } else {
+ index = container.indexOfId(ce);
+ }
+ ignoreContainerEvents();
+ Item item = container.getItem(container.getIdByIndex(index));
+ item.getItemProperty(startDateProperty)
+ .setValue(event.getNewStart());
+ item.getItemProperty(endDateProperty).setValue(event.getNewEnd());
+ listenToContainerEvents();
+ }
+ }
+
+ /**
+ * If you are reusing the container which previously have been attached to
+ * this ContainerEventProvider call this method to remove this event
+ * providers container listeners before attaching it to an other
+ * ContainerEventProvider
+ */
+ public void detachContainerDataSource() {
+ ignoreContainerEvents();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ Item item;
+ try {
+ item = container.addItem(event);
+ } catch (UnsupportedOperationException uop) {
+ // Thrown if container does not support adding items with custom
+ // ids. JPAContainer for example.
+ item = container.getItem(container.addItem());
+ }
+ if (item != null) {
+ item.getItemProperty(getCaptionProperty())
+ .setValue(event.getCaption());
+ item.getItemProperty(getStartDateProperty())
+ .setValue(event.getStart());
+ item.getItemProperty(getEndDateProperty()).setValue(event.getEnd());
+ item.getItemProperty(getStyleNameProperty())
+ .setValue(event.getStyleName());
+ item.getItemProperty(getDescriptionProperty())
+ .setValue(event.getDescription());
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ container.removeItem(event);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEvent.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEvent.java
new file mode 100644
index 0000000000..e2a580085a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEvent.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.event;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeNotifier;
+
+/**
+ * Simple implementation of {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent}. Has setters for all required fields and fires events when
+ * this event is changed.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEvent implements EditableCalendarEvent, EventChangeNotifier {
+
+ private String caption;
+ private String description;
+ private Date end;
+ private Date start;
+ private String styleName;
+ private transient List<EventChangeListener> listeners = new ArrayList<EventChangeListener>();
+
+ private boolean isAllDay;
+
+ /**
+ * Default constructor
+ */
+ public BasicEvent() {
+
+ }
+
+ /**
+ * Constructor for creating an event with the same start and end date
+ *
+ * @param caption
+ * The caption for the event
+ * @param description
+ * The description for the event
+ * @param date
+ * The date the event occurred
+ */
+ public BasicEvent(String caption, String description, Date date) {
+ this.caption = caption;
+ this.description = description;
+ start = date;
+ end = date;
+ }
+
+ /**
+ * Constructor for creating an event with a start date and an end date.
+ * Start date should be before the end date
+ *
+ * @param caption
+ * The caption for the event
+ * @param description
+ * The description for the event
+ * @param startDate
+ * The start date of the event
+ * @param endDate
+ * The end date of the event
+ */
+ public BasicEvent(String caption, String description, Date startDate,
+ Date endDate) {
+ this.caption = caption;
+ this.description = description;
+ start = startDate;
+ end = endDate;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getCaption()
+ */
+ @Override
+ public String getCaption() {
+ return caption;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getDescription()
+ */
+ @Override
+ public String getDescription() {
+ return description;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getEnd()
+ */
+ @Override
+ public Date getEnd() {
+ return end;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStart()
+ */
+ @Override
+ public Date getStart() {
+ return start;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#getStyleName()
+ */
+ @Override
+ public String getStyleName() {
+ return styleName;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.event.CalendarEvent#isAllDay()
+ */
+ @Override
+ public boolean isAllDay() {
+ return isAllDay;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setCaption(java.lang
+ * .String)
+ */
+ @Override
+ public void setCaption(String caption) {
+ this.caption = caption;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setDescription(java
+ * .lang.String)
+ */
+ @Override
+ public void setDescription(String description) {
+ this.description = description;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setEnd(java.util.
+ * Date)
+ */
+ @Override
+ public void setEnd(Date end) {
+ this.end = end;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setStart(java.util
+ * .Date)
+ */
+ @Override
+ public void setStart(Date start) {
+ this.start = start;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setStyleName(java
+ * .lang.String)
+ */
+ @Override
+ public void setStyleName(String styleName) {
+ this.styleName = styleName;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventEditor#setAllDay(boolean)
+ */
+ @Override
+ public void setAllDay(boolean isAllDay) {
+ this.isAllDay = isAllDay;
+ fireEventChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeNotifier
+ * #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * )
+ */
+ @Override
+ public void addEventChangeListener(EventChangeListener listener) {
+ listeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeNotifier
+ * #removeListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * )
+ */
+ @Override
+ public void removeEventChangeListener(EventChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fires an event change event to the listeners. Should be triggered when
+ * some property of the event changes.
+ */
+ protected void fireEventChange() {
+ EventChangeEvent event = new EventChangeEvent(this);
+
+ for (EventChangeListener listener : listeners) {
+ listener.eventChange(event);
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEventProvider.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEventProvider.java
new file mode 100644
index 0000000000..fbf197d3eb
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/BasicEventProvider.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.event;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.vaadin.ui.components.calendar.event.CalendarEvent.EventChangeEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider.EventSetChangeNotifier;
+
+/**
+ * <p>
+ * Simple implementation of
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider}. Use {@link #addEvent(CalendarEvent)} and
+ * {@link #removeEvent(CalendarEvent)} to add / remove events.
+ * </p>
+ *
+ * <p>
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider.EventSetChangeNotifier
+ * EventSetChangeNotifier} and
+ * {@link com.vaadin.addon.calendar.event.CalendarEvent.EventChangeListener
+ * EventChangeListener} are also implemented, so the Calendar is notified when
+ * an event is added, changed or removed.
+ * </p>
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventProvider implements CalendarEditableEventProvider,
+ EventSetChangeNotifier, CalendarEvent.EventChangeListener {
+
+ protected List<CalendarEvent> eventList = new ArrayList<CalendarEvent>();
+
+ private List<EventSetChangeListener> listeners = new ArrayList<EventSetChangeListener>();
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEventProvider#getEvents(java.
+ * util.Date, java.util.Date)
+ */
+ @Override
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate) {
+ ArrayList<CalendarEvent> activeEvents = new ArrayList<CalendarEvent>();
+
+ for (CalendarEvent ev : eventList) {
+ long from = startDate.getTime();
+ long to = endDate.getTime();
+
+ if (ev.getStart() != null && ev.getEnd() != null) {
+ long f = ev.getStart().getTime();
+ long t = ev.getEnd().getTime();
+ // Select only events that overlaps with startDate and
+ // endDate.
+ if ((f <= to && f >= from) || (t >= from && t <= to)
+ || (f <= from && t >= to)) {
+ activeEvents.add(ev);
+ }
+ }
+ }
+
+ return activeEvents;
+ }
+
+ /**
+ * Does this event provider container this event
+ *
+ * @param event
+ * The event to check for
+ * @return If this provider has the event then true is returned, else false
+ */
+ public boolean containsEvent(BasicEvent event) {
+ return eventList.contains(event);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.ui.CalendarComponentEvents.
+ * EventSetChangeNotifier #addListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.
+ * EventSetChangeListener )
+ */
+ @Override
+ public void addEventSetChangeListener(EventSetChangeListener listener) {
+ listeners.add(listener);
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.ui.CalendarComponentEvents.
+ * EventSetChangeNotifier #removeListener
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.
+ * EventSetChangeListener )
+ */
+ @Override
+ public void removeEventSetChangeListener(EventSetChangeListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fires a eventsetchange event. The event is fired when either an event is
+ * added or removed to the event provider
+ */
+ protected void fireEventSetChange() {
+ EventSetChangeEvent event = new EventSetChangeEvent(this);
+
+ for (EventSetChangeListener listener : listeners) {
+ listener.eventSetChange(event);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventChangeListener
+ * #eventChange
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventSetChange)
+ */
+ @Override
+ public void eventChange(EventChangeEvent changeEvent) {
+ // naive implementation
+ fireEventSetChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#addEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void addEvent(CalendarEvent event) {
+ eventList.add(event);
+ if (event instanceof BasicEvent) {
+ ((BasicEvent) event).addEventChangeListener(this);
+ }
+ fireEventSetChange();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.event.CalendarEditableEventProvider#removeEvent
+ * (com.vaadin.addon.calendar.event.CalendarEvent)
+ */
+ @Override
+ public void removeEvent(CalendarEvent event) {
+ eventList.remove(event);
+ if (event instanceof BasicEvent) {
+ ((BasicEvent) event).removeEventChangeListener(this);
+ }
+ fireEventSetChange();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java
new file mode 100644
index 0000000000..aaa76418a6
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEditableEventProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.ui.components.calendar.event;
+
+/**
+ * An event provider which allows adding and removing events
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarEditableEventProvider extends CalendarEventProvider {
+
+ /**
+ * Adds an event to the event provider
+ *
+ * @param event
+ * The event to add
+ */
+ void addEvent(CalendarEvent event);
+
+ /**
+ * Removes an event from the event provider
+ *
+ * @param event
+ * The event
+ */
+ void removeEvent(CalendarEvent event);
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEvent.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEvent.java
new file mode 100644
index 0000000000..b4195cf0b1
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEvent.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.event;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * Event in the calendar. Customize your own event by implementing this
+ * interface.
+ * </p>
+ *
+ * <li>Start and end fields are mandatory.</li>
+ *
+ * <li>In "allDay" events longer than one day, starting and ending clock times
+ * are omitted in UI and only dates are shown.</li>
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ *
+ */
+public interface CalendarEvent extends Serializable {
+
+ /**
+ * Gets start date of event.
+ *
+ * @return Start date.
+ */
+ public Date getStart();
+
+ /**
+ * Get end date of event.
+ *
+ * @return End date;
+ */
+ public Date getEnd();
+
+ /**
+ * Gets caption of event.
+ *
+ * @return Caption text
+ */
+ public String getCaption();
+
+ /**
+ * Gets description of event. Shown as a tooltip over the event.
+ *
+ * @return Description text.
+ */
+ public String getDescription();
+
+ /**
+ * <p>
+ * Gets style name of event. In the client, style name will be set to the
+ * event's element class name and can be styled by CSS
+ * </p>
+ * Styling example:</br>
+ * <code>Java code: </br>
+ * event.setStyleName("color1");
+ * </br></br>
+ * CSS:</br>
+ * .v-calendar-event-color1 {</br>
+ * &nbsp;&nbsp;&nbsp;background-color: #9effae;</br>}</code>
+ *
+ * @return Style name.
+ */
+ public String getStyleName();
+
+ /**
+ * An all-day event typically does not occur at a specific time but targets
+ * a whole day or days. The rendering of all-day events differs from normal
+ * events.
+ *
+ * @return true if this event is an all-day event, false otherwise
+ */
+ public boolean isAllDay();
+
+ /**
+ * Event to signal that an event has changed.
+ */
+ @SuppressWarnings("serial")
+ public class EventChangeEvent implements Serializable {
+
+ private CalendarEvent source;
+
+ public EventChangeEvent(CalendarEvent source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent} that has changed
+ */
+ public CalendarEvent getCalendarEvent() {
+ return source;
+ }
+ }
+
+ /**
+ * Listener for EventSetChange events.
+ */
+ public interface EventChangeListener extends Serializable {
+
+ /**
+ * Called when an Event has changed.
+ */
+ public void eventChange(EventChangeEvent eventChangeEvent);
+ }
+
+ /**
+ * Notifier interface for EventChange events.
+ */
+ public interface EventChangeNotifier extends Serializable {
+
+ /**
+ * Add a listener to listen for EventChangeEvents. These events are
+ * fired when a events properties are changed.
+ *
+ * @param listener
+ * The listener to add
+ */
+ void addEventChangeListener(EventChangeListener listener);
+
+ /**
+ * Remove a listener from the event provider.
+ *
+ * @param listener
+ * The listener to remove
+ */
+ void removeEventChangeListener(EventChangeListener listener);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java
new file mode 100644
index 0000000000..1d4fabed5a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/CalendarEventProvider.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.event;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * Interface for querying events. The Vaadin Calendar always has a
+ * CalendarEventProvider set.
+ *
+ * @since 7.1.0
+ * @author Vaadin Ltd.
+ */
+public interface CalendarEventProvider extends Serializable {
+ /**
+ * <p>
+ * Gets all available events in the target date range between startDate and
+ * endDate. The Vaadin Calendar queries the events from the range that is
+ * shown, which is not guaranteed to be the same as the date range that is
+ * set.
+ * </p>
+ *
+ * <p>
+ * For example, if you set the date range to be monday 22.2.2010 - wednesday
+ * 24.2.2010, the used Event Provider will be queried for events between
+ * monday 22.2.2010 00:00 and sunday 28.2.2010 23:59. Generally you can
+ * expect the date range to be expanded to whole days and whole weeks.
+ * </p>
+ *
+ * @param startDate
+ * Start date
+ * @param endDate
+ * End date
+ * @return List of events
+ */
+ public List<CalendarEvent> getEvents(Date startDate, Date endDate);
+
+ /**
+ * Event to signal that the set of events has changed and the calendar
+ * should refresh its view from the
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} .
+ *
+ */
+ @SuppressWarnings("serial")
+ public class EventSetChangeEvent implements Serializable {
+
+ private CalendarEventProvider source;
+
+ public EventSetChangeEvent(CalendarEventProvider source) {
+ this.source = source;
+ }
+
+ /**
+ * @return the
+ * {@link com.vaadin.addon.calendar.event.CalendarEventProvider
+ * CalendarEventProvider} that has changed
+ */
+ public CalendarEventProvider getProvider() {
+ return source;
+ }
+ }
+
+ /**
+ * Listener for EventSetChange events.
+ */
+ public interface EventSetChangeListener extends Serializable {
+
+ /**
+ * Called when the set of Events has changed.
+ */
+ public void eventSetChange(EventSetChangeEvent changeEvent);
+ }
+
+ /**
+ * Notifier interface for EventSetChange events.
+ */
+ public interface EventSetChangeNotifier extends Serializable {
+
+ /**
+ * Add a listener for listening to when new events are adding or removed
+ * from the event provider.
+ *
+ * @param listener
+ * The listener to add
+ */
+ void addEventSetChangeListener(EventSetChangeListener listener);
+
+ /**
+ * Remove a listener which listens to {@link EventSetChangeEvent}-events
+ *
+ * @param listener
+ * The listener to remove
+ */
+ void removeEventSetChangeListener(EventSetChangeListener listener);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java
new file mode 100644
index 0000000000..5a44cae4ac
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/event/EditableCalendarEvent.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.event;
+
+import java.util.Date;
+
+/**
+ * <p>
+ * Extension to the basic {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent}. This interface provides setters (and thus editing
+ * capabilities) for all {@link com.vaadin.addon.calendar.event.CalendarEvent
+ * CalendarEvent} fields. For descriptions on the fields, refer to the extended
+ * interface.
+ * </p>
+ *
+ * <p>
+ * This interface is used by some of the basic Calendar event handlers in the
+ * <code>com.vaadin.addon.calendar.ui.handler</code> package to determine
+ * whether an event can be edited.
+ * </p>
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+public interface EditableCalendarEvent extends CalendarEvent {
+
+ /**
+ * Set the visible text in the calendar for the event.
+ *
+ * @param caption
+ * The text to show in the calendar
+ */
+ void setCaption(String caption);
+
+ /**
+ * Set the description of the event. This is shown in the calendar when
+ * hoovering over the event.
+ *
+ * @param description
+ * The text which describes the event
+ */
+ void setDescription(String description);
+
+ /**
+ * Set the end date of the event. Must be after the start date.
+ *
+ * @param end
+ * The end date to set
+ */
+ void setEnd(Date end);
+
+ /**
+ * Set the start date for the event. Must be before the end date
+ *
+ * @param start
+ * The start date of the event
+ */
+ void setStart(Date start);
+
+ /**
+ * Set the style name for the event used for styling the event cells
+ *
+ * @param styleName
+ * The stylename to use
+ *
+ */
+ void setStyleName(String styleName);
+
+ /**
+ * Does the event span the whole day. If so then set this to true
+ *
+ * @param isAllDay
+ * True if the event spans the whole day. In this case the start
+ * and end times are ignored.
+ */
+ void setAllDay(boolean isAllDay);
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java
new file mode 100644
index 0000000000..61f738fcd0
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicBackwardHandler.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardHandler;
+
+/**
+ * Implements basic functionality needed to enable backwards navigation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicBackwardHandler implements BackwardHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardHandler#
+ * backward
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.BackwardEvent)
+ */
+ @Override
+ public void backward(BackwardEvent event) {
+ Date start = event.getComponent().getStartDate();
+ Date end = event.getComponent().getEndDate();
+
+ // calculate amount to move back
+ int durationInDays = (int) (((end.getTime()) - start.getTime())
+ / DateConstants.DAYINMILLIS);
+ durationInDays++;
+ // for week view durationInDays = -7, for day view durationInDays = -1
+ durationInDays = -durationInDays;
+
+ // set new start and end times
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.setTime(start);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newStart = javaCalendar.getTime();
+
+ javaCalendar.setTime(end);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newEnd = javaCalendar.getTime();
+
+ if (start.equals(end)) { // day view
+ int firstDay = event.getComponent().getFirstVisibleDayOfWeek();
+ int lastDay = event.getComponent().getLastVisibleDayOfWeek();
+ int dayOfWeek = javaCalendar.get(Calendar.DAY_OF_WEEK);
+
+ // we suppose that 7 >= lastDay >= firstDay >= 1
+ while (!(firstDay <= dayOfWeek && dayOfWeek <= lastDay)) {
+ javaCalendar.add(java.util.Calendar.DATE, -1);
+ dayOfWeek = javaCalendar.get(Calendar.DAY_OF_WEEK);
+ }
+
+ newStart = javaCalendar.getTime();
+ newEnd = javaCalendar.getTime();
+ }
+
+ setDates(event, newStart, newEnd);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(BackwardEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java
new file mode 100644
index 0000000000..0107e10e3d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicDateClickHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickHandler;
+
+/**
+ * Implements basic functionality needed to switch to day view when a single day
+ * is clicked.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicDateClickHandler implements DateClickHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickHandler
+ * #dateClick
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.DateClickEvent)
+ */
+ @Override
+ public void dateClick(DateClickEvent event) {
+ Date clickedDate = event.getDate();
+
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.setTime(clickedDate);
+
+ // as times are expanded, this is all that is needed to show one day
+ Date start = javaCalendar.getTime();
+ Date end = javaCalendar.getTime();
+
+ setDates(event, start, end);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(DateClickEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java
new file mode 100644
index 0000000000..60f0016312
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventMoveHandler.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventMoveHandler;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.EditableCalendarEvent;
+
+/**
+ * Implements basic functionality needed to enable moving events.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventMoveHandler implements EventMoveHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventMoveHandler
+ * #eventMove
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.MoveEvent)
+ */
+ @Override
+ public void eventMove(MoveEvent event) {
+ CalendarEvent calendarEvent = event.getCalendarEvent();
+
+ if (calendarEvent instanceof EditableCalendarEvent) {
+
+ EditableCalendarEvent editableEvent = (EditableCalendarEvent) calendarEvent;
+
+ Date newFromTime = event.getNewStart();
+
+ // Update event dates
+ long length = editableEvent.getEnd().getTime()
+ - editableEvent.getStart().getTime();
+ setDates(editableEvent, newFromTime,
+ new Date(newFromTime.getTime() + length));
+ }
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(EditableCalendarEvent event, Date start, Date end) {
+ event.setStart(start);
+ event.setEnd(end);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java
new file mode 100644
index 0000000000..51e4bc1cbc
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicEventResizeHandler.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Date;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResizeHandler;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+import com.vaadin.ui.components.calendar.event.EditableCalendarEvent;
+
+/**
+ * Implements basic functionality needed to enable event resizing.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicEventResizeHandler implements EventResizeHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResizeHandler
+ * #eventResize
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.EventResize)
+ */
+ @Override
+ public void eventResize(EventResize event) {
+ CalendarEvent calendarEvent = event.getCalendarEvent();
+
+ if (calendarEvent instanceof EditableCalendarEvent) {
+ Date newStartTime = event.getNewStart();
+ Date newEndTime = event.getNewEnd();
+
+ EditableCalendarEvent editableEvent = (EditableCalendarEvent) calendarEvent;
+
+ setDates(editableEvent, newStartTime, newEndTime);
+ }
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(EditableCalendarEvent event, Date start, Date end) {
+ event.setStart(start);
+ event.setEnd(end);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java
new file mode 100644
index 0000000000..6cdc00bee2
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicForwardHandler.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+
+import com.vaadin.shared.ui.calendar.DateConstants;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardHandler;
+
+/**
+ * Implements basic functionality needed to enable forward navigation.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicForwardHandler implements ForwardHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardHandler#
+ * forward
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.ForwardEvent)
+ */
+ @Override
+ public void forward(ForwardEvent event) {
+ Date start = event.getComponent().getStartDate();
+ Date end = event.getComponent().getEndDate();
+
+ // calculate amount to move forward
+ int durationInDays = (int) (((end.getTime()) - start.getTime())
+ / DateConstants.DAYINMILLIS);
+ // for week view durationInDays = 7, for day view durationInDays = 1
+ durationInDays++;
+
+ // set new start and end times
+ Calendar javaCalendar = Calendar.getInstance();
+ javaCalendar.setTime(start);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newStart = javaCalendar.getTime();
+
+ javaCalendar.setTime(end);
+ javaCalendar.add(java.util.Calendar.DATE, durationInDays);
+ Date newEnd = javaCalendar.getTime();
+
+ if (start.equals(end)) { // day view
+ int firstDay = event.getComponent().getFirstVisibleDayOfWeek();
+ int lastDay = event.getComponent().getLastVisibleDayOfWeek();
+ int dayOfWeek = javaCalendar.get(Calendar.DAY_OF_WEEK);
+
+ // we suppose that 7 >= lastDay >= firstDay >= 1
+ while (!(firstDay <= dayOfWeek && dayOfWeek <= lastDay)) {
+ javaCalendar.add(java.util.Calendar.DATE, 1);
+ dayOfWeek = javaCalendar.get(Calendar.DAY_OF_WEEK);
+ }
+
+ newStart = javaCalendar.getTime();
+ newEnd = javaCalendar.getTime();
+ }
+
+ setDates(event, newStart, newEnd);
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(ForwardEvent event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java
new file mode 100644
index 0000000000..128c6abfbd
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/calendar/handler/BasicWeekClickHandler.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.calendar.handler;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClick;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClickHandler;
+
+/**
+ * Implements basic functionality needed to change to week view when a week
+ * number is clicked.
+ *
+ * @since 7.1
+ * @author Vaadin Ltd.
+ */
+@SuppressWarnings("serial")
+public class BasicWeekClickHandler implements WeekClickHandler {
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClickHandler
+ * #weekClick
+ * (com.vaadin.addon.calendar.ui.CalendarComponentEvents.WeekClick)
+ */
+ @Override
+ public void weekClick(WeekClick event) {
+ int week = event.getWeek();
+ int year = event.getYear();
+
+ // set correct year and month
+ Calendar javaCalendar = event.getComponent().getInternalCalendar();
+ javaCalendar.set(GregorianCalendar.YEAR, year);
+ javaCalendar.set(GregorianCalendar.WEEK_OF_YEAR, week);
+
+ // starting at the beginning of the week
+ javaCalendar.set(GregorianCalendar.DAY_OF_WEEK,
+ javaCalendar.getFirstDayOfWeek());
+ Date start = javaCalendar.getTime();
+
+ // ending at the end of the week
+ javaCalendar.add(GregorianCalendar.DATE, 6);
+ Date end = javaCalendar.getTime();
+
+ setDates(event, start, end);
+
+ // times are automatically expanded, no need to worry about them
+ }
+
+ /**
+ * Set the start and end dates for the event
+ *
+ * @param event
+ * The event that the start and end dates should be set
+ * @param start
+ * The start date
+ * @param end
+ * The end date
+ */
+ protected void setDates(WeekClick event, Date start, Date end) {
+ event.getComponent().setStartDate(start);
+ event.getComponent().setEndDate(end);
+ }
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java
new file mode 100644
index 0000000000..aa703deb19
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeEvent.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.Component.Event;
+
+/**
+ * The color changed event which is passed to the listeners when a color change
+ * occurs.
+ *
+ * @since 7.0.0
+ */
+public class ColorChangeEvent extends Event {
+ private final Color color;
+
+ public ColorChangeEvent(Component source, Color color) {
+ super(source);
+
+ this.color = color;
+ }
+
+ /**
+ * Returns the new color.
+ */
+ public Color getColor() {
+ return color;
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeListener.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeListener.java
new file mode 100644
index 0000000000..b234dc3d5d
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorChangeListener.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.io.Serializable;
+
+/**
+ * The listener interface for receiving colorChange events. The class that is
+ * interested in processing a {@link ColorChangeEvent} implements this
+ * interface, and the object created with that class is registered with a
+ * component using the component's <code>addColorChangeListener</code> method.
+ * When the colorChange event occurs, that object's appropriate method is
+ * invoked.
+ *
+ * @since 7.0.0
+ *
+ * @see ColorChangeEvent
+ */
+public interface ColorChangeListener extends Serializable {
+
+ /**
+ * Called when a new color has been selected.
+ *
+ * @param event
+ * An event containing information about the color change.
+ */
+ void colorChanged(ColorChangeEvent event);
+
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java
new file mode 100644
index 0000000000..23748a967a
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGradient.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGradientState;
+import com.vaadin.ui.AbstractColorPicker.Coordinates2Color;
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * A component that represents a color gradient within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerGradient extends AbstractComponent
+ implements ColorSelector {
+
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ private ColorPickerGradientServerRpc rpc = new ColorPickerGradientServerRpc() {
+
+ @Override
+ public void select(int cursorX, int cursorY) {
+ x = cursorX;
+ y = cursorY;
+ color = converter.calculate(x, y);
+
+ fireColorChanged(color);
+ }
+ };
+
+ /** The converter. */
+ private Coordinates2Color converter;
+
+ /** The foreground color. */
+ private Color color;
+
+ /** The x-coordinate. */
+ private int x = 0;
+
+ /** The y-coordinate. */
+ private int y = 0;
+
+ private ColorPickerGradient() {
+ registerRpc(rpc);
+ // width and height must be set here instead of in theme, otherwise
+ // coordinate calculations fail
+ getState().width = "220px";
+ getState().height = "220px";
+ }
+
+ /**
+ * Instantiates a new color picker gradient.
+ *
+ * @param id
+ * the id
+ * @param converter
+ * the converter
+ */
+ public ColorPickerGradient(String id, Coordinates2Color converter) {
+ this();
+ addStyleName(id);
+ this.converter = converter;
+ }
+
+ @Override
+ public void setColor(Color c) {
+ color = c;
+
+ int[] coords = converter.calculate(c);
+ x = coords[0];
+ y = coords[1];
+
+ getState().cursorX = x;
+ getState().cursorY = y;
+
+ }
+
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ /**
+ * Sets the background color.
+ *
+ * @param color
+ * the new background color
+ */
+ public void setBackgroundColor(Color color) {
+ getState().bgColor = color.getCSS();
+ }
+
+ @Override
+ public Color getColor() {
+ return color;
+ }
+
+ /**
+ * Notifies the listeners that the color has changed
+ *
+ * @param color
+ * The color which it changed to
+ */
+ public void fireColorChanged(Color color) {
+ fireEvent(new ColorChangeEvent(this, color));
+ }
+
+ @Override
+ protected ColorPickerGradientState getState() {
+ return (ColorPickerGradientState) super.getState();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
new file mode 100644
index 0000000000..9e5580c719
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerGrid.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.awt.Point;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridServerRpc;
+import com.vaadin.shared.ui.colorpicker.ColorPickerGridState;
+import com.vaadin.ui.AbstractComponent;
+
+/**
+ * A component that represents a color selection grid within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerGrid extends AbstractComponent
+ implements ColorSelector {
+
+ private static final String STYLENAME = "v-colorpicker-grid";
+
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ private ColorPickerGridServerRpc rpc = new ColorPickerGridServerRpc() {
+
+ @Override
+ public void select(int x, int y) {
+ ColorPickerGrid.this.x = x;
+ ColorPickerGrid.this.y = y;
+
+ fireColorChanged(colorGrid[y][x]);
+ }
+
+ @Override
+ public void refresh() {
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < columns; col++) {
+ changedColors.put(new Point(row, col), colorGrid[row][col]);
+ }
+ }
+ sendChangedColors();
+ markAsDirty();
+ }
+ };
+
+ /** The x-coordinate. */
+ private int x = 0;
+
+ /** The y-coordinate. */
+ private int y = 0;
+
+ /** The rows. */
+ private int rows;
+
+ /** The columns. */
+ private int columns;
+
+ /** The color grid. */
+ private Color[][] colorGrid = new Color[1][1];
+
+ /** The changed colors. */
+ private final Map<Point, Color> changedColors = new HashMap<Point, Color>();
+
+ /**
+ * Instantiates a new color picker grid.
+ */
+ public ColorPickerGrid() {
+ registerRpc(rpc);
+ setPrimaryStyleName(STYLENAME);
+ setColorGrid(new Color[1][1]);
+ setColor(Color.WHITE);
+ }
+
+ /**
+ * Instantiates a new color picker grid.
+ *
+ * @param rows
+ * the rows
+ * @param columns
+ * the columns
+ */
+ public ColorPickerGrid(int rows, int columns) {
+ registerRpc(rpc);
+ setPrimaryStyleName(STYLENAME);
+ setColorGrid(new Color[rows][columns]);
+ setColor(Color.WHITE);
+ }
+
+ /**
+ * Instantiates a new color picker grid.
+ *
+ * @param colors
+ * the colors
+ */
+ public ColorPickerGrid(Color[][] colors) {
+ registerRpc(rpc);
+ setPrimaryStyleName(STYLENAME);
+ setColorGrid(colors);
+ }
+
+ private void setColumnCount(int columns) {
+ this.columns = columns;
+ getState().columnCount = columns;
+ }
+
+ private void setRowCount(int rows) {
+ this.rows = rows;
+ getState().rowCount = rows;
+ }
+
+ private void sendChangedColors() {
+ if (!changedColors.isEmpty()) {
+ String[] colors = new String[changedColors.size()];
+ String[] XCoords = new String[changedColors.size()];
+ String[] YCoords = new String[changedColors.size()];
+ int counter = 0;
+ for (Point p : changedColors.keySet()) {
+ Color c = changedColors.get(p);
+ if (c == null) {
+ continue;
+ }
+
+ String color = c.getCSS();
+
+ colors[counter] = color;
+ XCoords[counter] = String.valueOf((int) p.getX());
+ YCoords[counter] = String.valueOf((int) p.getY());
+ counter++;
+ }
+ getState().changedColor = colors;
+ getState().changedX = XCoords;
+ getState().changedY = YCoords;
+
+ changedColors.clear();
+ }
+ }
+
+ /**
+ * Sets the color grid.
+ *
+ * @param colors
+ * the new color grid
+ */
+ public void setColorGrid(Color[][] colors) {
+ setRowCount(colors.length);
+ setColumnCount(colors[0].length);
+ colorGrid = colors;
+
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < columns; col++) {
+ changedColors.put(new Point(row, col), colorGrid[row][col]);
+ }
+ }
+ sendChangedColors();
+
+ markAsDirty();
+ }
+
+ /**
+ * Adds a color change listener
+ *
+ * @param listener
+ * The color change listener
+ */
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ @Override
+ public Color getColor() {
+ return colorGrid[x][y];
+ }
+
+ /**
+ * Removes a color change listener
+ *
+ * @param listener
+ * The listener
+ */
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ @Override
+ public void setColor(Color color) {
+ colorGrid[x][y] = color;
+ changedColors.put(new Point(x, y), color);
+ sendChangedColors();
+ markAsDirty();
+ }
+
+ /**
+ * Sets the position.
+ *
+ * @param x
+ * the x
+ * @param y
+ * the y
+ */
+ public void setPosition(int x, int y) {
+ if (x >= 0 && x < columns && y >= 0 && y < rows) {
+ this.x = x;
+ this.y = y;
+ }
+ }
+
+ /**
+ * Gets the position.
+ *
+ * @return the position
+ */
+ public int[] getPosition() {
+ return new int[] { x, y };
+ }
+
+ /**
+ * Notifies the listeners that a color change has occurred
+ *
+ * @param color
+ * The color which it changed to
+ */
+ public void fireColorChanged(Color color) {
+ fireEvent(new ColorChangeEvent(this, color));
+ }
+
+ @Override
+ protected ColorPickerGridState getState() {
+ return (ColorPickerGridState) super.getState();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
new file mode 100644
index 0000000000..1173faf152
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerHistory.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.CustomComponent;
+
+/**
+ * A component that represents color selection history within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerHistory extends CustomComponent
+ implements ColorSelector, ColorChangeListener {
+
+ private static final String STYLENAME = "v-colorpicker-history";
+
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ /** The rows. */
+ private static final int rows = 4;
+
+ /** The columns. */
+ private static final int columns = 15;
+
+ /** Temporary color history for when the component is detached. */
+ private ArrayBlockingQueue<Color> tempHistory = new ArrayBlockingQueue<Color>(
+ rows * columns);
+
+ /** The grid. */
+ private final ColorPickerGrid grid;
+
+ /**
+ * Instantiates a new color picker history.
+ */
+ public ColorPickerHistory() {
+ setPrimaryStyleName(STYLENAME);
+
+ grid = new ColorPickerGrid(rows, columns);
+ grid.setWidth("100%");
+ grid.setPosition(0, 0);
+ grid.addColorChangeListener(this);
+
+ setCompositionRoot(grid);
+ }
+
+ @Override
+ public void attach() {
+ super.attach();
+ createColorHistoryIfNecessary();
+ }
+
+ private void createColorHistoryIfNecessary() {
+ List<Color> tempColors = new ArrayList<Color>(tempHistory);
+ if (getSession().getAttribute("colorPickerHistory") == null) {
+ getSession().setAttribute("colorPickerHistory",
+ new ArrayBlockingQueue<Color>(rows * columns));
+ }
+ for (Color color : tempColors) {
+ setColor(color);
+ }
+ tempHistory.clear();
+ }
+
+ @SuppressWarnings("unchecked")
+ private ArrayBlockingQueue<Color> getColorHistory() {
+ if (isAttached()) {
+ Object colorHistory = getSession()
+ .getAttribute("colorPickerHistory");
+ if (colorHistory instanceof ArrayBlockingQueue<?>) {
+ return (ArrayBlockingQueue<Color>) colorHistory;
+ }
+ }
+ return tempHistory;
+ }
+
+ @Override
+ public void setHeight(String height) {
+ super.setHeight(height);
+ grid.setHeight(height);
+ }
+
+ @Override
+ public void setColor(Color color) {
+
+ ArrayBlockingQueue<Color> colorHistory = getColorHistory();
+
+ // Check that the color does not already exist
+ boolean exists = false;
+ Iterator<Color> iter = colorHistory.iterator();
+ while (iter.hasNext()) {
+ if (color.equals(iter.next())) {
+ exists = true;
+ break;
+ }
+ }
+
+ // If the color does not exist then add it
+ if (!exists) {
+ if (!colorHistory.offer(color)) {
+ colorHistory.poll();
+ colorHistory.offer(color);
+ }
+ }
+
+ List<Color> colorList = new ArrayList<Color>(colorHistory);
+
+ // Invert order of colors
+ Collections.reverse(colorList);
+
+ // Move the selected color to the front of the list
+ Collections.swap(colorList, colorList.indexOf(color), 0);
+
+ // Create 2d color map
+ Color[][] colors = new Color[rows][columns];
+ iter = colorList.iterator();
+
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < columns; col++) {
+ if (iter.hasNext()) {
+ colors[row][col] = iter.next();
+ } else {
+ colors[row][col] = Color.WHITE;
+ }
+ }
+ }
+
+ grid.setColorGrid(colors);
+ grid.markAsDirty();
+ }
+
+ @Override
+ public Color getColor() {
+ return getColorHistory().peek();
+ }
+
+ /**
+ * Gets the history.
+ *
+ * @return the history
+ */
+ public List<Color> getHistory() {
+ ArrayBlockingQueue<Color> colorHistory = getColorHistory();
+ Color[] array = colorHistory.toArray(new Color[colorHistory.size()]);
+ return Collections.unmodifiableList(Arrays.asList(array));
+ }
+
+ /**
+ * Checks if the history contains given color.
+ *
+ * @param c
+ * the color
+ *
+ * @return true, if successful
+ */
+ public boolean hasColor(Color c) {
+ return getColorHistory().contains(c);
+ }
+
+ /**
+ * Adds a color change listener
+ *
+ * @param listener
+ * The listener
+ */
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ /**
+ * Removes a color change listener
+ *
+ * @param listener
+ * The listener
+ */
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ @Override
+ public void colorChanged(ColorChangeEvent event) {
+ fireEvent(new ColorChangeEvent(this, event.getColor()));
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
new file mode 100644
index 0000000000..dbf3b18bf3
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPopup.java
@@ -0,0 +1,759 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.vaadin.shared.ui.MarginInfo;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.AbstractColorPicker.Coordinates2Color;
+import com.vaadin.ui.Alignment;
+import com.vaadin.ui.Button;
+import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.ui.Button.ClickListener;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.HorizontalLayout;
+import com.vaadin.ui.Layout;
+import com.vaadin.ui.Slider;
+import com.vaadin.ui.Slider.ValueOutOfBoundsException;
+import com.vaadin.ui.TabSheet;
+import com.vaadin.ui.VerticalLayout;
+import com.vaadin.ui.Window;
+
+/**
+ * A component that represents color selection popup within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerPopup extends Window
+ implements ClickListener, ColorChangeListener, ColorSelector {
+
+ private static final String STYLENAME = "v-colorpicker-popup";
+
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ /** The tabs. */
+ private final TabSheet tabs = new TabSheet();
+
+ private Component rgbTab;
+
+ private Component hsvTab;
+
+ private Component swatchesTab;
+
+ /** The layout. */
+ private final VerticalLayout layout;
+
+ /** The ok button. */
+ private final Button ok = new Button("OK");
+
+ /** The cancel button. */
+ private final Button cancel = new Button("Cancel");
+
+ /** The resize button. */
+ private final Button resize = new Button("show/hide history");
+
+ /** The selected color. */
+ private Color selectedColor = Color.WHITE;
+
+ /** The history. */
+ private ColorPickerHistory history;
+
+ /** The history container. */
+ private Layout historyContainer;
+
+ /** The rgb gradient. */
+ private ColorPickerGradient rgbGradient;
+
+ /** The hsv gradient. */
+ private ColorPickerGradient hsvGradient;
+
+ /** The red slider. */
+ private Slider redSlider;
+
+ /** The green slider. */
+ private Slider greenSlider;
+
+ /** The blue slider. */
+ private Slider blueSlider;
+
+ /** The hue slider. */
+ private Slider hueSlider;
+
+ /** The saturation slider. */
+ private Slider saturationSlider;
+
+ /** The value slider. */
+ private Slider valueSlider;
+
+ /** The preview on the rgb tab. */
+ private ColorPickerPreview rgbPreview;
+
+ /** The preview on the hsv tab. */
+ private ColorPickerPreview hsvPreview;
+
+ /** The preview on the swatches tab. */
+ private ColorPickerPreview selPreview;
+
+ /** The color select. */
+ private ColorPickerSelect colorSelect;
+
+ /** The selectors. */
+ private final Set<ColorSelector> selectors = new HashSet<ColorSelector>();
+
+ /**
+ * Set true while the slider values are updated after colorChange. When
+ * true, valueChange reactions from the sliders are disabled, because
+ * otherwise the set color may become corrupted as it is repeatedly re-set
+ * in valueChangeListeners using values from sliders that may not have been
+ * updated yet.
+ */
+ private boolean updatingColors = false;
+
+ private ColorPickerPopup() {
+ // Set the layout
+ layout = new VerticalLayout();
+ layout.setSpacing(false);
+ layout.setMargin(false);
+ layout.setWidth("100%");
+ layout.setHeight(null);
+
+ setContent(layout);
+ setStyleName(STYLENAME);
+ setResizable(false);
+ setImmediate(true);
+ // Create the history
+ history = new ColorPickerHistory();
+ history.addColorChangeListener(this);
+ }
+
+ /**
+ * Instantiates a new color picker popup.
+ */
+ public ColorPickerPopup(Color initialColor) {
+ this();
+ selectedColor = initialColor;
+ initContents();
+ }
+
+ private void initContents() {
+ // Create the preview on the rgb tab
+ rgbPreview = new ColorPickerPreview(selectedColor);
+ rgbPreview.setWidth("240px");
+ rgbPreview.setHeight("20px");
+ rgbPreview.addColorChangeListener(this);
+ selectors.add(rgbPreview);
+
+ // Create the preview on the hsv tab
+ hsvPreview = new ColorPickerPreview(selectedColor);
+ hsvPreview.setWidth("240px");
+ hsvPreview.setHeight("20px");
+ hsvPreview.addColorChangeListener(this);
+ selectors.add(hsvPreview);
+
+ // Create the preview on the swatches tab
+ selPreview = new ColorPickerPreview(selectedColor);
+ selPreview.setWidth("100%");
+ selPreview.setHeight("20px");
+ selPreview.addColorChangeListener(this);
+ selectors.add(selPreview);
+
+ // Create the tabs
+ rgbTab = createRGBTab(selectedColor);
+ tabs.addTab(rgbTab, "RGB", null);
+
+ hsvTab = createHSVTab(selectedColor);
+ tabs.addTab(hsvTab, "HSV", null);
+
+ swatchesTab = createSelectTab();
+ tabs.addTab(swatchesTab, "Swatches", null);
+
+ // Add the tabs
+ tabs.setWidth("100%");
+
+ layout.addComponent(tabs);
+
+ // Add the history
+ history.setWidth("97%");
+ history.setHeight("22px");
+
+ // Create the default colors
+ List<Color> defaultColors = new ArrayList<Color>();
+ defaultColors.add(Color.BLACK);
+ defaultColors.add(Color.WHITE);
+
+ // Create the history
+ VerticalLayout innerContainer = new VerticalLayout();
+ innerContainer.setWidth("100%");
+ innerContainer.setHeight(null);
+ innerContainer.addComponent(history);
+
+ VerticalLayout outerContainer = new VerticalLayout();
+ outerContainer.setWidth("99%");
+ outerContainer.setHeight("27px");
+ outerContainer.addComponent(innerContainer);
+ historyContainer = outerContainer;
+
+ layout.addComponent(historyContainer);
+
+ // Add the resize button for the history
+ resize.addClickListener(this);
+ resize.setData(new Boolean(false));
+ resize.setWidth("100%");
+ resize.setHeight("10px");
+ resize.setPrimaryStyleName("resize-button");
+ layout.addComponent(resize);
+
+ // Add the buttons
+ ok.setWidth("70px");
+ ok.addClickListener(this);
+
+ cancel.setWidth("70px");
+ cancel.addClickListener(this);
+
+ HorizontalLayout buttons = new HorizontalLayout();
+ buttons.addComponent(ok);
+ buttons.addComponent(cancel);
+ buttons.setWidth("100%");
+ buttons.setHeight("30px");
+ buttons.setComponentAlignment(ok, Alignment.MIDDLE_CENTER);
+ buttons.setComponentAlignment(cancel, Alignment.MIDDLE_CENTER);
+ layout.addComponent(buttons);
+ }
+
+ /**
+ * Creates the RGB tab.
+ *
+ * @return the component
+ */
+ private Component createRGBTab(Color color) {
+ VerticalLayout rgbLayout = new VerticalLayout();
+ rgbLayout.setMargin(new MarginInfo(false, false, true, false));
+ rgbLayout.addComponent(rgbPreview);
+ rgbLayout.setStyleName("rgbtab");
+
+ // Add the RGB color gradient
+ rgbGradient = new ColorPickerGradient("rgb-gradient", RGBConverter);
+ rgbGradient.setColor(color);
+ rgbGradient.addColorChangeListener(this);
+ rgbLayout.addComponent(rgbGradient);
+ selectors.add(rgbGradient);
+
+ // Add the RGB sliders
+ VerticalLayout sliders = new VerticalLayout();
+ sliders.setStyleName("rgb-sliders");
+
+ redSlider = createRGBSlider("Red", "red");
+ greenSlider = createRGBSlider("Green", "green");
+ blueSlider = createRGBSlider("Blue", "blue");
+ setRgbSliderValues(color);
+
+ redSlider.addValueChangeListener(e -> {
+ double red = e.getValue();
+ if (!updatingColors) {
+ Color newColor = new Color((int) red, selectedColor.getGreen(),
+ selectedColor.getBlue());
+ setColor(newColor);
+ }
+ });
+
+ sliders.addComponent(redSlider);
+
+ greenSlider.addValueChangeListener(e -> {
+ double green = e.getValue();
+ if (!updatingColors) {
+ Color newColor = new Color(selectedColor.getRed(), (int) green,
+ selectedColor.getBlue());
+ setColor(newColor);
+ }
+ });
+ sliders.addComponent(greenSlider);
+
+ blueSlider.addValueChangeListener(e -> {
+ double blue = e.getValue();
+ if (!updatingColors) {
+ Color newColor = new Color(selectedColor.getRed(),
+ selectedColor.getGreen(), (int) blue);
+ setColor(newColor);
+ }
+ });
+ sliders.addComponent(blueSlider);
+
+ rgbLayout.addComponent(sliders);
+
+ return rgbLayout;
+ }
+
+ private Slider createRGBSlider(String caption, String styleName) {
+ Slider redSlider = new Slider(caption, 0, 255);
+ redSlider.setImmediate(true);
+ redSlider.setStyleName("rgb-slider");
+ redSlider.setWidth("220px");
+ redSlider.addStyleName(styleName);
+ return redSlider;
+ }
+
+ /**
+ * Creates the hsv tab.
+ *
+ * @return the component
+ */
+ private Component createHSVTab(Color color) {
+ VerticalLayout hsvLayout = new VerticalLayout();
+ hsvLayout.setMargin(new MarginInfo(false, false, true, false));
+ hsvLayout.addComponent(hsvPreview);
+ hsvLayout.setStyleName("hsvtab");
+
+ // Add the hsv gradient
+ hsvGradient = new ColorPickerGradient("hsv-gradient", HSVConverter);
+ hsvGradient.setColor(color);
+ hsvGradient.addColorChangeListener(this);
+ hsvLayout.addComponent(hsvGradient);
+ selectors.add(hsvGradient);
+
+ VerticalLayout sliders = new VerticalLayout();
+ sliders.setStyleName("hsv-sliders");
+
+ hueSlider = new Slider("Hue", 0, 360);
+ saturationSlider = new Slider("Saturation", 0, 100);
+ valueSlider = new Slider("Value", 0, 100);
+
+ float[] hsv = color.getHSV();
+ setHsvSliderValues(hsv);
+
+ hueSlider.setStyleName("hsv-slider");
+ hueSlider.addStyleName("hue-slider");
+ hueSlider.setWidth("220px");
+ hueSlider.setImmediate(true);
+ hueSlider.addValueChangeListener(event -> {
+ if (!updatingColors) {
+ float hue = (Float.parseFloat(event.getValue().toString()))
+ / 360f;
+ float saturation = (Float
+ .parseFloat(saturationSlider.getValue().toString()))
+ / 100f;
+ float value = (Float
+ .parseFloat(valueSlider.getValue().toString())) / 100f;
+
+ // Set the color
+ Color newColor = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ setColor(newColor);
+
+ /*
+ * Set the background color of the hue gradient. This has to be
+ * done here since in the conversion the base color information
+ * is lost when color is black/white
+ */
+ Color bgColor = new Color(Color.HSVtoRGB(hue, 1f, 1f));
+ hsvGradient.setBackgroundColor(bgColor);
+ }
+ });
+ sliders.addComponent(hueSlider);
+
+ saturationSlider.setStyleName("hsv-slider");
+ saturationSlider.setWidth("220px");
+ saturationSlider.setImmediate(true);
+ saturationSlider.addValueChangeListener(event -> {
+ if (!updatingColors) {
+ float hue = (Float.parseFloat(hueSlider.getValue().toString()))
+ / 360f;
+ float saturation = (Float
+ .parseFloat(event.getValue().toString())) / 100f;
+ float value = (Float
+ .parseFloat(valueSlider.getValue().toString())) / 100f;
+ Color newColor = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ setColor(newColor);
+ }
+ });
+ sliders.addComponent(saturationSlider);
+
+ valueSlider.setStyleName("hsv-slider");
+ valueSlider.setWidth("220px");
+ valueSlider.setImmediate(true);
+ valueSlider.addValueChangeListener(event -> {
+ if (!updatingColors) {
+ float hue = (Float.parseFloat(hueSlider.getValue().toString()))
+ / 360f;
+ float saturation = (Float
+ .parseFloat(saturationSlider.getValue().toString()))
+ / 100f;
+ float value = (Float.parseFloat(event.getValue().toString()))
+ / 100f;
+
+ Color newColor = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ setColor(newColor);
+ }
+ });
+
+ sliders.addComponent(valueSlider);
+ hsvLayout.addComponent(sliders);
+
+ return hsvLayout;
+ }
+
+ /**
+ * Creates the select tab.
+ *
+ * @return the component
+ */
+ private Component createSelectTab() {
+ VerticalLayout selLayout = new VerticalLayout();
+ selLayout.setMargin(new MarginInfo(false, false, true, false));
+ selLayout.addComponent(selPreview);
+ selLayout.addStyleName("seltab");
+
+ colorSelect = new ColorPickerSelect();
+ colorSelect.addColorChangeListener(this);
+ selLayout.addComponent(colorSelect);
+
+ return selLayout;
+ }
+
+ @Override
+ public void buttonClick(ClickEvent event) {
+ // History resize was clicked
+ if (event.getButton() == resize) {
+ boolean state = (Boolean) resize.getData();
+
+ // minimize
+ if (state) {
+ historyContainer.setHeight("27px");
+ history.setHeight("22px");
+
+ // maximize
+ } else {
+ historyContainer.setHeight("90px");
+ history.setHeight("85px");
+ }
+
+ resize.setData(new Boolean(!state));
+ }
+
+ // Ok button was clicked
+ else if (event.getButton() == ok) {
+ history.setColor(getColor());
+ fireColorChanged();
+ close();
+ }
+
+ // Cancel button was clicked
+ else if (event.getButton() == cancel) {
+ close();
+ }
+
+ }
+
+ /**
+ * Notifies the listeners that the color changed
+ */
+ public void fireColorChanged() {
+ fireEvent(new ColorChangeEvent(this, getColor()));
+ }
+
+ /**
+ * Gets the history.
+ *
+ * @return the history
+ */
+ public ColorPickerHistory getHistory() {
+ return history;
+ }
+
+ @Override
+ public void setColor(Color color) {
+ if (color == null) {
+ return;
+ }
+
+ selectedColor = color;
+
+ hsvGradient.setColor(selectedColor);
+ hsvPreview.setColor(selectedColor);
+
+ rgbGradient.setColor(selectedColor);
+ rgbPreview.setColor(selectedColor);
+
+ selPreview.setColor(selectedColor);
+ }
+
+ @Override
+ public Color getColor() {
+ return selectedColor;
+ }
+
+ /**
+ * Gets the color history.
+ *
+ * @return the color history
+ */
+ public List<Color> getColorHistory() {
+ return Collections.unmodifiableList(history.getHistory());
+ }
+
+ @Override
+ public void colorChanged(ColorChangeEvent event) {
+ setColor(event.getColor());
+
+ updatingColors = true;
+
+ setRgbSliderValues(selectedColor);
+ float[] hsv = selectedColor.getHSV();
+ setHsvSliderValues(hsv);
+
+ updatingColors = false;
+
+ for (ColorSelector s : selectors) {
+ if (event.getSource() != s && s != this
+ && s.getColor() != selectedColor) {
+ s.setColor(selectedColor);
+ }
+ }
+ }
+
+ private void setRgbSliderValues(Color color) {
+ try {
+ redSlider.setValue(((Integer) color.getRed()).doubleValue());
+ blueSlider.setValue(((Integer) color.getBlue()).doubleValue());
+ greenSlider.setValue(((Integer) color.getGreen()).doubleValue());
+ } catch (ValueOutOfBoundsException e) {
+ getLogger().log(Level.WARNING,
+ "Unable to set RGB color value to " + color.getRed() + ","
+ + color.getGreen() + "," + color.getBlue(),
+ e);
+ }
+ }
+
+ private void setHsvSliderValues(float[] hsv) {
+ try {
+ hueSlider.setValue(((Float) (hsv[0] * 360f)).doubleValue());
+ saturationSlider.setValue(((Float) (hsv[1] * 100f)).doubleValue());
+ valueSlider.setValue(((Float) (hsv[2] * 100f)).doubleValue());
+ } catch (ValueOutOfBoundsException e) {
+ getLogger().log(Level.WARNING, "Unable to set HSV color value to "
+ + hsv[0] + "," + hsv[1] + "," + hsv[2], e);
+ }
+ }
+
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ /**
+ * Checks the visibility of the given tab
+ *
+ * @param tab
+ * The tab to check
+ * @return true if tab is visible, false otherwise
+ */
+ private boolean tabIsVisible(Component tab) {
+ Iterator<Component> tabIterator = tabs.getComponentIterator();
+ while (tabIterator.hasNext()) {
+ if (tabIterator.next() == tab) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * How many tabs are visible
+ *
+ * @return The number of tabs visible
+ */
+ private int tabsNumVisible() {
+ Iterator<Component> tabIterator = tabs.getComponentIterator();
+ int tabCounter = 0;
+ while (tabIterator.hasNext()) {
+ tabIterator.next();
+ tabCounter++;
+ }
+ return tabCounter;
+ }
+
+ /**
+ * Checks if tabs are needed and hides them if not
+ */
+ private void checkIfTabsNeeded() {
+ tabs.hideTabs(tabsNumVisible() == 1);
+ }
+
+ /**
+ * Set RGB tab visibility
+ *
+ * @param visible
+ * The visibility of the RGB tab
+ */
+ public void setRGBTabVisible(boolean visible) {
+ if (visible && !tabIsVisible(rgbTab)) {
+ tabs.addTab(rgbTab, "RGB", null);
+ checkIfTabsNeeded();
+ } else if (!visible && tabIsVisible(rgbTab)) {
+ tabs.removeComponent(rgbTab);
+ checkIfTabsNeeded();
+ }
+ }
+
+ /**
+ * Set HSV tab visibility
+ *
+ * @param visible
+ * The visibility of the HSV tab
+ */
+ public void setHSVTabVisible(boolean visible) {
+ if (visible && !tabIsVisible(hsvTab)) {
+ tabs.addTab(hsvTab, "HSV", null);
+ checkIfTabsNeeded();
+ } else if (!visible && tabIsVisible(hsvTab)) {
+ tabs.removeComponent(hsvTab);
+ checkIfTabsNeeded();
+ }
+ }
+
+ /**
+ * Set Swatches tab visibility
+ *
+ * @param visible
+ * The visibility of the Swatches tab
+ */
+ public void setSwatchesTabVisible(boolean visible) {
+ if (visible && !tabIsVisible(swatchesTab)) {
+ tabs.addTab(swatchesTab, "Swatches", null);
+ checkIfTabsNeeded();
+ } else if (!visible && tabIsVisible(swatchesTab)) {
+ tabs.removeComponent(swatchesTab);
+ checkIfTabsNeeded();
+ }
+ }
+
+ /**
+ * Set the History visibility
+ *
+ * @param visible
+ */
+ public void setHistoryVisible(boolean visible) {
+ historyContainer.setVisible(visible);
+ resize.setVisible(visible);
+ }
+
+ /**
+ * Set the preview visibility
+ *
+ * @param visible
+ */
+ public void setPreviewVisible(boolean visible) {
+ hsvPreview.setVisible(visible);
+ rgbPreview.setVisible(visible);
+ selPreview.setVisible(visible);
+ }
+
+ /** RGB color converter */
+ private Coordinates2Color RGBConverter = new Coordinates2Color() {
+
+ @Override
+ public Color calculate(int x, int y) {
+ float h = (x / 220f);
+ float s = 1f;
+ float v = 1f;
+
+ if (y < 110) {
+ s = y / 110f;
+ } else if (y > 110) {
+ v = 1f - (y - 110f) / 110f;
+ }
+
+ return new Color(Color.HSVtoRGB(h, s, v));
+ }
+
+ @Override
+ public int[] calculate(Color color) {
+
+ float[] hsv = color.getHSV();
+
+ int x = Math.round(hsv[0] * 220f);
+ int y = 0;
+
+ // lower half
+ if (hsv[1] == 1f) {
+ y = Math.round(110f - (hsv[1] + hsv[2]) * 110f);
+ } else {
+ y = Math.round(hsv[1] * 110f);
+ }
+
+ return new int[] { x, y };
+ }
+ };
+
+ /** HSV color converter */
+ Coordinates2Color HSVConverter = new Coordinates2Color() {
+ @Override
+ public int[] calculate(Color color) {
+
+ float[] hsv = color.getHSV();
+
+ // Calculate coordinates
+ int x = Math.round(hsv[2] * 220.0f);
+ int y = Math.round(220 - hsv[1] * 220.0f);
+
+ // Create background color of clean color
+ Color bgColor = new Color(Color.HSVtoRGB(hsv[0], 1f, 1f));
+ hsvGradient.setBackgroundColor(bgColor);
+
+ return new int[] { x, y };
+ }
+
+ @Override
+ public Color calculate(int x, int y) {
+ float saturation = 1f - (y / 220.0f);
+ float value = (x / 220.0f);
+ float hue = Float.parseFloat(hueSlider.getValue().toString())
+ / 360f;
+
+ Color color = new Color(Color.HSVtoRGB(hue, saturation, value));
+ return color;
+ }
+ };
+
+ private static Logger getLogger() {
+ return Logger.getLogger(ColorPickerPopup.class.getName());
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java
new file mode 100644
index 0000000000..dc133ce156
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerPreview.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.lang.reflect.Method;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.CssLayout;
+import com.vaadin.v7.ui.LegacyTextField;
+
+/**
+ * A component that represents color selection preview within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerPreview extends CssLayout
+ implements ColorSelector, ValueChangeListener {
+
+ private static final String STYLE_DARK_COLOR = "v-textfield-dark";
+ private static final String STYLE_LIGHT_COLOR = "v-textfield-light";
+
+ private static final Method COLOR_CHANGE_METHOD;
+ static {
+ try {
+ COLOR_CHANGE_METHOD = ColorChangeListener.class.getDeclaredMethod(
+ "colorChanged", new Class[] { ColorChangeEvent.class });
+ } catch (final java.lang.NoSuchMethodException e) {
+ // This should never happen
+ throw new java.lang.RuntimeException(
+ "Internal error finding methods in ColorPicker");
+ }
+ }
+
+ /** The color. */
+ private Color color;
+
+ /** The field. */
+ private final LegacyTextField field;
+
+ /** The old value. */
+ private String oldValue;
+
+ private ColorPickerPreview() {
+ setStyleName("v-colorpicker-preview");
+ setImmediate(true);
+ field = new LegacyTextField();
+ field.setImmediate(true);
+ field.setSizeFull();
+ field.setStyleName("v-colorpicker-preview-textfield");
+ field.setData(this);
+ field.addValueChangeListener(this);
+ addComponent(field);
+ }
+
+ /**
+ * Instantiates a new color picker preview.
+ */
+ public ColorPickerPreview(Color color) {
+ this();
+ setColor(color);
+ }
+
+ @Override
+ public void setColor(Color color) {
+ this.color = color;
+
+ // Unregister listener
+ field.removeValueChangeListener(this);
+
+ String colorCSS = color.getCSS();
+ field.setValue(colorCSS);
+
+ if (field.isValid()) {
+ oldValue = colorCSS;
+ } else {
+ field.setValue(oldValue);
+ }
+
+ // Re-register listener
+ field.addValueChangeListener(this);
+
+ // Set the text color
+ field.removeStyleName(STYLE_DARK_COLOR);
+ field.removeStyleName(STYLE_LIGHT_COLOR);
+ if (this.color.getRed() + this.color.getGreen()
+ + this.color.getBlue() < 3 * 128) {
+ field.addStyleName(STYLE_DARK_COLOR);
+ } else {
+ field.addStyleName(STYLE_LIGHT_COLOR);
+ }
+
+ markAsDirty();
+ }
+
+ @Override
+ public Color getColor() {
+ return color;
+ }
+
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ addListener(ColorChangeEvent.class, listener, COLOR_CHANGE_METHOD);
+ }
+
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ removeListener(ColorChangeEvent.class, listener);
+ }
+
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ String value = (String) event.getProperty().getValue();
+ try {
+ if (value != null) {
+ /*
+ * Description of supported formats see
+ * http://www.w3schools.com/cssref/css_colors_legal.asp
+ */
+ if (value.length() == 7 && value.startsWith("#")) {
+ // CSS color format (e.g. #000000)
+ int red = Integer.parseInt(value.substring(1, 3), 16);
+ int green = Integer.parseInt(value.substring(3, 5), 16);
+ int blue = Integer.parseInt(value.substring(5, 7), 16);
+ color = new Color(red, green, blue);
+
+ } else if (value.startsWith("rgb")) {
+ // RGB color format rgb/rgba(255,255,255,0.1)
+ String[] colors = value.substring(value.indexOf("(") + 1,
+ value.length() - 1).split(",");
+
+ int red = Integer.parseInt(colors[0]);
+ int green = Integer.parseInt(colors[1]);
+ int blue = Integer.parseInt(colors[2]);
+ if (colors.length > 3) {
+ int alpha = (int) (Double.parseDouble(colors[3])
+ * 255d);
+ color = new Color(red, green, blue, alpha);
+ } else {
+ color = new Color(red, green, blue);
+ }
+
+ } else if (value.startsWith("hsl")) {
+ // HSL color format hsl/hsla(100,50%,50%,1.0)
+ String[] colors = value.substring(value.indexOf("(") + 1,
+ value.length() - 1).split(",");
+
+ int hue = Integer.parseInt(colors[0]);
+ int saturation = Integer
+ .parseInt(colors[1].replace("%", ""));
+ int lightness = Integer
+ .parseInt(colors[2].replace("%", ""));
+ int rgb = Color.HSLtoRGB(hue, saturation, lightness);
+
+ if (colors.length > 3) {
+ int alpha = (int) (Double.parseDouble(colors[3])
+ * 255d);
+ color = new Color(rgb);
+ color.setAlpha(alpha);
+ } else {
+ color = new Color(rgb);
+ }
+ }
+
+ oldValue = value;
+ fireEvent(new ColorChangeEvent((Component) field.getData(),
+ color));
+ }
+
+ } catch (NumberFormatException nfe) {
+ // Revert value
+ field.setValue(oldValue);
+ }
+ }
+
+ /**
+ * Called when the component is refreshing
+ */
+ @Override
+ protected String getCss(Component c) {
+ return "background: " + color.getCSS();
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java
new file mode 100644
index 0000000000..ae3dee4069
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorPickerSelect.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.ui.CustomComponent;
+import com.vaadin.ui.VerticalLayout;
+
+/**
+ * A component that represents color selection swatches within a color picker.
+ *
+ * @since 7.0.0
+ */
+public class ColorPickerSelect extends CustomComponent
+ implements ColorSelector, ValueChangeListener {
+
+ /** The range. */
+ private final ComboBox range;
+
+ /** The grid. */
+ private final ColorPickerGrid grid;
+
+ /**
+ * The Enum ColorRangePropertyId.
+ */
+ private enum ColorRangePropertyId {
+ ALL("All colors"), RED("Red colors"), GREEN("Green colors"), BLUE(
+ "Blue colors");
+
+ /** The caption. */
+ private String caption;
+
+ /**
+ * Instantiates a new color range property id.
+ *
+ * @param caption
+ * the caption
+ */
+ ColorRangePropertyId(String caption) {
+ this.caption = caption;
+ }
+
+ @Override
+ public String toString() {
+ return caption;
+ }
+ }
+
+ /**
+ * Instantiates a new color picker select.
+ *
+ * @param rows
+ * the rows
+ * @param columns
+ * the columns
+ */
+ public ColorPickerSelect() {
+
+ VerticalLayout layout = new VerticalLayout();
+ setCompositionRoot(layout);
+
+ setStyleName("colorselect");
+ setWidth("100%");
+
+ range = new ComboBox();
+ range.setImmediate(true);
+ range.setImmediate(true);
+ range.setNullSelectionAllowed(false);
+ range.setNewItemsAllowed(false);
+ range.setWidth("100%");
+ range.addValueChangeListener(this);
+
+ for (ColorRangePropertyId id : ColorRangePropertyId.values()) {
+ range.addItem(id);
+ }
+ range.select(ColorRangePropertyId.ALL);
+
+ layout.addComponent(range);
+
+ grid = new ColorPickerGrid(createAllColors(14, 10));
+ grid.setWidth("100%");
+
+ layout.addComponent(grid);
+ }
+
+ /**
+ * Creates the all colors.
+ *
+ * @param rows
+ * the rows
+ * @param columns
+ * the columns
+ *
+ * @return the color[][]
+ */
+ private Color[][] createAllColors(int rows, int columns) {
+ Color[][] colors = new Color[rows][columns];
+
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < columns; col++) {
+
+ // Create the color grid by varying the saturation and value
+ if (row < (rows - 1)) {
+ // Calculate new hue value
+ float hue = ((float) col / (float) columns);
+ float saturation = 1f;
+ float value = 1f;
+
+ // For the upper half use value=1 and variable
+ // saturation
+ if (row < (rows / 2)) {
+ saturation = ((row + 1f) / (rows / 2f));
+ } else {
+ value = 1f - ((row - (rows / 2f)) / (rows / 2f));
+ }
+
+ colors[row][col] = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ }
+
+ // The last row should have the black&white gradient
+ else {
+ float hue = 0f;
+ float saturation = 0f;
+ float value = 1f - ((float) col / (float) columns);
+
+ colors[row][col] = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ }
+ }
+ }
+
+ return colors;
+ }
+
+ /**
+ * Creates the color.
+ *
+ * @param color
+ * the color
+ * @param rows
+ * the rows
+ * @param columns
+ * the columns
+ *
+ * @return the color[][]
+ */
+ private Color[][] createColors(Color color, int rows, int columns) {
+ Color[][] colors = new Color[rows][columns];
+
+ float[] hsv = color.getHSV();
+
+ float hue = hsv[0];
+ float saturation = 1f;
+ float value = 1f;
+
+ for (int row = 0; row < rows; row++) {
+ for (int col = 0; col < columns; col++) {
+
+ int index = row * columns + col;
+ saturation = 1f;
+ value = 1f;
+
+ if (index <= ((rows * columns) / 2)) {
+ saturation = index
+ / (((float) rows * (float) columns) / 2f);
+ } else {
+ index -= ((rows * columns) / 2);
+ value = 1f
+ - index / (((float) rows * (float) columns) / 2f);
+ }
+
+ colors[row][col] = new Color(
+ Color.HSVtoRGB(hue, saturation, value));
+ }
+ }
+
+ return colors;
+ }
+
+ @Override
+ public Color getColor() {
+ return grid.getColor();
+ }
+
+ @Override
+ public void setColor(Color color) {
+ grid.getColor();
+ }
+
+ @Override
+ public void addColorChangeListener(ColorChangeListener listener) {
+ grid.addColorChangeListener(listener);
+ }
+
+ @Override
+ public void removeColorChangeListener(ColorChangeListener listener) {
+ grid.removeColorChangeListener(listener);
+ }
+
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ if (grid == null) {
+ return;
+ }
+
+ if (event.getProperty().getValue() == ColorRangePropertyId.ALL) {
+ grid.setColorGrid(createAllColors(14, 10));
+ } else if (event.getProperty().getValue() == ColorRangePropertyId.RED) {
+ grid.setColorGrid(createColors(new Color(0xFF, 0, 0), 14, 10));
+ } else if (event.getProperty()
+ .getValue() == ColorRangePropertyId.GREEN) {
+ grid.setColorGrid(createColors(new Color(0, 0xFF, 0), 14, 10));
+ } else if (event.getProperty()
+ .getValue() == ColorRangePropertyId.BLUE) {
+ grid.setColorGrid(createColors(new Color(0, 0, 0xFF), 14, 10));
+ }
+ }
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorSelector.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorSelector.java
new file mode 100644
index 0000000000..d9264745a8
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/ColorSelector.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+/**
+ * An interface for a color selector.
+ *
+ * @since 7.0.0
+ */
+public interface ColorSelector extends Serializable, HasColorChangeListener {
+
+ /**
+ * Sets the color.
+ *
+ * @param color
+ * the new color
+ */
+ public void setColor(Color color);
+
+ /**
+ * Gets the color.
+ *
+ * @return the color
+ */
+ public Color getColor();
+}
diff --git a/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java
new file mode 100644
index 0000000000..7980111e2b
--- /dev/null
+++ b/compatibility-server/src/main/java/com/vaadin/ui/components/colorpicker/HasColorChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.ui.components.colorpicker;
+
+import java.io.Serializable;
+
+public interface HasColorChangeListener extends Serializable {
+
+ /**
+ * Adds a {@link ColorChangeListener} to the component.
+ *
+ * @param listener
+ */
+ void addColorChangeListener(ColorChangeListener listener);
+
+ /**
+ * Removes a {@link ColorChangeListener} from the component.
+ *
+ * @param listener
+ */
+ void removeColorChangeListener(ColorChangeListener listener);
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/AbstractFilterTestBase.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/AbstractFilterTestBase.java
new file mode 100644
index 0000000000..979f472e20
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/AbstractFilterTestBase.java
@@ -0,0 +1,97 @@
+package com.vaadin.data.util.filter;
+
+import junit.framework.TestCase;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+
+public abstract class AbstractFilterTestBase<FILTERTYPE extends Filter>
+ extends TestCase {
+
+ protected static final String PROPERTY1 = "property1";
+ protected static final String PROPERTY2 = "property2";
+
+ protected static class TestItem<T1, T2> extends PropertysetItem {
+
+ public TestItem(T1 value1, T2 value2) {
+ addItemProperty(PROPERTY1, new ObjectProperty<T1>(value1));
+ addItemProperty(PROPERTY2, new ObjectProperty<T2>(value2));
+ }
+ }
+
+ protected static class NullProperty implements Property<String> {
+
+ @Override
+ public String getValue() {
+ return null;
+ }
+
+ @Override
+ public void setValue(String newValue) throws ReadOnlyException {
+ throw new ReadOnlyException();
+ }
+
+ @Override
+ public Class<String> getType() {
+ return String.class;
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return true;
+ }
+
+ @Override
+ public void setReadOnly(boolean newStatus) {
+ // do nothing
+ }
+
+ }
+
+ public static class SameItemFilter implements Filter {
+
+ private final Item item;
+ private final Object propertyId;
+
+ public SameItemFilter(Item item) {
+ this(item, "");
+ }
+
+ public SameItemFilter(Item item, Object propertyId) {
+ this.item = item;
+ this.propertyId = propertyId;
+ }
+
+ @Override
+ public boolean passesFilter(Object itemId, Item item)
+ throws UnsupportedOperationException {
+ return this.item == item;
+ }
+
+ @Override
+ public boolean appliesToProperty(Object propertyId) {
+ return this.propertyId != null ? this.propertyId.equals(propertyId)
+ : true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null || !getClass().equals(obj.getClass())) {
+ return false;
+ }
+ SameItemFilter other = (SameItemFilter) obj;
+ return item == other.item
+ && (propertyId == null ? other.propertyId == null
+ : propertyId.equals(other.propertyId));
+ }
+
+ @Override
+ public int hashCode() {
+ return item.hashCode();
+ }
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/AndOrFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/AndOrFilterTest.java
new file mode 100644
index 0000000000..f825ef64c6
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/AndOrFilterTest.java
@@ -0,0 +1,246 @@
+package com.vaadin.data.util.filter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.BeanItem;
+
+public class AndOrFilterTest
+ extends AbstractFilterTestBase<AbstractJunctionFilter> {
+
+ protected Item item1 = new BeanItem<Integer>(1);
+ protected Item item2 = new BeanItem<Integer>(2);
+
+ @Test
+ public void testNoFilterAnd() {
+ Filter filter = new And();
+
+ Assert.assertTrue(filter.passesFilter(null, item1));
+ }
+
+ @Test
+ public void testSingleFilterAnd() {
+ Filter filter = new And(new SameItemFilter(item1));
+
+ Assert.assertTrue(filter.passesFilter(null, item1));
+ Assert.assertFalse(filter.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testTwoFilterAnd() {
+ Filter filter1 = new And(new SameItemFilter(item1),
+ new SameItemFilter(item1));
+ Filter filter2 = new And(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+
+ Assert.assertTrue(filter1.passesFilter(null, item1));
+ Assert.assertFalse(filter1.passesFilter(null, item2));
+
+ Assert.assertFalse(filter2.passesFilter(null, item1));
+ Assert.assertFalse(filter2.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testThreeFilterAnd() {
+ Filter filter1 = new And(new SameItemFilter(item1),
+ new SameItemFilter(item1), new SameItemFilter(item1));
+ Filter filter2 = new And(new SameItemFilter(item1),
+ new SameItemFilter(item1), new SameItemFilter(item2));
+
+ Assert.assertTrue(filter1.passesFilter(null, item1));
+ Assert.assertFalse(filter1.passesFilter(null, item2));
+
+ Assert.assertFalse(filter2.passesFilter(null, item1));
+ Assert.assertFalse(filter2.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testNoFilterOr() {
+ Filter filter = new Or();
+
+ Assert.assertFalse(filter.passesFilter(null, item1));
+ }
+
+ @Test
+ public void testSingleFilterOr() {
+ Filter filter = new Or(new SameItemFilter(item1));
+
+ Assert.assertTrue(filter.passesFilter(null, item1));
+ Assert.assertFalse(filter.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testTwoFilterOr() {
+ Filter filter1 = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item1));
+ Filter filter2 = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+
+ Assert.assertTrue(filter1.passesFilter(null, item1));
+ Assert.assertFalse(filter1.passesFilter(null, item2));
+
+ Assert.assertTrue(filter2.passesFilter(null, item1));
+ Assert.assertTrue(filter2.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testThreeFilterOr() {
+ Filter filter1 = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item1), new SameItemFilter(item1));
+ Filter filter2 = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item1), new SameItemFilter(item2));
+
+ Assert.assertTrue(filter1.passesFilter(null, item1));
+ Assert.assertFalse(filter1.passesFilter(null, item2));
+
+ Assert.assertTrue(filter2.passesFilter(null, item1));
+ Assert.assertTrue(filter2.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testAndEqualsHashCode() {
+ Filter filter0 = new And();
+ Filter filter0b = new And();
+ Filter filter1a = new And(new SameItemFilter(item1));
+ Filter filter1a2 = new And(new SameItemFilter(item1));
+ Filter filter1b = new And(new SameItemFilter(item2));
+ Filter filter2a = new And(new SameItemFilter(item1),
+ new SameItemFilter(item1));
+ Filter filter2b = new And(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+ Filter filter2b2 = new And(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+ Filter other0 = new Or();
+ Filter other1 = new Or(new SameItemFilter(item1));
+
+ Assert.assertEquals(filter0, filter0);
+ Assert.assertEquals(filter0, filter0b);
+ Assert.assertFalse(filter0.equals(filter1a));
+ Assert.assertFalse(filter0.equals(other0));
+ Assert.assertFalse(filter0.equals(other1));
+
+ Assert.assertFalse(filter1a.equals(filter1b));
+ Assert.assertFalse(filter1a.equals(other1));
+
+ Assert.assertFalse(filter1a.equals(filter2a));
+ Assert.assertFalse(filter2a.equals(filter1a));
+
+ Assert.assertFalse(filter2a.equals(filter2b));
+ Assert.assertEquals(filter2b, filter2b2);
+
+ // hashCode()
+ Assert.assertEquals(filter0.hashCode(), filter0.hashCode());
+ Assert.assertEquals(filter0.hashCode(), filter0b.hashCode());
+ Assert.assertEquals(filter1a.hashCode(), filter1a.hashCode());
+ Assert.assertEquals(filter1a.hashCode(), filter1a2.hashCode());
+ Assert.assertEquals(filter2a.hashCode(), filter2a.hashCode());
+ Assert.assertEquals(filter2b.hashCode(), filter2b2.hashCode());
+ }
+
+ @Test
+ public void testOrEqualsHashCode() {
+ Filter filter0 = new Or();
+ Filter filter0b = new Or();
+ Filter filter1a = new Or(new SameItemFilter(item1));
+ Filter filter1a2 = new Or(new SameItemFilter(item1));
+ Filter filter1b = new Or(new SameItemFilter(item2));
+ Filter filter2a = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item1));
+ Filter filter2b = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+ Filter filter2b2 = new Or(new SameItemFilter(item1),
+ new SameItemFilter(item2));
+ Filter other0 = new And();
+ Filter other1 = new And(new SameItemFilter(item1));
+
+ Assert.assertEquals(filter0, filter0);
+ Assert.assertEquals(filter0, filter0b);
+ Assert.assertFalse(filter0.equals(filter1a));
+ Assert.assertFalse(filter0.equals(other0));
+ Assert.assertFalse(filter0.equals(other1));
+
+ Assert.assertFalse(filter1a.equals(filter1b));
+ Assert.assertFalse(filter1a.equals(other1));
+
+ Assert.assertFalse(filter1a.equals(filter2a));
+ Assert.assertFalse(filter2a.equals(filter1a));
+
+ Assert.assertFalse(filter2a.equals(filter2b));
+ Assert.assertEquals(filter2b, filter2b2);
+
+ // hashCode()
+ Assert.assertEquals(filter0.hashCode(), filter0.hashCode());
+ Assert.assertEquals(filter0.hashCode(), filter0b.hashCode());
+ Assert.assertEquals(filter1a.hashCode(), filter1a.hashCode());
+ Assert.assertEquals(filter1a.hashCode(), filter1a2.hashCode());
+ Assert.assertEquals(filter2a.hashCode(), filter2a.hashCode());
+ Assert.assertEquals(filter2b.hashCode(), filter2b2.hashCode());
+ }
+
+ @Test
+ public void testAndAppliesToProperty() {
+ Filter filter0 = new And();
+ Filter filter1a = new And(new SameItemFilter(item1, "a"));
+ Filter filter1b = new And(new SameItemFilter(item1, "b"));
+ Filter filter2aa = new And(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "a"));
+ Filter filter2ab = new And(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "b"));
+ Filter filter3abc = new And(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "b"), new SameItemFilter(item1, "c"));
+
+ // empty And does not filter out anything
+ Assert.assertFalse(filter0.appliesToProperty("a"));
+ Assert.assertFalse(filter0.appliesToProperty("d"));
+
+ Assert.assertTrue(filter1a.appliesToProperty("a"));
+ Assert.assertFalse(filter1a.appliesToProperty("b"));
+ Assert.assertFalse(filter1b.appliesToProperty("a"));
+ Assert.assertTrue(filter1b.appliesToProperty("b"));
+
+ Assert.assertTrue(filter2aa.appliesToProperty("a"));
+ Assert.assertFalse(filter2aa.appliesToProperty("b"));
+ Assert.assertTrue(filter2ab.appliesToProperty("a"));
+ Assert.assertTrue(filter2ab.appliesToProperty("b"));
+
+ Assert.assertTrue(filter3abc.appliesToProperty("a"));
+ Assert.assertTrue(filter3abc.appliesToProperty("b"));
+ Assert.assertTrue(filter3abc.appliesToProperty("c"));
+ Assert.assertFalse(filter3abc.appliesToProperty("d"));
+ }
+
+ @Test
+ public void testOrAppliesToProperty() {
+ Filter filter0 = new Or();
+ Filter filter1a = new Or(new SameItemFilter(item1, "a"));
+ Filter filter1b = new Or(new SameItemFilter(item1, "b"));
+ Filter filter2aa = new Or(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "a"));
+ Filter filter2ab = new Or(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "b"));
+ Filter filter3abc = new Or(new SameItemFilter(item1, "a"),
+ new SameItemFilter(item1, "b"), new SameItemFilter(item1, "c"));
+
+ // empty Or filters out everything
+ Assert.assertTrue(filter0.appliesToProperty("a"));
+ Assert.assertTrue(filter0.appliesToProperty("d"));
+
+ Assert.assertTrue(filter1a.appliesToProperty("a"));
+ Assert.assertFalse(filter1a.appliesToProperty("b"));
+ Assert.assertFalse(filter1b.appliesToProperty("a"));
+ Assert.assertTrue(filter1b.appliesToProperty("b"));
+
+ Assert.assertTrue(filter2aa.appliesToProperty("a"));
+ Assert.assertFalse(filter2aa.appliesToProperty("b"));
+ Assert.assertTrue(filter2ab.appliesToProperty("a"));
+ Assert.assertTrue(filter2ab.appliesToProperty("b"));
+
+ Assert.assertTrue(filter3abc.appliesToProperty("a"));
+ Assert.assertTrue(filter3abc.appliesToProperty("b"));
+ Assert.assertTrue(filter3abc.appliesToProperty("c"));
+ Assert.assertFalse(filter3abc.appliesToProperty("d"));
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterDateTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterDateTest.java
new file mode 100644
index 0000000000..7c3dba9db3
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterDateTest.java
@@ -0,0 +1,142 @@
+package com.vaadin.data.util.filter;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+import com.vaadin.data.util.filter.Compare.Equal;
+import com.vaadin.data.util.filter.Compare.Greater;
+import com.vaadin.data.util.filter.Compare.GreaterOrEqual;
+import com.vaadin.data.util.filter.Compare.Less;
+import com.vaadin.data.util.filter.Compare.LessOrEqual;
+
+public class CompareFilterDateTest extends AbstractFilterTestBase<Compare> {
+
+ protected Item itemNullUtilDate;
+ protected Item itemNullSqlDate;
+ protected Item itemUtilDate;
+ protected Item itemSqlDate;
+
+ protected SimpleDateFormat formatter = new SimpleDateFormat("ddMMyyyy");
+
+ protected Filter equalCompUtilDate;
+ protected Filter greaterCompUtilDate;
+ protected Filter lessCompUtilDate;
+ protected Filter greaterEqualCompUtilDate;
+ protected Filter lessEqualCompUtilDate;
+
+ protected Filter equalCompSqlDate;
+ protected Filter greaterCompSqlDate;
+ protected Filter lessCompSqlDate;
+ protected Filter greaterEqualCompSqlDate;
+ protected Filter lessEqualCompSqlDate;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ equalCompUtilDate = new Equal(PROPERTY1, formatter.parse("26072016"));
+ greaterCompUtilDate = new Greater(PROPERTY1,
+ formatter.parse("26072016"));
+ lessCompUtilDate = new Less(PROPERTY1, formatter.parse("26072016"));
+ greaterEqualCompUtilDate = new GreaterOrEqual(PROPERTY1,
+ formatter.parse("26072016"));
+ lessEqualCompUtilDate = new LessOrEqual(PROPERTY1,
+ formatter.parse("26072016"));
+
+ equalCompSqlDate = new Equal(PROPERTY1,
+ new java.sql.Date(formatter.parse("26072016").getTime()));
+ greaterCompSqlDate = new Greater(PROPERTY1,
+ new java.sql.Date(formatter.parse("26072016").getTime()));
+ lessCompSqlDate = new Less(PROPERTY1,
+ new java.sql.Date(formatter.parse("26072016").getTime()));
+ greaterEqualCompSqlDate = new GreaterOrEqual(PROPERTY1,
+ new java.sql.Date(formatter.parse("26072016").getTime()));
+ lessEqualCompSqlDate = new LessOrEqual(PROPERTY1,
+ new java.sql.Date(formatter.parse("26072016").getTime()));
+
+ itemNullUtilDate = new PropertysetItem();
+ itemNullUtilDate.addItemProperty(PROPERTY1,
+ new ObjectProperty<Date>(null, Date.class));
+ itemNullSqlDate = new PropertysetItem();
+ itemNullSqlDate.addItemProperty(PROPERTY1,
+ new ObjectProperty<java.sql.Date>(null, java.sql.Date.class));
+ itemUtilDate = new PropertysetItem();
+ itemUtilDate.addItemProperty(PROPERTY1, new ObjectProperty<Date>(
+ formatter.parse("25072016"), Date.class));
+ itemSqlDate = new PropertysetItem();
+ itemSqlDate.addItemProperty(PROPERTY1,
+ new ObjectProperty<java.sql.Date>(
+ new java.sql.Date(
+ formatter.parse("25072016").getTime()),
+ java.sql.Date.class));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ itemNullUtilDate = null;
+ itemNullSqlDate = null;
+ itemUtilDate = null;
+ itemSqlDate = null;
+ }
+
+ @Test
+ public void testCompareUtilDatesAndUtilDates() {
+ Assert.assertFalse(
+ equalCompUtilDate.passesFilter(null, itemNullUtilDate));
+ Assert.assertFalse(equalCompUtilDate.passesFilter(null, itemUtilDate));
+ Assert.assertFalse(
+ greaterCompUtilDate.passesFilter(null, itemUtilDate));
+ Assert.assertTrue(lessCompUtilDate.passesFilter(null, itemUtilDate));
+ Assert.assertFalse(
+ greaterEqualCompUtilDate.passesFilter(null, itemUtilDate));
+ Assert.assertTrue(
+ lessEqualCompUtilDate.passesFilter(null, itemUtilDate));
+ }
+
+ @Test
+ public void testCompareUtilDatesAndSqlDates() {
+ Assert.assertFalse(
+ equalCompUtilDate.passesFilter(null, itemNullSqlDate));
+ Assert.assertFalse(equalCompUtilDate.passesFilter(null, itemSqlDate));
+ Assert.assertFalse(greaterCompUtilDate.passesFilter(null, itemSqlDate));
+ Assert.assertTrue(lessCompUtilDate.passesFilter(null, itemSqlDate));
+ Assert.assertFalse(
+ greaterEqualCompUtilDate.passesFilter(null, itemSqlDate));
+ Assert.assertTrue(
+ lessEqualCompUtilDate.passesFilter(null, itemSqlDate));
+ }
+
+ @Test
+ public void testCompareSqlDatesAndSqlDates() {
+ Assert.assertFalse(
+ equalCompSqlDate.passesFilter(null, itemNullSqlDate));
+ Assert.assertFalse(equalCompSqlDate.passesFilter(null, itemSqlDate));
+ Assert.assertFalse(greaterCompSqlDate.passesFilter(null, itemSqlDate));
+ Assert.assertTrue(lessCompSqlDate.passesFilter(null, itemSqlDate));
+ Assert.assertFalse(
+ greaterEqualCompSqlDate.passesFilter(null, itemSqlDate));
+ Assert.assertTrue(lessEqualCompSqlDate.passesFilter(null, itemSqlDate));
+ }
+
+ @Test
+ public void testCompareSqlDatesAndUtilDates() {
+ Assert.assertFalse(
+ equalCompSqlDate.passesFilter(null, itemNullUtilDate));
+ Assert.assertFalse(equalCompSqlDate.passesFilter(null, itemUtilDate));
+ Assert.assertFalse(greaterCompSqlDate.passesFilter(null, itemUtilDate));
+ Assert.assertTrue(lessCompSqlDate.passesFilter(null, itemUtilDate));
+ Assert.assertFalse(
+ greaterEqualCompSqlDate.passesFilter(null, itemUtilDate));
+ Assert.assertTrue(
+ lessEqualCompSqlDate.passesFilter(null, itemUtilDate));
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterTest.java
new file mode 100644
index 0000000000..4bdad3c54d
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/CompareFilterTest.java
@@ -0,0 +1,322 @@
+package com.vaadin.data.util.filter;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+import com.vaadin.data.util.filter.Compare.Equal;
+import com.vaadin.data.util.filter.Compare.Greater;
+import com.vaadin.data.util.filter.Compare.GreaterOrEqual;
+import com.vaadin.data.util.filter.Compare.Less;
+import com.vaadin.data.util.filter.Compare.LessOrEqual;
+
+public class CompareFilterTest extends AbstractFilterTestBase<Compare> {
+
+ protected Item itemNull;
+ protected Item itemEmpty;
+ protected Item itemA;
+ protected Item itemB;
+ protected Item itemC;
+
+ protected final Filter equalB = new Equal(PROPERTY1, "b");
+ protected final Filter greaterB = new Greater(PROPERTY1, "b");
+ protected final Filter lessB = new Less(PROPERTY1, "b");
+ protected final Filter greaterEqualB = new GreaterOrEqual(PROPERTY1, "b");
+ protected final Filter lessEqualB = new LessOrEqual(PROPERTY1, "b");
+
+ protected final Filter equalNull = new Equal(PROPERTY1, null);
+ protected final Filter greaterNull = new Greater(PROPERTY1, null);
+ protected final Filter lessNull = new Less(PROPERTY1, null);
+ protected final Filter greaterEqualNull = new GreaterOrEqual(PROPERTY1,
+ null);
+ protected final Filter lessEqualNull = new LessOrEqual(PROPERTY1, null);
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ itemNull = new PropertysetItem();
+ itemNull.addItemProperty(PROPERTY1,
+ new ObjectProperty<String>(null, String.class));
+ itemEmpty = new PropertysetItem();
+ itemEmpty.addItemProperty(PROPERTY1,
+ new ObjectProperty<String>("", String.class));
+ itemA = new PropertysetItem();
+ itemA.addItemProperty(PROPERTY1,
+ new ObjectProperty<String>("a", String.class));
+ itemB = new PropertysetItem();
+ itemB.addItemProperty(PROPERTY1,
+ new ObjectProperty<String>("b", String.class));
+ itemC = new PropertysetItem();
+ itemC.addItemProperty(PROPERTY1,
+ new ObjectProperty<String>("c", String.class));
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+ itemNull = null;
+ itemEmpty = null;
+ itemA = null;
+ itemB = null;
+ }
+
+ @Test
+ public void testCompareString() {
+ Assert.assertFalse(equalB.passesFilter(null, itemEmpty));
+ Assert.assertFalse(equalB.passesFilter(null, itemA));
+ Assert.assertTrue(equalB.passesFilter(null, itemB));
+ Assert.assertFalse(equalB.passesFilter(null, itemC));
+
+ Assert.assertFalse(greaterB.passesFilter(null, itemEmpty));
+ Assert.assertFalse(greaterB.passesFilter(null, itemA));
+ Assert.assertFalse(greaterB.passesFilter(null, itemB));
+ Assert.assertTrue(greaterB.passesFilter(null, itemC));
+
+ Assert.assertTrue(lessB.passesFilter(null, itemEmpty));
+ Assert.assertTrue(lessB.passesFilter(null, itemA));
+ Assert.assertFalse(lessB.passesFilter(null, itemB));
+ Assert.assertFalse(lessB.passesFilter(null, itemC));
+
+ Assert.assertFalse(greaterEqualB.passesFilter(null, itemEmpty));
+ Assert.assertFalse(greaterEqualB.passesFilter(null, itemA));
+ Assert.assertTrue(greaterEqualB.passesFilter(null, itemB));
+ Assert.assertTrue(greaterEqualB.passesFilter(null, itemC));
+
+ Assert.assertTrue(lessEqualB.passesFilter(null, itemEmpty));
+ Assert.assertTrue(lessEqualB.passesFilter(null, itemA));
+ Assert.assertTrue(lessEqualB.passesFilter(null, itemB));
+ Assert.assertFalse(lessEqualB.passesFilter(null, itemC));
+ }
+
+ @Test
+ public void testCompareWithNull() {
+ // null comparisons: null is less than any other value
+ Assert.assertFalse(equalB.passesFilter(null, itemNull));
+ Assert.assertTrue(greaterB.passesFilter(null, itemNull));
+ Assert.assertFalse(lessB.passesFilter(null, itemNull));
+ Assert.assertTrue(greaterEqualB.passesFilter(null, itemNull));
+ Assert.assertFalse(lessEqualB.passesFilter(null, itemNull));
+
+ Assert.assertTrue(equalNull.passesFilter(null, itemNull));
+ Assert.assertFalse(greaterNull.passesFilter(null, itemNull));
+ Assert.assertFalse(lessNull.passesFilter(null, itemNull));
+ Assert.assertTrue(greaterEqualNull.passesFilter(null, itemNull));
+ Assert.assertTrue(lessEqualNull.passesFilter(null, itemNull));
+
+ Assert.assertFalse(equalNull.passesFilter(null, itemA));
+ Assert.assertFalse(greaterNull.passesFilter(null, itemA));
+ Assert.assertTrue(lessNull.passesFilter(null, itemA));
+ Assert.assertFalse(greaterEqualNull.passesFilter(null, itemA));
+ Assert.assertTrue(lessEqualNull.passesFilter(null, itemA));
+ }
+
+ @Test
+ public void testCompareInteger() {
+ int negative = -1;
+ int zero = 0;
+ int positive = 1;
+
+ Item itemNegative = new PropertysetItem();
+ itemNegative.addItemProperty(PROPERTY1,
+ new ObjectProperty<Integer>(negative, Integer.class));
+ Item itemZero = new PropertysetItem();
+ itemZero.addItemProperty(PROPERTY1,
+ new ObjectProperty<Integer>(zero, Integer.class));
+ Item itemPositive = new PropertysetItem();
+ itemPositive.addItemProperty(PROPERTY1,
+ new ObjectProperty<Integer>(positive, Integer.class));
+
+ Filter equalZero = new Equal(PROPERTY1, zero);
+ Assert.assertFalse(equalZero.passesFilter(null, itemNegative));
+ Assert.assertTrue(equalZero.passesFilter(null, itemZero));
+ Assert.assertFalse(equalZero.passesFilter(null, itemPositive));
+
+ Filter isPositive = new Greater(PROPERTY1, zero);
+ Assert.assertFalse(isPositive.passesFilter(null, itemNegative));
+ Assert.assertFalse(isPositive.passesFilter(null, itemZero));
+ Assert.assertTrue(isPositive.passesFilter(null, itemPositive));
+
+ Filter isNegative = new Less(PROPERTY1, zero);
+ Assert.assertTrue(isNegative.passesFilter(null, itemNegative));
+ Assert.assertFalse(isNegative.passesFilter(null, itemZero));
+ Assert.assertFalse(isNegative.passesFilter(null, itemPositive));
+
+ Filter isNonNegative = new GreaterOrEqual(PROPERTY1, zero);
+ Assert.assertFalse(isNonNegative.passesFilter(null, itemNegative));
+ Assert.assertTrue(isNonNegative.passesFilter(null, itemZero));
+ Assert.assertTrue(isNonNegative.passesFilter(null, itemPositive));
+
+ Filter isNonPositive = new LessOrEqual(PROPERTY1, zero);
+ Assert.assertTrue(isNonPositive.passesFilter(null, itemNegative));
+ Assert.assertTrue(isNonPositive.passesFilter(null, itemZero));
+ Assert.assertFalse(isNonPositive.passesFilter(null, itemPositive));
+ }
+
+ @Test
+ public void testCompareBigDecimal() {
+ BigDecimal negative = new BigDecimal(-1);
+ BigDecimal zero = new BigDecimal(0);
+ BigDecimal positive = new BigDecimal(1);
+ positive.setScale(1);
+ BigDecimal positiveScaleTwo = new BigDecimal(1).setScale(2);
+
+ Item itemNegative = new PropertysetItem();
+ itemNegative.addItemProperty(PROPERTY1,
+ new ObjectProperty<BigDecimal>(negative, BigDecimal.class));
+ Item itemZero = new PropertysetItem();
+ itemZero.addItemProperty(PROPERTY1,
+ new ObjectProperty<BigDecimal>(zero, BigDecimal.class));
+ Item itemPositive = new PropertysetItem();
+ itemPositive.addItemProperty(PROPERTY1,
+ new ObjectProperty<BigDecimal>(positive, BigDecimal.class));
+ Item itemPositiveScaleTwo = new PropertysetItem();
+ itemPositiveScaleTwo.addItemProperty(PROPERTY1,
+ new ObjectProperty<BigDecimal>(positiveScaleTwo,
+ BigDecimal.class));
+
+ Filter equalZero = new Equal(PROPERTY1, zero);
+ Assert.assertFalse(equalZero.passesFilter(null, itemNegative));
+ Assert.assertTrue(equalZero.passesFilter(null, itemZero));
+ Assert.assertFalse(equalZero.passesFilter(null, itemPositive));
+
+ Filter isPositive = new Greater(PROPERTY1, zero);
+ Assert.assertFalse(isPositive.passesFilter(null, itemNegative));
+ Assert.assertFalse(isPositive.passesFilter(null, itemZero));
+ Assert.assertTrue(isPositive.passesFilter(null, itemPositive));
+
+ Filter isNegative = new Less(PROPERTY1, zero);
+ Assert.assertTrue(isNegative.passesFilter(null, itemNegative));
+ Assert.assertFalse(isNegative.passesFilter(null, itemZero));
+ Assert.assertFalse(isNegative.passesFilter(null, itemPositive));
+
+ Filter isNonNegative = new GreaterOrEqual(PROPERTY1, zero);
+ Assert.assertFalse(isNonNegative.passesFilter(null, itemNegative));
+ Assert.assertTrue(isNonNegative.passesFilter(null, itemZero));
+ Assert.assertTrue(isNonNegative.passesFilter(null, itemPositive));
+
+ Filter isNonPositive = new LessOrEqual(PROPERTY1, zero);
+ Assert.assertTrue(isNonPositive.passesFilter(null, itemNegative));
+ Assert.assertTrue(isNonPositive.passesFilter(null, itemZero));
+ Assert.assertFalse(isNonPositive.passesFilter(null, itemPositive));
+
+ Filter isPositiveScaleTwo = new Equal(PROPERTY1, positiveScaleTwo);
+ Assert.assertTrue(
+ isPositiveScaleTwo.passesFilter(null, itemPositiveScaleTwo));
+ Assert.assertTrue(isPositiveScaleTwo.passesFilter(null, itemPositive));
+
+ }
+
+ @Test
+ public void testCompareDate() {
+ Date now = new Date();
+ // new Date() is only accurate to the millisecond, so repeating it gives
+ // the same date
+ Date earlier = new Date(now.getTime() - 1);
+ Date later = new Date(now.getTime() + 1);
+
+ Item itemEarlier = new PropertysetItem();
+ itemEarlier.addItemProperty(PROPERTY1,
+ new ObjectProperty<Date>(earlier, Date.class));
+ Item itemNow = new PropertysetItem();
+ itemNow.addItemProperty(PROPERTY1,
+ new ObjectProperty<Date>(now, Date.class));
+ Item itemLater = new PropertysetItem();
+ itemLater.addItemProperty(PROPERTY1,
+ new ObjectProperty<Date>(later, Date.class));
+
+ Filter equalNow = new Equal(PROPERTY1, now);
+ Assert.assertFalse(equalNow.passesFilter(null, itemEarlier));
+ Assert.assertTrue(equalNow.passesFilter(null, itemNow));
+ Assert.assertFalse(equalNow.passesFilter(null, itemLater));
+
+ Filter after = new Greater(PROPERTY1, now);
+ Assert.assertFalse(after.passesFilter(null, itemEarlier));
+ Assert.assertFalse(after.passesFilter(null, itemNow));
+ Assert.assertTrue(after.passesFilter(null, itemLater));
+
+ Filter before = new Less(PROPERTY1, now);
+ Assert.assertTrue(before.passesFilter(null, itemEarlier));
+ Assert.assertFalse(before.passesFilter(null, itemNow));
+ Assert.assertFalse(before.passesFilter(null, itemLater));
+
+ Filter afterOrNow = new GreaterOrEqual(PROPERTY1, now);
+ Assert.assertFalse(afterOrNow.passesFilter(null, itemEarlier));
+ Assert.assertTrue(afterOrNow.passesFilter(null, itemNow));
+ Assert.assertTrue(afterOrNow.passesFilter(null, itemLater));
+
+ Filter beforeOrNow = new LessOrEqual(PROPERTY1, now);
+ Assert.assertTrue(beforeOrNow.passesFilter(null, itemEarlier));
+ Assert.assertTrue(beforeOrNow.passesFilter(null, itemNow));
+ Assert.assertFalse(beforeOrNow.passesFilter(null, itemLater));
+ }
+
+ @Test
+ public void testCompareAppliesToProperty() {
+ Filter filterA = new Equal("a", 1);
+ Filter filterB = new Equal("b", 1);
+
+ Assert.assertTrue(filterA.appliesToProperty("a"));
+ Assert.assertFalse(filterA.appliesToProperty("b"));
+ Assert.assertFalse(filterB.appliesToProperty("a"));
+ Assert.assertTrue(filterB.appliesToProperty("b"));
+ }
+
+ @Test
+ public void testCompareEqualsHashCode() {
+ // most checks with Equal filter, then only some with others
+ Filter equalNull2 = new Equal(PROPERTY1, null);
+ Filter equalNullProperty2 = new Equal(PROPERTY2, null);
+ Filter equalEmpty = new Equal(PROPERTY1, "");
+ Filter equalEmpty2 = new Equal(PROPERTY1, "");
+ Filter equalEmptyProperty2 = new Equal(PROPERTY2, "");
+ Filter equalA = new Equal(PROPERTY1, "a");
+ Filter equalB2 = new Equal(PROPERTY1, "b");
+ Filter equalBProperty2 = new Equal(PROPERTY2, "b");
+
+ Filter greaterEmpty = new Greater(PROPERTY1, "");
+
+ // equals()
+ Assert.assertEquals(equalNull, equalNull);
+ Assert.assertEquals(equalNull, equalNull2);
+ Assert.assertFalse(equalNull.equals(equalNullProperty2));
+ Assert.assertFalse(equalNull.equals(equalEmpty));
+ Assert.assertFalse(equalNull.equals(equalB));
+
+ Assert.assertEquals(equalEmpty, equalEmpty);
+ Assert.assertFalse(equalEmpty.equals(equalNull));
+ Assert.assertEquals(equalEmpty, equalEmpty2);
+ Assert.assertFalse(equalEmpty.equals(equalEmptyProperty2));
+ Assert.assertFalse(equalEmpty.equals(equalB));
+
+ Assert.assertEquals(equalB, equalB);
+ Assert.assertFalse(equalB.equals(equalNull));
+ Assert.assertFalse(equalB.equals(equalEmpty));
+ Assert.assertEquals(equalB, equalB2);
+ Assert.assertFalse(equalB.equals(equalBProperty2));
+ Assert.assertFalse(equalB.equals(equalA));
+
+ Assert.assertEquals(greaterB, greaterB);
+ Assert.assertFalse(greaterB.equals(lessB));
+ Assert.assertFalse(greaterB.equals(greaterEqualB));
+ Assert.assertFalse(greaterB.equals(lessEqualB));
+
+ Assert.assertFalse(greaterNull.equals(greaterEmpty));
+ Assert.assertFalse(greaterNull.equals(greaterB));
+ Assert.assertFalse(greaterEmpty.equals(greaterNull));
+ Assert.assertFalse(greaterEmpty.equals(greaterB));
+ Assert.assertFalse(greaterB.equals(greaterNull));
+ Assert.assertFalse(greaterB.equals(greaterEmpty));
+
+ // hashCode()
+ Assert.assertEquals(equalNull.hashCode(), equalNull2.hashCode());
+ Assert.assertEquals(equalEmpty.hashCode(), equalEmpty2.hashCode());
+ Assert.assertEquals(equalB.hashCode(), equalB2.hashCode());
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/IsNullFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/IsNullFilterTest.java
new file mode 100644
index 0000000000..18013cd41c
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/IsNullFilterTest.java
@@ -0,0 +1,61 @@
+package com.vaadin.data.util.filter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+
+public class IsNullFilterTest extends AbstractFilterTestBase<IsNull> {
+
+ @Test
+ public void testIsNull() {
+ Item item1 = new PropertysetItem();
+ item1.addItemProperty("a",
+ new ObjectProperty<String>(null, String.class));
+ item1.addItemProperty("b",
+ new ObjectProperty<String>("b", String.class));
+ Item item2 = new PropertysetItem();
+ item2.addItemProperty("a",
+ new ObjectProperty<String>("a", String.class));
+ item2.addItemProperty("b",
+ new ObjectProperty<String>(null, String.class));
+
+ Filter filter1 = new IsNull("a");
+ Filter filter2 = new IsNull("b");
+
+ Assert.assertTrue(filter1.passesFilter(null, item1));
+ Assert.assertFalse(filter1.passesFilter(null, item2));
+ Assert.assertFalse(filter2.passesFilter(null, item1));
+ Assert.assertTrue(filter2.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testIsNullAppliesToProperty() {
+ Filter filterA = new IsNull("a");
+ Filter filterB = new IsNull("b");
+
+ Assert.assertTrue(filterA.appliesToProperty("a"));
+ Assert.assertFalse(filterA.appliesToProperty("b"));
+ Assert.assertFalse(filterB.appliesToProperty("a"));
+ Assert.assertTrue(filterB.appliesToProperty("b"));
+ }
+
+ @Test
+ public void testIsNullEqualsHashCode() {
+ Filter filter1 = new IsNull("a");
+ Filter filter1b = new IsNull("a");
+ Filter filter2 = new IsNull("b");
+
+ // equals()
+ Assert.assertEquals(filter1, filter1b);
+ Assert.assertFalse(filter1.equals(filter2));
+ Assert.assertFalse(filter1.equals(new And()));
+
+ // hashCode()
+ Assert.assertEquals(filter1.hashCode(), filter1b.hashCode());
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/LikeFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/LikeFilterTest.java
new file mode 100644
index 0000000000..39054008cd
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/LikeFilterTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.vaadin.data.util.filter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.util.ObjectProperty;
+import com.vaadin.data.util.PropertysetItem;
+
+public class LikeFilterTest extends AbstractFilterTestBase<Like> {
+
+ protected Item item1 = new PropertysetItem();
+ protected Item item2 = new PropertysetItem();
+ protected Item item3 = new PropertysetItem();
+
+ @Test
+ public void testLikeWithNulls() {
+
+ Like filter = new Like("value", "a");
+
+ item1.addItemProperty("value", new ObjectProperty<String>("a"));
+ item2.addItemProperty("value", new ObjectProperty<String>("b"));
+ item3.addItemProperty("value",
+ new ObjectProperty<String>(null, String.class));
+
+ Assert.assertTrue(filter.passesFilter(null, item1));
+ Assert.assertFalse(filter.passesFilter(null, item2));
+ Assert.assertFalse(filter.passesFilter(null, item3));
+
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/NotFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/NotFilterTest.java
new file mode 100644
index 0000000000..e797b484f8
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/NotFilterTest.java
@@ -0,0 +1,54 @@
+package com.vaadin.data.util.filter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.util.BeanItem;
+
+public class NotFilterTest extends AbstractFilterTestBase<Not> {
+
+ protected Item item1 = new BeanItem<Integer>(1);
+ protected Item item2 = new BeanItem<Integer>(2);
+
+ @Test
+ public void testNot() {
+ Filter origFilter = new SameItemFilter(item1);
+ Filter filter = new Not(origFilter);
+
+ Assert.assertTrue(origFilter.passesFilter(null, item1));
+ Assert.assertFalse(origFilter.passesFilter(null, item2));
+ Assert.assertFalse(filter.passesFilter(null, item1));
+ Assert.assertTrue(filter.passesFilter(null, item2));
+ }
+
+ @Test
+ public void testANotAppliesToProperty() {
+ Filter filterA = new Not(new SameItemFilter(item1, "a"));
+ Filter filterB = new Not(new SameItemFilter(item1, "b"));
+
+ Assert.assertTrue(filterA.appliesToProperty("a"));
+ Assert.assertFalse(filterA.appliesToProperty("b"));
+ Assert.assertFalse(filterB.appliesToProperty("a"));
+ Assert.assertTrue(filterB.appliesToProperty("b"));
+ }
+
+ @Test
+ public void testNotEqualsHashCode() {
+ Filter origFilter = new SameItemFilter(item1);
+ Filter filter1 = new Not(origFilter);
+ Filter filter1b = new Not(new SameItemFilter(item1));
+ Filter filter2 = new Not(new SameItemFilter(item2));
+
+ // equals()
+ Assert.assertEquals(filter1, filter1b);
+ Assert.assertFalse(filter1.equals(filter2));
+ Assert.assertFalse(filter1.equals(origFilter));
+ Assert.assertFalse(filter1.equals(new And()));
+
+ // hashCode()
+ Assert.assertEquals(filter1.hashCode(), filter1b.hashCode());
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/data/util/filter/SimpleStringFilterTest.java b/compatibility-server/src/test/java/com/vaadin/data/util/filter/SimpleStringFilterTest.java
new file mode 100644
index 0000000000..dd8267107a
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/data/util/filter/SimpleStringFilterTest.java
@@ -0,0 +1,139 @@
+package com.vaadin.data.util.filter;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SimpleStringFilterTest
+ extends AbstractFilterTestBase<SimpleStringFilter> {
+
+ protected static TestItem<String, String> createTestItem() {
+ return new TestItem<String, String>("abcde", "TeSt");
+ }
+
+ protected TestItem<String, String> getTestItem() {
+ return createTestItem();
+ }
+
+ protected SimpleStringFilter f(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ return new SimpleStringFilter(propertyId, filterString, ignoreCase,
+ onlyMatchPrefix);
+ }
+
+ protected boolean passes(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ return f(propertyId, filterString, ignoreCase, onlyMatchPrefix)
+ .passesFilter(null, getTestItem());
+ }
+
+ @Test
+ public void testStartsWithCaseSensitive() {
+ Assert.assertTrue(passes(PROPERTY1, "ab", false, true));
+ Assert.assertTrue(passes(PROPERTY1, "", false, true));
+
+ Assert.assertFalse(passes(PROPERTY2, "ab", false, true));
+ Assert.assertFalse(passes(PROPERTY1, "AB", false, true));
+ }
+
+ @Test
+ public void testStartsWithCaseInsensitive() {
+ Assert.assertTrue(passes(PROPERTY1, "AB", true, true));
+ Assert.assertTrue(passes(PROPERTY2, "te", true, true));
+ Assert.assertFalse(passes(PROPERTY2, "AB", true, true));
+ }
+
+ @Test
+ public void testContainsCaseSensitive() {
+ Assert.assertTrue(passes(PROPERTY1, "ab", false, false));
+ Assert.assertTrue(passes(PROPERTY1, "abcde", false, false));
+ Assert.assertTrue(passes(PROPERTY1, "cd", false, false));
+ Assert.assertTrue(passes(PROPERTY1, "e", false, false));
+ Assert.assertTrue(passes(PROPERTY1, "", false, false));
+
+ Assert.assertFalse(passes(PROPERTY2, "ab", false, false));
+ Assert.assertFalse(passes(PROPERTY1, "es", false, false));
+ }
+
+ @Test
+ public void testContainsCaseInsensitive() {
+ Assert.assertTrue(passes(PROPERTY1, "AB", true, false));
+ Assert.assertTrue(passes(PROPERTY1, "aBcDe", true, false));
+ Assert.assertTrue(passes(PROPERTY1, "CD", true, false));
+ Assert.assertTrue(passes(PROPERTY1, "", true, false));
+
+ Assert.assertTrue(passes(PROPERTY2, "es", true, false));
+
+ Assert.assertFalse(passes(PROPERTY2, "ab", true, false));
+ }
+
+ @Test
+ public void testAppliesToProperty() {
+ SimpleStringFilter filter = f(PROPERTY1, "ab", false, true);
+ Assert.assertTrue(filter.appliesToProperty(PROPERTY1));
+ Assert.assertFalse(filter.appliesToProperty(PROPERTY2));
+ Assert.assertFalse(filter.appliesToProperty("other"));
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ SimpleStringFilter filter = f(PROPERTY1, "ab", false, true);
+
+ SimpleStringFilter f1 = f(PROPERTY2, "ab", false, true);
+ SimpleStringFilter f1b = f(PROPERTY2, "ab", false, true);
+ SimpleStringFilter f2 = f(PROPERTY1, "cd", false, true);
+ SimpleStringFilter f2b = f(PROPERTY1, "cd", false, true);
+ SimpleStringFilter f3 = f(PROPERTY1, "ab", true, true);
+ SimpleStringFilter f3b = f(PROPERTY1, "ab", true, true);
+ SimpleStringFilter f4 = f(PROPERTY1, "ab", false, false);
+ SimpleStringFilter f4b = f(PROPERTY1, "ab", false, false);
+
+ // equal but not same instance
+ Assert.assertEquals(f1, f1b);
+ Assert.assertEquals(f2, f2b);
+ Assert.assertEquals(f3, f3b);
+ Assert.assertEquals(f4, f4b);
+
+ // more than one property differ
+ Assert.assertFalse(f1.equals(f2));
+ Assert.assertFalse(f1.equals(f3));
+ Assert.assertFalse(f1.equals(f4));
+ Assert.assertFalse(f2.equals(f1));
+ Assert.assertFalse(f2.equals(f3));
+ Assert.assertFalse(f2.equals(f4));
+ Assert.assertFalse(f3.equals(f1));
+ Assert.assertFalse(f3.equals(f2));
+ Assert.assertFalse(f3.equals(f4));
+ Assert.assertFalse(f4.equals(f1));
+ Assert.assertFalse(f4.equals(f2));
+ Assert.assertFalse(f4.equals(f3));
+
+ // only one property differs
+ Assert.assertFalse(filter.equals(f1));
+ Assert.assertFalse(filter.equals(f2));
+ Assert.assertFalse(filter.equals(f3));
+ Assert.assertFalse(filter.equals(f4));
+
+ Assert.assertFalse(f1.equals(null));
+ Assert.assertFalse(f1.equals(new Object()));
+
+ Assert.assertEquals(f1.hashCode(), f1b.hashCode());
+ Assert.assertEquals(f2.hashCode(), f2b.hashCode());
+ Assert.assertEquals(f3.hashCode(), f3b.hashCode());
+ Assert.assertEquals(f4.hashCode(), f4b.hashCode());
+ }
+
+ @Test
+ public void testNonExistentProperty() {
+ Assert.assertFalse(passes("other1", "ab", false, true));
+ }
+
+ @Test
+ public void testNullValueForProperty() {
+ TestItem<String, String> item = createTestItem();
+ item.addItemProperty("other1", new NullProperty());
+
+ Assert.assertFalse(
+ f("other1", "ab", false, true).passesFilter(null, item));
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractBeanContainerListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractBeanContainerListenersTest.java
new file mode 100644
index 0000000000..98a2513515
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractBeanContainerListenersTest.java
@@ -0,0 +1,16 @@
+package com.vaadin.tests.server;
+
+import com.vaadin.data.Container.PropertySetChangeEvent;
+import com.vaadin.data.Container.PropertySetChangeListener;
+import com.vaadin.data.util.BeanItemContainer;
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+
+public class AbstractBeanContainerListenersTest
+ extends AbstractListenerMethodsTestBase {
+ public void testPropertySetChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(BeanItemContainer.class,
+ PropertySetChangeEvent.class, PropertySetChangeListener.class,
+ new BeanItemContainer<PropertySetChangeListener>(
+ PropertySetChangeListener.class));
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractContainerListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractContainerListenersTest.java
new file mode 100644
index 0000000000..5e0d464fe8
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractContainerListenersTest.java
@@ -0,0 +1,26 @@
+package com.vaadin.tests.server;
+
+import org.junit.Test;
+
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeListener;
+import com.vaadin.data.Container.PropertySetChangeEvent;
+import com.vaadin.data.Container.PropertySetChangeListener;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+
+public class AbstractContainerListenersTest
+ extends AbstractListenerMethodsTestBase {
+
+ @Test
+ public void testItemSetChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(IndexedContainer.class,
+ ItemSetChangeEvent.class, ItemSetChangeListener.class);
+ }
+
+ @Test
+ public void testPropertySetChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(IndexedContainer.class,
+ PropertySetChangeEvent.class, PropertySetChangeListener.class);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractInMemoryContainerListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractInMemoryContainerListenersTest.java
new file mode 100644
index 0000000000..d5a7131182
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/AbstractInMemoryContainerListenersTest.java
@@ -0,0 +1,14 @@
+package com.vaadin.tests.server;
+
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeListener;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+
+public class AbstractInMemoryContainerListenersTest
+ extends AbstractListenerMethodsTestBase {
+ public void testItemSetChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(IndexedContainer.class,
+ ItemSetChangeEvent.class, ItemSetChangeListener.class);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/IndexedContainerListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/IndexedContainerListenersTest.java
new file mode 100644
index 0000000000..d8a1290b68
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/IndexedContainerListenersTest.java
@@ -0,0 +1,26 @@
+package com.vaadin.tests.server;
+
+import org.junit.Test;
+
+import com.vaadin.data.Container.PropertySetChangeEvent;
+import com.vaadin.data.Container.PropertySetChangeListener;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+
+public class IndexedContainerListenersTest
+ extends AbstractListenerMethodsTestBase {
+
+ @Test
+ public void testValueChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(IndexedContainer.class, ValueChangeEvent.class,
+ ValueChangeListener.class);
+ }
+
+ @Test
+ public void testPropertySetChangeListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(IndexedContainer.class,
+ PropertySetChangeEvent.class, PropertySetChangeListener.class);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/SerializationTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/SerializationTest.java
new file mode 100644
index 0000000000..30b0729ace
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/SerializationTest.java
@@ -0,0 +1,135 @@
+package com.vaadin.tests.server;
+
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import org.junit.Test;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.data.util.MethodProperty;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.v7.data.validator.LegacyRegexpValidator;
+
+public class SerializationTest {
+
+ @Test
+ public void testValidators() throws Exception {
+ LegacyRegexpValidator validator = new LegacyRegexpValidator(".*",
+ "Error");
+ validator.validate("aaa");
+ LegacyRegexpValidator validator2 = serializeAndDeserialize(validator);
+ validator2.validate("aaa");
+ }
+
+ @Test
+ public void testIndedexContainerItemIds() throws Exception {
+ IndexedContainer ic = new IndexedContainer();
+ ic.addContainerProperty("prop1", String.class, null);
+ Object id = ic.addItem();
+ ic.getItem(id).getItemProperty("prop1").setValue("1");
+
+ Item item2 = ic.addItem("item2");
+ item2.getItemProperty("prop1").setValue("2");
+
+ serializeAndDeserialize(ic);
+ }
+
+ @Test
+ public void testMethodPropertyGetter() throws Exception {
+ MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
+ "dummyGetter");
+ serializeAndDeserialize(mp);
+ }
+
+ @Test
+ public void testMethodPropertyGetterAndSetter() throws Exception {
+ MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
+ "dummyGetterAndSetter");
+ serializeAndDeserialize(mp);
+ }
+
+ @Test
+ public void testMethodPropertyInt() throws Exception {
+ MethodProperty<?> mp = new MethodProperty<Object>(new Data(),
+ "dummyInt");
+ serializeAndDeserialize(mp);
+ }
+
+ @Test
+ public void testVaadinSession() throws Exception {
+ VaadinSession session = new VaadinSession(null);
+
+ session = serializeAndDeserialize(session);
+
+ assertNotNull(
+ "Pending access queue was not recreated after deserialization",
+ session.getPendingAccessQueue());
+ }
+
+ private static <S extends Serializable> S serializeAndDeserialize(S s)
+ throws IOException, ClassNotFoundException {
+ // Serialize and deserialize
+
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ ObjectOutputStream out = new ObjectOutputStream(bs);
+ out.writeObject(s);
+ byte[] data = bs.toByteArray();
+ ObjectInputStream in = new ObjectInputStream(
+ new ByteArrayInputStream(data));
+ @SuppressWarnings("unchecked")
+ S s2 = (S) in.readObject();
+
+ // using special toString(Object) method to avoid calling
+ // Property.toString(), which will be temporarily disabled
+ // TODO This is hilariously broken (#12723)
+ if (s.equals(s2)) {
+ System.out.println(toString(s) + " equals " + toString(s2));
+ } else {
+ System.out.println(toString(s) + " does NOT equal " + toString(s2));
+ }
+
+ return s2;
+ }
+
+ private static String toString(Object o) {
+ if (o instanceof Property) {
+ return String.valueOf(((Property<?>) o).getValue());
+ } else {
+ return String.valueOf(o);
+ }
+ }
+
+ public static class Data implements Serializable {
+ private String dummyGetter;
+ private String dummyGetterAndSetter;
+ private int dummyInt;
+
+ public String getDummyGetterAndSetter() {
+ return dummyGetterAndSetter;
+ }
+
+ public void setDummyGetterAndSetter(String dummyGetterAndSetter) {
+ this.dummyGetterAndSetter = dummyGetterAndSetter;
+ }
+
+ public int getDummyInt() {
+ return dummyInt;
+ }
+
+ public void setDummyInt(int dummyInt) {
+ this.dummyInt = dummyInt;
+ }
+
+ public String getDummyGetter() {
+ return dummyGetter;
+ }
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarBasicsTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarBasicsTest.java
new file mode 100644
index 0000000000..7d011afc8b
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarBasicsTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.calendar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.Calendar.TimeFormat;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.BackwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.DateClickEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.EventResize;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.ForwardEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.MoveEvent;
+import com.vaadin.ui.components.calendar.CalendarComponentEvents.WeekClick;
+import com.vaadin.ui.components.calendar.event.BasicEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEventProvider;
+
+/**
+ * Basic API tests for the calendar
+ */
+public class CalendarBasicsTest {
+
+ @Test
+ public void testEmptyConstructorInitialization() {
+
+ Calendar calendar = new Calendar();
+
+ // The calendar should have a basic event provider with no events
+ CalendarEventProvider provider = calendar.getEventProvider();
+ assertNotNull("Event provider should not be null", provider);
+
+ // Basic event handlers should be registered
+ assertNotNull(calendar.getHandler(BackwardEvent.EVENT_ID));
+ assertNotNull(calendar.getHandler(ForwardEvent.EVENT_ID));
+ assertNotNull(calendar.getHandler(WeekClick.EVENT_ID));
+ assertNotNull(calendar.getHandler(DateClickEvent.EVENT_ID));
+ assertNotNull(calendar.getHandler(MoveEvent.EVENT_ID));
+ assertNotNull(calendar.getHandler(EventResize.EVENT_ID));
+
+ // Calendar should have undefined size
+ assertTrue(calendar.getWidth() < 0);
+ assertTrue(calendar.getHeight() < 0);
+ }
+
+ @Test
+ public void testConstructorWithCaption() {
+ final String caption = "My Calendar Caption";
+ Calendar calendar = new Calendar(caption);
+ assertEquals(caption, calendar.getCaption());
+ }
+
+ @Test
+ public void testConstructorWithCustomEventProvider() {
+ BasicEventProvider myProvider = new BasicEventProvider();
+ Calendar calendar = new Calendar(myProvider);
+ assertEquals(myProvider, calendar.getEventProvider());
+ }
+
+ @Test
+ public void testConstructorWithCustomEventProviderAndCaption() {
+ BasicEventProvider myProvider = new BasicEventProvider();
+ final String caption = "My Calendar Caption";
+ Calendar calendar = new Calendar(caption, myProvider);
+ assertEquals(caption, calendar.getCaption());
+ assertEquals(myProvider, calendar.getEventProvider());
+ }
+
+ @Test
+ public void testDefaultStartAndEndDates() {
+ Calendar calendar = new Calendar();
+
+ // If no start and end date is set the calendar will display the current
+ // week
+ java.util.Calendar c = new GregorianCalendar();
+ java.util.Calendar c2 = new GregorianCalendar();
+
+ c2.setTime(calendar.getStartDate());
+ assertEquals(c.getFirstDayOfWeek(),
+ c2.get(java.util.Calendar.DAY_OF_WEEK));
+ c2.setTime(calendar.getEndDate());
+
+ c.set(java.util.Calendar.DAY_OF_WEEK, c.getFirstDayOfWeek() + 6);
+ assertEquals(c.get(java.util.Calendar.DAY_OF_WEEK),
+ c2.get(java.util.Calendar.DAY_OF_WEEK));
+ }
+
+ @Test
+ public void testCustomStartAndEndDates() {
+ Calendar calendar = new Calendar();
+ java.util.Calendar c = new GregorianCalendar();
+
+ Date start = c.getTime();
+ c.add(java.util.Calendar.DATE, 3);
+ Date end = c.getTime();
+
+ calendar.setStartDate(start);
+ calendar.setEndDate(end);
+
+ assertEquals(start.getTime(), calendar.getStartDate().getTime());
+ assertEquals(end.getTime(), calendar.getEndDate().getTime());
+ }
+
+ @Test
+ public void testCustomLocale() {
+ Calendar calendar = new Calendar();
+ calendar.setLocale(Locale.CANADA_FRENCH);
+
+ // Setting the locale should set the internal calendars locale
+ assertEquals(Locale.CANADA_FRENCH, calendar.getLocale());
+ java.util.Calendar c = new GregorianCalendar(Locale.CANADA_FRENCH);
+ assertEquals(c.getTimeZone().getRawOffset(),
+ calendar.getInternalCalendar().getTimeZone().getRawOffset());
+ }
+
+ @Test
+ public void testTimeFormat() {
+ Calendar calendar = new Calendar();
+
+ // The default timeformat depends on the current locale
+ calendar.setLocale(Locale.ENGLISH);
+ assertEquals(TimeFormat.Format12H, calendar.getTimeFormat());
+
+ calendar.setLocale(Locale.ITALIAN);
+ assertEquals(TimeFormat.Format24H, calendar.getTimeFormat());
+
+ // Setting a specific time format overrides the locale
+ calendar.setTimeFormat(TimeFormat.Format12H);
+ assertEquals(TimeFormat.Format12H, calendar.getTimeFormat());
+ }
+
+ @Test
+ public void testTimeZone() {
+ Calendar calendar = new Calendar();
+ calendar.setLocale(Locale.CANADA_FRENCH);
+
+ // By default the calendars timezone is returned
+ assertEquals(calendar.getInternalCalendar().getTimeZone(),
+ calendar.getTimeZone());
+
+ // One can override the default behaviour by specifying a timezone
+ TimeZone customTimeZone = TimeZone.getTimeZone("Europe/Helsinki");
+ calendar.setTimeZone(customTimeZone);
+ assertEquals(customTimeZone, calendar.getTimeZone());
+ }
+
+ @Test
+ public void testVisibleDaysOfWeek() {
+ Calendar calendar = new Calendar();
+
+ // The defaults are the whole week
+ assertEquals(1, calendar.getFirstVisibleDayOfWeek());
+ assertEquals(7, calendar.getLastVisibleDayOfWeek());
+
+ calendar.setFirstVisibleDayOfWeek(0); // Invalid input
+ assertEquals(1, calendar.getFirstVisibleDayOfWeek());
+
+ calendar.setLastVisibleDayOfWeek(0); // Invalid input
+ assertEquals(7, calendar.getLastVisibleDayOfWeek());
+
+ calendar.setFirstVisibleDayOfWeek(8); // Invalid input
+ assertEquals(1, calendar.getFirstVisibleDayOfWeek());
+
+ calendar.setLastVisibleDayOfWeek(8); // Invalid input
+ assertEquals(7, calendar.getLastVisibleDayOfWeek());
+
+ calendar.setFirstVisibleDayOfWeek(4);
+ assertEquals(4, calendar.getFirstVisibleDayOfWeek());
+
+ calendar.setLastVisibleDayOfWeek(6);
+ assertEquals(6, calendar.getLastVisibleDayOfWeek());
+
+ calendar.setFirstVisibleDayOfWeek(7); // Invalid since last day is 6
+ assertEquals(4, calendar.getFirstVisibleDayOfWeek());
+
+ calendar.setLastVisibleDayOfWeek(2); // Invalid since first day is 4
+ assertEquals(6, calendar.getLastVisibleDayOfWeek());
+ }
+
+ @Test
+ public void testVisibleHoursInDay() {
+ Calendar calendar = new Calendar();
+
+ // Defaults are the whole day
+ assertEquals(0, calendar.getFirstVisibleHourOfDay());
+ assertEquals(23, calendar.getLastVisibleHourOfDay());
+ }
+
+ @Test
+ public void isClientChangeAllowed_connectorEnabled() {
+ TestCalendar calendar = new TestCalendar(true);
+ Assert.assertTrue(
+ "Calendar with enabled connector doesn't allow client change",
+ calendar.isClientChangeAllowed());
+ }
+
+ // regression test to ensure old functionality is not broken
+ @Test
+ public void defaultFirstDayOfWeek() {
+ Calendar calendar = new Calendar();
+ calendar.setLocale(Locale.GERMAN);
+ // simulating consequences of markAsDirty
+ calendar.beforeClientResponse(true);
+ assertEquals(java.util.Calendar.MONDAY,
+ calendar.getInternalCalendar().getFirstDayOfWeek());
+ }
+
+ @Test
+ public void customFirstDayOfWeek() {
+ Calendar calendar = new Calendar();
+ calendar.setLocale(Locale.GERMAN);
+ calendar.setFirstDayOfWeek(java.util.Calendar.SUNDAY);
+
+ // simulating consequences of markAsDirty
+ calendar.beforeClientResponse(true);
+ assertEquals(java.util.Calendar.SUNDAY,
+ calendar.getInternalCalendar().getFirstDayOfWeek());
+ }
+
+ @Test
+ public void customFirstDayOfWeekCanSetEvenBeforeLocale() {
+ Calendar calendar = new Calendar();
+ calendar.setFirstDayOfWeek(java.util.Calendar.SUNDAY);
+
+ calendar.setLocale(Locale.GERMAN);
+ // simulating consequences of markAsDirty
+ calendar.beforeClientResponse(true);
+ assertEquals(java.util.Calendar.SUNDAY,
+ calendar.getInternalCalendar().getFirstDayOfWeek());
+ }
+
+ @Test
+ public void customFirstDayOfWeekSetNullRestoresDefault() {
+ Calendar calendar = new Calendar();
+ calendar.setLocale(Locale.GERMAN);
+ calendar.setFirstDayOfWeek(java.util.Calendar.SUNDAY);
+ calendar.setFirstDayOfWeek(null);
+ // simulating consequences of markAsDirty
+ calendar.beforeClientResponse(true);
+ assertEquals(java.util.Calendar.MONDAY,
+ calendar.getInternalCalendar().getFirstDayOfWeek());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void customFirstDayOfWeekValidation() {
+ Calendar calendar = new Calendar();
+ int someWrongDayOfWeek = 10;
+ calendar.setFirstDayOfWeek(someWrongDayOfWeek);
+ }
+
+ private static class TestCalendar extends Calendar {
+ TestCalendar(boolean connectorEnabled) {
+ isConnectorEnabled = connectorEnabled;
+ }
+
+ @Override
+ public boolean isConnectorEnabled() {
+ return isConnectorEnabled;
+ }
+
+ @Override
+ public boolean isClientChangeAllowed() {
+ return super.isClientChangeAllowed();
+ }
+
+ private final boolean isConnectorEnabled;
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarDeclarativeTest.java
new file mode 100644
index 0000000000..f6896ff15d
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/CalendarDeclarativeTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.calendar;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.Calendar.TimeFormat;
+
+public class CalendarDeclarativeTest extends DeclarativeTestBase<Calendar> {
+
+ @Test
+ public void testEmpty() {
+ verifyDeclarativeDesign("<vaadin-calendar></vaadin-calendar>",
+ new Calendar());
+ }
+
+ @Test
+ public void testCalendarAllFeatures() throws ParseException {
+ String design = "<vaadin-calendar start-date='2014-11-17' end-date='2014-11-23' "
+ + "first-visible-day-of-week=2 last-visible-day-of-week=5 "
+ + "time-zone='EST' time-format='12h' first-visible-hour-of-day=8 "
+ + "last-visible-hour-of-day=18 weekly-caption-format='mmm MM/dd' />";
+
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
+ Calendar calendar = new Calendar();
+ calendar.setStartDate(format.parse("2014-11-17"));
+ calendar.setEndDate(format.parse("2014-11-23"));
+ calendar.setFirstVisibleDayOfWeek(2);
+ calendar.setLastVisibleDayOfWeek(5);
+ calendar.setTimeZone(TimeZone.getTimeZone("EST"));
+ calendar.setTimeFormat(TimeFormat.Format12H);
+ calendar.setFirstVisibleHourOfDay(8);
+ calendar.setLastVisibleHourOfDay(18);
+ calendar.setWeeklyCaptionFormat("mmm MM/dd");
+ verifyDeclarativeDesign(design, calendar);
+ }
+
+ protected void verifyDeclarativeDesign(String design, Calendar expected) {
+ testRead(design, expected);
+ testWrite(design, expected);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerDataSourceTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerDataSourceTest.java
new file mode 100644
index 0000000000..9cc78269f8
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerDataSourceTest.java
@@ -0,0 +1,396 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.calendar;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.Sortable;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.util.BeanItemContainer;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.ui.Calendar;
+import com.vaadin.ui.components.calendar.ContainerEventProvider;
+import com.vaadin.ui.components.calendar.event.BasicEvent;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+
+public class ContainerDataSourceTest {
+
+ private Calendar calendar;
+
+ @Before
+ public void setUp() {
+ calendar = new Calendar();
+ }
+
+ /**
+ * Tests adding a bean item container to the Calendar
+ */
+ @Test
+ public void testWithBeanItemContainer() {
+
+ // Create a container to use as a datasource
+ Indexed container = createTestBeanItemContainer();
+
+ // Set datasource
+ calendar.setContainerDataSource(container);
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.setTime(((CalendarEvent) container.getIdByIndex(0)).getStart());
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Test the all events are returned
+ List<CalendarEvent> events = calendar.getEventProvider()
+ .getEvents(start, end);
+ assertEquals(container.size(), events.size());
+
+ // Test that a certain range is returned
+ cal.setTime(((CalendarEvent) container.getIdByIndex(6)).getStart());
+ end = cal.getTime();
+ events = calendar.getEventProvider().getEvents(start, end);
+ assertEquals(6, events.size());
+ }
+
+ /**
+ * This tests tests that if you give the Calendar an unsorted (== not sorted
+ * by starting date) container then the calendar should gracefully handle
+ * it. In this case the size of the container will be wrong. The test is
+ * exactly the same as {@link #testWithBeanItemContainer()} except that the
+ * beans has been intentionally sorted by caption instead of date.
+ */
+ @Test
+ public void testWithUnsortedBeanItemContainer() {
+ // Create a container to use as a datasource
+ Indexed container = createTestBeanItemContainer();
+
+ // Make the container sorted by caption
+ ((Sortable) container).sort(new Object[] { "caption" },
+ new boolean[] { true });
+
+ // Set data source
+ calendar.setContainerDataSource(container);
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.setTime(((CalendarEvent) container.getIdByIndex(0)).getStart());
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Test the all events are returned
+ List<CalendarEvent> events = calendar.getEventProvider()
+ .getEvents(start, end);
+ assertEquals(container.size(), events.size());
+
+ // Test that a certain range is returned
+ cal.setTime(((CalendarEvent) container.getIdByIndex(6)).getStart());
+ end = cal.getTime();
+ events = calendar.getEventProvider().getEvents(start, end);
+
+ // The events size is 1 since the getEvents returns the wrong range
+ assertEquals(1, events.size());
+ }
+
+ /**
+ * Tests adding a Indexed container to the Calendar
+ */
+ @Test
+ public void testWithIndexedContainer() {
+
+ // Create a container to use as a datasource
+ Indexed container = createTestIndexedContainer();
+
+ // Set datasource
+ calendar.setContainerDataSource(container, "testCaption",
+ "testDescription", "testStartDate", "testEndDate", null);
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.setTime((Date) container.getItem(container.getIdByIndex(0))
+ .getItemProperty("testStartDate").getValue());
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Test the all events are returned
+ List<CalendarEvent> events = calendar.getEventProvider()
+ .getEvents(start, end);
+ assertEquals(container.size(), events.size());
+
+ // Check that event values are present
+ CalendarEvent e = events.get(0);
+ assertEquals("Test 1", e.getCaption());
+ assertEquals("Description 1", e.getDescription());
+ assertTrue(e.getStart().compareTo(start) == 0);
+
+ // Test that a certain range is returned
+ cal.setTime((Date) container.getItem(container.getIdByIndex(6))
+ .getItemProperty("testStartDate").getValue());
+ end = cal.getTime();
+ events = calendar.getEventProvider().getEvents(start, end);
+ assertEquals(6, events.size());
+ }
+
+ @Test
+ public void testNullLimitsBeanItemContainer() {
+ // Create a container to use as a datasource
+ Indexed container = createTestBeanItemContainer();
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.setTime(((CalendarEvent) container.getIdByIndex(0)).getStart());
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Set datasource
+ calendar.setContainerDataSource(container);
+
+ // Test null start time
+ List<CalendarEvent> events = calendar.getEventProvider().getEvents(null,
+ end);
+ assertEquals(container.size(), events.size());
+
+ // Test null end time
+ events = calendar.getEventProvider().getEvents(start, null);
+ assertEquals(container.size(), events.size());
+
+ // Test both null times
+ events = calendar.getEventProvider().getEvents(null, null);
+ assertEquals(container.size(), events.size());
+ }
+
+ @Test
+ public void testNullLimitsIndexedContainer() {
+ // Create a container to use as a datasource
+ Indexed container = createTestIndexedContainer();
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ cal.setTime((Date) container.getItem(container.getIdByIndex(0))
+ .getItemProperty("testStartDate").getValue());
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Set datasource
+ calendar.setContainerDataSource(container, "testCaption",
+ "testDescription", "testStartDate", "testEndDate", null);
+
+ // Test null start time
+ List<CalendarEvent> events = calendar.getEventProvider().getEvents(null,
+ end);
+ assertEquals(container.size(), events.size());
+
+ // Test null end time
+ events = calendar.getEventProvider().getEvents(start, null);
+ assertEquals(container.size(), events.size());
+
+ // Test both null times
+ events = calendar.getEventProvider().getEvents(null, null);
+ assertEquals(container.size(), events.size());
+ }
+
+ /**
+ * Tests the addEvent convenience method with the default event provider
+ */
+ @Test
+ public void testAddEventConvinienceMethod() {
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+
+ // Add an event
+ BasicEvent event = new BasicEvent("Test", "Test", start);
+ calendar.addEvent(event);
+
+ // Ensure event exists
+ List<CalendarEvent> events = calendar.getEvents(start, end);
+ assertEquals(1, events.size());
+ assertEquals(events.get(0).getCaption(), event.getCaption());
+ assertEquals(events.get(0).getDescription(), event.getDescription());
+ assertEquals(events.get(0).getStart(), event.getStart());
+ }
+
+ /**
+ * Test the removeEvent convenience method with the default event provider
+ */
+ @Test
+ public void testRemoveEventConvinienceMethod() {
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+
+ // Add an event
+ CalendarEvent event = new BasicEvent("Test", "Test", start);
+ calendar.addEvent(event);
+
+ // Ensure event exists
+ assertEquals(1, calendar.getEvents(start, end).size());
+
+ // Remove event
+ calendar.removeEvent(event);
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+ }
+
+ @Test
+ public void testAddEventConvinienceMethodWithCustomEventProvider() {
+
+ // Use a container data source
+ calendar.setEventProvider(new ContainerEventProvider(
+ new BeanItemContainer<BasicEvent>(BasicEvent.class)));
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+
+ // Add an event
+ BasicEvent event = new BasicEvent("Test", "Test", start);
+ calendar.addEvent(event);
+
+ // Ensure event exists
+ List<CalendarEvent> events = calendar.getEvents(start, end);
+ assertEquals(1, events.size());
+ assertEquals(events.get(0).getCaption(), event.getCaption());
+ assertEquals(events.get(0).getDescription(), event.getDescription());
+ assertEquals(events.get(0).getStart(), event.getStart());
+ }
+
+ @Test
+ public void testRemoveEventConvinienceMethodWithCustomEventProvider() {
+
+ // Use a container data source
+ calendar.setEventProvider(new ContainerEventProvider(
+ new BeanItemContainer<BasicEvent>(BasicEvent.class)));
+
+ // Start and end dates to query for
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ Date start = cal.getTime();
+ cal.add(java.util.Calendar.MONTH, 1);
+ Date end = cal.getTime();
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+
+ // Add an event
+ BasicEvent event = new BasicEvent("Test", "Test", start);
+ calendar.addEvent(event);
+
+ // Ensure event exists
+ List<CalendarEvent> events = calendar.getEvents(start, end);
+ assertEquals(1, events.size());
+
+ // Remove event
+ calendar.removeEvent(event);
+
+ // Ensure no events
+ assertEquals(0, calendar.getEvents(start, end).size());
+ }
+
+ @Test
+ public void testStyleNamePropertyRetrieved() {
+ IndexedContainer ic = (IndexedContainer) createTestIndexedContainer();
+ ic.addContainerProperty("testStyleName", String.class, "");
+ for (int i = 0; i < 10; i++) {
+ Item item = ic.getItem(ic.getIdByIndex(i));
+ @SuppressWarnings("unchecked")
+ Property<String> itemProperty = item
+ .getItemProperty("testStyleName");
+ itemProperty.setValue("testStyle");
+ }
+
+ ContainerEventProvider provider = new ContainerEventProvider(ic);
+ provider.setCaptionProperty("testCaption");
+ provider.setDescriptionProperty("testDescription");
+ provider.setStartDateProperty("testStartDate");
+ provider.setEndDateProperty("testEndDate");
+ provider.setStyleNameProperty("testStyleName");
+
+ calendar.setEventProvider(provider);
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ Date now = cal.getTime();
+ cal.add(java.util.Calendar.DAY_OF_MONTH, 20);
+ Date then = cal.getTime();
+ List<CalendarEvent> events = calendar.getEventProvider().getEvents(now,
+ then);
+ for (CalendarEvent ce : events) {
+ assertEquals("testStyle", ce.getStyleName());
+ }
+ }
+
+ private static Indexed createTestBeanItemContainer() {
+ BeanItemContainer<CalendarEvent> eventContainer = new BeanItemContainer<CalendarEvent>(
+ CalendarEvent.class);
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ for (int i = 1; i <= 10; i++) {
+ eventContainer.addBean(new BasicEvent("Test " + i,
+ "Description " + i, cal.getTime()));
+ cal.add(java.util.Calendar.DAY_OF_MONTH, 2);
+ }
+ return eventContainer;
+ }
+
+ private static Indexed createTestIndexedContainer() {
+ IndexedContainer container = new IndexedContainer();
+ container.addContainerProperty("testCaption", String.class, "");
+ container.addContainerProperty("testDescription", String.class, "");
+ container.addContainerProperty("testStartDate", Date.class, null);
+ container.addContainerProperty("testEndDate", Date.class, null);
+
+ java.util.Calendar cal = java.util.Calendar.getInstance();
+ for (int i = 1; i <= 10; i++) {
+ Item item = container.getItem(container.addItem());
+ item.getItemProperty("testCaption").setValue("Test " + i);
+ item.getItemProperty("testDescription")
+ .setValue("Description " + i);
+ item.getItemProperty("testStartDate").setValue(cal.getTime());
+ item.getItemProperty("testEndDate").setValue(cal.getTime());
+ cal.add(java.util.Calendar.DAY_OF_MONTH, 2);
+ }
+ return container;
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerEventProviderTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerEventProviderTest.java
new file mode 100644
index 0000000000..401b5861ce
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/calendar/ContainerEventProviderTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.calendar;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.data.util.BeanItemContainer;
+import com.vaadin.ui.components.calendar.ContainerEventProvider;
+import com.vaadin.ui.components.calendar.event.CalendarEvent;
+
+/**
+ *
+ * @author Vaadin Ltd
+ */
+public class ContainerEventProviderTest {
+
+ @Test
+ public void testDefaultAllDayProperty() {
+ ContainerEventProvider provider = new ContainerEventProvider(null);
+ Assert.assertEquals(ContainerEventProvider.ALL_DAY_PROPERTY,
+ provider.getAllDayProperty());
+
+ }
+
+ @Test
+ public void testSetAllDayProperty() {
+ ContainerEventProvider provider = new ContainerEventProvider(null);
+ Object prop = new Object();
+ provider.setAllDayProperty(prop);
+ Assert.assertEquals(prop, provider.getAllDayProperty());
+ }
+
+ @Test
+ public void testGetEvents() {
+ BeanItemContainer<EventBean> container = new BeanItemContainer<EventBean>(
+ EventBean.class);
+ EventBean bean = new EventBean();
+ container.addBean(bean);
+ ContainerEventProvider provider = new ContainerEventProvider(container);
+ List<CalendarEvent> events = provider.getEvents(bean.getStart(),
+ bean.getEnd());
+ Assert.assertTrue(events.get(0).isAllDay());
+ }
+
+ public static class EventBean {
+
+ public boolean isAllDay() {
+ return true;
+ }
+
+ public void setAllDay(boolean allDay) {
+ }
+
+ public Date getStart() {
+ return Calendar.getInstance().getTime();
+ }
+
+ public Date getEnd() {
+ Calendar calendar = Calendar.getInstance();
+ calendar.add(Calendar.MINUTE, 10);
+ return calendar.getTime();
+ }
+
+ public void setStart(Date date) {
+ }
+
+ public void setEnd(Date date) {
+ }
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java
new file mode 100644
index 0000000000..2e3b8e412e
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/AbstractColorPickerDeclarativeTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.colorpicker;
+
+import org.junit.Test;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.AbstractColorPicker;
+import com.vaadin.ui.AbstractColorPicker.PopupStyle;
+import com.vaadin.ui.ColorPicker;
+import com.vaadin.ui.ColorPickerArea;
+
+public class AbstractColorPickerDeclarativeTest
+ extends DeclarativeTestBase<AbstractColorPicker> {
+
+ @Test
+ public void testAllAbstractColorPickerFeatures() {
+ String design = "<vaadin-color-picker color='#fafafa' default-caption-enabled position='100,100'"
+ + " popup-style='simple' rgb-visibility='false' hsv-visibility='false'"
+ + " history-visibility=false textfield-visibility=false />";
+ ColorPicker colorPicker = new ColorPicker();
+ int colorInt = Integer.parseInt("fafafa", 16);
+ colorPicker.setColor(new Color(colorInt));
+ colorPicker.setDefaultCaptionEnabled(true);
+ colorPicker.setPosition(100, 100);
+ colorPicker.setPopupStyle(PopupStyle.POPUP_SIMPLE);
+ colorPicker.setRGBVisibility(false);
+ colorPicker.setHSVVisibility(false);
+ colorPicker.setSwatchesVisibility(true);
+ colorPicker.setHistoryVisibility(false);
+ colorPicker.setTextfieldVisibility(false);
+
+ testWrite(design, colorPicker);
+ testRead(design, colorPicker);
+ }
+
+ @Test
+ public void testEmptyColorPicker() {
+ String design = "<vaadin-color-picker />";
+ ColorPicker colorPicker = new ColorPicker();
+ testRead(design, colorPicker);
+ testWrite(design, colorPicker);
+ }
+
+ @Test
+ public void testAllAbstractColorPickerAreaFeatures() {
+ String design = "<vaadin-color-picker-area color='#fafafa' default-caption-enabled position='100,100'"
+ + " popup-style='simple' rgb-visibility='false' hsv-visibility='false'"
+ + " history-visibility=false textfield-visibility=false />";
+ AbstractColorPicker colorPicker = new ColorPickerArea();
+ int colorInt = Integer.parseInt("fafafa", 16);
+ colorPicker.setColor(new Color(colorInt));
+ colorPicker.setDefaultCaptionEnabled(true);
+ colorPicker.setPosition(100, 100);
+ colorPicker.setPopupStyle(PopupStyle.POPUP_SIMPLE);
+ colorPicker.setRGBVisibility(false);
+ colorPicker.setHSVVisibility(false);
+ colorPicker.setSwatchesVisibility(true);
+ colorPicker.setHistoryVisibility(false);
+ colorPicker.setTextfieldVisibility(false);
+
+ testWrite(design, colorPicker);
+ testRead(design, colorPicker);
+ }
+
+ @Test
+ public void testEmptyColorPickerArea() {
+ String design = "<vaadin-color-picker-area />";
+ AbstractColorPicker colorPicker = new ColorPickerArea();
+ testRead(design, colorPicker);
+ testWrite(design, colorPicker);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/ColorConversionsTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/ColorConversionsTest.java
new file mode 100644
index 0000000000..a55ed89691
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/colorpicker/ColorConversionsTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.colorpicker;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+import com.vaadin.shared.ui.colorpicker.Color;
+
+public class ColorConversionsTest {
+
+ @Test
+ public void convertHSL2RGB() {
+
+ int rgb = Color.HSLtoRGB(100, 50, 50);
+ Color c = new Color(rgb);
+ assertEquals(106, c.getRed());
+ assertEquals(191, c.getGreen());
+ assertEquals(64, c.getBlue());
+ assertEquals("#6abf40", c.getCSS());
+
+ rgb = Color.HSLtoRGB(0, 50, 50);
+ c = new Color(rgb);
+ assertEquals(191, c.getRed());
+ assertEquals(64, c.getGreen());
+ assertEquals(64, c.getBlue());
+ assertEquals("#bf4040", c.getCSS());
+
+ rgb = Color.HSLtoRGB(50, 0, 50);
+ c = new Color(rgb);
+ assertEquals(128, c.getRed());
+ assertEquals(128, c.getGreen());
+ assertEquals(128, c.getBlue());
+ assertEquals("#808080", c.getCSS());
+
+ rgb = Color.HSLtoRGB(50, 100, 0);
+ c = new Color(rgb);
+ assertEquals(0, c.getRed(), 0);
+ assertEquals(0, c.getGreen(), 0);
+ assertEquals(0, c.getBlue(), 0);
+ assertEquals("#000000", c.getCSS());
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java
new file mode 100644
index 0000000000..482267f63d
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxDeclarativeTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.combobox;
+
+import org.junit.Test;
+
+import com.vaadin.shared.ui.combobox.FilteringMode;
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.ComboBox;
+
+public class ComboBoxDeclarativeTest extends DeclarativeTestBase<ComboBox> {
+
+ @Test
+ public void testReadOnlyWithOptionsRead() {
+ testRead(getReadOnlyWithOptionsDesign(),
+ getReadOnlyWithOptionsExpected());
+ }
+
+ private ComboBox getReadOnlyWithOptionsExpected() {
+ ComboBox cb = new ComboBox();
+ cb.setTextInputAllowed(false);
+ cb.addItem("Hello");
+ cb.addItem("World");
+ return cb;
+ }
+
+ private String getReadOnlyWithOptionsDesign() {
+ return "<vaadin-combo-box text-input-allowed='false'><option>Hello</option><option>World</option></vaadin-combo-box>";
+ }
+
+ @Test
+ public void testReadOnlyWithOptionsWrite() {
+ testWrite(stripOptionTags(getReadOnlyWithOptionsDesign()),
+ getReadOnlyWithOptionsExpected());
+ }
+
+ @Test
+ public void testBasicRead() {
+ testRead(getBasicDesign(), getBasicExpected());
+ }
+
+ @Test
+ public void testBasicWrite() {
+ testWrite(getBasicDesign(), getBasicExpected());
+ }
+
+ @Test
+ public void testReadOnlyValue() {
+ String design = "<vaadin-combo-box readonly value='foo'><option selected>foo</option></vaadin-combo-box>";
+
+ ComboBox comboBox = new ComboBox();
+ comboBox.addItems("foo", "bar");
+ comboBox.setValue("foo");
+ comboBox.setReadOnly(true);
+
+ testRead(design, comboBox);
+
+ // Selects items are not written out by default
+ String design2 = "<vaadin-combo-box readonly></vaadin-combo-box>";
+ testWrite(design2, comboBox);
+ }
+
+ private String getBasicDesign() {
+ return "<vaadin-combo-box input-prompt=\"Select something\" filtering-mode=\"off\" scroll-to-selected-item='false'>";
+ }
+
+ private ComboBox getBasicExpected() {
+ ComboBox cb = new ComboBox();
+ cb.setInputPrompt("Select something");
+ cb.setTextInputAllowed(true);
+ cb.setFilteringMode(FilteringMode.OFF);
+ cb.setScrollToSelectedItem(false);
+ return cb;
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxStateTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxStateTest.java
new file mode 100644
index 0000000000..e8ca64895a
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/combobox/ComboBoxStateTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.combobox;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.shared.ui.combobox.ComboBoxState;
+import com.vaadin.ui.ComboBox;
+
+/**
+ * Tests for ComboBox state.
+ *
+ */
+public class ComboBoxStateTest {
+ @Test
+ public void getState_comboboxHasCustomState() {
+ TestComboBox combobox = new TestComboBox();
+ ComboBoxState state = combobox.getState();
+ Assert.assertEquals("Unexpected state class", ComboBoxState.class,
+ state.getClass());
+ }
+
+ @Test
+ public void getPrimaryStyleName_comboboxHasCustomPrimaryStyleName() {
+ ComboBox combobox = new ComboBox();
+ ComboBoxState state = new ComboBoxState();
+ Assert.assertEquals("Unexpected primary style name",
+ state.primaryStyleName, combobox.getPrimaryStyleName());
+ }
+
+ @Test
+ public void comboboxStateHasCustomPrimaryStyleName() {
+ ComboBoxState state = new ComboBoxState();
+ Assert.assertEquals("Unexpected primary style name", "v-filterselect",
+ state.primaryStyleName);
+ }
+
+ private static class TestComboBox extends ComboBox {
+
+ @Override
+ public ComboBoxState getState() {
+ return super.getState();
+ }
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/ListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/ListenersTest.java
new file mode 100644
index 0000000000..ade24a0df6
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/ListenersTest.java
@@ -0,0 +1,143 @@
+package com.vaadin.tests.server.component.tree;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Tree.CollapseEvent;
+import com.vaadin.ui.Tree.CollapseListener;
+import com.vaadin.ui.Tree.ExpandEvent;
+import com.vaadin.ui.Tree.ExpandListener;
+
+public class ListenersTest implements ExpandListener, CollapseListener {
+ private int expandCalled;
+ private int collapseCalled;
+ private Object lastExpanded;
+ private Object lastCollapsed;
+
+ @Before
+ public void setUp() {
+ expandCalled = 0;
+ }
+
+ @Test
+ public void testExpandListener() {
+ Tree tree = createTree(10, 20, false);
+ tree.addListener((ExpandListener) this);
+ List<Object> rootIds = new ArrayList<Object>(tree.rootItemIds());
+
+ assertEquals(10, rootIds.size());
+ assertEquals(10 + 10 * 20 + 10, tree.size());
+
+ // Expanding should send one expand event for the root item id
+ tree.expandItem(rootIds.get(0));
+ assertEquals(1, expandCalled);
+ assertEquals(rootIds.get(0), lastExpanded);
+
+ // Expand should send one event for each expanded item id.
+ // In this case root + child 4
+ expandCalled = 0;
+ tree.expandItemsRecursively(rootIds.get(1));
+ assertEquals(2, expandCalled);
+ List<Object> c = new ArrayList<Object>(
+ tree.getChildren(rootIds.get(1)));
+
+ assertEquals(c.get(4), lastExpanded);
+
+ // Expanding an already expanded item should send no expand event
+ expandCalled = 0;
+ tree.expandItem(rootIds.get(0));
+ assertEquals(0, expandCalled);
+ }
+
+ /**
+ * Creates a tree with "rootItems" roots, each with "children" children,
+ * each with 1 child.
+ *
+ * @param rootItems
+ * @param children
+ * @param expand
+ * @return
+ */
+ private Tree createTree(int rootItems, int children, boolean expand) {
+ Tree tree = new Tree();
+ for (int i = 0; i < rootItems; i++) {
+ String rootId = "root " + i;
+ tree.addItem(rootId);
+ if (expand) {
+ tree.expandItemsRecursively(rootId);
+ } else {
+ tree.collapseItemsRecursively(rootId);
+
+ }
+ for (int j = 0; j < children; j++) {
+ String childId = "child " + i + "/" + j;
+ tree.addItem(childId);
+ tree.setParent(childId, rootId);
+ tree.setChildrenAllowed(childId, false);
+ if (j == 4) {
+ tree.setChildrenAllowed(childId, true);
+ Object grandChildId = tree.addItem();
+ tree.setParent(grandChildId, childId);
+ tree.setChildrenAllowed(grandChildId, false);
+ if (expand) {
+ tree.expandItemsRecursively(childId);
+ } else {
+ tree.collapseItemsRecursively(childId);
+ }
+ }
+ }
+ }
+
+ return tree;
+ }
+
+ @Test
+ public void testCollapseListener() {
+ Tree tree = createTree(7, 15, true);
+ tree.addListener((CollapseListener) this);
+
+ List<Object> rootIds = new ArrayList<Object>(tree.rootItemIds());
+
+ assertEquals(7, rootIds.size());
+ assertEquals(7 + 7 * 15 + 7, tree.size());
+
+ // Expanding should send one expand event for the root item id
+ tree.collapseItem(rootIds.get(0));
+ assertEquals(1, collapseCalled);
+ assertEquals(rootIds.get(0), lastCollapsed);
+
+ // Collapse sends one event for each collapsed node.
+ // In this case root + child 4
+ collapseCalled = 0;
+ tree.collapseItemsRecursively(rootIds.get(1));
+ assertEquals(2, collapseCalled);
+ List<Object> c = new ArrayList<Object>(
+ tree.getChildren(rootIds.get(1)));
+ assertEquals(c.get(4), lastCollapsed);
+
+ // Collapsing an already expanded item should send no expand event
+ collapseCalled = 0;
+ tree.collapseItem(rootIds.get(0));
+ assertEquals(0, collapseCalled);
+ }
+
+ @Override
+ public void nodeExpand(ExpandEvent event) {
+ lastExpanded = event.getItemId();
+ expandCalled++;
+
+ }
+
+ @Override
+ public void nodeCollapse(CollapseEvent event) {
+ lastCollapsed = event.getItemId();
+ collapseCalled++;
+
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java
new file mode 100644
index 0000000000..1e73f953b2
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeDeclarativeTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.tree;
+
+import org.junit.Test;
+
+import com.vaadin.server.ExternalResource;
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Tree.TreeDragMode;
+
+/**
+ * Tests the declarative support for implementations of {@link Tree}.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class TreeDeclarativeTest extends DeclarativeTestBase<Tree> {
+
+ @Test
+ public void testDragMode() {
+ String design = "<vaadin-tree drag-mode='node' />";
+
+ Tree tree = new Tree();
+ tree.setDragMode(TreeDragMode.NODE);
+
+ testRead(design, tree);
+ testWrite(design, tree);
+ }
+
+ @Test
+ public void testEmpty() {
+ testRead("<vaadin-tree />", new Tree());
+ testWrite("<vaadin-tree />", new Tree());
+ }
+
+ @Test
+ public void testNodes() {
+ String design = "<vaadin-tree>" //
+ + " <node text='Node'/>" //
+ + " <node text='Parent'>" //
+ + " <node text='Child'>" //
+ + " <node text='Grandchild'/>" //
+ + " </node>" //
+ + " </node>" //
+ + " <node text='With icon' icon='http://example.com/icon.png'/>" //
+ + "</vaadin-tree>";
+
+ Tree tree = new Tree();
+
+ tree.addItem("Node");
+
+ tree.addItem("Parent");
+
+ tree.addItem("Child");
+ tree.setParent("Child", "Parent");
+
+ tree.addItem("Grandchild");
+ tree.setParent("Grandchild", "Child");
+
+ tree.addItem("With icon");
+ tree.setItemIcon("With icon",
+ new ExternalResource("http://example.com/icon.png"));
+
+ testRead(design, tree);
+ testWrite(design, tree, true);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeListenersTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeListenersTest.java
new file mode 100644
index 0000000000..ec10c4fe39
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeListenersTest.java
@@ -0,0 +1,33 @@
+package com.vaadin.tests.server.component.tree;
+
+import org.junit.Test;
+
+import com.vaadin.event.ItemClickEvent;
+import com.vaadin.event.ItemClickEvent.ItemClickListener;
+import com.vaadin.tests.server.component.AbstractListenerMethodsTestBase;
+import com.vaadin.ui.Tree;
+import com.vaadin.ui.Tree.CollapseEvent;
+import com.vaadin.ui.Tree.CollapseListener;
+import com.vaadin.ui.Tree.ExpandEvent;
+import com.vaadin.ui.Tree.ExpandListener;
+
+public class TreeListenersTest extends AbstractListenerMethodsTestBase {
+
+ @Test
+ public void testExpandListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(Tree.class, ExpandEvent.class,
+ ExpandListener.class);
+ }
+
+ @Test
+ public void testItemClickListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(Tree.class, ItemClickEvent.class,
+ ItemClickListener.class);
+ }
+
+ @Test
+ public void testCollapseListenerAddGetRemove() throws Exception {
+ testListenerAddGetRemove(Tree.class, CollapseEvent.class,
+ CollapseListener.class);
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeTest.java
new file mode 100644
index 0000000000..ed455415ea
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/tree/TreeTest.java
@@ -0,0 +1,178 @@
+package com.vaadin.tests.server.component.tree;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Field;
+import java.util.HashSet;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.util.HierarchicalContainer;
+import com.vaadin.data.util.IndexedContainer;
+import com.vaadin.shared.ui.tree.TreeState;
+import com.vaadin.ui.Tree;
+
+public class TreeTest {
+
+ private Tree tree;
+ private Tree tree2;
+ private Tree tree3;
+ private Tree tree4;
+
+ @Before
+ public void setUp() {
+ tree = new Tree();
+ tree.addItem("parent");
+ tree.addItem("child");
+ tree.setChildrenAllowed("parent", true);
+ tree.setParent("child", "parent");
+
+ tree2 = new Tree("Caption");
+ tree2.addItem("parent");
+ tree2.addItem("child");
+ tree2.setChildrenAllowed("parent", true);
+ tree2.setParent("child", "parent");
+
+ tree3 = new Tree("Caption", null);
+ tree3.addItem("parent");
+ tree3.addItem("child");
+ tree3.setChildrenAllowed("parent", true);
+ tree3.setParent("child", "parent");
+
+ tree4 = new Tree("Caption", new IndexedContainer());
+ tree4.addItem("parent");
+ tree4.addItem("child");
+ tree4.setChildrenAllowed("parent", true);
+ tree4.setParent("child", "parent");
+ }
+
+ @Test
+ public void testRemoveChildren() {
+ assertTrue(tree.hasChildren("parent"));
+ tree.removeItem("child");
+ assertFalse(tree.hasChildren("parent"));
+
+ assertTrue(tree2.hasChildren("parent"));
+ tree2.removeItem("child");
+ assertFalse(tree2.hasChildren("parent"));
+
+ assertTrue(tree3.hasChildren("parent"));
+ tree3.removeItem("child");
+ assertFalse(tree3.hasChildren("parent"));
+
+ assertTrue(tree4.hasChildren("parent"));
+ tree4.removeItem("child");
+ assertFalse(tree4.hasChildren("parent"));
+ }
+
+ @Test
+ public void testContainerTypeIsHierarchical() {
+ assertTrue(HierarchicalContainer.class
+ .isAssignableFrom(tree.getContainerDataSource().getClass()));
+ assertTrue(HierarchicalContainer.class
+ .isAssignableFrom(tree2.getContainerDataSource().getClass()));
+ assertTrue(HierarchicalContainer.class
+ .isAssignableFrom(tree3.getContainerDataSource().getClass()));
+ assertFalse(HierarchicalContainer.class
+ .isAssignableFrom(tree4.getContainerDataSource().getClass()));
+ assertTrue(Container.Hierarchical.class
+ .isAssignableFrom(tree4.getContainerDataSource().getClass()));
+ }
+
+ @Test
+ public void testRemoveExpandedItems() throws Exception {
+ tree.expandItem("parent");
+ tree.expandItem("child");
+
+ Field expandedField = tree.getClass().getDeclaredField("expanded");
+ Field expandedItemIdField = tree.getClass()
+ .getDeclaredField("expandedItemId");
+
+ expandedField.setAccessible(true);
+ expandedItemIdField.setAccessible(true);
+
+ HashSet<Object> expanded = (HashSet<Object>) expandedField.get(tree);
+ Object expandedItemId = expandedItemIdField.get(tree);
+
+ assertEquals(2, expanded.size());
+ assertTrue("Contains parent", expanded.contains("parent"));
+ assertTrue("Contains child", expanded.contains("child"));
+ assertEquals("child", expandedItemId);
+
+ tree.removeItem("parent");
+
+ expanded = (HashSet<Object>) expandedField.get(tree);
+ expandedItemId = expandedItemIdField.get(tree);
+
+ assertEquals(1, expanded.size());
+ assertTrue("Contains child", expanded.contains("child"));
+ assertEquals("child", expandedItemId);
+
+ tree.removeItem("child");
+
+ expanded = (HashSet<Object>) expandedField.get(tree);
+ expandedItemId = expandedItemIdField.get(tree);
+
+ assertEquals(0, expanded.size());
+ assertNull(expandedItemId);
+ }
+
+ @Test
+ public void testRemoveExpandedItemsOnContainerChange() throws Exception {
+ tree.expandItem("parent");
+ tree.expandItem("child");
+
+ tree.setContainerDataSource(new HierarchicalContainer());
+
+ Field expandedField = tree.getClass().getDeclaredField("expanded");
+ Field expandedItemIdField = tree.getClass()
+ .getDeclaredField("expandedItemId");
+
+ expandedField.setAccessible(true);
+ expandedItemIdField.setAccessible(true);
+
+ HashSet<Object> expanded = (HashSet<Object>) expandedField.get(tree);
+ assertEquals(0, expanded.size());
+
+ Object expandedItemId = expandedItemIdField.get(tree);
+ assertNull(expandedItemId);
+ }
+
+ @Test
+ public void getState_treeHasCustomState() {
+ TestTree table = new TestTree();
+ TreeState state = table.getState();
+ Assert.assertEquals("Unexpected state class", TreeState.class,
+ state.getClass());
+ }
+
+ @Test
+ public void getPrimaryStyleName_treeHasCustomPrimaryStyleName() {
+ Tree table = new Tree();
+ TreeState state = new TreeState();
+ Assert.assertEquals("Unexpected primary style name",
+ state.primaryStyleName, table.getPrimaryStyleName());
+ }
+
+ @Test
+ public void treeStateHasCustomPrimaryStyleName() {
+ TreeState state = new TreeState();
+ Assert.assertEquals("Unexpected primary style name", "v-tree",
+ state.primaryStyleName);
+ }
+
+ private static class TestTree extends Tree {
+
+ @Override
+ public TreeState getState() {
+ return super.getState();
+ }
+ }
+
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java
new file mode 100644
index 0000000000..02a2ebfd77
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectDeclarativeTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.twincolselect;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import com.vaadin.tests.design.DeclarativeTestBase;
+import com.vaadin.ui.TwinColSelect;
+
+/**
+ * Test cases for reading the properties of selection components.
+ *
+ * @author Vaadin Ltd
+ */
+public class TwinColSelectDeclarativeTest
+ extends DeclarativeTestBase<TwinColSelect> {
+
+ public String getBasicDesign() {
+ return "<vaadin-twin-col-select rows=5 right-column-caption='Selected values' left-column-caption='Unselected values'>\n"
+ + " <option>First item</option>\n"
+ + " <option selected>Second item</option>\n"
+ + " <option selected>Third item</option>\n"
+ + "</vaadin-twin-col-select>";
+
+ }
+
+ public TwinColSelect getBasicExpected() {
+ TwinColSelect s = new TwinColSelect();
+ s.setRightColumnCaption("Selected values");
+ s.setLeftColumnCaption("Unselected values");
+ s.addItem("First item");
+ s.addItem("Second item");
+ s.addItem("Third item");
+ s.setValue(Arrays.asList(new Object[] { "Second item", "Third item" }));
+ s.setRows(5);
+ return s;
+ }
+
+ @Test
+ public void testReadBasic() {
+ testRead(getBasicDesign(), getBasicExpected());
+ }
+
+ @Test
+ public void testWriteBasic() {
+ testWrite(stripOptionTags(getBasicDesign()), getBasicExpected());
+ }
+
+ @Test
+ public void testReadEmpty() {
+ testRead("<vaadin-twin-col-select />", new TwinColSelect());
+ }
+
+ @Test
+ public void testWriteEmpty() {
+ testWrite("<vaadin-twin-col-select />", new TwinColSelect());
+ }
+
+} \ No newline at end of file
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectStateTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectStateTest.java
new file mode 100644
index 0000000000..9348de63c2
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/component/twincolselect/TwinColSelectStateTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2016 Vaadin Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.vaadin.tests.server.component.twincolselect;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.shared.ui.twincolselect.TwinColSelectState;
+import com.vaadin.ui.TwinColSelect;
+
+/**
+ * Tests for TwinColSelectState.
+ *
+ */
+public class TwinColSelectStateTest {
+
+ @Test
+ public void getState_selectHasCustomState() {
+ TestTwinColSelect select = new TestTwinColSelect();
+ TwinColSelectState state = select.getState();
+ Assert.assertEquals("Unexpected state class", TwinColSelectState.class,
+ state.getClass());
+ }
+
+ @Test
+ public void getPrimaryStyleName_selectHasCustomPrimaryStyleName() {
+ TwinColSelect table = new TwinColSelect();
+ TwinColSelectState state = new TwinColSelectState();
+ Assert.assertEquals("Unexpected primary style name",
+ state.primaryStyleName, table.getPrimaryStyleName());
+ }
+
+ @Test
+ public void selectStateHasCustomPrimaryStyleName() {
+ TwinColSelectState state = new TwinColSelectState();
+ Assert.assertEquals("Unexpected primary style name", "v-select-twincol",
+ state.primaryStyleName);
+ }
+
+ private static class TestTwinColSelect extends TwinColSelect {
+
+ @Override
+ public TwinColSelectState getState() {
+ return super.getState();
+ }
+ }
+}
diff --git a/compatibility-server/src/test/java/com/vaadin/tests/server/components/ComboBoxValueChangeTest.java b/compatibility-server/src/test/java/com/vaadin/tests/server/components/ComboBoxValueChangeTest.java
new file mode 100644
index 0000000000..66a4ec2370
--- /dev/null
+++ b/compatibility-server/src/test/java/com/vaadin/tests/server/components/ComboBoxValueChangeTest.java
@@ -0,0 +1,46 @@
+package com.vaadin.tests.server.components;
+
+import org.junit.Before;
+
+import com.vaadin.server.ServerRpcManager;
+import com.vaadin.server.ServerRpcMethodInvocation;
+import com.vaadin.shared.ui.combobox.ComboBoxServerRpc;
+import com.vaadin.ui.ComboBox;
+import com.vaadin.v7.ui.LegacyAbstractField;
+
+/**
+ * Check that the value change listener for a combo box is triggered exactly
+ * once when setting the value, at the correct time.
+ *
+ * See <a href="http://dev.vaadin.com/ticket/4394">Ticket 4394</a>.
+ */
+public class ComboBoxValueChangeTest
+ extends AbstractFieldValueChangeTestBase<Object> {
+
+ @Before
+ public void setUp() {
+ ComboBox combo = new ComboBox() {
+ @Override
+ public String getConnectorId() {
+ return "id";
+ }
+ };
+ combo.addItem("myvalue");
+ super.setUp(combo);
+ }
+
+ @Override
+ protected void setValue(LegacyAbstractField<Object> field) {
+ ComboBox combo = (ComboBox) field;
+ ServerRpcMethodInvocation invocation = new ServerRpcMethodInvocation(
+ combo.getConnectorId(), ComboBoxServerRpc.class,
+ "setSelectedItem", 1);
+ invocation.setParameters(new Object[] { "myvalue" });
+ try {
+ ServerRpcManager.applyInvocation(combo, invocation);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}