diff options
author | Henri Sara <henri.sara@itmill.com> | 2009-05-11 09:19:03 +0000 |
---|---|---|
committer | Henri Sara <henri.sara@itmill.com> | 2009-05-11 09:19:03 +0000 |
commit | adc8c0ad3573272c236040c3a76005b9e73a5737 (patch) | |
tree | a3860704dbd5b82dc6af38684b80f8ef79a32722 /src/com/vaadin/data/util | |
parent | 5abc870dda584d0c2fc47fd5eec4ae3de3fa240e (diff) | |
download | vaadin-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.java | 194 | ||||
-rw-r--r-- | src/com/vaadin/data/util/BeanItemContainer.java | 535 | ||||
-rw-r--r-- | src/com/vaadin/data/util/ContainerHierarchicalWrapper.java | 688 | ||||
-rw-r--r-- | src/com/vaadin/data/util/ContainerOrderedWrapper.java | 604 | ||||
-rw-r--r-- | src/com/vaadin/data/util/FilesystemContainer.java | 894 | ||||
-rw-r--r-- | src/com/vaadin/data/util/Filter.java | 90 | ||||
-rw-r--r-- | src/com/vaadin/data/util/HierarchicalContainer.java | 314 | ||||
-rw-r--r-- | src/com/vaadin/data/util/IndexedContainer.java | 1652 | ||||
-rw-r--r-- | src/com/vaadin/data/util/MethodProperty.java | 938 | ||||
-rw-r--r-- | src/com/vaadin/data/util/ObjectProperty.java | 349 | ||||
-rw-r--r-- | src/com/vaadin/data/util/PropertyFormatter.java | 380 | ||||
-rw-r--r-- | src/com/vaadin/data/util/PropertysetItem.java | 314 | ||||
-rw-r--r-- | src/com/vaadin/data/util/QueryContainer.java | 641 | ||||
-rw-r--r-- | src/com/vaadin/data/util/package.html | 37 |
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> |