summaryrefslogtreecommitdiffstats
path: root/src/com/vaadin/data/util
diff options
context:
space:
mode:
authorHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
committerHenri Sara <henri.sara@itmill.com>2009-05-11 09:19:03 +0000
commitadc8c0ad3573272c236040c3a76005b9e73a5737 (patch)
treea3860704dbd5b82dc6af38684b80f8ef79a32722 /src/com/vaadin/data/util
parent5abc870dda584d0c2fc47fd5eec4ae3de3fa240e (diff)
downloadvaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.tar.gz
vaadin-framework-adc8c0ad3573272c236040c3a76005b9e73a5737.zip
#2904: initial bulk rename "com.itmill.toolkit" -> "com.vaadin"
- com.itmill.toolkit.external not yet fully renamed svn changeset:7715/svn branch:6.0
Diffstat (limited to 'src/com/vaadin/data/util')
-rw-r--r--src/com/vaadin/data/util/BeanItem.java194
-rw-r--r--src/com/vaadin/data/util/BeanItemContainer.java535
-rw-r--r--src/com/vaadin/data/util/ContainerHierarchicalWrapper.java688
-rw-r--r--src/com/vaadin/data/util/ContainerOrderedWrapper.java604
-rw-r--r--src/com/vaadin/data/util/FilesystemContainer.java894
-rw-r--r--src/com/vaadin/data/util/Filter.java90
-rw-r--r--src/com/vaadin/data/util/HierarchicalContainer.java314
-rw-r--r--src/com/vaadin/data/util/IndexedContainer.java1652
-rw-r--r--src/com/vaadin/data/util/MethodProperty.java938
-rw-r--r--src/com/vaadin/data/util/ObjectProperty.java349
-rw-r--r--src/com/vaadin/data/util/PropertyFormatter.java380
-rw-r--r--src/com/vaadin/data/util/PropertysetItem.java314
-rw-r--r--src/com/vaadin/data/util/QueryContainer.java641
-rw-r--r--src/com/vaadin/data/util/package.html37
14 files changed, 7630 insertions, 0 deletions
diff --git a/src/com/vaadin/data/util/BeanItem.java b/src/com/vaadin/data/util/BeanItem.java
new file mode 100644
index 0000000000..5e12bc3c1d
--- /dev/null
+++ b/src/com/vaadin/data/util/BeanItem.java
@@ -0,0 +1,194 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.beans.BeanInfo;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+
+import com.vaadin.data.Property;
+
+/**
+ * A wrapper class for adding the Item interface to any Java Bean.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class BeanItem extends PropertysetItem {
+
+ /**
+ * The bean which this Item is based on.
+ */
+ private final Object 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(Object bean) {
+ this(bean, getPropertyDescriptors(bean.getClass()));
+ }
+
+ /**
+ * <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(Object bean,
+ LinkedHashMap<String, PropertyDescriptor> propertyDescriptors) {
+
+ this.bean = bean;
+
+ for (PropertyDescriptor pd : propertyDescriptors.values()) {
+ final Method getMethod = pd.getReadMethod();
+ final Method setMethod = pd.getWriteMethod();
+ final Class<?> type = pd.getPropertyType();
+ final String name = pd.getName();
+ final Property p = new MethodProperty(type, bean, getMethod,
+ setMethod);
+ addItemProperty(name, p);
+
+ }
+ }
+
+ /**
+ * <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(Object bean, Collection<?> propertyIds) {
+
+ this.bean = bean;
+
+ // Create bean information
+ LinkedHashMap<String, PropertyDescriptor> pds = getPropertyDescriptors(bean
+ .getClass());
+
+ // Add all the bean properties as MethodProperties to this Item
+ for (Object id : propertyIds) {
+ PropertyDescriptor pd = pds.get(id);
+ if (pd != null) {
+ final String name = pd.getName();
+ final Method getMethod = pd.getReadMethod();
+ final Method setMethod = pd.getWriteMethod();
+ final Class<?> type = pd.getPropertyType();
+ final Property p = new MethodProperty(type, bean, getMethod,
+ setMethod);
+ addItemProperty(name, p);
+ }
+ }
+
+ }
+
+ /**
+ * <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(Object 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 LinkedHashMap<String, PropertyDescriptor> getPropertyDescriptors(
+ final Class<?> beanClass) {
+ final LinkedHashMap<String, PropertyDescriptor> pdMap = new LinkedHashMap<String, PropertyDescriptor>();
+
+ // Try to introspect, if it fails, we just have an empty Item
+ try {
+ final BeanInfo info = Introspector.getBeanInfo(beanClass);
+ final PropertyDescriptor[] pds = info.getPropertyDescriptors();
+
+ // Add all the bean properties as MethodProperties to this Item
+ for (int i = 0; i < pds.length; i++) {
+ final Method getMethod = pds[i].getReadMethod();
+ if ((getMethod != null)
+ && getMethod.getDeclaringClass() != Object.class) {
+ pdMap.put(pds[i].getName(), pds[i]);
+ }
+ }
+ } catch (final java.beans.IntrospectionException ignored) {
+ }
+
+ return pdMap;
+ }
+
+ /**
+ * Gets the underlying JavaBean object.
+ *
+ * @return the bean object.
+ */
+ public Object getBean() {
+ return bean;
+ }
+
+}
diff --git a/src/com/vaadin/data/util/BeanItemContainer.java b/src/com/vaadin/data/util/BeanItemContainer.java
new file mode 100644
index 0000000000..6a64e57f36
--- /dev/null
+++ b/src/com/vaadin/data/util/BeanItemContainer.java
@@ -0,0 +1,535 @@
+package com.vaadin.data.util;
+
+import java.beans.PropertyDescriptor;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Property;
+import com.vaadin.data.Container.Filterable;
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
+import com.vaadin.data.Container.Sortable;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.Property.ValueChangeNotifier;
+
+/**
+ * An {@link ArrayList} backed container for {@link BeanItem}s.
+ * <p>
+ * Bean objects act as identifiers. For this reason, they should implement
+ * Object.equals(Object) and Object.hashCode().
+ * </p>
+ *
+ * @param <BT>
+ *
+ * @since 5.4
+ */
+@SuppressWarnings("serial")
+public class BeanItemContainer<BT> implements Indexed, Sortable, Filterable,
+ ItemSetChangeNotifier, ValueChangeListener {
+ // filtered and unfiltered item IDs
+ private ArrayList<BT> list = new ArrayList<BT>();
+ private ArrayList<BT> allItems = new ArrayList<BT>();
+ private final Map<BT, BeanItem> beanToItem = new HashMap<BT, BeanItem>();
+
+ // internal data model to obtain property IDs etc.
+ private final Class<BT> type;
+ private transient LinkedHashMap<String, PropertyDescriptor> model;
+
+ private List<ItemSetChangeListener> itemSetChangeListeners;
+
+ private Set<Filter> filters = new HashSet<Filter>();
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in) throws IOException,
+ ClassNotFoundException {
+ in.defaultReadObject();
+ model = BeanItem.getPropertyDescriptors(type);
+ }
+
+ /**
+ * Constructs BeanItemContainer for beans of a given type.
+ *
+ * @param type
+ * the class of beans to be used with this containers.
+ * @throws IllegalArgumentException
+ * If the type is null
+ */
+ public BeanItemContainer(Class<BT> type) {
+ if (type == null) {
+ throw new IllegalArgumentException(
+ "The type passed to BeanItemContainer must not be null");
+ }
+ this.type = type;
+ model = BeanItem.getPropertyDescriptors(type);
+ }
+
+ /**
+ * Constructs BeanItemContainer with given collection of beans in it. The
+ * collection must not be empty or an IllegalArgument is thrown.
+ *
+ * @param collection
+ * non empty {@link Collection} of beans.
+ * @throws IllegalArgumentException
+ * If the collection is null or empty.
+ */
+ public BeanItemContainer(Collection<BT> collection)
+ throws IllegalArgumentException {
+ if (collection == null || collection.isEmpty()) {
+ throw new IllegalArgumentException(
+ "The collection passed to BeanItemContainer must not be null or empty");
+ }
+
+ type = (Class<BT>) collection.iterator().next().getClass();
+ model = BeanItem.getPropertyDescriptors(type);
+ int i = 0;
+ for (BT bt : collection) {
+ addItemAt(i++, bt);
+ }
+ }
+
+ /**
+ * Unsupported operation.
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int)
+ */
+ public Object addItemAt(int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds new item at given index.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int, Object)
+ */
+ public BeanItem addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ if (index < 0 || index > size()) {
+ return null;
+ } else if (index == 0) {
+ // add before any item, visible or not
+ return addItemAtInternalIndex(0, newItemId);
+ } else {
+ // if index==size(), adds immediately after last visible item
+ return addItemAfter(getIdByIndex(index - 1), newItemId);
+ }
+ }
+
+ /**
+ * Adds new item at given index of the internal (unfiltered) list.
+ * <p>
+ * The item is also added in the visible part of the list if it passes the
+ * filters.
+ * </p>
+ *
+ * @param index
+ * Internal index to add the new item.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return Returns new item or null if the operation fails.
+ */
+ private BeanItem addItemAtInternalIndex(int index, Object newItemId) {
+ // Make sure that the Item has not been created yet
+ if (allItems.contains(newItemId)) {
+ return null;
+ }
+ if (type.isAssignableFrom(newItemId.getClass())) {
+ BT pojo = (BT) newItemId;
+ // "list" will be updated in filterAll()
+ allItems.add(index, pojo);
+ BeanItem beanItem = new BeanItem(pojo, model);
+ beanToItem.put(pojo, beanItem);
+ // add listeners to be able to update filtering on property changes
+ for (Filter filter : filters) {
+ // addValueChangeListener avoids adding duplicates
+ addValueChangeListener(beanItem, filter.propertyId);
+ }
+
+ // it is somewhat suboptimal to filter all items
+ filterAll();
+ return beanItem;
+ } else {
+ return null;
+ }
+ }
+
+ public BT getIdByIndex(int index) {
+ return list.get(index);
+ }
+
+ public int indexOfId(Object itemId) {
+ return list.indexOf(itemId);
+ }
+
+ /**
+ * Unsupported operation.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object)
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object,
+ * Object)
+ */
+ public BeanItem addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ // only add if the previous item is visible
+ if (list.contains(previousItemId)) {
+ return addItemAtInternalIndex(allItems.indexOf(previousItemId) + 1,
+ newItemId);
+ } else {
+ return null;
+ }
+ }
+
+ public BT firstItemId() {
+ if (list.size() > 0) {
+ return list.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ public boolean isFirstId(Object itemId) {
+ return firstItemId() == itemId;
+ }
+
+ public boolean isLastId(Object itemId) {
+ return lastItemId() == itemId;
+ }
+
+ public BT lastItemId() {
+ if (list.size() > 0) {
+ return list.get(list.size() - 1);
+ } else {
+ return null;
+ }
+ }
+
+ public BT nextItemId(Object itemId) {
+ int index = list.indexOf(itemId);
+ if (index >= 0 && index < list.size() - 1) {
+ return list.get(index + 1);
+ } else {
+ // out of bounds
+ return null;
+ }
+ }
+
+ public BT prevItemId(Object itemId) {
+ int index = list.indexOf(itemId);
+ if (index > 0) {
+ return list.get(index - 1);
+ } else {
+ // out of bounds
+ return null;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public boolean addContainerProperty(Object propertyId, Class type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Unsupported operation.
+ *
+ * @see com.vaadin.data.Container#addItem()
+ */
+ public Object addItem() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Creates a new Item with the bean into the Container.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ public BeanItem addBean(BT bean) {
+ return addItem(bean);
+ }
+
+ /**
+ * Creates a new Item with the bean into the Container.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ public BeanItem addItem(Object itemId) throws UnsupportedOperationException {
+ if (list.size() > 0) {
+ // add immediately after last visible item
+ int lastIndex = allItems.indexOf(lastItemId());
+ return addItemAtInternalIndex(lastIndex + 1, itemId);
+ } else {
+ return addItemAtInternalIndex(0, itemId);
+ }
+ }
+
+ public boolean containsId(Object itemId) {
+ // only look at visible items after filtering
+ return list.contains(itemId);
+ }
+
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ return beanToItem.get(itemId).getItemProperty(propertyId);
+ }
+
+ public Collection<String> getContainerPropertyIds() {
+ return model.keySet();
+ }
+
+ public BeanItem getItem(Object itemId) {
+ return beanToItem.get(itemId);
+ }
+
+ public Collection<BT> getItemIds() {
+ return (Collection<BT>) list.clone();
+ }
+
+ public Class<?> getType(Object propertyId) {
+ return model.get(propertyId).getPropertyType();
+ }
+
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ allItems.clear();
+ list.clear();
+ // detach listeners from all BeanItems
+ for (BeanItem item : beanToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ beanToItem.clear();
+ fireItemSetChange();
+ return true;
+ }
+
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ if (!allItems.remove(itemId)) {
+ return false;
+ }
+ // detach listeners from Item
+ removeAllValueChangeListeners(beanToItem.get(itemId));
+ // remove item
+ beanToItem.remove(itemId);
+ list.remove(itemId);
+ fireItemSetChange();
+ return true;
+ }
+
+ private void addValueChangeListener(BeanItem beanItem, Object propertyId) {
+ Property property = beanItem.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);
+ }
+ }
+
+ private void removeValueChangeListener(BeanItem item, Object propertyId) {
+ Property property = item.getItemProperty(propertyId);
+ if (property instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) property).removeListener(this);
+ }
+ }
+
+ private void removeAllValueChangeListeners(BeanItem item) {
+ for (Object propertyId : item.getItemPropertyIds()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ }
+
+ public int size() {
+ return list.size();
+ }
+
+ public Collection<Object> getSortableContainerPropertyIds() {
+ LinkedList<Object> sortables = new LinkedList<Object>();
+ for (Object propertyId : getContainerPropertyIds()) {
+ Class<?> propertyType = getType(propertyId);
+ if (Comparable.class.isAssignableFrom(propertyType)) {
+ sortables.add(propertyId);
+ }
+ }
+ return sortables;
+ }
+
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ for (int i = 0; i < ascending.length; i++) {
+ final boolean asc = ascending[i];
+ final Object property = propertyId[i];
+ // sort allItems, then filter and notify
+ Collections.sort(allItems, new Comparator<BT>() {
+ @SuppressWarnings("unchecked")
+ public int compare(BT a, BT b) {
+ Comparable va, vb;
+ if (asc) {
+ va = (Comparable) beanToItem.get(a).getItemProperty(
+ property).getValue();
+ vb = (Comparable) beanToItem.get(b).getItemProperty(
+ property).getValue();
+ } else {
+ va = (Comparable) beanToItem.get(b).getItemProperty(
+ property).getValue();
+ vb = (Comparable) beanToItem.get(a).getItemProperty(
+ property).getValue();
+ }
+
+ /*
+ * Null values are considered less than all others. The
+ * compareTo method cannot handle null values for the
+ * standard types.
+ */
+ if (va == null) {
+ return (vb == null) ? 0 : -1;
+ } else if (vb == null) {
+ return (va == null) ? 0 : 1;
+ }
+
+ return va.compareTo(vb);
+ }
+ });
+ }
+ // notifies if anything changes in the filtered list, including order
+ filterAll();
+ }
+
+ public void addListener(ItemSetChangeListener listener) {
+ if (itemSetChangeListeners == null) {
+ itemSetChangeListeners = new LinkedList<ItemSetChangeListener>();
+ }
+ itemSetChangeListeners.add(listener);
+ }
+
+ public void removeListener(ItemSetChangeListener listener) {
+ if (itemSetChangeListeners != null) {
+ itemSetChangeListeners.remove(listener);
+ }
+ }
+
+ private void fireItemSetChange() {
+ if (itemSetChangeListeners != null) {
+ final Container.ItemSetChangeEvent event = new Container.ItemSetChangeEvent() {
+ public Container getContainer() {
+ return BeanItemContainer.this;
+ }
+ };
+ for (ItemSetChangeListener listener : itemSetChangeListeners) {
+ listener.containerItemSetChange(event);
+ }
+ }
+ }
+
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ if (filters.isEmpty()) {
+ list = (ArrayList<BT>) allItems.clone();
+ }
+ // listen to change events to be able to update filtering
+ for (BeanItem item : beanToItem.values()) {
+ addValueChangeListener(item, propertyId);
+ }
+ Filter f = new Filter(propertyId, filterString, ignoreCase,
+ onlyMatchPrefix);
+ filter(f);
+ filters.add(f);
+ fireItemSetChange();
+ }
+
+ /**
+ * 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() {
+ // avoid notification if the filtering had no effect
+ List<BT> originalItems = list;
+ // it is somewhat inefficient to do a (shallow) clone() every time
+ list = (ArrayList<BT>) allItems.clone();
+ for (Filter f : filters) {
+ filter(f);
+ }
+ // check if exactly the same items are there after filtering to avoid
+ // unnecessary notifications
+ // this may be slow in some cases as it uses BT.equals()
+ if (!originalItems.equals(list)) {
+ fireItemSetChange();
+ }
+ }
+
+ protected void filter(Filter f) {
+ Iterator<BT> iterator = list.iterator();
+ while (iterator.hasNext()) {
+ BT bean = iterator.next();
+ if (!f.passesFilter(getItem(bean))) {
+ iterator.remove();
+ }
+ }
+ }
+
+ public void removeAllContainerFilters() {
+ if (!filters.isEmpty()) {
+ filters = new HashSet<Filter>();
+ // stop listening to change events for any property
+ for (BeanItem item : beanToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ filterAll();
+ }
+ }
+
+ public void removeContainerFilters(Object propertyId) {
+ if (!filters.isEmpty()) {
+ for (Iterator<Filter> iterator = filters.iterator(); iterator
+ .hasNext();) {
+ Filter f = iterator.next();
+ if (f.propertyId.equals(propertyId)) {
+ iterator.remove();
+ }
+ }
+ // stop listening to change events for the property
+ for (BeanItem item : beanToItem.values()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ filterAll();
+ }
+ }
+
+ public void valueChange(ValueChangeEvent event) {
+ // if a property that is used in a filter is changed, refresh filtering
+ filterAll();
+ }
+
+}
diff --git a/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
new file mode 100644
index 0000000000..4188591014
--- /dev/null
+++ b/src/com/vaadin/data/util/ContainerHierarchicalWrapper.java
@@ -0,0 +1,688 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.util.Collection;
+import java.util.Collections;
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @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 noChildrenAllowed = null;
+
+ /** Mapping from Item ID to parent Item */
+ private Hashtable parent = null;
+
+ /** Mapping from Item ID to a list of child IDs */
+ private Hashtable children = null;
+
+ /** List that contains all root elements of the container. */
+ private LinkedHashSet roots = null;
+
+ /** Is the wrapped container hierarchical by itself ? */
+ private boolean hierarchical;
+
+ /**
+ * 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();
+ parent = new Hashtable();
+ children = new Hashtable();
+ roots = new LinkedHashSet(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();
+ parent = new Hashtable();
+ children = new Hashtable();
+ roots = new LinkedHashSet(container.getItemIds());
+ }
+
+ // Check that the hierarchy is up-to-date
+ else {
+
+ // Calculate the set of all items in the hierarchy
+ final HashSet s = new HashSet();
+ s.addAll(parent.keySet());
+ s.addAll(children.keySet());
+ s.addAll(roots);
+
+ // Remove unnecessary items
+ for (final Iterator 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);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * 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) {
+
+ if (isRoot(itemId)) {
+ roots.remove(itemId);
+ }
+ final Object p = parent.get(itemId);
+ if (p != null) {
+ final LinkedList c = (LinkedList) children.get(p);
+ if (c != null) {
+ c.remove(itemId);
+ }
+ }
+ parent.remove(itemId);
+ children.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.
+ */
+ public boolean areChildrenAllowed(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container)
+ .areChildrenAllowed(itemId);
+ }
+ return !noChildrenAllowed.contains(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.
+ */
+ 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 = (Collection) 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.
+ */
+ 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.
+ */
+ public boolean hasChildren(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).hasChildren(itemId);
+ }
+
+ return children.get(itemId) != null;
+ }
+
+ /*
+ * 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.
+ */
+ public boolean isRoot(Object itemId) {
+
+ // If the wrapped container implements the method directly, use it
+ if (hierarchical) {
+ return ((Container.Hierarchical) container).isRoot(itemId);
+ }
+
+ return parent.get(itemId) == null;
+ }
+
+ /*
+ * 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.
+ */
+ 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
+ */
+ 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
+ */
+ 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 l = (LinkedList) children.get(itemId);
+ 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);
+
+ 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 pcl = (LinkedList) children.get(newParentId);
+ if (pcl == null) {
+ pcl = new LinkedList();
+ children.put(newParentId, pcl);
+ }
+ pcl.add(itemId);
+
+ // Remove from old parent or root
+ if (oldParentId == null) {
+ roots.remove(itemId);
+ } else {
+ final LinkedList l = (LinkedList) children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(oldParentId);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+
+ // Null ids are not accepted
+ if (itemId == null) {
+ throw new NullPointerException("Container item id can not be 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.
+ */
+ 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 hierarcy.
+ *
+ * @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.
+ */
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ final boolean success = container.removeItem(itemId);
+
+ if (!hierarchical && success) {
+ removeFromHierarchyWrapper(itemId);
+ }
+
+ return success;
+ }
+
+ /**
+ * 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public int size() {
+ return container.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.
+ */
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .addListener(new PiggybackListener(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.
+ */
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .removeListener(new PiggybackListener(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.
+ */
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .addListener(new PiggybackListener(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.
+ */
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .removeListener(new PiggybackListener(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;
+ }
+
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ updateHierarchicalWrapper();
+ ((Container.ItemSetChangeListener) listener)
+ .containerItemSetChange(event);
+
+ }
+
+ 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/src/com/vaadin/data/util/ContainerOrderedWrapper.java b/src/com/vaadin/data/util/ContainerOrderedWrapper.java
new file mode 100644
index 0000000000..0d4600caab
--- /dev/null
+++ b/src/com/vaadin/data/util/ContainerOrderedWrapper.java
@@ -0,0 +1,604 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.Iterator;
+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 ordering to containers not implementing
+ * the {@link com.vaadin.data.Container.Ordered} 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 #updateOrderWrapper()} method.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class ContainerOrderedWrapper implements Container.Ordered,
+ Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier {
+
+ /**
+ * The wrapped container
+ */
+ private final Container container;
+
+ /**
+ * Ordering information, ie. the mapping from Item ID to the next item ID
+ */
+ private Hashtable next;
+
+ /**
+ * Reverse ordering information for convenience and performance reasons.
+ */
+ private Hashtable prev;
+
+ /**
+ * ID of the first Item in the container.
+ */
+ private Object first;
+
+ /**
+ * ID of the last Item in the container.
+ */
+ private Object last;
+
+ /**
+ * Is the wrapped container ordered by itself, ie. does it implement the
+ * Container.Ordered interface by itself? If it does, this class will use
+ * the methods of the underlying container directly.
+ */
+ private boolean ordered = false;
+
+ /**
+ * Constructs a new ordered wrapper for an existing Container. Works even if
+ * the to-be-wrapped container already implements the Container.Ordered
+ * interface.
+ *
+ * @param toBeWrapped
+ * the container whose contents need to be ordered.
+ */
+ public ContainerOrderedWrapper(Container toBeWrapped) {
+
+ container = toBeWrapped;
+ ordered = container instanceof Container.Ordered;
+
+ // Checks arguments
+ if (container == null) {
+ throw new NullPointerException("Null can not be wrapped");
+ }
+
+ // Creates initial order if needed
+ updateOrderWrapper();
+ }
+
+ /**
+ * Removes the specified Item from the wrapper's internal hierarchy
+ * structure.
+ * <p>
+ * Note : The Item is not removed from the underlying Container.
+ * </p>
+ *
+ * @param id
+ * the ID of the Item to be removed from the ordering.
+ */
+ private void removeFromOrderWrapper(Object id) {
+ if (id != null) {
+ final Object pid = prev.get(id);
+ final Object nid = next.get(id);
+ if (first.equals(id)) {
+ first = nid;
+ }
+ if (last.equals(id)) {
+ first = pid;
+ }
+ if (nid != null) {
+ prev.put(nid, pid);
+ }
+ if (pid != null) {
+ next.put(pid, nid);
+ }
+ next.remove(id);
+ prev.remove(id);
+ }
+ }
+
+ /**
+ * Registers the specified Item to the last position in the wrapper's
+ * internal ordering. The underlying container is not modified.
+ *
+ * @param id
+ * the ID of the Item to be added to the ordering.
+ */
+ private void addToOrderWrapper(Object id) {
+
+ // Adds the if to tail
+ if (last != null) {
+ next.put(last, id);
+ prev.put(id, last);
+ last = id;
+ } else {
+ first = last = id;
+ }
+ }
+
+ /**
+ * Registers the specified Item after the specified itemId in the wrapper's
+ * internal ordering. The underlying container is not modified. Given item
+ * id must be in the container, or must be null.
+ *
+ * @param id
+ * the ID of the Item to be added to the ordering.
+ * @param previousItemId
+ * the Id of the previous item.
+ */
+ private void addToOrderWrapper(Object id, Object previousItemId) {
+
+ if (last == previousItemId || last == null) {
+ addToOrderWrapper(id);
+ } else {
+ if (previousItemId == null) {
+ next.put(id, first);
+ prev.put(first, id);
+ first = id;
+ } else {
+ prev.put(id, previousItemId);
+ next.put(id, next.get(previousItemId));
+ prev.put(next.get(previousItemId), id);
+ next.put(previousItemId, id);
+ }
+ }
+ }
+
+ /**
+ * Updates the wrapper's internal ordering information to include all Items
+ * in the underlying container.
+ * <p>
+ * Note : If the contents of the wrapped container change without the
+ * wrapper's knowledge, this method needs to be called to update the
+ * ordering information of the Items.
+ * </p>
+ */
+ public void updateOrderWrapper() {
+
+ if (!ordered) {
+
+ final Collection ids = container.getItemIds();
+
+ // Recreates ordering if some parts of it are missing
+ if (next == null || first == null || last == null || prev != null) {
+ first = null;
+ last = null;
+ next = new Hashtable();
+ prev = new Hashtable();
+ }
+
+ // Filter out all the missing items
+ final LinkedList l = new LinkedList(next.keySet());
+ for (final Iterator i = l.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ if (!container.containsId(id)) {
+ removeFromOrderWrapper(id);
+ }
+ }
+
+ // Adds missing items
+ for (final Iterator i = ids.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ if (!next.containsKey(id)) {
+ addToOrderWrapper(id);
+ }
+ }
+ }
+ }
+
+ /*
+ * Gets the first item stored in the ordered container Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public Object firstItemId() {
+ if (ordered) {
+ return ((Container.Ordered) container).firstItemId();
+ }
+ return first;
+ }
+
+ /*
+ * Tests if the given item is the first item in the container Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public boolean isFirstId(Object itemId) {
+ if (ordered) {
+ return ((Container.Ordered) container).isFirstId(itemId);
+ }
+ return first != null && first.equals(itemId);
+ }
+
+ /*
+ * Tests if the given item is the last item in the container Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public boolean isLastId(Object itemId) {
+ if (ordered) {
+ return ((Container.Ordered) container).isLastId(itemId);
+ }
+ return last != null && last.equals(itemId);
+ }
+
+ /*
+ * Gets the last item stored in the ordered container Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public Object lastItemId() {
+ if (ordered) {
+ return ((Container.Ordered) container).lastItemId();
+ }
+ return last;
+ }
+
+ /*
+ * Gets the item that is next from the specified item. Don't add a JavaDoc
+ * comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public Object nextItemId(Object itemId) {
+ if (ordered) {
+ return ((Container.Ordered) container).nextItemId(itemId);
+ }
+ if (itemId == null) {
+ return null;
+ }
+ return next.get(itemId);
+ }
+
+ /*
+ * Gets the item that is previous from the specified item. Don't add a
+ * JavaDoc comment here, we use the default documentation from implemented
+ * interface.
+ */
+ public Object prevItemId(Object itemId) {
+ if (ordered) {
+ return ((Container.Ordered) container).prevItemId(itemId);
+ }
+ if (itemId == null) {
+ return null;
+ }
+ return prev.get(itemId);
+ }
+
+ /**
+ * Registers 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
+ */
+ public boolean addContainerProperty(Object propertyId, Class type,
+ Object defaultValue) throws UnsupportedOperationException {
+
+ return container.addContainerProperty(propertyId, type, defaultValue);
+ }
+
+ /**
+ * Creates a new Item into the Container, assigns it an automatic ID, and
+ * adds it to the ordering.
+ *
+ * @return the autogenerated ID of the new Item or <code>null</code> if the
+ * operation failed
+ * @throws UnsupportedOperationException
+ * if the addItem is not supported.
+ */
+ public Object addItem() throws UnsupportedOperationException {
+
+ final Object id = container.addItem();
+ if (!ordered && id != null) {
+ addToOrderWrapper(id);
+ }
+ return id;
+ }
+
+ /**
+ * Registers a new Item by its ID to the underlying container and to the
+ * ordering.
+ *
+ * @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.
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ final Item item = container.addItem(itemId);
+ if (!ordered && item != null) {
+ addToOrderWrapper(itemId);
+ }
+ return item;
+ }
+
+ /**
+ * Removes all items from the underlying container and from the ordering.
+ *
+ * @return <code>true</code> if the operation succeeded, otherwise
+ * <code>false</code>
+ * @throws UnsupportedOperationException
+ * if the removeAllItems is not supported.
+ */
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ final boolean success = container.removeAllItems();
+ if (!ordered && success) {
+ first = last = null;
+ next.clear();
+ prev.clear();
+ }
+ return success;
+ }
+
+ /**
+ * Removes an Item specified by the itemId from the underlying container and
+ * from the ordering.
+ *
+ * @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.
+ */
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+
+ final boolean success = container.removeItem(itemId);
+ if (!ordered && success) {
+ removeFromOrderWrapper(itemId);
+ }
+ return success;
+ }
+
+ /**
+ * Removes the specified Property from the underlying container and from the
+ * ordering.
+ * <p>
+ * Note : The Property will be removed from all the 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public int size() {
+ return container.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.
+ */
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .addListener(new PiggybackListener(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.
+ */
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (container instanceof Container.ItemSetChangeNotifier) {
+ ((Container.ItemSetChangeNotifier) container)
+ .removeListener(new PiggybackListener(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.
+ */
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .addListener(new PiggybackListener(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.
+ */
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ if (container instanceof Container.PropertySetChangeNotifier) {
+ ((Container.PropertySetChangeNotifier) container)
+ .removeListener(new PiggybackListener(listener));
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+
+ // If the previous item is not in the container, fail
+ if (previousItemId != null && !containsId(previousItemId)) {
+ return null;
+ }
+
+ // Adds the item to container
+ final Item item = container.addItem(newItemId);
+
+ // Puts the new item to its correct place
+ if (!ordered && item != null) {
+ addToOrderWrapper(newItemId, previousItemId);
+ }
+
+ return item;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+
+ // If the previous item is not in the container, fail
+ if (previousItemId != null && !containsId(previousItemId)) {
+ return null;
+ }
+
+ // Adds the item to container
+ final Object id = container.addItem();
+
+ // Puts the new item to its correct place
+ if (!ordered && id != null) {
+ addToOrderWrapper(id, previousItemId);
+ }
+
+ return id;
+ }
+
+ /**
+ * 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;
+ }
+
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+ updateOrderWrapper();
+ ((Container.ItemSetChangeListener) listener)
+ .containerItemSetChange(event);
+
+ }
+
+ public void containerPropertySetChange(PropertySetChangeEvent event) {
+ updateOrderWrapper();
+ ((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/src/com/vaadin/data/util/FilesystemContainer.java b/src/com/vaadin/data/util/FilesystemContainer.java
new file mode 100644
index 0000000000..3c8fcac92f
--- /dev/null
+++ b/src/com/vaadin/data/util/FilesystemContainer.java
@@ -0,0 +1,894 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+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.service.FileTypeResolver;
+import com.vaadin.terminal.Resource;
+
+/**
+ * A hierarchical container wrapper for a filesystem.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+/**
+ * @author mattitahvonen
+ *
+ */
+@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 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();
+ 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.
+ */
+ 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.
+ */
+ public Collection getChildren(Object itemId) {
+
+ if (!(itemId instanceof File)) {
+ return Collections.unmodifiableCollection(new LinkedList());
+ }
+ File[] f;
+ if (filter != null) {
+ f = ((File) itemId).listFiles(filter);
+ } else {
+ f = ((File) itemId).listFiles();
+ }
+
+ if (f == null) {
+ return Collections.unmodifiableCollection(new LinkedList());
+ }
+
+ final List 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ public Collection 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());
+ }
+
+ final List 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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.
+ */
+ 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 col, File f) {
+ File[] l;
+ if (filter != null) {
+ l = f.listFiles(filter);
+ } else {
+ l = f.listFiles();
+ }
+ final List ll = Arrays.asList(l);
+ Collections.sort(ll);
+
+ for (final Iterator i = ll.iterator(); i.hasNext();) {
+ final File lf = (File) i.next();
+ if (lf.isDirectory()) {
+ addItemIds(col, lf);
+ } else {
+ col.add(lf);
+ }
+ }
+ }
+
+ /*
+ * Gets the IDs of Items in the filesystem. Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public Collection getItemIds() {
+
+ if (recursive) {
+ final Collection col = new ArrayList();
+ 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());
+ }
+
+ final List 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>
+ */
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+
+ if (!(itemId instanceof File)) {
+ return null;
+ }
+
+ if (propertyId.equals(PROPERTY_NAME)) {
+ return new MethodProperty(getType(propertyId), new FileItem(
+ (File) itemId), FILEITEM_NAME, null);
+ }
+
+ if (propertyId.equals(PROPERTY_ICON)) {
+ return new MethodProperty(getType(propertyId), new FileItem(
+ (File) itemId), FILEITEM_ICON, null);
+ }
+
+ if (propertyId.equals(PROPERTY_SIZE)) {
+ return new MethodProperty(getType(propertyId), new FileItem(
+ (File) itemId), FILEITEM_SIZE, null);
+ }
+
+ if (propertyId.equals(PROPERTY_LASTMODIFIED)) {
+ return new MethodProperty(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.
+ */
+ public Collection 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>
+ */
+ 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.
+ */
+ 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @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.
+ */
+ 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.
+ */
+ public Collection 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)
+ */
+ 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)
+ */
+ 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @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)
+ */
+ 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)
+ */
+ 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()
+ */
+ 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)
+ */
+ 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()
+ */
+ 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)
+ */
+ 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
+ * )
+ */
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "File system container does not support this operation");
+ }
+}
diff --git a/src/com/vaadin/data/util/Filter.java b/src/com/vaadin/data/util/Filter.java
new file mode 100644
index 0000000000..d8d9172a28
--- /dev/null
+++ b/src/com/vaadin/data/util/Filter.java
@@ -0,0 +1,90 @@
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * A default filter that can be used to implement
+ * {@link com.vaadin.data.Container.Filterable}.
+ *
+ * @since 5.4
+ */
+@SuppressWarnings("serial")
+public class Filter implements Serializable {
+ final Object propertyId;
+ final String filterString;
+ final boolean ignoreCase;
+ final boolean onlyMatchPrefix;
+
+ Filter(Object propertyId, String filterString, boolean ignoreCase,
+ boolean onlyMatchPrefix) {
+ this.propertyId = propertyId;
+ ;
+ this.filterString = ignoreCase ? filterString.toLowerCase()
+ : filterString;
+ this.ignoreCase = ignoreCase;
+ this.onlyMatchPrefix = onlyMatchPrefix;
+ }
+
+ /**
+ * Check if an item passes the filter.
+ *
+ * @param item
+ * @return true if the item is accepted by this filter
+ */
+ public boolean passesFilter(Item item) {
+ final Property p = item.getItemProperty(propertyId);
+ if (p == null || p.toString() == null) {
+ return false;
+ }
+ final String value = ignoreCase ? p.toString().toLowerCase() : p
+ .toString();
+ if (onlyMatchPrefix) {
+ if (!value.startsWith(filterString)) {
+ return false;
+ }
+ } else {
+ if (!value.contains(filterString)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+
+ // Only ones of the objects of the same class can be equal
+ if (!(obj instanceof Filter)) {
+ return false;
+ }
+ final Filter o = (Filter) obj;
+
+ // Checks the properties one by one
+ if (propertyId != o.propertyId && o.propertyId != null
+ && !o.propertyId.equals(propertyId)) {
+ return false;
+ }
+ if (filterString != o.filterString && o.filterString != null
+ && !o.filterString.equals(filterString)) {
+ return false;
+ }
+ if (ignoreCase != o.ignoreCase) {
+ return false;
+ }
+ if (onlyMatchPrefix != o.onlyMatchPrefix) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return (propertyId != null ? propertyId.hashCode() : 0)
+ ^ (filterString != null ? filterString.hashCode() : 0);
+ }
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/data/util/HierarchicalContainer.java b/src/com/vaadin/data/util/HierarchicalContainer.java
new file mode 100644
index 0000000000..9ef4cdeb60
--- /dev/null
+++ b/src/com/vaadin/data/util/HierarchicalContainer.java
@@ -0,0 +1,314 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.LinkedList;
+
+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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @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 noChildrenAllowed = new HashSet();
+
+ /**
+ * Mapping from Item ID to parent Item.
+ */
+ private final Hashtable parent = new Hashtable();
+
+ /**
+ * Mapping from Item ID to a list of child IDs.
+ */
+ private final Hashtable children = new Hashtable();
+
+ /**
+ * List that contains all root elements of the container.
+ */
+ private final LinkedList roots = new LinkedList();
+
+ /*
+ * Can the specified Item have any children? Don't add a JavaDoc comment
+ * here, we use the default documentation from implemented interface.
+ */
+ public boolean areChildrenAllowed(Object itemId) {
+ return !noChildrenAllowed.contains(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.
+ */
+ public Collection getChildren(Object itemId) {
+ final Collection c = (Collection) 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.
+ */
+ public Object getParent(Object 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.
+ */
+ public boolean hasChildren(Object itemId) {
+ return children.get(itemId) != null;
+ }
+
+ /*
+ * 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.
+ */
+ public boolean isRoot(Object itemId) {
+ return parent.get(itemId) == null;
+ }
+
+ /*
+ * 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.
+ */
+ public Collection 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
+ */
+ 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
+ */
+ 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) {
+
+ // Removes from old parents children list
+ final LinkedList l = (LinkedList) children.get(itemId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(itemId);
+ }
+ }
+
+ // Add to be a root
+ roots.add(itemId);
+
+ // Updates parent
+ parent.remove(itemId);
+
+ return true;
+ }
+
+ // 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 pcl = (LinkedList) children.get(newParentId);
+ if (pcl == null) {
+ pcl = new LinkedList();
+ children.put(newParentId, pcl);
+ }
+ pcl.add(itemId);
+
+ // Removes from old parent or root
+ if (oldParentId == null) {
+ roots.remove(itemId);
+ } else {
+ final LinkedList l = (LinkedList) children.get(oldParentId);
+ if (l != null) {
+ l.remove(itemId);
+ if (l.isEmpty()) {
+ children.remove(oldParentId);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#addItem()
+ */
+ @Override
+ public Object addItem() {
+ final Object id = super.addItem();
+ if (id != null && !roots.contains(id)) {
+ roots.add(id);
+ }
+ return id;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.IndexedContainer#addItem(java.lang.Object)
+ */
+ @Override
+ public Item addItem(Object itemId) {
+ final Item item = super.addItem(itemId);
+ if (item != null) {
+ roots.add(itemId);
+ }
+ return item;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.util.IndexedContainer#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() {
+ final boolean success = super.removeAllItems();
+
+ if (success) {
+ roots.clear();
+ parent.clear();
+ children.clear();
+ noChildrenAllowed.clear();
+ }
+ return success;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.util.IndexedContainer#removeItem(java.lang.Object
+ * )
+ */
+ @Override
+ public boolean removeItem(Object itemId) {
+ final boolean success = super.removeItem(itemId);
+
+ if (success) {
+ if (isRoot(itemId)) {
+ roots.remove(itemId);
+ }
+ children.remove(itemId);
+ final Object p = parent.get(itemId);
+ if (p != null) {
+ final LinkedList c = (LinkedList) children.get(p);
+ if (c != null) {
+ c.remove(itemId);
+ }
+ }
+ parent.remove(itemId);
+ noChildrenAllowed.remove(itemId);
+ }
+
+ return success;
+ }
+
+}
diff --git a/src/com/vaadin/data/util/IndexedContainer.java b/src/com/vaadin/data/util/IndexedContainer.java
new file mode 100644
index 0000000000..4cad9e3995
--- /dev/null
+++ b/src/com/vaadin/data/util/IndexedContainer.java
@@ -0,0 +1,1652 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * 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}
+ * <li>Sends all needed events on content changes.
+ * </ul>
+ *
+ * @see com.vaadin.data.Container
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+
+@SuppressWarnings("serial")
+public class IndexedContainer implements Container.Indexed,
+ Container.ItemSetChangeNotifier, Container.PropertySetChangeNotifier,
+ Property.ValueChangeNotifier, Container.Sortable, Comparator,
+ Cloneable, Container.Filterable {
+
+ /* Internal structure */
+
+ /**
+ * Linked list of ordered Item IDs.
+ */
+ private ArrayList itemIds = new ArrayList();
+
+ /** List of item ids that passes the filtering */
+ private LinkedHashSet filteredItemIds = null;
+
+ /**
+ * Linked list of ordered Property IDs.
+ */
+ private ArrayList propertyIds = new ArrayList();
+
+ /**
+ * Property ID to type mapping.
+ */
+ private Hashtable types = new Hashtable();
+
+ /**
+ * Hash of Items, where each Item is implemented as a mapping from Property
+ * ID to Property value.
+ */
+ private Hashtable items = new Hashtable();
+
+ /**
+ * Set of properties that are read-only.
+ */
+ private HashSet readOnlyProperties = new HashSet();
+
+ /**
+ * List of all Property value change event listeners listening all the
+ * properties.
+ */
+ private LinkedList 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 singlePropertyValueChangeListeners = null;
+
+ /**
+ * List of all Property set change event listeners.
+ */
+ private LinkedList propertySetChangeListeners = null;
+
+ /**
+ * List of all container Item set change event listeners.
+ */
+ private LinkedList itemSetChangeListeners = null;
+
+ /**
+ * Temporary store for sorting property ids.
+ */
+ private Object[] sortPropertyId;
+
+ /**
+ * Temporary store for sorting direction.
+ */
+ private boolean[] sortDirection;
+
+ /**
+ * Filters that are applied to the container to limit the items visible in
+ * it
+ */
+ private HashSet<Filter> filters;
+
+ private HashMap<Object, Object> defaultPropertyValues;
+
+ /* Container constructors */
+
+ public IndexedContainer() {
+ }
+
+ public IndexedContainer(Collection itemIds) {
+ if (items != null) {
+ for (final Iterator i = itemIds.iterator(); i.hasNext();) {
+ this.addItem(i.next());
+ }
+ }
+ }
+
+ /* Container methods */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItem(java.lang.Object)
+ */
+ public Item getItem(Object itemId) {
+
+ if (itemId != null
+ && items.containsKey(itemId)
+ && (filteredItemIds == null || filteredItemIds.contains(itemId))) {
+ return new IndexedContainerItem(itemId);
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItemIds()
+ */
+ public Collection getItemIds() {
+ if (filteredItemIds != null) {
+ return Collections.unmodifiableCollection(filteredItemIds);
+ }
+ return Collections.unmodifiableCollection(itemIds);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
+ 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
+ */
+ public Class getType(Object propertyId) {
+ return (Class) types.get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
+ public Property getContainerProperty(Object itemId, Object propertyId) {
+ if (itemId == null) {
+ return null;
+ } else if (filteredItemIds == null) {
+ if (!items.containsKey(itemId)) {
+ return null;
+ }
+ } else if (!filteredItemIds.contains(itemId)) {
+ return null;
+ }
+
+ return new IndexedContainerProperty(itemId, propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#size()
+ */
+ public int size() {
+ if (filteredItemIds == null) {
+ return itemIds.size();
+ }
+ return filteredItemIds.size();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#containsId(java.lang.Object)
+ */
+ public boolean containsId(Object itemId) {
+ if (itemId == null) {
+ return false;
+ }
+ if (filteredItemIds != null) {
+ return filteredItemIds.contains(itemId);
+ }
+ return items.containsKey(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container#addContainerProperty(java.lang.Object,
+ * java.lang.Class, java.lang.Object)
+ */
+ 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 = itemIds.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()
+ */
+ public boolean removeAllItems() {
+
+ // Removes all Items
+ itemIds.clear();
+ items.clear();
+ if (filteredItemIds != null) {
+ filteredItemIds.clear();
+ }
+
+ // Sends a change event
+ fireContentsChange(-1);
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem()
+ */
+ public Object addItem() {
+
+ // Creates a new id
+ final Object id = new Object();
+
+ // Adds the Item into container
+ addItem(id);
+
+ return id;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#addItem(java.lang.Object)
+ */
+ public Item addItem(Object itemId) {
+
+ // Make sure that the Item is valid and has not been created yet
+ if (itemId == null || items.containsKey(itemId)) {
+ return null;
+ }
+
+ // Adds the Item to container (at the end of the unfiltered list)
+ itemIds.add(itemId);
+ Hashtable t = new Hashtable();
+ items.put(itemId, t);
+
+ addDefaultValues(t);
+
+ // this optimization is why some code is duplicated with
+ // addItemAtInternalIndex()
+ final Item item = new IndexedContainerItem(itemId);
+ if (filteredItemIds != null) {
+ if (passesFilters(item)) {
+ filteredItemIds.add(itemId);
+ }
+ }
+
+ // Sends the event
+ fireContentsChange(itemIds.size() - 1);
+
+ return item;
+ }
+
+ /**
+ * Helper method to add default values for items if available
+ *
+ * @param t
+ * data table of added item
+ */
+ private void addDefaultValues(Hashtable 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)
+ */
+ public boolean removeItem(Object itemId) {
+
+ if (items.remove(itemId) == null) {
+ return false;
+ }
+ itemIds.remove(itemId);
+ if (filteredItemIds != null) {
+ filteredItemIds.remove(itemId);
+ }
+
+ fireContentsChange(-1);
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container#removeContainerProperty(java.lang.Object
+ * )
+ */
+ 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);
+ defaultPropertyValues.remove(propertyId);
+
+ // If remove the Property from all Items
+ for (final Iterator i = itemIds.iterator(); i.hasNext();) {
+ ((Hashtable) items.get(i.next())).remove(propertyId);
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+ /* Container.Ordered methods */
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#firstItemId()
+ */
+ public Object firstItemId() {
+ try {
+ if (filteredItemIds != null) {
+ return filteredItemIds.iterator().next();
+ }
+ return itemIds.get(0);
+ } catch (final IndexOutOfBoundsException e) {
+ } catch (final NoSuchElementException e) {
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#lastItemId()
+ */
+ public Object lastItemId() {
+ try {
+ if (filteredItemIds != null) {
+ final Iterator i = filteredItemIds.iterator();
+ Object last = null;
+ while (i.hasNext()) {
+ last = i.next();
+ }
+ return last;
+ }
+ return itemIds.get(itemIds.size() - 1);
+ } catch (final IndexOutOfBoundsException e) {
+ }
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#nextItemId(java.lang.Object)
+ */
+ public Object nextItemId(Object itemId) {
+ if (filteredItemIds != null) {
+ if (itemId == null || !filteredItemIds.contains(itemId)) {
+ return null;
+ }
+ final Iterator i = filteredItemIds.iterator();
+ while (i.hasNext() && !itemId.equals(i.next())) {
+ ;
+ }
+ if (i.hasNext()) {
+ return i.next();
+ }
+ return null;
+ }
+ try {
+ return itemIds.get(itemIds.indexOf(itemId) + 1);
+ } catch (final IndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#prevItemId(java.lang.Object)
+ */
+ public Object prevItemId(Object itemId) {
+ if (filteredItemIds != null) {
+ if (!filteredItemIds.contains(itemId)) {
+ return null;
+ }
+ final Iterator i = filteredItemIds.iterator();
+ if (itemId == null) {
+ return null;
+ }
+ Object prev = null;
+ Object current;
+ while (i.hasNext() && !itemId.equals(current = i.next())) {
+ prev = current;
+ }
+ return prev;
+ }
+ try {
+ return itemIds.get(itemIds.indexOf(itemId) - 1);
+ } catch (final IndexOutOfBoundsException e) {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#isFirstId(java.lang.Object)
+ */
+ public boolean isFirstId(Object itemId) {
+ if (filteredItemIds != null) {
+ try {
+ final Object first = filteredItemIds.iterator().next();
+ return (itemId != null && itemId.equals(first));
+ } catch (final NoSuchElementException e) {
+ return false;
+ }
+ }
+ return (size() >= 1 && itemIds.get(0).equals(itemId));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Ordered#isLastId(java.lang.Object)
+ */
+ public boolean isLastId(Object itemId) {
+ if (filteredItemIds != null) {
+ try {
+ Object last = null;
+ for (final Iterator i = filteredItemIds.iterator(); i.hasNext();) {
+ last = i.next();
+ }
+ return (itemId != null && itemId.equals(last));
+ } catch (final NoSuchElementException e) {
+ return false;
+ }
+ }
+ final int s = size();
+ return (s >= 1 && itemIds.get(s - 1).equals(itemId));
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object,
+ * java.lang.Object)
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId) {
+
+ // Get the index of the addition
+ int index = -1;
+ if (previousItemId != null) {
+ index = 1 + indexOfId(previousItemId);
+ if (index <= 0 || index > size()) {
+ return null;
+ }
+ }
+
+ return addItemAt(index, newItemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
+ */
+ public Object addItemAfter(Object previousItemId) {
+
+ // Creates a new id
+ final Object id = new Object();
+
+ return addItemAfter(previousItemId, id);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#getIdByIndex(int)
+ */
+ public Object getIdByIndex(int index) {
+
+ if (filteredItemIds != null) {
+ if (index < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ try {
+ final Iterator i = filteredItemIds.iterator();
+ while (index-- > 0) {
+ i.next();
+ }
+ return i.next();
+ } catch (final NoSuchElementException e) {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ return itemIds.get(index);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Indexed#indexOfId(java.lang.Object)
+ */
+ public int indexOfId(Object itemId) {
+ if (filteredItemIds != null) {
+ int index = 0;
+ if (itemId == null) {
+ return -1;
+ }
+ final Iterator i = filteredItemIds.iterator();
+ while (i.hasNext()) {
+ Object id = i.next();
+ if (itemId.equals(id)) {
+ return index;
+ }
+ index++;
+ }
+ return -1;
+ }
+ return itemIds.indexOf(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int,
+ * java.lang.Object)
+ */
+ public Item addItemAt(int index, Object newItemId) {
+
+ // add item based on a filtered index
+ int internalIndex = -1;
+ if (filteredItemIds == null) {
+ internalIndex = index;
+ } else if (index == 0) {
+ internalIndex = 0;
+ } else if (index == size()) {
+ // add just after the last item
+ Object id = getIdByIndex(index - 1);
+ internalIndex = itemIds.indexOf(id) + 1;
+ } else if (index > 0 && index < size()) {
+ // map the index of the visible item to its unfiltered index
+ Object id = getIdByIndex(index);
+ internalIndex = itemIds.indexOf(id);
+ }
+ if (internalIndex >= 0) {
+ return addItemAtInternalIndex(internalIndex, newItemId);
+ } else {
+ return null;
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Indexed#addItemAt(int)
+ */
+ public Object addItemAt(int index) {
+
+ // Creates a new id
+ final Object id = new Object();
+
+ // Adds the Item into container
+ addItemAt(index, id);
+
+ return id;
+ }
+
+ /* Event notifiers */
+
+ /**
+ * Adds new item at given index of the internal (unfiltered) list.
+ * <p>
+ * The item is also added in the visible part of the list if it passes the
+ * filters.
+ * </p>
+ *
+ * @param index
+ * Internal index to add the new item.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return Returns new item or null if the operation fails.
+ */
+ private Item addItemAtInternalIndex(int index, Object newItemId) {
+ // Make sure that the Item is valid and has not been created yet
+ if (index < 0 || index > itemIds.size() || newItemId == null
+ || items.containsKey(newItemId)) {
+ return null;
+ }
+
+ // Adds the Item to container
+ itemIds.add(index, newItemId);
+ Hashtable t = new Hashtable();
+ items.put(newItemId, t);
+ addDefaultValues(t);
+
+ if (filteredItemIds != null) {
+ // when the item data is set later (IndexedContainerProperty),
+ // filtering is updated
+ updateContainerFiltering();
+ } else {
+ fireContentsChange(index);
+ }
+
+ return new IndexedContainerItem(newItemId);
+ }
+
+ /**
+ * An <code>event</code> object specifying the list whose Property set has
+ * changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class PropertySetChangeEvent extends EventObject implements
+ Container.PropertySetChangeEvent, Serializable {
+
+ private PropertySetChangeEvent(IndexedContainer source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.PropertySetChangeEvent#getContainer
+ * ()
+ */
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+ }
+
+ /**
+ * An <code>event</code> object specifying the list whose Item set has
+ * changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class ItemSetChangeEvent extends EventObject implements
+ Container.ItemSetChangeEvent, Serializable {
+
+ private final int addedItemIndex;
+
+ private ItemSetChangeEvent(IndexedContainer source, int addedItemIndex) {
+ super(source);
+ this.addedItemIndex = addedItemIndex;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeEvent#getContainer()
+ */
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ /**
+ * 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 IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class PropertyValueChangeEvent extends EventObject implements
+ Property.ValueChangeEvent, Serializable {
+
+ private PropertyValueChangeEvent(Property source) {
+ super(source);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property.ValueChangeEvent#getProperty()
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.PropertySetChangeNotifier#addListener
+ * (com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ public void addListener(Container.PropertySetChangeListener listener) {
+ if (propertySetChangeListeners == null) {
+ propertySetChangeListeners = new LinkedList();
+ }
+ propertySetChangeListeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.PropertySetChangeNotifier#removeListener
+ * (com.vaadin.data.Container.PropertySetChangeListener)
+ */
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ if (propertySetChangeListeners != null) {
+ propertySetChangeListeners.remove(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#addListener(com
+ * .itmill.toolkit.data.Container.ItemSetChangeListener)
+ */
+ public void addListener(Container.ItemSetChangeListener listener) {
+ if (itemSetChangeListeners == null) {
+ itemSetChangeListeners = new LinkedList();
+ }
+ itemSetChangeListeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.ItemSetChangeNotifier#removeListener
+ * (com.vaadin.data.Container.ItemSetChangeListener)
+ */
+ public void removeListener(Container.ItemSetChangeListener listener) {
+ if (itemSetChangeListeners != null) {
+ itemSetChangeListeners.remove(listener);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeNotifier#addListener(com.
+ * itmill.toolkit.data.Property.ValueChangeListener)
+ */
+ public void addListener(Property.ValueChangeListener listener) {
+ if (propertyValueChangeListeners == null) {
+ propertyValueChangeListeners = new LinkedList();
+ }
+ propertyValueChangeListeners.add(listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeNotifier#removeListener(com
+ * .itmill.toolkit.data.Property.ValueChangeListener)
+ */
+ public void removeListener(Property.ValueChangeListener listener) {
+ if (propertyValueChangeListeners != null) {
+ propertyValueChangeListeners.remove(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 Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
+ .get(source.propertyId);
+ if (propertySetToListenerListMap != null) {
+ final LinkedList listenerList = (LinkedList) 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);
+ }
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Sends a Property set change event to all interested listeners.
+ */
+ private void fireContainerPropertySetChange() {
+ if (propertySetChangeListeners != null) {
+ final Object[] l = propertySetChangeListeners.toArray();
+ final Container.PropertySetChangeEvent event = new IndexedContainer.PropertySetChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Container.PropertySetChangeListener) l[i])
+ .containerPropertySetChange(event);
+ }
+ }
+ }
+
+ /**
+ * Sends Item set change event to all registered interested listeners.
+ *
+ * @param addedItemIndex
+ * index of new item if change event was an item addition
+ */
+ private void fireContentsChange(int addedItemIndex) {
+ if (itemSetChangeListeners != null) {
+ final Object[] l = itemSetChangeListeners.toArray();
+ final Container.ItemSetChangeEvent event = new IndexedContainer.ItemSetChangeEvent(
+ this, addedItemIndex);
+ for (int i = 0; i < l.length; i++) {
+ ((Container.ItemSetChangeListener) l[i])
+ .containerItemSetChange(event);
+ }
+ }
+ }
+
+ /**
+ * 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();
+ }
+ Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
+ .get(propertyId);
+ if (propertySetToListenerListMap == null) {
+ propertySetToListenerListMap = new Hashtable();
+ singlePropertyValueChangeListeners.put(propertyId,
+ propertySetToListenerListMap);
+ }
+ LinkedList listenerList = (LinkedList) propertySetToListenerListMap
+ .get(itemId);
+ if (listenerList == null) {
+ listenerList = new LinkedList();
+ propertySetToListenerListMap.put(itemId, listenerList);
+ }
+ listenerList.addLast(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 Hashtable propertySetToListenerListMap = (Hashtable) singlePropertyValueChangeListeners
+ .get(propertyId);
+ if (propertySetToListenerListMap != null) {
+ final LinkedList listenerList = (LinkedList) 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 IT Mill Ltd.
+ *
+ * @version @VERSION@
+ *
+ * @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) {
+
+ // Gets the item contents from the host
+ if (itemId == null) {
+ throw new NullPointerException();
+ }
+ this.itemId = itemId;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Item#getItemProperty(java.lang.Object)
+ */
+ public Property getItemProperty(Object id) {
+ return new IndexedContainerProperty(itemId, id);
+ }
+
+ 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 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).toString();
+ 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)
+ */
+ 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)
+ */
+ 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 IT Mill Ltd.
+ *
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class IndexedContainerProperty implements Property,
+ 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()
+ */
+ public Class getType() {
+ return (Class) types.get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#getValue()
+ */
+ public Object getValue() {
+ return ((Hashtable) items.get(itemId)).get(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#isReadOnly()
+ */
+ public boolean isReadOnly() {
+ return readOnlyProperties.contains(this);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Property#setReadOnly(boolean)
+ */
+ 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)
+ */
+ public void setValue(Object newValue)
+ throws Property.ReadOnlyException, Property.ConversionException {
+
+ // Gets the Property set
+ final Hashtable propertySet = (Hashtable) 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 {
+ try {
+
+ // Gets the string constructor
+ final Constructor constr = getType().getConstructor(
+ new Class[] { String.class });
+
+ // Creates new object from the string
+ propertySet.put(propertyId, constr
+ .newInstance(new Object[] { newValue.toString() }));
+
+ } catch (final java.lang.Exception e) {
+ throw new Property.ConversionException(
+ "Conversion for value '" + newValue + "' of class "
+ + newValue.getClass().getName() + " to "
+ + getType().getName() + " failed");
+ }
+ }
+
+ // update the container filtering if this property is being filtered
+ updateContainerFiltering(propertyId);
+
+ firePropertyValueChange(this);
+ }
+
+ /**
+ * Returns the value of the Property in human readable textual format.
+ * The return value should be assignable to the <code>setValue</code>
+ * method if the Property is not in read-only mode.
+ *
+ * @return <code>String</code> representation of the value stored in the
+ * Property
+ */
+ @Override
+ public String toString() {
+ final Object value = getValue();
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ /**
+ * 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)
+ */
+ public void addListener(Property.ValueChangeListener listener) {
+ addSinglePropertyChangeListener(propertyId, itemId, listener);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Property.ValueChangeNotifier#removeListener
+ * (com.vaadin.data.Property.ValueChangeListener)
+ */
+ public void removeListener(Property.ValueChangeListener listener) {
+ removeSinglePropertyChangeListener(propertyId, itemId, listener);
+ }
+
+ private IndexedContainer getHost() {
+ return IndexedContainer.this;
+ }
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+ public void sort(Object[] propertyId, boolean[] ascending) {
+
+ // Removes any non-sortable property ids
+ final List ids = new ArrayList();
+ final List<Boolean> orders = new ArrayList<Boolean>();
+ final Collection sortable = getSortableContainerPropertyIds();
+ for (int i = 0; i < propertyId.length; i++) {
+ if (sortable.contains(propertyId[i])) {
+ ids.add(propertyId[i]);
+ orders.add(new Boolean(i < ascending.length ? ascending[i]
+ : true));
+ }
+ }
+
+ if (ids.size() == 0) {
+ return;
+ }
+ sortPropertyId = ids.toArray();
+ sortDirection = new boolean[orders.size()];
+ for (int i = 0; i < sortDirection.length; i++) {
+ sortDirection[i] = (orders.get(i)).booleanValue();
+ }
+
+ // Sort
+ Collections.sort(itemIds, this);
+ if (filteredItemIds != null) {
+ updateContainerFiltering();
+ } else {
+ fireContentsChange(-1);
+ }
+
+ // Remove temporary references
+ sortPropertyId = null;
+ sortDirection = null;
+
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds
+ * ()
+ */
+ public Collection getSortableContainerPropertyIds() {
+
+ final LinkedList list = new LinkedList();
+ for (final Iterator i = propertyIds.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ final Class type = getType(id);
+ if (type != null && Comparable.class.isAssignableFrom(type)) {
+ list.add(id);
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Compares two items for sorting.
+ *
+ * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
+ * @see #sort((java.lang.Object[], boolean[])
+ */
+ public int compare(Object o1, Object o2) {
+
+ for (int i = 0; i < sortPropertyId.length; i++) {
+
+ // Get the compared properties
+ final Property pp1 = getContainerProperty(o1, sortPropertyId[i]);
+ final Property pp2 = getContainerProperty(o2, sortPropertyId[i]);
+
+ // Get the compared values
+ final Object p1 = pp1 == null ? null : pp1.getValue();
+ final Object p2 = pp2 == null ? null : pp2.getValue();
+
+ // Result of the comparison
+ int r = 0;
+
+ // Normal non-null comparison
+ if (p1 != null && p2 != null) {
+ if ((p1 instanceof Boolean) && (p2 instanceof Boolean)) {
+ r = p1.equals(p2) ? 0
+ : ((sortDirection[i] ? 1 : -1) * (((Boolean) p1)
+ .booleanValue() ? 1 : -1));
+ } else {
+ r = sortDirection[i] ? ((Comparable) p1).compareTo(p2)
+ : -((Comparable) p1).compareTo(p2);
+ }
+ }
+
+ // If both are nulls
+ else if (p1 == p2) {
+ r = 0;
+ } else {
+ r = (sortDirection[i] ? 1 : -1) * (p1 == null ? -1 : 1);
+ }
+
+ // If order can be decided
+ if (r != 0) {
+ return r;
+ }
+ }
+
+ return 0;
+ }
+
+ /**
+ * Supports cloning of the IndexedContainer cleanly.
+ *
+ * @throws CloneNotSupportedException
+ * if an object cannot be cloned. .
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+
+ // Creates the clone
+ final IndexedContainer nc = new IndexedContainer();
+
+ // Clone the shallow properties
+ nc.itemIds = itemIds != null ? (ArrayList) itemIds.clone() : null;
+ nc.itemSetChangeListeners = itemSetChangeListeners != null ? (LinkedList) itemSetChangeListeners
+ .clone()
+ : null;
+ nc.propertyIds = propertyIds != null ? (ArrayList) propertyIds.clone()
+ : null;
+ nc.propertySetChangeListeners = propertySetChangeListeners != null ? (LinkedList) propertySetChangeListeners
+ .clone()
+ : null;
+ nc.propertyValueChangeListeners = propertyValueChangeListeners != null ? (LinkedList) propertyValueChangeListeners
+ .clone()
+ : null;
+ nc.readOnlyProperties = readOnlyProperties != null ? (HashSet) readOnlyProperties
+ .clone()
+ : null;
+ nc.singlePropertyValueChangeListeners = singlePropertyValueChangeListeners != null ? (Hashtable) singlePropertyValueChangeListeners
+ .clone()
+ : null;
+ nc.sortDirection = sortDirection != null ? (boolean[]) sortDirection
+ .clone() : null;
+ nc.sortPropertyId = sortPropertyId != null ? (Object[]) sortPropertyId
+ .clone() : null;
+ nc.types = types != null ? (Hashtable) types.clone() : null;
+
+ nc.filters = filters == null ? null : (HashSet<Filter>) filters.clone();
+
+ nc.filteredItemIds = filteredItemIds == null ? null
+ : (LinkedHashSet) filteredItemIds.clone();
+
+ // Clone property-values
+ if (items == null) {
+ nc.items = null;
+ } else {
+ nc.items = new Hashtable();
+ for (final Iterator i = items.keySet().iterator(); i.hasNext();) {
+ final Object id = i.next();
+ final Hashtable it = (Hashtable) items.get(id);
+ nc.items.put(id, it.clone());
+ }
+ }
+
+ return nc;
+ }
+
+ /**
+ * Note! In Toolkit version 5.2.6 removed complex equals method due the old
+ * one was practically useless and caused serious performance issues.
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ if (filters == null) {
+ filters = new HashSet<Filter>();
+ }
+ filters.add(new Filter(propertyId, filterString, ignoreCase,
+ onlyMatchPrefix));
+ updateContainerFiltering();
+ }
+
+ public void removeAllContainerFilters() {
+ if (filters == null) {
+ return;
+ }
+ filters.clear();
+ updateContainerFiltering();
+ }
+
+ public void removeContainerFilters(Object propertyId) {
+ if (filters == null || propertyId == null) {
+ return;
+ }
+ final Iterator<Filter> i = filters.iterator();
+ while (i.hasNext()) {
+ final Filter f = i.next();
+ if (propertyId.equals(f.propertyId)) {
+ i.remove();
+ }
+ }
+ updateContainerFiltering();
+ }
+
+ private void updateContainerFiltering(Object propertyId) {
+ if (filters == null || propertyId == null) {
+ return;
+ }
+ // update container filtering if there is a filter for the given
+ // property
+ final Iterator<Filter> i = filters.iterator();
+ while (i.hasNext()) {
+ final Filter f = i.next();
+ if (propertyId.equals(f.propertyId)) {
+ updateContainerFiltering();
+ return;
+ }
+ }
+ }
+
+ private void updateContainerFiltering() {
+
+ // Clearing filters?
+ if (filters == null || filters.isEmpty()) {
+ filteredItemIds = null;
+ if (filters != null) {
+ filters = null;
+ fireContentsChange(-1);
+ }
+ return;
+ }
+
+ // Reset filtered list
+ if (filteredItemIds == null) {
+ filteredItemIds = new LinkedHashSet();
+ } else {
+ filteredItemIds.clear();
+ }
+
+ // Filter
+ for (final Iterator i = itemIds.iterator(); i.hasNext();) {
+ final Object id = i.next();
+ if (passesFilters(new IndexedContainerItem(id))) {
+ filteredItemIds.add(id);
+ }
+ }
+
+ fireContentsChange(-1);
+ }
+
+ private boolean passesFilters(Item item) {
+ if (filters == null) {
+ return true;
+ }
+ if (item == null) {
+ return false;
+ }
+ final Iterator<Filter> i = filters.iterator();
+ while (i.hasNext()) {
+ final Filter f = i.next();
+ if (!f.passesFilter(item)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+} \ No newline at end of file
diff --git a/src/com/vaadin/data/util/MethodProperty.java b/src/com/vaadin/data/util/MethodProperty.java
new file mode 100644
index 0000000000..f74e84ea93
--- /dev/null
+++ b/src/com/vaadin/data/util/MethodProperty.java
@@ -0,0 +1,938 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.LinkedList;
+
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * Proxy class for creating Properties from pairs of getter and setter methods
+ * of a Bean property. An instance of this class can be thought as having been
+ * attached to a field of an object. Accessing the object through the Property
+ * interface directly manipulates the underlying field.
+ * </p>
+ *
+ * <p>
+ * It's assumed that the return value returned by the getter method is
+ * assignable to the type of the property, and the setter method parameter is
+ * assignable to that value.
+ * </p>
+ *
+ * <p>
+ * A valid getter method must always be available, but instance of this class
+ * can be constructed with a <code>null</code> setter method in which case the
+ * resulting MethodProperty is read-only.
+ * </p>
+ *
+ * <p>
+ * MethodProperty implements Property.ValueChangeNotifier, but does not
+ * automatically know whether or not the getter method will actually return a
+ * new value - value change listeners are always notified when setValue is
+ * called, without verifying what the getter returns.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class MethodProperty implements Property, Property.ValueChangeNotifier,
+ Property.ReadOnlyStatusChangeNotifier {
+
+ /**
+ * The object that includes the property the MethodProperty is bound to.
+ */
+ private transient Object instance;
+
+ /**
+ * Argument arrays for the getter and setter methods.
+ */
+ private transient Object[] setArgs, getArgs;
+
+ /**
+ * Is the MethodProperty read-only?
+ */
+ private boolean readOnly;
+
+ /**
+ * The getter and setter methods.
+ */
+ private transient Method setMethod, getMethod;
+
+ /**
+ * Index of the new value in the argument list for the setter method. If the
+ * setter method requires several parameters, this index tells which one is
+ * the actual value to change.
+ */
+ private int setArgumentIndex;
+
+ /**
+ * Type of the property.
+ */
+ private Class type;
+
+ /**
+ * List of listeners who are interested in the read-only status changes of
+ * the MethodProperty
+ */
+ private LinkedList readOnlyStatusChangeListeners = null;
+
+ /**
+ * List of listeners who are interested in the value changes of the
+ * MethodProperty
+ */
+ private LinkedList valueChangeListeners = null;
+
+ /* Special serialization to handle method references */
+ private void writeObject(java.io.ObjectOutputStream out) throws IOException {
+ out.defaultWriteObject();
+ out.writeObject(instance);
+ out.writeObject(setArgs);
+ out.writeObject(getArgs);
+ if (setMethod != null) {
+ out.writeObject(setMethod.getName());
+ out.writeObject(setMethod.getParameterTypes());
+ } else {
+ out.writeObject("");
+ out.writeObject("");
+ }
+ if (getMethod != null) {
+ out.writeObject(getMethod.getName());
+ out.writeObject(getMethod.getParameterTypes());
+ } else {
+ out.writeObject("");
+ out.writeObject("");
+ }
+ };
+
+ /* Special serialization to handle method references */
+ private void readObject(java.io.ObjectInputStream in) throws IOException,
+ ClassNotFoundException {
+ in.defaultReadObject();
+ try {
+ instance = in.readObject();
+ setArgs = (Object[]) in.readObject();
+ getArgs = (Object[]) in.readObject();
+ String name = (String) in.readObject();
+ Class<?>[] paramTypes = (Class<?>[]) in.readObject();
+ if (name != null && !name.equals("")) {
+ setMethod = instance.getClass().getMethod(name, paramTypes);
+ } else {
+ setMethod = null;
+ }
+ name = (String) in.readObject();
+ paramTypes = (Class<?>[]) in.readObject();
+ if (name != null && !name.equals("")) {
+ getMethod = instance.getClass().getMethod(name, paramTypes);
+ } else {
+ getMethod = null;
+ }
+ } catch (SecurityException e) {
+ System.err.println("Internal deserialization error");
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ System.err.println("Internal deserialization error");
+ e.printStackTrace();
+ }
+ };
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from a named bean
+ * property. This constructor takes an object and the name of a bean
+ * property and initializes itself with the accessor methods for the
+ * property.
+ * </p>
+ * <p>
+ * The getter method of a <code>MethodProperty</code> instantiated with this
+ * constructor will be called with no arguments, and the setter method with
+ * only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is unavailable, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * <p>
+ * Method names are constucted from the bean property by adding
+ * get/is/are/set prefix and capitalising the first character in the name of
+ * the given bean property.
+ * </p>
+ *
+ * @param instance
+ * the object that includes the property.
+ * @param beanPropertyName
+ * the name of the property to bind to.
+ */
+ public MethodProperty(Object instance, String beanPropertyName) {
+
+ final Class beanClass = instance.getClass();
+
+ // Assure that the first letter is upper cased (it is a common
+ // mistake to write firstName, not FirstName).
+ if (Character.isLowerCase(beanPropertyName.charAt(0))) {
+ final char[] buf = beanPropertyName.toCharArray();
+ buf[0] = Character.toUpperCase(buf[0]);
+ beanPropertyName = new String(buf);
+ }
+
+ // Find the get method
+ getMethod = null;
+ try {
+ getMethod = beanClass.getMethod("get" + beanPropertyName,
+ new Class[] {});
+ } catch (final java.lang.NoSuchMethodException ignored) {
+ try {
+ getMethod = beanClass.getMethod("is" + beanPropertyName,
+ new Class[] {});
+ } catch (final java.lang.NoSuchMethodException ignoredAsWell) {
+ try {
+ getMethod = beanClass.getMethod("are" + beanPropertyName,
+ new Class[] {});
+ } catch (final java.lang.NoSuchMethodException e) {
+ throw new MethodProperty.MethodException("Bean property "
+ + beanPropertyName + " can not be found");
+ }
+ }
+ }
+
+ // In case the get method is found, resolve the type
+ type = getMethod.getReturnType();
+
+ // Finds the set method
+ setMethod = null;
+ try {
+ setMethod = beanClass.getMethod("set" + beanPropertyName,
+ new Class[] { type });
+ } catch (final java.lang.NoSuchMethodException skipped) {
+ }
+
+ // Gets the return type from get method
+ if (type.isPrimitive()) {
+ if (type.equals(Boolean.TYPE)) {
+ type = Boolean.class;
+ } else if (type.equals(Integer.TYPE)) {
+ type = Integer.class;
+ } else if (type.equals(Float.TYPE)) {
+ type = Float.class;
+ } else if (type.equals(Double.TYPE)) {
+ type = Double.class;
+ } else if (type.equals(Byte.TYPE)) {
+ type = Byte.class;
+ } else if (type.equals(Character.TYPE)) {
+ type = Character.class;
+ } else if (type.equals(Short.TYPE)) {
+ type = Short.class;
+ } else if (type.equals(Long.TYPE)) {
+ type = Long.class;
+ }
+ }
+
+ setArguments(new Object[] {}, new Object[] { null }, 0);
+ readOnly = (setMethod == null);
+ this.instance = instance;
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from named getter
+ * and setter methods. The getter method of a <code>MethodProperty</code>
+ * instantiated with this constructor will be called with no arguments, and
+ * the setter method with only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is <code>null</code>, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethodName
+ * the name of the getter method.
+ * @param setMethodName
+ * the name of the setter method.
+ *
+ */
+ public MethodProperty(Class type, Object instance, String getMethodName,
+ String setMethodName) {
+ this(type, instance, getMethodName, setMethodName, new Object[] {},
+ new Object[] { null }, 0);
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> with the getter and
+ * setter methods. The getter method of a <code>MethodProperty</code>
+ * instantiated with this constructor will be called with no arguments, and
+ * the setter method with only the new value as the sole argument.
+ * </p>
+ *
+ * <p>
+ * If the setter method is <code>null</code>, the resulting
+ * <code>MethodProperty</code> will be read-only, otherwise it will be
+ * read-write.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethod
+ * the getter method.
+ * @param setMethod
+ * the setter method.
+ */
+ public MethodProperty(Class type, Object instance, Method getMethod,
+ Method setMethod) {
+ this(type, instance, getMethod, setMethod, new Object[] {},
+ new Object[] { null }, 0);
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from named getter
+ * and setter methods and argument lists. The getter method of a
+ * <code>MethodProperty</code> instantiated with this constructor will be
+ * called with the getArgs as arguments. The setArgs will be used as the
+ * arguments for the setter method, though the argument indexed by the
+ * setArgumentIndex will be replaced with the argument passed to the
+ * {@link #setValue(Object newValue)} method.
+ * </p>
+ *
+ * <p>
+ * For example, if the <code>setArgs</code> contains <code>A</code>,
+ * <code>B</code> and <code>C</code>, and <code>setArgumentIndex =
+ * 1</code>, the call <code>methodProperty.setValue(X)</code> would result
+ * in the setter method to be called with the parameter set of
+ * <code>{A, X, C}</code>
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethodName
+ * the name of the getter method.
+ * @param setMethodName
+ * the name of the setter method.
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ public MethodProperty(Class type, Object instance, String getMethodName,
+ String setMethodName, Object[] getArgs, Object[] setArgs,
+ int setArgumentIndex) {
+
+ // Check the setargs and setargs index
+ if (setMethodName != null && setArgs == null) {
+ throw new IndexOutOfBoundsException("The setArgs can not be null");
+ }
+ if (setMethodName != null
+ && (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length)) {
+ throw new IndexOutOfBoundsException(
+ "The setArgumentIndex must be >= 0 and < setArgs.length");
+ }
+
+ // Set type
+ this.type = type;
+
+ // Find set and get -methods
+ final Method[] m = instance.getClass().getMethods();
+
+ // Finds get method
+ boolean found = false;
+ for (int i = 0; i < m.length; i++) {
+
+ // Tests the name of the get Method
+ if (!m[i].getName().equals(getMethodName)) {
+
+ // name does not match, try next method
+ continue;
+ }
+
+ // Tests return type
+ if (!type.equals(m[i].getReturnType())) {
+ continue;
+ }
+
+ // Tests the parameter types
+ final Class[] c = m[i].getParameterTypes();
+ if (c.length != getArgs.length) {
+
+ // not the right amount of parameters, try next method
+ continue;
+ }
+ int j = 0;
+ while (j < c.length) {
+ if (getArgs[j] != null
+ && !c[j].isAssignableFrom(getArgs[j].getClass())) {
+
+ // parameter type does not match, try next method
+ break;
+ }
+ j++;
+ }
+ if (j == c.length) {
+
+ // all paramteters matched
+ if (found == true) {
+ throw new MethodProperty.MethodException(
+ "Could not uniquely identify " + getMethodName
+ + "-method");
+ } else {
+ found = true;
+ getMethod = m[i];
+ }
+ }
+ }
+ if (found != true) {
+ throw new MethodProperty.MethodException("Could not find "
+ + getMethodName + "-method");
+ }
+
+ // Finds set method
+ if (setMethodName != null) {
+
+ // Finds setMethod
+ found = false;
+ for (int i = 0; i < m.length; i++) {
+
+ // Checks name
+ if (!m[i].getName().equals(setMethodName)) {
+
+ // name does not match, try next method
+ continue;
+ }
+
+ // Checks parameter compatibility
+ final Class[] c = m[i].getParameterTypes();
+ if (c.length != setArgs.length) {
+
+ // not the right amount of parameters, try next method
+ continue;
+ }
+ int j = 0;
+ while (j < c.length) {
+ if (setArgs[j] != null
+ && !c[j].isAssignableFrom(setArgs[j].getClass())) {
+
+ // parameter type does not match, try next method
+ break;
+ } else if (j == setArgumentIndex && !c[j].equals(type)) {
+
+ // Property type is not the same as setArg type
+ break;
+ }
+ j++;
+ }
+ if (j == c.length) {
+
+ // all parameters match
+ if (found == true) {
+ throw new MethodProperty.MethodException(
+ "Could not identify unique " + setMethodName
+ + "-method");
+ } else {
+ found = true;
+ setMethod = m[i];
+ }
+ }
+ }
+ if (found != true) {
+ throw new MethodProperty.MethodException("Could not identify "
+ + setMethodName + "-method");
+ }
+ }
+
+ // Gets the return type from get method
+ if (type.isPrimitive()) {
+ if (type.equals(Boolean.TYPE)) {
+ type = Boolean.class;
+ } else if (type.equals(Integer.TYPE)) {
+ type = Integer.class;
+ } else if (type.equals(Float.TYPE)) {
+ type = Float.class;
+ } else if (type.equals(Double.TYPE)) {
+ type = Double.class;
+ } else if (type.equals(Byte.TYPE)) {
+ type = Byte.class;
+ } else if (type.equals(Character.TYPE)) {
+ type = Character.class;
+ } else if (type.equals(Short.TYPE)) {
+ type = Short.class;
+ } else if (type.equals(Long.TYPE)) {
+ type = Long.class;
+ }
+ }
+
+ setArguments(getArgs, setArgs, setArgumentIndex);
+ readOnly = (setMethod == null);
+ this.instance = instance;
+ }
+
+ /**
+ * <p>
+ * Creates a new instance of <code>MethodProperty</code> from the getter and
+ * setter methods, and argument lists.
+ * </p>
+ * <p>
+ * This constructor behaves exactly like
+ * {@link #MethodProperty(Class type, Object instance, String getMethodName, String setMethodName, Object [] getArgs, Object [] setArgs, int setArgumentIndex)}
+ * except that instead of names of the getter and setter methods this
+ * constructor is given the actual methods themselves.
+ * </p>
+ *
+ * @param type
+ * the type of the property.
+ * @param instance
+ * the object that includes the property.
+ * @param getMethod
+ * the getter method.
+ * @param setMethod
+ * the setter method.
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ public MethodProperty(Class type, Object instance, Method getMethod,
+ Method setMethod, Object[] getArgs, Object[] setArgs,
+ int setArgumentIndex) {
+
+ if (getMethod == null) {
+ throw new MethodProperty.MethodException(
+ "Property GET-method cannot not be null: " + type);
+ }
+
+ if (setMethod != null) {
+ if (setArgs == null) {
+ throw new IndexOutOfBoundsException(
+ "The setArgs can not be null");
+ }
+ if (setArgumentIndex < 0 || setArgumentIndex >= setArgs.length) {
+ throw new IndexOutOfBoundsException(
+ "The setArgumentIndex must be >= 0 and < setArgs.length");
+ }
+ }
+
+ // Gets the return type from get method
+ if (type.isPrimitive()) {
+ if (type.equals(Boolean.TYPE)) {
+ type = Boolean.class;
+ } else if (type.equals(Integer.TYPE)) {
+ type = Integer.class;
+ } else if (type.equals(Float.TYPE)) {
+ type = Float.class;
+ } else if (type.equals(Double.TYPE)) {
+ type = Double.class;
+ } else if (type.equals(Byte.TYPE)) {
+ type = Byte.class;
+ } else if (type.equals(Character.TYPE)) {
+ type = Character.class;
+ } else if (type.equals(Short.TYPE)) {
+ type = Short.class;
+ } else if (type.equals(Long.TYPE)) {
+ type = Long.class;
+ }
+ }
+
+ this.getMethod = getMethod;
+ this.setMethod = setMethod;
+ setArguments(getArgs, setArgs, setArgumentIndex);
+ readOnly = (setMethod == null);
+ this.instance = instance;
+ this.type = type;
+ }
+
+ /**
+ * Returns the type of the Property. The methods <code>getValue</code> and
+ * <code>setValue</code> must be compatible with this type: one must be able
+ * to safely cast the value returned from <code>getValue</code> to the given
+ * type and pass any variable assignable to this type as an argument to
+ * <code>setValue</code>.
+ *
+ * @return type of the Property
+ */
+ public final Class getType() {
+ return type;
+ }
+
+ /**
+ * Tests if the object is in read-only mode. In read-only mode calls to
+ * <code>setValue</code> will throw <code>ReadOnlyException</code> and will
+ * not modify the value of the Property.
+ *
+ * @return <code>true</code> if the object is in read-only mode,
+ * <code>false</code> if it's not
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Gets the value stored in the Property. The value is resolved by calling
+ * the specified getter method with the argument specified at instantiation.
+ *
+ * @return the value of the Property
+ */
+ public Object getValue() {
+ try {
+ return getMethod.invoke(instance, getArgs);
+ } catch (final Throwable e) {
+ throw new MethodProperty.MethodException(e);
+ }
+ }
+
+ /**
+ * Returns the value of the <code>MethodProperty</code> in human readable
+ * textual format. The return value should be assignable to the
+ * <code>setValue</code> method if the Property is not in read-only mode.
+ *
+ * @return String representation of the value stored in the Property
+ */
+ @Override
+ public String toString() {
+ final Object value = getValue();
+ if (value == null) {
+ return null;
+ }
+ return value.toString();
+ }
+
+ /**
+ * <p>
+ * Sets the setter method and getter method argument lists.
+ * </p>
+ *
+ * @param getArgs
+ * the fixed argument list to be passed to the getter method.
+ * @param setArgs
+ * the fixed argument list to be passed to the setter method.
+ * @param setArgumentIndex
+ * the index of the argument in <code>setArgs</code> to be
+ * replaced with <code>newValue</code> when
+ * {@link #setValue(Object newValue)} is called.
+ */
+ public void setArguments(Object[] getArgs, Object[] setArgs,
+ int setArgumentIndex) {
+ this.getArgs = new Object[getArgs.length];
+ for (int i = 0; i < getArgs.length; i++) {
+ this.getArgs[i] = getArgs[i];
+ }
+ this.setArgs = new Object[setArgs.length];
+ for (int i = 0; i < setArgs.length; i++) {
+ this.setArgs[i] = setArgs[i];
+ }
+ this.setArgumentIndex = setArgumentIndex;
+ }
+
+ /**
+ * Sets the value of the property. This method supports setting from
+ * <code>String</code>s if either <code>String</code> is directly assignable
+ * to property type, or the type class contains a string constructor.
+ *
+ * @param newValue
+ * the New value of the property.
+ * @throws <code>Property.ReadOnlyException</code> if the object is in
+ * read-only mode.
+ * @throws <code>Property.ConversionException</code> if
+ * <code>newValue</code> can't be converted into the Property's
+ * native type directly or through <code>String</code>.
+ * @see #invokeSetMethod(Object)
+ */
+ public void setValue(Object newValue) throws Property.ReadOnlyException,
+ Property.ConversionException {
+
+ // Checks the mode
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ // Try to assign the compatible value directly
+ if (newValue == null || type.isAssignableFrom(newValue.getClass())) {
+ invokeSetMethod(newValue);
+ } else {
+
+ Object value;
+ try {
+
+ // Gets the string constructor
+ final Constructor constr = getType().getConstructor(
+ new Class[] { String.class });
+
+ value = constr
+ .newInstance(new Object[] { newValue.toString() });
+
+ } catch (final java.lang.Exception e) {
+ throw new Property.ConversionException(e);
+ }
+
+ // Creates new object from the string
+ invokeSetMethod(value);
+ }
+ fireValueChange();
+ }
+
+ /**
+ * Internal method to actually call the setter method of the wrapped
+ * property.
+ *
+ * @param value
+ */
+ private void invokeSetMethod(Object value) {
+
+ try {
+ // Construct a temporary argument array only if needed
+ if (setArgs.length == 1) {
+ setMethod.invoke(instance, new Object[] { value });
+ } else {
+
+ // Sets the value to argument array
+ final Object[] args = new Object[setArgs.length];
+ for (int i = 0; i < setArgs.length; i++) {
+ args[i] = (i == setArgumentIndex) ? value : setArgs[i];
+ }
+ setMethod.invoke(instance, args);
+ }
+ } catch (final InvocationTargetException e) {
+ final Throwable targetException = e.getTargetException();
+ throw new MethodProperty.MethodException(targetException);
+ } catch (final Exception e) {
+ throw new MethodProperty.MethodException(e);
+ }
+ }
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * @param newStatus
+ * the new read-only status of the Property.
+ */
+ public void setReadOnly(boolean newStatus) {
+ final boolean prevStatus = readOnly;
+ if (newStatus) {
+ readOnly = true;
+ } else {
+ readOnly = (setMethod == null);
+ }
+ if (prevStatus != readOnly) {
+ fireReadOnlyStatusChange();
+ }
+ }
+
+ /**
+ * <code>Exception</code> object that signals that there were problems
+ * calling or finding the specified getter or setter methods of the
+ * property.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ public class MethodException extends RuntimeException {
+
+ /**
+ * Cause of the method exception
+ */
+ private Throwable cause;
+
+ /**
+ * Constructs a new <code>MethodException</code> with the specified
+ * detail message.
+ *
+ * @param msg
+ * the detail message.
+ */
+ public MethodException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs a new <code>MethodException</code> from another exception.
+ *
+ * @param cause
+ * the cause of the exception.
+ */
+ public MethodException(Throwable cause) {
+ this.cause = cause;
+ }
+
+ /**
+ * @see java.lang.Throwable#getCause()
+ */
+ @Override
+ public Throwable getCause() {
+ return cause;
+ }
+
+ /**
+ * Gets the method property this exception originates from.
+ */
+ public MethodProperty getMethodProperty() {
+ return MethodProperty.this;
+ }
+ }
+
+ /* Events */
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has been changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class ReadOnlyStatusChangeEvent extends java.util.EventObject
+ implements Property.ReadOnlyStatusChangeEvent {
+
+ /**
+ * Constructs a new read-only status change event for this object.
+ *
+ * @param source
+ * source object of the event.
+ */
+ protected ReadOnlyStatusChangeEvent(MethodProperty source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered.
+ */
+ public void addListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners == null) {
+ readOnlyStatusChangeListeners = new LinkedList();
+ }
+ readOnlyStatusChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners != null) {
+ readOnlyStatusChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Sends a read only status change event to all registered listeners.
+ */
+ private void fireReadOnlyStatusChange() {
+ if (readOnlyStatusChangeListeners != null) {
+ final Object[] l = readOnlyStatusChangeListeners.toArray();
+ final Property.ReadOnlyStatusChangeEvent event = new MethodProperty.ReadOnlyStatusChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ReadOnlyStatusChangeListener) l[i])
+ .readOnlyStatusChange(event);
+ }
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose value has been
+ * changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 5.3
+ */
+ private class ValueChangeEvent extends java.util.EventObject implements
+ Property.ValueChangeEvent {
+
+ /**
+ * Constructs a new value change event for this object.
+ *
+ * @param source
+ * source object of the event.
+ */
+ protected ValueChangeEvent(MethodProperty source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose value has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+
+ }
+
+ public void addListener(ValueChangeListener listener) {
+ if (valueChangeListeners == null) {
+ valueChangeListeners = new LinkedList();
+ }
+ valueChangeListeners.add(listener);
+
+ }
+
+ public void removeListener(ValueChangeListener listener) {
+ if (valueChangeListeners != null) {
+ valueChangeListeners.remove(listener);
+ }
+
+ }
+
+ /**
+ * Sends a value change event to all registered listeners.
+ */
+ public void fireValueChange() {
+ if (valueChangeListeners != null) {
+ final Object[] l = valueChangeListeners.toArray();
+ final Property.ValueChangeEvent event = new MethodProperty.ValueChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ValueChangeListener) l[i]).valueChange(event);
+ }
+ }
+ }
+
+}
diff --git a/src/com/vaadin/data/util/ObjectProperty.java b/src/com/vaadin/data/util/ObjectProperty.java
new file mode 100644
index 0000000000..6d58b2d92d
--- /dev/null
+++ b/src/com/vaadin/data/util/ObjectProperty.java
@@ -0,0 +1,349 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.lang.reflect.Constructor;
+import java.util.LinkedList;
+
+import com.vaadin.data.Property;
+
+/**
+ * A simple data object containing one typed value. This class is a
+ * straightforward implementation of the the
+ * {@link com.vaadin.data.Property} interface.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class ObjectProperty implements Property, Property.ValueChangeNotifier,
+ Property.ReadOnlyStatusChangeNotifier {
+
+ /**
+ * A boolean value storing the Property's read-only status information.
+ */
+ private boolean readOnly = false;
+
+ /**
+ * The value contained by the Property.
+ */
+ private Object value;
+
+ /**
+ * Data type of the Property's value.
+ */
+ private final Class type;
+
+ /**
+ * Internal list of registered value change listeners.
+ */
+ private LinkedList valueChangeListeners = null;
+
+ /**
+ * Internal list of registered read-only status change listeners.
+ */
+ private LinkedList readOnlyStatusChangeListeners = null;
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value. The type
+ * of the property is automatically initialized to be the type of the given
+ * value.
+ *
+ * @param value
+ * the Initial value of the Property.
+ */
+ public ObjectProperty(Object value) {
+ this(value, value.getClass());
+ }
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value and type.
+ *
+ * @param value
+ * the Initial value of the Property.
+ * @param type
+ * the type of the value. The value must be assignable to given
+ * type.
+ */
+ public ObjectProperty(Object value, Class type) {
+
+ // Set the values
+ this.type = type;
+ setValue(value);
+ }
+
+ /**
+ * Creates a new instance of ObjectProperty with the given value, type and
+ * read-only mode status.
+ *
+ * @param value
+ * the Initial value of the property.
+ * @param type
+ * the type of the value. <code>value</code> must be assignable
+ * to this type.
+ * @param readOnly
+ * Sets the read-only mode.
+ */
+ public ObjectProperty(Object value, Class type, boolean readOnly) {
+ this(value, type);
+ setReadOnly(readOnly);
+ }
+
+ /**
+ * Returns the type of the ObjectProperty. The methods <code>getValue</code>
+ * and <code>setValue</code> must be compatible with this type: one must be
+ * able to safely cast the value returned from <code>getValue</code> to the
+ * given type and pass any variable assignable to this type as an argument
+ * to <code>setValue</code>.
+ *
+ * @return type of the Property
+ */
+ public final Class getType() {
+ return type;
+ }
+
+ /**
+ * Gets the value stored in the Property.
+ *
+ * @return the value stored in the Property
+ */
+ public Object getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the value of the ObjectProperty in human readable textual format.
+ * The return value should be assignable to the <code>setValue</code> method
+ * if the Property is not in read-only mode.
+ *
+ * @return <code>String</code> representation of the value stored in the
+ * ObjectProperty
+ */
+ @Override
+ public String toString() {
+ final Object value = getValue();
+ if (value != null) {
+ return value.toString();
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Tests if the Property is in read-only mode. In read-only mode calls to
+ * the method <code>setValue</code> will throw
+ * <code>ReadOnlyException</code>s and will not modify the value of the
+ * Property.
+ *
+ * @return <code>true</code> if the Property is in read-only mode,
+ * <code>false</code> if it's not
+ */
+ public boolean isReadOnly() {
+ return readOnly;
+ }
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * @param newStatus
+ * the new read-only status of the Property.
+ */
+ public void setReadOnly(boolean newStatus) {
+ if (newStatus != readOnly) {
+ readOnly = newStatus;
+ fireReadOnlyStatusChange();
+ }
+ }
+
+ /**
+ * Sets the value of the property. This method supports setting from
+ * <code>String</code> if either <code>String</code> is directly assignable
+ * to property type, or the type class contains a string constructor.
+ *
+ * @param newValue
+ * the New value of the property.
+ * @throws <code>Property.ReadOnlyException</code> if the object is in
+ * read-only mode
+ * @throws <code>Property.ConversionException</code> if the newValue can't
+ * be converted into the Property's native type directly or through
+ * <code>String</code>
+ */
+ public void setValue(Object newValue) throws Property.ReadOnlyException,
+ Property.ConversionException {
+
+ // Checks the mode
+ if (isReadOnly()) {
+ throw new Property.ReadOnlyException();
+ }
+
+ // Tries to assign the compatible value directly
+ if (newValue == null || type.isAssignableFrom(newValue.getClass())) {
+ value = newValue;
+ } else {
+ try {
+
+ // Gets the string constructor
+ final Constructor constr = getType().getConstructor(
+ new Class[] { String.class });
+
+ // Creates new object from the string
+ value = constr
+ .newInstance(new Object[] { newValue.toString() });
+
+ } catch (final java.lang.Exception e) {
+ throw new Property.ConversionException(e);
+ }
+ }
+
+ fireValueChange();
+ }
+
+ /* Events */
+
+ /**
+ * An <code>Event</code> object specifying the ObjectProperty whose value
+ * has changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class ValueChangeEvent extends java.util.EventObject implements
+ Property.ValueChangeEvent {
+
+ /**
+ * Constructs a new value change event for this object.
+ *
+ * @param source
+ * the source object of the event.
+ */
+ protected ValueChangeEvent(ObjectProperty source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source the Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has been changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class ReadOnlyStatusChangeEvent extends java.util.EventObject
+ implements Property.ReadOnlyStatusChangeEvent {
+
+ /**
+ * Constructs a new read-only status change event for this object.
+ *
+ * @param source
+ * source object of the event
+ */
+ protected ReadOnlyStatusChangeEvent(ObjectProperty source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /**
+ * Removes a previously registered value change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Property.ValueChangeListener listener) {
+ if (valueChangeListeners != null) {
+ valueChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Registers a new value change listener for this ObjectProperty.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ValueChangeListener listener) {
+ if (valueChangeListeners == null) {
+ valueChangeListeners = new LinkedList();
+ }
+ valueChangeListeners.add(listener);
+ }
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners == null) {
+ readOnlyStatusChangeListeners = new LinkedList();
+ }
+ readOnlyStatusChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners != null) {
+ readOnlyStatusChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Sends a value change event to all registered listeners.
+ */
+ private void fireValueChange() {
+ if (valueChangeListeners != null) {
+ final Object[] l = valueChangeListeners.toArray();
+ final Property.ValueChangeEvent event = new ObjectProperty.ValueChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ValueChangeListener) l[i]).valueChange(event);
+ }
+ }
+ }
+
+ /**
+ * Sends a read only status change event to all registered listeners.
+ */
+ private void fireReadOnlyStatusChange() {
+ if (readOnlyStatusChangeListeners != null) {
+ final Object[] l = readOnlyStatusChangeListeners.toArray();
+ final Property.ReadOnlyStatusChangeEvent event = new ObjectProperty.ReadOnlyStatusChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ReadOnlyStatusChangeListener) l[i])
+ .readOnlyStatusChange(event);
+ }
+ }
+ }
+}
diff --git a/src/com/vaadin/data/util/PropertyFormatter.java b/src/com/vaadin/data/util/PropertyFormatter.java
new file mode 100644
index 0000000000..616c1beb0a
--- /dev/null
+++ b/src/com/vaadin/data/util/PropertyFormatter.java
@@ -0,0 +1,380 @@
+package com.vaadin.data.util;
+
+import java.util.LinkedList;
+
+import com.vaadin.data.Property;
+
+/**
+ * Formatting proxy for a property.
+ *
+ * <p>
+ * This class can be used to implement formatting for any type of Property
+ * datasources. The idea is to connect this as proxy between UI component and
+ * the original datasource.
+ * </p>
+ *
+ * <p>
+ * For example <code>
+ * textfield.setPropertyDataSource(new PropertyFormatter(property) {
+ public String format(Object value) {
+ return ((Double) value).toString() + "000000000";
+ }
+
+ public Object parse(String formattedValue) throws Exception {
+ return Double.parseDouble(formattedValue);
+ }
+
+ });</code> adds formatter for Double-typed property that extends standard
+ * "1.0" notation with more zeroes.
+ * </p>
+ *
+ * @author IT Mill Ltd.
+ * @since 5.3.0
+ */
+@SuppressWarnings("serial")
+public abstract class PropertyFormatter implements Property,
+ Property.ValueChangeNotifier, Property.ValueChangeListener,
+ Property.ReadOnlyStatusChangeListener,
+ Property.ReadOnlyStatusChangeNotifier {
+
+ /**
+ * Internal list of registered value change listeners.
+ */
+ private LinkedList valueChangeListeners = null;
+
+ /**
+ * Internal list of registered read-only status change listeners.
+ */
+ private LinkedList readOnlyStatusChangeListeners = null;
+
+ /** Datasource that stores the actual value. */
+ Property dataSource;
+
+ /**
+ * Construct a new formatter that is connected to given datasource.
+ *
+ * @param propertyDataSource
+ * to connect this property to.
+ */
+ public PropertyFormatter(Property propertyDataSource) {
+
+ setPropertyDataSource(propertyDataSource);
+ }
+
+ /**
+ * Gets the current data source of the formatter, if any.
+ *
+ * @return the current data source as a Property, or <code>null</code> if
+ * none defined.
+ */
+ public Property getPropertyDataSource() {
+ return dataSource;
+ }
+
+ /**
+ * Sets the specified Property as the data source for the formatter.
+ *
+ *
+ * <p>
+ * Remember that new data sources getValue() must return objects that are
+ * compatible with parse() and format() methods.
+ * </p>
+ *
+ * @param newDataSource
+ * the new data source Property.
+ */
+ public void setPropertyDataSource(Property newDataSource) {
+
+ boolean readOnly = false;
+ String prevValue = null;
+
+ if (dataSource != null) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeListener) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .removeListener(this);
+ }
+ readOnly = isReadOnly();
+ prevValue = toString();
+ }
+
+ dataSource = newDataSource;
+
+ if (dataSource != null) {
+ if (dataSource instanceof Property.ValueChangeNotifier) {
+ ((Property.ValueChangeNotifier) dataSource).addListener(this);
+ }
+ if (dataSource instanceof Property.ReadOnlyStatusChangeListener) {
+ ((Property.ReadOnlyStatusChangeNotifier) dataSource)
+ .addListener(this);
+ }
+ }
+
+ if (isReadOnly() != readOnly) {
+ fireReadOnlyStatusChange();
+ }
+ String newVal = toString();
+ if ((prevValue == null && newVal != null)
+ || !prevValue.equals(prevValue)) {
+ fireValueChange();
+ }
+ }
+
+ /* Documented in the interface */
+ public Class getType() {
+ return String.class;
+ }
+
+ /**
+ * Get the formatted value.
+ *
+ * @return If the datasource returns null, this is null. Otherwise this is
+ * String given by format().
+ */
+ public Object getValue() {
+ return toString();
+ }
+
+ /**
+ * Get the formatted value.
+ *
+ * @return If the datasource returns null, this is null. Otherwise this is
+ * String given by format().
+ */
+ @Override
+ public String toString() {
+ Object value = dataSource == null ? false : dataSource.getValue();
+ if (value == null) {
+ return null;
+ }
+ return format(value);
+ }
+
+ /** Reflects the read-only status of the datasource. */
+ public boolean isReadOnly() {
+ return dataSource == null ? false : dataSource.isReadOnly();
+ }
+
+ /**
+ * This method must be implemented to format the values received from
+ * DataSource.
+ *
+ * @param value
+ * Value object got from the datasource. This is guaranteed to be
+ * non-null and of the type compatible with getType() of the
+ * datasource.
+ * @return
+ */
+ abstract public String format(Object value);
+
+ /**
+ * Parse string and convert it to format compatible with datasource.
+ *
+ * The method is required to assure that parse(format(x)) equals x.
+ *
+ * @param formattedValue
+ * This is guaranteed to be non-null string.
+ * @return Non-null value compatible with datasource.
+ * @throws Exception
+ * Any type of exception can be thrown to indicate that the
+ * conversion was not succesful.
+ */
+ abstract public Object parse(String formattedValue) throws Exception;
+
+ /**
+ * Sets the Property's read-only mode to the specified status.
+ *
+ * @param newStatus
+ * the new read-only status of the Property.
+ */
+ public void setReadOnly(boolean newStatus) {
+ if (dataSource != null) {
+ dataSource.setReadOnly(newStatus);
+ }
+ }
+
+ public void setValue(Object newValue) throws ReadOnlyException,
+ ConversionException {
+ if (dataSource == null) {
+ return;
+ }
+ if (newValue == null) {
+ dataSource.setValue(null);
+ }
+ try {
+ dataSource.setValue(parse((String) newValue));
+ if (!newValue.equals(toString())) {
+ fireValueChange();
+ }
+ } catch (Exception e) {
+ if (e instanceof ConversionException) {
+ throw (ConversionException) e;
+ } else {
+ throw new ConversionException(e);
+ }
+ }
+
+ }
+
+ /**
+ * An <code>Event</code> object specifying the ObjectProperty whose value
+ * has changed.
+ *
+ * @author IT Mill Ltd.
+ * @since 5.3.0
+ */
+ private class ValueChangeEvent extends java.util.EventObject implements
+ Property.ValueChangeEvent {
+
+ /**
+ * Constructs a new value change event for this object.
+ *
+ * @param source
+ * the source object of the event.
+ */
+ protected ValueChangeEvent(PropertyFormatter source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source the Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying the Property whose read-only
+ * status has been changed.
+ *
+ * @author IT Mill Ltd.
+ * @since 5.3.0
+ */
+ private class ReadOnlyStatusChangeEvent extends java.util.EventObject
+ implements Property.ReadOnlyStatusChangeEvent {
+
+ /**
+ * Constructs a new read-only status change event for this object.
+ *
+ * @param source
+ * source object of the event
+ */
+ protected ReadOnlyStatusChangeEvent(PropertyFormatter source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Property whose read-only state has changed.
+ *
+ * @return source Property of the event.
+ */
+ public Property getProperty() {
+ return (Property) getSource();
+ }
+ }
+
+ /**
+ * Removes a previously registered value change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Property.ValueChangeListener listener) {
+ if (valueChangeListeners != null) {
+ valueChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Registers a new value change listener for this ObjectProperty.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ValueChangeListener listener) {
+ if (valueChangeListeners == null) {
+ valueChangeListeners = new LinkedList();
+ }
+ valueChangeListeners.add(listener);
+ }
+
+ /**
+ * Registers a new read-only status change listener for this Property.
+ *
+ * @param listener
+ * the new Listener to be registered
+ */
+ public void addListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners == null) {
+ readOnlyStatusChangeListeners = new LinkedList();
+ }
+ readOnlyStatusChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered read-only status change listener.
+ *
+ * @param listener
+ * the listener to be removed.
+ */
+ public void removeListener(Property.ReadOnlyStatusChangeListener listener) {
+ if (readOnlyStatusChangeListeners != null) {
+ readOnlyStatusChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Sends a value change event to all registered listeners.
+ */
+ private void fireValueChange() {
+ if (valueChangeListeners != null) {
+ final Object[] l = valueChangeListeners.toArray();
+ final Property.ValueChangeEvent event = new ValueChangeEvent(this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ValueChangeListener) l[i]).valueChange(event);
+ }
+ }
+ }
+
+ /**
+ * Sends a read only status change event to all registered listeners.
+ */
+ private void fireReadOnlyStatusChange() {
+ if (readOnlyStatusChangeListeners != null) {
+ final Object[] l = readOnlyStatusChangeListeners.toArray();
+ final Property.ReadOnlyStatusChangeEvent event = new ReadOnlyStatusChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Property.ReadOnlyStatusChangeListener) l[i])
+ .readOnlyStatusChange(event);
+ }
+ }
+ }
+
+ /**
+ * Listens for changes in the datasource.
+ *
+ * This should not be called directly.
+ */
+ public void valueChange(
+ com.vaadin.data.Property.ValueChangeEvent event) {
+ fireValueChange();
+ }
+
+ /**
+ * Listens for changes in the datasource.
+ *
+ * This should not be called directly.
+ */
+ public void readOnlyStatusChange(
+ com.vaadin.data.Property.ReadOnlyStatusChangeEvent event) {
+ fireReadOnlyStatusChange();
+ }
+
+}
diff --git a/src/com/vaadin/data/util/PropertysetItem.java b/src/com/vaadin/data/util/PropertysetItem.java
new file mode 100644
index 0000000000..83eb15b6ad
--- /dev/null
+++ b/src/com/vaadin/data/util/PropertysetItem.java
@@ -0,0 +1,314 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * Class for handling a set of identified Properties. The elements contained in
+ * a </code>MapItem</code> can be referenced using locally unique identifiers.
+ * The class supports listeners who are interested in changes to the Property
+ * set managed by the class.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+@SuppressWarnings("serial")
+public class PropertysetItem implements Item, Item.PropertySetChangeNotifier,
+ Cloneable {
+
+ /* Private representation of the item */
+
+ /**
+ * Mapping from property id to property.
+ */
+ private HashMap map = new HashMap();
+
+ /**
+ * List of all property ids to maintain the order.
+ */
+ private LinkedList list = new LinkedList();
+
+ /**
+ * List of property set modification listeners.
+ */
+ private LinkedList propertySetChangeListeners = null;
+
+ /* Item methods */
+
+ /**
+ * Gets the Property corresponding to the given Property ID stored in the
+ * Item. If the Item does not contain the Property, <code>null</code> is
+ * returned.
+ *
+ * @param id
+ * the identifier of the Property to get.
+ * @return the Property with the given ID or <code>null</code>
+ */
+ public Property getItemProperty(Object id) {
+ return (Property) map.get(id);
+ }
+
+ /**
+ * Gets the collection of IDs of all Properties stored in the Item.
+ *
+ * @return unmodifiable collection containing IDs of the Properties stored
+ * the Item
+ */
+ public Collection getItemPropertyIds() {
+ return Collections.unmodifiableCollection(list);
+ }
+
+ /* Item.Managed methods */
+
+ /**
+ * Removes the Property identified by ID from the Item. This functionality
+ * is optional. If the method is not implemented, the method always returns
+ * <code>false</code>.
+ *
+ * @param id
+ * the ID of the Property to be removed.
+ * @return <code>true</code> if the operation succeeded <code>false</code>
+ * if not
+ */
+ public boolean removeItemProperty(Object id) {
+
+ // Cant remove missing properties
+ if (map.remove(id) == null) {
+ return false;
+ }
+ list.remove(id);
+
+ // Send change events
+ fireItemPropertySetChange();
+
+ return true;
+ }
+
+ /**
+ * Tries to add a new Property into the Item.
+ *
+ * @param id
+ * the ID of the new Property.
+ * @param property
+ * the Property to be added and associated with the id.
+ * @return <code>true</code> if the operation succeeded, <code>false</code>
+ * if not
+ */
+ public boolean addItemProperty(Object id, Property property) {
+
+ // Null ids are not accepted
+ if (id == null) {
+ throw new NullPointerException("Item property id can not be null");
+ }
+
+ // Cant add a property twice
+ if (map.containsKey(id)) {
+ return false;
+ }
+
+ // Put the property to map
+ map.put(id, property);
+ list.add(id);
+
+ // Send event
+ fireItemPropertySetChange();
+
+ return true;
+ }
+
+ /**
+ * 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 Properties contained by the
+ * Item.
+ *
+ * @return <code>String</code> representation of the Item contents
+ */
+ @Override
+ public String toString() {
+ String retValue = "";
+
+ for (final Iterator i = getItemPropertyIds().iterator(); i.hasNext();) {
+ final Object propertyId = i.next();
+ retValue += getItemProperty(propertyId).toString();
+ if (i.hasNext()) {
+ retValue += " ";
+ }
+ }
+
+ return retValue;
+ }
+
+ /* Notifiers */
+
+ /**
+ * An <code>event</code> object specifying an Item whose Property set has
+ * changed.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @VERSION@
+ * @since 3.0
+ */
+ private class PropertySetChangeEvent extends EventObject implements
+ Item.PropertySetChangeEvent {
+
+ private PropertySetChangeEvent(Item source) {
+ super(source);
+ }
+
+ /**
+ * Gets the Item whose Property set has changed.
+ *
+ * @return source object of the event as an <code>Item</code>
+ */
+ public Item getItem() {
+ return (Item) getSource();
+ }
+ }
+
+ /**
+ * Registers a new property set change listener for this Item.
+ *
+ * @param listener
+ * the new Listener to be registered.
+ */
+ public void addListener(Item.PropertySetChangeListener listener) {
+ if (propertySetChangeListeners == null) {
+ propertySetChangeListeners = new LinkedList();
+ }
+ propertySetChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes a previously registered property set change listener.
+ *
+ * @param listener
+ * the Listener to be removed.
+ */
+ public void removeListener(Item.PropertySetChangeListener listener) {
+ if (propertySetChangeListeners != null) {
+ propertySetChangeListeners.remove(listener);
+ }
+ }
+
+ /**
+ * Sends a Property set change event to all interested listeners.
+ */
+ private void fireItemPropertySetChange() {
+ if (propertySetChangeListeners != null) {
+ final Object[] l = propertySetChangeListeners.toArray();
+ final Item.PropertySetChangeEvent event = new PropertysetItem.PropertySetChangeEvent(
+ this);
+ for (int i = 0; i < l.length; i++) {
+ ((Item.PropertySetChangeListener) l[i])
+ .itemPropertySetChange(event);
+ }
+ }
+ }
+
+ /**
+ * Creates and returns a copy of this object.
+ * <p>
+ * The method <code>clone</code> performs a shallow copy of the
+ * <code>PropertysetItem</code>.
+ * </p>
+ * <p>
+ * Note : All arrays are considered to implement the interface Cloneable.
+ * Otherwise, this method creates a new instance of the class of this object
+ * and initializes all its fields with exactly the contents of the
+ * corresponding fields of this object, as if by assignment, the contents of
+ * the fields are not themselves cloned. Thus, this method performs a
+ * "shallow copy" of this object, not a "deep copy" operation.
+ * </p>
+ *
+ * @throws CloneNotSupportedException
+ * if the object's class does not support the Cloneable
+ * interface.
+ *
+ * @see java.lang.Object#clone()
+ */
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+
+ final PropertysetItem npsi = new PropertysetItem();
+
+ npsi.list = list != null ? (LinkedList) list.clone() : null;
+ npsi.propertySetChangeListeners = propertySetChangeListeners != null ? (LinkedList) propertySetChangeListeners
+ .clone()
+ : null;
+ npsi.map = (HashMap) map.clone();
+
+ return npsi;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals(Object obj) {
+
+ if (obj == null || !(obj instanceof PropertysetItem)) {
+ return false;
+ }
+
+ final PropertysetItem other = (PropertysetItem) obj;
+
+ if (other.list != list) {
+ if (other.list == null) {
+ return false;
+ }
+ if (!other.list.equals(list)) {
+ return false;
+ }
+ }
+ if (other.map != map) {
+ if (other.map == null) {
+ return false;
+ }
+ if (!other.map.equals(map)) {
+ return false;
+ }
+ }
+ if (other.propertySetChangeListeners != propertySetChangeListeners) {
+ if (other.propertySetChangeListeners == null) {
+ return false;
+ }
+ if (!other.propertySetChangeListeners
+ .equals(propertySetChangeListeners)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+
+ return (list == null ? 0 : list.hashCode())
+ ^ (map == null ? 0 : map.hashCode())
+ ^ (propertySetChangeListeners == null ? 0
+ : propertySetChangeListeners.hashCode());
+ }
+}
diff --git a/src/com/vaadin/data/util/QueryContainer.java b/src/com/vaadin/data/util/QueryContainer.java
new file mode 100644
index 0000000000..3716a7dce7
--- /dev/null
+++ b/src/com/vaadin/data/util/QueryContainer.java
@@ -0,0 +1,641 @@
+/*
+@ITMillApache2LicenseForJavaFiles@
+ */
+
+package com.vaadin.data.util;
+
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+
+/**
+ * <p>
+ * The <code>QueryContainer</code> is the specialized form of Container which is
+ * Ordered and Indexed. This is used to represent the contents of relational
+ * database tables accessed through the JDBC Connection in the Toolkit Table.
+ * This creates Items based on the queryStatement provided to the container.
+ * </p>
+ *
+ * <p>
+ * The <code>QueryContainer</code> can be visualized as a representation of a
+ * relational database table.Each Item in the container represents the row
+ * fetched by the query.All cells in a column have same data type and the data
+ * type information is retrieved from the metadata of the resultset.
+ * </p>
+ *
+ * <p>
+ * Note : If data in the tables gets modified, Container will not get reflected
+ * with the updates, we have to explicity invoke QueryContainer.refresh method.
+ * {@link com.vaadin.data.util.QueryContainer#refresh() refresh()}
+ * </p>
+ *
+ * @see com.vaadin.data.Container
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @since 4.0
+ */
+
+@SuppressWarnings("serial")
+public class QueryContainer implements Container, Container.Ordered,
+ Container.Indexed {
+
+ // default ResultSet type
+ public static final int DEFAULT_RESULTSET_TYPE = ResultSet.TYPE_SCROLL_INSENSITIVE;
+
+ // default ResultSet concurrency
+ public static final int DEFAULT_RESULTSET_CONCURRENCY = ResultSet.CONCUR_READ_ONLY;
+
+ private int resultSetType = DEFAULT_RESULTSET_TYPE;
+
+ private int resultSetConcurrency = DEFAULT_RESULTSET_CONCURRENCY;
+
+ private final String queryStatement;
+
+ private final Connection connection;
+
+ private ResultSet result;
+
+ private Collection propertyIds;
+
+ private final HashMap propertyTypes = new HashMap();
+
+ private int size = -1;
+
+ private Statement statement;
+
+ /**
+ * Constructs new <code>QueryContainer</code> with the specified
+ * <code>queryStatement</code>.
+ *
+ * @param queryStatement
+ * Database query
+ * @param connection
+ * Connection object
+ * @param resultSetType
+ * @param resultSetConcurrency
+ * @throws SQLException
+ * when database operation fails
+ */
+ public QueryContainer(String queryStatement, Connection connection,
+ int resultSetType, int resultSetConcurrency) throws SQLException {
+ this.queryStatement = queryStatement;
+ this.connection = connection;
+ this.resultSetType = resultSetType;
+ this.resultSetConcurrency = resultSetConcurrency;
+ init();
+ }
+
+ /**
+ * Constructs new <code>QueryContainer</code> with the specified
+ * queryStatement using the default resultset type and default resultset
+ * concurrency.
+ *
+ * @param queryStatement
+ * Database query
+ * @param connection
+ * Connection object
+ * @see QueryContainer#DEFAULT_RESULTSET_TYPE
+ * @see QueryContainer#DEFAULT_RESULTSET_CONCURRENCY
+ * @throws SQLException
+ * when database operation fails
+ */
+ public QueryContainer(String queryStatement, Connection connection)
+ throws SQLException {
+ this(queryStatement, connection, DEFAULT_RESULTSET_TYPE,
+ DEFAULT_RESULTSET_CONCURRENCY);
+ }
+
+ /**
+ * Fills the Container with the items and properties. Invoked by the
+ * constructor.
+ *
+ * @throws SQLException
+ * when parameter initialization fails.
+ * @see QueryContainer#QueryContainer(String, Connection, int, int).
+ */
+ private void init() throws SQLException {
+ refresh();
+ ResultSetMetaData metadata;
+ metadata = result.getMetaData();
+ final int count = metadata.getColumnCount();
+ final ArrayList list = new ArrayList(count);
+ for (int i = 1; i <= count; i++) {
+ final String columnName = metadata.getColumnName(i);
+ list.add(columnName);
+ final Property p = getContainerProperty(new Integer(1), columnName);
+ propertyTypes.put(columnName, p == null ? Object.class : p
+ .getType());
+ }
+ propertyIds = Collections.unmodifiableCollection(list);
+ }
+
+ /**
+ * <p>
+ * Restores items in the container. This method will update the latest data
+ * to the container.
+ * </p>
+ * Note: This method should be used to update the container with the latest
+ * items.
+ *
+ * @throws SQLException
+ * when database operation fails
+ *
+ */
+
+ public void refresh() throws SQLException {
+ close();
+ statement = connection.createStatement(resultSetType,
+ resultSetConcurrency);
+ result = statement.executeQuery(queryStatement);
+ result.last();
+ size = result.getRow();
+ }
+
+ /**
+ * Releases and nullifies the <code>statement</code>.
+ *
+ * @throws SQLException
+ * when database operation fails
+ */
+
+ public void close() throws SQLException {
+ if (statement != null) {
+ statement.close();
+ }
+ statement = null;
+ }
+
+ /**
+ * Gets the Item with the given Item ID from the Container.
+ *
+ * @param id
+ * ID of the Item to retrieve
+ * @return Item Id.
+ */
+
+ public Item getItem(Object id) {
+ return new Row(id);
+ }
+
+ /**
+ * Gets the collection of propertyId from the Container.
+ *
+ * @return Collection of Property ID.
+ */
+
+ public Collection getContainerPropertyIds() {
+ return propertyIds;
+ }
+
+ /**
+ * Gets an collection of all the item IDs in the container.
+ *
+ * @return collection of Item IDs
+ */
+ public Collection getItemIds() {
+ final Collection c = new ArrayList(size);
+ for (int i = 1; i <= size; i++) {
+ c.add(new Integer(i));
+ }
+ return c;
+ }
+
+ /**
+ * Gets the property identified by the given itemId and propertyId from the
+ * container. If the container does not contain the property
+ * <code>null</code> is returned.
+ *
+ * @param itemId
+ * ID of the Item which contains the Property
+ * @param propertyId
+ * ID of the Property to retrieve
+ *
+ * @return Property with the given ID if exists; <code>null</code>
+ * otherwise.
+ */
+
+ public synchronized Property getContainerProperty(Object itemId,
+ Object propertyId) {
+ if (!(itemId instanceof Integer && propertyId instanceof String)) {
+ return null;
+ }
+ Object value;
+ try {
+ result.absolute(((Integer) itemId).intValue());
+ value = result.getObject((String) propertyId);
+ } catch (final Exception e) {
+ return null;
+ }
+
+ // Handle also null values from the database
+ return new ObjectProperty(value != null ? value : new String(""));
+ }
+
+ /**
+ * Gets the data type of all properties identified by the given type ID.
+ *
+ * @param id
+ * ID identifying the Properties
+ *
+ * @return data type of the Properties
+ */
+
+ public Class getType(Object id) {
+ return (Class) propertyTypes.get(id);
+ }
+
+ /**
+ * Gets the number of items in the container.
+ *
+ * @return the number of items in the container.
+ */
+ public int size() {
+ return size;
+ }
+
+ /**
+ * Tests if the list contains the specified Item.
+ *
+ * @param id
+ * ID the of Item to be tested.
+ * @return <code>true</code> if given id is in the container;
+ * <code>false</code> otherwise.
+ */
+ public boolean containsId(Object id) {
+ if (!(id instanceof Integer)) {
+ return false;
+ }
+ final int i = ((Integer) id).intValue();
+ if (i < 1) {
+ return false;
+ }
+ if (i > size) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Creates new Item with the given ID into the Container.
+ *
+ * @param itemId
+ * ID of the Item to be created.
+ *
+ * @return Created new Item, or <code>null</code> if it fails.
+ *
+ * @throws UnsupportedOperationException
+ * if the addItem method is not supported.
+ */
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Creates a new Item into the Container, and assign it an ID.
+ *
+ * @return ID of the newly created Item, or <code>null</code> if it fails.
+ * @throws UnsupportedOperationException
+ * if the addItem method is not supported.
+ */
+ public Object addItem() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes the Item identified by ItemId from the Container.
+ *
+ * @param itemId
+ * ID of the Item to remove.
+ * @return <code>true</code> if the operation succeeded; <code>false</code>
+ * otherwise.
+ * @throws UnsupportedOperationException
+ * if the removeItem method is not supported.
+ */
+ public boolean removeItem(Object itemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds new Property to all Items in the Container.
+ *
+ * @param propertyId
+ * ID of the Property
+ * @param type
+ * 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>
+ * otherwise.
+ * @throws UnsupportedOperationException
+ * if the addContainerProperty method is not supported.
+ */
+ public boolean addContainerProperty(Object propertyId, Class type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes a Property specified by the given Property ID from the Container.
+ *
+ * @param propertyId
+ * ID of the Property to remove
+ * @return <code>true</code> if the operation succeeded; <code>false</code>
+ * otherwise.
+ * @throws UnsupportedOperationException
+ * if the removeContainerProperty method is not supported.
+ */
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Removes all Items from the Container.
+ *
+ * @return <code>true</code> if the operation succeeded; <code>false</code>
+ * otherwise.
+ * @throws UnsupportedOperationException
+ * if the removeAllItems method is not supported.
+ */
+ public boolean removeAllItems() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * @param previousItemId
+ * Id of the previous item in ordered container.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return Returns new item or <code>null</code> if the operation fails.
+ * @throws UnsupportedOperationException
+ * if the addItemAfter method is not supported.
+ */
+ public Item addItemAfter(Object previousItemId, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds new item after the given item.
+ *
+ * @param previousItemId
+ * Id of the previous item in ordered container.
+ * @return Returns item id created new item or <code>null</code> if the
+ * operation fails.
+ * @throws UnsupportedOperationException
+ * if the addItemAfter method is not supported.
+ */
+ public Object addItemAfter(Object previousItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Returns id of first item in the Container.
+ *
+ * @return ID of the first Item in the list.
+ */
+ public Object firstItemId() {
+ if (size < 1) {
+ return null;
+ }
+ return new Integer(1);
+ }
+
+ /**
+ * Returns <code>true</code> if given id is first id at first index.
+ *
+ * @param id
+ * ID of an Item in the Container.
+ */
+ public boolean isFirstId(Object id) {
+ return size > 0 && (id instanceof Integer)
+ && ((Integer) id).intValue() == 1;
+ }
+
+ /**
+ * Returns <code>true</code> if given id is last id at last index.
+ *
+ * @param id
+ * ID of an Item in the Container
+ *
+ */
+ public boolean isLastId(Object id) {
+ return size > 0 && (id instanceof Integer)
+ && ((Integer) id).intValue() == size;
+ }
+
+ /**
+ * Returns id of last item in the Container.
+ *
+ * @return ID of the last Item.
+ */
+ public Object lastItemId() {
+ if (size < 1) {
+ return null;
+ }
+ return new Integer(size);
+ }
+
+ /**
+ * Returns id of next item in container at next index.
+ *
+ * @param id
+ * ID of an Item in the Container.
+ * @return ID of the next Item or null.
+ */
+ public Object nextItemId(Object id) {
+ if (size < 1 || !(id instanceof Integer)) {
+ return null;
+ }
+ final int i = ((Integer) id).intValue();
+ if (i >= size) {
+ return null;
+ }
+ return new Integer(i + 1);
+ }
+
+ /**
+ * Returns id of previous item in container at previous index.
+ *
+ * @param id
+ * ID of an Item in the Container.
+ * @return ID of the previous Item or null.
+ */
+ public Object prevItemId(Object id) {
+ if (size < 1 || !(id instanceof Integer)) {
+ return null;
+ }
+ final int i = ((Integer) id).intValue();
+ if (i <= 1) {
+ return null;
+ }
+ return new Integer(i - 1);
+ }
+
+ /**
+ * The <code>Row</code> class implements methods of Item.
+ *
+ * @author IT Mill Ltd.
+ * @version
+ * @since 4.0
+ */
+ class Row implements Item {
+
+ Object id;
+
+ private Row(Object rowId) {
+ id = rowId;
+ }
+
+ /**
+ * Adds the item property.
+ *
+ * @param id
+ * ID of the new Property.
+ * @param property
+ * Property to be added and associated with ID.
+ * @return <code>true</code> if the operation succeeded;
+ * <code>false</code> otherwise.
+ * @throws UnsupportedOperationException
+ * if the addItemProperty method is not supported.
+ */
+ public boolean addItemProperty(Object id, Property property)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets the property corresponding to the given property ID stored in
+ * the Item.
+ *
+ * @param propertyId
+ * identifier of the Property to get
+ * @return the Property with the given ID or <code>null</code>
+ */
+ public Property getItemProperty(Object propertyId) {
+ return getContainerProperty(id, propertyId);
+ }
+
+ /**
+ * Gets the collection of property IDs stored in the Item.
+ *
+ * @return unmodifiable collection containing IDs of the Properties
+ * stored the Item.
+ */
+ public Collection getItemPropertyIds() {
+ return propertyIds;
+ }
+
+ /**
+ * Removes given item property.
+ *
+ * @param id
+ * ID of the Property to be removed.
+ * @return <code>true</code> if the item property is removed;
+ * <code>false</code> otherwise.
+ * @throws UnsupportedOperationException
+ * if the removeItemProperty is not supported.
+ */
+ public boolean removeItemProperty(Object id)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ }
+
+ /**
+ * Closes the statement.
+ *
+ * @see #close()
+ */
+ @Override
+ public void finalize() {
+ try {
+ close();
+ } catch (final SQLException ignored) {
+
+ }
+ }
+
+ /**
+ * Adds the given item at the position of given index.
+ *
+ * @param index
+ * Index to add the new item.
+ * @param newItemId
+ * Id of the new item to be added.
+ * @return new item or <code>null</code> if the operation fails.
+ * @throws UnsupportedOperationException
+ * if the addItemAt is not supported.
+ */
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Adds item at the position of provided index in the container.
+ *
+ * @param index
+ * Index to add the new item.
+ * @return item id created new item or <code>null</code> if the operation
+ * fails.
+ *
+ * @throws UnsupportedOperationException
+ * if the addItemAt is not supported.
+ */
+
+ public Object addItemAt(int index) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Gets the Index id in the container.
+ *
+ * @param index
+ * Index Id.
+ * @return ID in the given index.
+ */
+ public Object getIdByIndex(int index) {
+ if (size < 1 || index < 0 || index >= size) {
+ return null;
+ }
+ return new Integer(index + 1);
+ }
+
+ /**
+ * Gets the index of the Item corresponding to id in the container.
+ *
+ * @param id
+ * ID of an Item in the Container
+ * @return index of the Item, or -1 if the Container does not include the
+ * Item
+ */
+
+ public int indexOfId(Object id) {
+ if (size < 1 || !(id instanceof Integer)) {
+ return -1;
+ }
+ final int i = ((Integer) id).intValue();
+ if (i >= size || i < 1) {
+ return -1;
+ }
+ return i - 1;
+ }
+
+}
diff --git a/src/com/vaadin/data/util/package.html b/src/com/vaadin/data/util/package.html
new file mode 100644
index 0000000000..332d8f8143
--- /dev/null
+++ b/src/com/vaadin/data/util/package.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+<head>
+
+</head>
+
+<body bgcolor="white">
+
+<p>Provides various utility classes that implement the data layer
+functionality.</p>
+
+<p>The first {@link com.vaadin.data.Property Property} class,
+{@link com.vaadin.data.util.ObjectProperty ObjectProperty}, provides
+a simple class containing a typed data value. The second,
+{@link com.vaadin.data.util.MethodProperty MethodProperty}, provides
+a way to bind a field of an object to the Property interface using the
+accessor methods for the field.</p>
+
+<p>The next level of the data layer, the
+{@link com.vaadin.data.Item Item}, is implemented by
+{@link com.vaadin.data.util.BeanItem BeanItem}, though it is only a
+simple wrapper to the former to provide the Item interface for any regular
+Java Bean.</p>
+
+<p>The third level, the {@link com.vaadin.data.Container Container},
+has several implementations in the {@link com.vaadin.data.util}
+package.</p>
+
+<!-- <h2>Package Specification</h2> -->
+
+<!-- Package spec here -->
+
+
+<!-- Put @see and @since tags down here. -->
+
+</body>
+</html>