summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/data/util/AbstractBeanContainer.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/data/util/AbstractBeanContainer.java')
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java856
1 files changed, 856 insertions, 0 deletions
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
new file mode 100644
index 0000000000..2f428d2cb6
--- /dev/null
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -0,0 +1,856 @@
+/*
+@VaadinApache2LicenseForJavaFiles@
+ */
+package com.vaadin.data.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.vaadin.data.Container;
+import com.vaadin.data.Container.Filterable;
+import com.vaadin.data.Container.PropertySetChangeNotifier;
+import com.vaadin.data.Container.SimpleFilterable;
+import com.vaadin.data.Container.Sortable;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.data.util.MethodProperty.MethodException;
+import com.vaadin.data.util.filter.SimpleStringFilter;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ * An abstract base class for in-memory containers for JavaBeans.
+ *
+ * <p>
+ * The properties of the container are determined automatically by introspecting
+ * the used JavaBean class and explicitly adding or removing properties is not
+ * supported. Only beans of the same type can be added to the container.
+ * </p>
+ *
+ * <p>
+ * Subclasses should implement any public methods adding items to the container,
+ * typically calling the protected methods {@link #addItem(Object, Object)},
+ * {@link #addItemAfter(Object, Object, Object)} and
+ * {@link #addItemAt(int, Object, Object)}.
+ * </p>
+ *
+ * @param <IDTYPE>
+ * The type of the item identifier
+ * @param <BEANTYPE>
+ * The type of the Bean
+ *
+ * @since 6.5
+ */
+public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
+ AbstractInMemoryContainer<IDTYPE, String, BeanItem<BEANTYPE>> implements
+ Filterable, SimpleFilterable, Sortable, ValueChangeListener,
+ PropertySetChangeNotifier {
+
+ /**
+ * Resolver that maps beans to their (item) identifiers, removing the need
+ * to explicitly specify item identifiers when there is no need to customize
+ * this.
+ *
+ * Note that beans can also be added with an explicit id even if a resolver
+ * has been set.
+ *
+ * @param <IDTYPE>
+ * @param <BEANTYPE>
+ *
+ * @since 6.5
+ */
+ public static interface BeanIdResolver<IDTYPE, BEANTYPE> extends
+ Serializable {
+ /**
+ * Return the item identifier for a bean.
+ *
+ * @param bean
+ * @return
+ */
+ public IDTYPE getIdForBean(BEANTYPE bean);
+ }
+
+ /**
+ * A item identifier resolver that returns the value of a bean property.
+ *
+ * The bean must have a getter for the property, and the getter must return
+ * an object of type IDTYPE.
+ */
+ protected class PropertyBasedBeanIdResolver implements
+ BeanIdResolver<IDTYPE, BEANTYPE> {
+
+ private final Object propertyId;
+
+ public PropertyBasedBeanIdResolver(Object propertyId) {
+ if (propertyId == null) {
+ throw new IllegalArgumentException(
+ "Property identifier must not be null");
+ }
+ this.propertyId = propertyId;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public IDTYPE getIdForBean(BEANTYPE bean)
+ throws IllegalArgumentException {
+ VaadinPropertyDescriptor<BEANTYPE> pd = model.get(propertyId);
+ if (null == pd) {
+ throw new IllegalStateException("Property " + propertyId
+ + " not found");
+ }
+ try {
+ Property<IDTYPE> property = (Property<IDTYPE>) pd
+ .createProperty(bean);
+ return property.getValue();
+ } catch (MethodException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ }
+
+ /**
+ * The resolver that finds the item ID for a bean, or null not to use
+ * automatic resolving.
+ *
+ * Methods that add a bean without specifying an ID must not be called if no
+ * resolver has been set.
+ */
+ private BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver = null;
+
+ /**
+ * Maps all item ids in the container (including filtered) to their
+ * corresponding BeanItem.
+ */
+ private final Map<IDTYPE, BeanItem<BEANTYPE>> itemIdToItem = new HashMap<IDTYPE, BeanItem<BEANTYPE>>();
+
+ /**
+ * The type of the beans in the container.
+ */
+ private final Class<? super BEANTYPE> type;
+
+ /**
+ * A description of the properties found in beans of type {@link #type}.
+ * Determines the property ids that are present in the container.
+ */
+ private LinkedHashMap<String, VaadinPropertyDescriptor<BEANTYPE>> model;
+
+ /**
+ * Constructs a {@code AbstractBeanContainer} for beans of the given type.
+ *
+ * @param type
+ * the type of the beans that will be added to the container.
+ * @throws IllegalArgumentException
+ * If {@code type} is null
+ */
+ protected AbstractBeanContainer(Class<? super BEANTYPE> type) {
+ if (type == null) {
+ throw new IllegalArgumentException(
+ "The bean type passed to AbstractBeanContainer must not be null");
+ }
+ this.type = type;
+ model = BeanItem.getPropertyDescriptors((Class<BEANTYPE>) type);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getType(java.lang.Object)
+ */
+ @Override
+ public Class<?> getType(Object propertyId) {
+ return model.get(propertyId).getPropertyType();
+ }
+
+ /**
+ * Create a BeanItem for a bean using pre-parsed bean metadata (based on
+ * {@link #getBeanType()}).
+ *
+ * @param bean
+ * @return created {@link BeanItem} or null if bean is null
+ */
+ protected BeanItem<BEANTYPE> createBeanItem(BEANTYPE bean) {
+ return bean == null ? null : new BeanItem<BEANTYPE>(bean, model);
+ }
+
+ /**
+ * Returns the type of beans this Container can contain.
+ *
+ * This comes from the bean type constructor parameter, and bean metadata
+ * (including container properties) is based on this.
+ *
+ * @return
+ */
+ public Class<? super BEANTYPE> getBeanType() {
+ return type;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerPropertyIds()
+ */
+ @Override
+ public Collection<String> getContainerPropertyIds() {
+ return model.keySet();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeAllItems()
+ */
+ @Override
+ public boolean removeAllItems() {
+ int origSize = size();
+
+ internalRemoveAllItems();
+
+ // detach listeners from all Items
+ for (Item item : itemIdToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ itemIdToItem.clear();
+
+ // fire event only if the visible view changed, regardless of whether
+ // filtered out items were removed or not
+ if (origSize != 0) {
+ fireItemSetChange();
+ }
+
+ return true;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItem(java.lang.Object)
+ */
+ @Override
+ public BeanItem<BEANTYPE> getItem(Object itemId) {
+ // TODO return only if visible?
+ return getUnfilteredItem(itemId);
+ }
+
+ @Override
+ protected BeanItem<BEANTYPE> getUnfilteredItem(Object itemId) {
+ return itemIdToItem.get(itemId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getItemIds()
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public List<IDTYPE> getItemIds() {
+ return (List<IDTYPE>) super.getItemIds();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object,
+ * java.lang.Object)
+ */
+ @Override
+ public Property<?> getContainerProperty(Object itemId, Object propertyId) {
+ Item item = getItem(itemId);
+ if (item == null) {
+ return null;
+ }
+ return item.getItemProperty(propertyId);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container#removeItem(java.lang.Object)
+ */
+ @Override
+ public boolean removeItem(Object itemId) {
+ // TODO should also remove items that are filtered out
+ int origSize = size();
+ Item item = getItem(itemId);
+ int position = indexOfId(itemId);
+
+ if (internalRemoveItem(itemId)) {
+ // detach listeners from Item
+ removeAllValueChangeListeners(item);
+
+ // remove item
+ itemIdToItem.remove(itemId);
+
+ // fire event only if the visible view changed, regardless of
+ // whether filtered out items were removed or not
+ if (size() != origSize) {
+ fireItemRemoved(position, itemId);
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Re-filter the container when one of the monitored properties changes.
+ */
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ // if a property that is used in a filter is changed, refresh filtering
+ filterAll();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#addContainerFilter(java.lang.Object,
+ * java.lang.String, boolean, boolean)
+ */
+ @Override
+ public void addContainerFilter(Object propertyId, String filterString,
+ boolean ignoreCase, boolean onlyMatchPrefix) {
+ try {
+ addFilter(new SimpleStringFilter(propertyId, filterString,
+ ignoreCase, onlyMatchPrefix));
+ } catch (UnsupportedFilterException e) {
+ // the filter instance created here is always valid for in-memory
+ // containers
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Filterable#removeAllContainerFilters()
+ */
+ @Override
+ public void removeAllContainerFilters() {
+ if (!getFilters().isEmpty()) {
+ for (Item item : itemIdToItem.values()) {
+ removeAllValueChangeListeners(item);
+ }
+ removeAllFilters();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see
+ * com.vaadin.data.Container.Filterable#removeContainerFilters(java.lang
+ * .Object)
+ */
+ @Override
+ public void removeContainerFilters(Object propertyId) {
+ Collection<Filter> removedFilters = super.removeFilters(propertyId);
+ if (!removedFilters.isEmpty()) {
+ // stop listening to change events for the property
+ for (Item item : itemIdToItem.values()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ }
+ }
+
+ @Override
+ public void addContainerFilter(Filter filter)
+ throws UnsupportedFilterException {
+ addFilter(filter);
+ }
+
+ @Override
+ public void removeContainerFilter(Filter filter) {
+ removeFilter(filter);
+ }
+
+ /**
+ * Make this container listen to the given property provided it notifies
+ * when its value changes.
+ *
+ * @param item
+ * The {@link Item} that contains the property
+ * @param propertyId
+ * The id of the property
+ */
+ private void addValueChangeListener(Item item, Object propertyId) {
+ Property<?> property = item.getItemProperty(propertyId);
+ if (property instanceof ValueChangeNotifier) {
+ // avoid multiple notifications for the same property if
+ // multiple filters are in use
+ ValueChangeNotifier notifier = (ValueChangeNotifier) property;
+ notifier.removeListener(this);
+ notifier.addListener(this);
+ }
+ }
+
+ /**
+ * Remove this container as a listener for the given property.
+ *
+ * @param item
+ * The {@link Item} that contains the property
+ * @param propertyId
+ * The id of the property
+ */
+ private void removeValueChangeListener(Item item, Object propertyId) {
+ Property<?> property = item.getItemProperty(propertyId);
+ if (property instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) property).removeListener(this);
+ }
+ }
+
+ /**
+ * Remove this contains as a listener for all the properties in the given
+ * {@link Item}.
+ *
+ * @param item
+ * The {@link Item} that contains the properties
+ */
+ private void removeAllValueChangeListeners(Item item) {
+ for (Object propertyId : item.getItemPropertyIds()) {
+ removeValueChangeListener(item, propertyId);
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds()
+ */
+ @Override
+ public Collection<?> getSortableContainerPropertyIds() {
+ return getSortablePropertyIds();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[],
+ * boolean[])
+ */
+ @Override
+ public void sort(Object[] propertyId, boolean[] ascending) {
+ sortContainer(propertyId, ascending);
+ }
+
+ @Override
+ public ItemSorter getItemSorter() {
+ return super.getItemSorter();
+ }
+
+ @Override
+ public void setItemSorter(ItemSorter itemSorter) {
+ super.setItemSorter(itemSorter);
+ }
+
+ @Override
+ protected void registerNewItem(int position, IDTYPE itemId,
+ BeanItem<BEANTYPE> item) {
+ itemIdToItem.put(itemId, item);
+
+ // add listeners to be able to update filtering on property
+ // changes
+ for (Filter filter : getFilters()) {
+ for (String propertyId : getContainerPropertyIds()) {
+ if (filter.appliesToProperty(propertyId)) {
+ // addValueChangeListener avoids adding duplicates
+ addValueChangeListener(item, propertyId);
+ }
+ }
+ }
+ }
+
+ /**
+ * Check that a bean can be added to the container (is of the correct type
+ * for the container).
+ *
+ * @param bean
+ * @return
+ */
+ private boolean validateBean(BEANTYPE bean) {
+ return bean != null && getBeanType().isAssignableFrom(bean.getClass());
+ }
+
+ /**
+ * Adds the bean to the Container.
+ *
+ * Note: the behavior of this method changed in Vaadin 6.6 - now items are
+ * added at the very end of the unfiltered container and not after the last
+ * visible item if filtering is used.
+ *
+ * @see com.vaadin.data.Container#addItem(Object)
+ */
+ protected BeanItem<BEANTYPE> addItem(IDTYPE itemId, BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAtEnd(itemId, createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds the bean after the given bean.
+ *
+ * @see com.vaadin.data.Container.Ordered#addItemAfter(Object, Object)
+ */
+ protected BeanItem<BEANTYPE> addItemAfter(IDTYPE previousItemId,
+ IDTYPE newItemId, BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAfter(previousItemId, newItemId,
+ createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds a new bean at the given index.
+ *
+ * The bean is used both as the item contents and as the item identifier.
+ *
+ * @param index
+ * Index at which the bean should be added.
+ * @param newItemId
+ * The item id for the bean to add to the container.
+ * @param bean
+ * The bean to add to the container.
+ *
+ * @return Returns the new BeanItem or null if the operation fails.
+ */
+ protected BeanItem<BEANTYPE> addItemAt(int index, IDTYPE newItemId,
+ BEANTYPE bean) {
+ if (!validateBean(bean)) {
+ return null;
+ }
+ return internalAddItemAt(index, newItemId, createBeanItem(bean), true);
+ }
+
+ /**
+ * Adds a bean to the container using the bean item id resolver to find its
+ * identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItem(Object, Object)
+ *
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBean(BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItem(itemId, bean);
+ }
+
+ /**
+ * Adds a bean to the container after a specified item identifier, using the
+ * bean item id resolver to find its identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItemAfter(Object, Object, Object)
+ *
+ * @param previousItemId
+ * the identifier of the bean after which this bean should be
+ * added, null to add to the beginning
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBeanAfter(IDTYPE previousItemId,
+ BEANTYPE bean) throws IllegalStateException,
+ IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItemAfter(previousItemId, itemId, bean);
+ }
+
+ /**
+ * Adds a bean at a specified (filtered view) position in the container
+ * using the bean item id resolver to find its identifier.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * @see #addItemAfter(Object, Object, Object)
+ *
+ * @param index
+ * the index (in the filtered view) at which to add the item
+ * @param bean
+ * the bean to add
+ * @return BeanItem<BEANTYPE> item added or null
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if an identifier cannot be resolved for the bean
+ */
+ protected BeanItem<BEANTYPE> addBeanAt(int index, BEANTYPE bean)
+ throws IllegalStateException, IllegalArgumentException {
+ if (bean == null) {
+ return null;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+ return addItemAt(index, itemId, bean);
+ }
+
+ /**
+ * Adds all the beans from a {@link Collection} in one operation using the
+ * bean item identifier resolver. More efficient than adding them one by
+ * one.
+ *
+ * A bean id resolver must be set before calling this method.
+ *
+ * Note: the behavior of this method changed in Vaadin 6.6 - now items are
+ * added at the very end of the unfiltered container and not after the last
+ * visible item if filtering is used.
+ *
+ * @param collection
+ * The collection of beans to add. Must not be null.
+ * @throws IllegalStateException
+ * if no bean identifier resolver has been set
+ * @throws IllegalArgumentException
+ * if the resolver returns a null itemId for one of the beans in
+ * the collection
+ */
+ protected void addAll(Collection<? extends BEANTYPE> collection)
+ throws IllegalStateException, IllegalArgumentException {
+ boolean modified = false;
+ for (BEANTYPE bean : collection) {
+ // TODO skipping invalid beans - should not allow them in javadoc?
+ if (bean == null
+ || !getBeanType().isAssignableFrom(bean.getClass())) {
+ continue;
+ }
+ IDTYPE itemId = resolveBeanId(bean);
+ if (itemId == null) {
+ throw new IllegalArgumentException(
+ "Resolved identifier for a bean must not be null");
+ }
+
+ if (internalAddItemAtEnd(itemId, createBeanItem(bean), false) != null) {
+ modified = true;
+ }
+ }
+
+ if (modified) {
+ // Filter the contents when all items have been added
+ if (isFiltered()) {
+ filterAll();
+ } else {
+ fireItemSetChange();
+ }
+ }
+ }
+
+ /**
+ * Use the bean resolver to get the identifier for a bean.
+ *
+ * @param bean
+ * @return resolved bean identifier, null if could not be resolved
+ * @throws IllegalStateException
+ * if no bean resolver is set
+ */
+ protected IDTYPE resolveBeanId(BEANTYPE bean) {
+ if (beanIdResolver == null) {
+ throw new IllegalStateException(
+ "Bean item identifier resolver is required.");
+ }
+ return beanIdResolver.getIdForBean(bean);
+ }
+
+ /**
+ * Sets the resolver that finds the item id for a bean, or null not to use
+ * automatic resolving.
+ *
+ * Methods that add a bean without specifying an id must not be called if no
+ * resolver has been set.
+ *
+ * Note that methods taking an explicit id can be used whether a resolver
+ * has been defined or not.
+ *
+ * @param beanIdResolver
+ * to use or null to disable automatic id resolution
+ */
+ protected void setBeanIdResolver(
+ BeanIdResolver<IDTYPE, BEANTYPE> beanIdResolver) {
+ this.beanIdResolver = beanIdResolver;
+ }
+
+ /**
+ * Returns the resolver that finds the item ID for a bean.
+ *
+ * @return resolver used or null if automatic item id resolving is disabled
+ */
+ public BeanIdResolver<IDTYPE, BEANTYPE> getBeanIdResolver() {
+ return beanIdResolver;
+ }
+
+ /**
+ * Create an item identifier resolver using a named bean property.
+ *
+ * @param propertyId
+ * property identifier, which must map to a getter in BEANTYPE
+ * @return created resolver
+ */
+ protected BeanIdResolver<IDTYPE, BEANTYPE> createBeanPropertyResolver(
+ Object propertyId) {
+ return new PropertyBasedBeanIdResolver(propertyId);
+ }
+
+ @Override
+ public void addListener(Container.PropertySetChangeListener listener) {
+ super.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(Container.PropertySetChangeListener listener) {
+ super.removeListener(listener);
+ }
+
+ @Override
+ public boolean addContainerProperty(Object propertyId, Class<?> type,
+ Object defaultValue) throws UnsupportedOperationException {
+ throw new UnsupportedOperationException(
+ "Use addNestedContainerProperty(String) to add container properties to a "
+ + getClass().getSimpleName());
+ }
+
+ /**
+ * Adds a property for the container and all its items.
+ *
+ * Primarily for internal use, may change in future versions.
+ *
+ * @param propertyId
+ * @param propertyDescriptor
+ * @return true if the property was added
+ */
+ protected final boolean addContainerProperty(String propertyId,
+ VaadinPropertyDescriptor<BEANTYPE> propertyDescriptor) {
+ if (null == propertyId || null == propertyDescriptor) {
+ return false;
+ }
+
+ // Fails if the Property is already present
+ if (model.containsKey(propertyId)) {
+ return false;
+ }
+
+ model.put(propertyId, propertyDescriptor);
+ for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
+ item.addItemProperty(propertyId,
+ propertyDescriptor.createProperty(item.getBean()));
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+ /**
+ * Adds a nested container property for the container, e.g.
+ * "manager.address.street".
+ *
+ * All intermediate getters must exist and must return non-null values when
+ * the property value is accessed.
+ *
+ * @see NestedMethodProperty
+ *
+ * @param propertyId
+ * @return true if the property was added
+ */
+ public boolean addNestedContainerProperty(String propertyId) {
+ return addContainerProperty(propertyId, new NestedPropertyDescriptor(
+ propertyId, type));
+ }
+
+ /**
+ * Adds a nested container properties for all sub-properties of a named
+ * property to the container. The named property itself is removed from the
+ * model as its subproperties are added.
+ *
+ * All intermediate getters must exist and must return non-null values when
+ * the property value is accessed.
+ *
+ * @see NestedMethodProperty
+ * @see #addNestedContainerProperty(String)
+ *
+ * @param propertyId
+ */
+ @SuppressWarnings("unchecked")
+ public void addNestedContainerBean(String propertyId) {
+ Class<?> propertyType = getType(propertyId);
+ LinkedHashMap<String, VaadinPropertyDescriptor<Object>> pds = BeanItem
+ .getPropertyDescriptors((Class<Object>) propertyType);
+ for (String subPropertyId : pds.keySet()) {
+ String qualifiedPropertyId = propertyId + "." + subPropertyId;
+ NestedPropertyDescriptor<BEANTYPE> pd = new NestedPropertyDescriptor<BEANTYPE>(
+ qualifiedPropertyId, (Class<BEANTYPE>) type);
+ model.put(qualifiedPropertyId, pd);
+ model.remove(propertyId);
+ for (BeanItem<BEANTYPE> item : itemIdToItem.values()) {
+ item.addItemProperty(propertyId,
+ pd.createProperty(item.getBean()));
+ item.removeItemProperty(propertyId);
+ }
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+ }
+
+ @Override
+ public boolean removeContainerProperty(Object propertyId)
+ throws UnsupportedOperationException {
+ // Fails if the Property is not present
+ if (!model.containsKey(propertyId)) {
+ return false;
+ }
+
+ // Removes the Property to Property list and types
+ model.remove(propertyId);
+
+ // If remove the Property from all Items
+ for (final Iterator<IDTYPE> i = getAllItemIds().iterator(); i.hasNext();) {
+ getUnfilteredItem(i.next()).removeItemProperty(propertyId);
+ }
+
+ // Sends a change event
+ fireContainerPropertySetChange();
+
+ return true;
+ }
+
+}