All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.mhus.lib.vaadin.container.MhuAbstractBeanContainer Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2018 Mike Hummel
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.mhus.lib.vaadin.container;
/*
 * Copyright 2000-2013 Vaadin Ltd.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

import java.io.Serializable;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.Container.Filterable;
import com.vaadin.v7.data.Container.PropertySetChangeNotifier;
import com.vaadin.v7.data.Container.SimpleFilterable;
import com.vaadin.v7.data.Container.Sortable;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.Property.ValueChangeEvent;
import com.vaadin.v7.data.Property.ValueChangeListener;
import com.vaadin.v7.data.Property.ValueChangeNotifier;
import com.vaadin.v7.data.util.AbstractInMemoryContainer;
import com.vaadin.v7.data.util.ItemSorter;
import com.vaadin.v7.data.util.MethodProperty.MethodException;
import com.vaadin.v7.data.util.VaadinPropertyDescriptor;
import com.vaadin.v7.data.util.filter.SimpleStringFilter;
import com.vaadin.v7.data.util.filter.UnsupportedFilterException;

import de.mhus.lib.vaadin.container.MhuBeanItem.PojoPropertyDescriptor;

/**
 * An abstract base class for in-memory containers for JavaBeans.
 * 
 * 

* 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. *

* *

* 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)}. *

* * @param * The type of the item identifier * @param * The type of the Bean * * @since 6.5 */ @SuppressWarnings("deprecation") public abstract class MhuAbstractBeanContainer extends AbstractInMemoryContainer> implements Filterable, SimpleFilterable, Sortable, ValueChangeListener, PropertySetChangeNotifier { private static final long serialVersionUID = 1L; /** * 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 * @param * * @since 6.5 */ public static interface BeanIdResolver extends Serializable { /** * Return the item identifier for a bean. * * @param bean * @return x */ 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 { private static final long serialVersionUID = 1L; 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 pd = model.get(propertyId); if (null == pd) { throw new IllegalStateException("Property " + propertyId + " not found"); } try { Property property = (Property) 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 beanIdResolver = null; /** * Maps all item ids in the container (including filtered) to their * corresponding BeanItem. */ private final Map> itemIdToItem = new HashMap>(); /** * The type of the beans in the container. */ private final Class type; /** * A description of the properties found in beans of type {@link #type}. * Determines the property ids that are present in the container. */ protected LinkedHashMap> 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 */ @SuppressWarnings("unchecked") protected MhuAbstractBeanContainer(Class type) { if (type == null) { throw new IllegalArgumentException( "The bean type passed to AbstractBeanContainer must not be null"); } this.type = type; model = MhuBeanItem.getPropertyDescriptors((Class) type); } /* * (non-Javadoc) * * @see com.vaadin.data.Container#getType(java.lang.Object) */ @Override public Class getType(Object propertyId) { return model.get(propertyId).getPropertyType(); // return doMapPrimitives( model.get(propertyId).getPropertyType() ); } // protected Class doMapPrimitives(Class propertyType) { // if (propertyType == int.class) // return Integer.class; // return propertyType; // } /** * Create a BeanItem for a bean using pre-parsed bean metadata (based on * {@link #getBeanType()}). * * @param bean * @return created {@link MhuBeanItem} or null if bean is null */ protected MhuBeanItem createBeanItem(BEANTYPE bean) { return bean == null ? null : new MhuBeanItem(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 x */ public Class getBeanType() { return type; } /* * (non-Javadoc) * * @see com.vaadin.data.Container#getContainerPropertyIds() */ @Override public Collection 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 MhuBeanItem getItem(Object itemId) { // TODO return only if visible? return getUnfilteredItem(itemId); } @Override protected MhuBeanItem getUnfilteredItem(Object itemId) { return itemIdToItem.get(itemId); } /* * (non-Javadoc) * * @see com.vaadin.data.Container#getItemIds() */ @Override @SuppressWarnings("unchecked") public List getItemIds() { return (List) super.getItemIds(); } /* * (non-Javadoc) * * @see com.vaadin.data.Container#getContainerProperty(java.lang.Object, * java.lang.Object) */ @SuppressWarnings("rawtypes") @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 removedFilters = super.removeFilters(propertyId); if (!removedFilters.isEmpty()) { // stop listening to change events for the property for (Item item : itemIdToItem.values()) { removeValueChangeListener(item, propertyId); } } } @Override public void addContainerFilter(Filter filter) throws UnsupportedFilterException { addFilter(filter); } @Override public void removeContainerFilter(Filter filter) { removeFilter(filter); } /* * (non-Javadoc) * * @see com.vaadin.data.util.AbstractInMemoryContainer#hasContainerFilters() */ @Override public boolean hasContainerFilters() { return super.hasContainerFilters(); } /* * (non-Javadoc) * * @see com.vaadin.data.util.AbstractInMemoryContainer#getContainerFilters() */ @Override public Collection getContainerFilters() { return super.getContainerFilters(); } /** * Make this container listen to the given property provided it notifies * when its value changes. * * @param item * The {@link Item} that contains the property * @param propertyId * The id of the property */ private void addValueChangeListener(Item item, Object propertyId) { Property property = item.getItemProperty(propertyId); if (property instanceof ValueChangeNotifier) { // avoid multiple notifications for the same property if // multiple filters are in use ValueChangeNotifier notifier = (ValueChangeNotifier) property; notifier.removeListener(this); notifier.addListener(this); } } /** * Remove this container as a listener for the given property. * * @param item * The {@link Item} that contains the property * @param propertyId * The id of the property */ private void removeValueChangeListener(Item item, Object propertyId) { Property property = item.getItemProperty(propertyId); if (property instanceof ValueChangeNotifier) { ((ValueChangeNotifier) property).removeListener(this); } } /** * Remove this contains as a listener for all the properties in the given * {@link Item}. * * @param item * The {@link Item} that contains the properties */ private void removeAllValueChangeListeners(Item item) { for (Object propertyId : item.getItemPropertyIds()) { removeValueChangeListener(item, propertyId); } } /* * (non-Javadoc) * * @see com.vaadin.data.Container.Sortable#getSortableContainerPropertyIds() */ @Override public Collection getSortableContainerPropertyIds() { return getSortablePropertyIds(); } /* * (non-Javadoc) * * @see com.vaadin.data.Container.Sortable#sort(java.lang.Object[], * boolean[]) */ @Override public void sort(Object[] propertyId, boolean[] ascending) { sortContainer(propertyId, ascending); } @Override public ItemSorter getItemSorter() { return super.getItemSorter(); } @Override public void setItemSorter(ItemSorter itemSorter) { super.setItemSorter(itemSorter); } @Override protected void registerNewItem(int position, IDTYPE itemId, MhuBeanItem 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 MhuBeanItem 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 MhuBeanItem 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 MhuBeanItem 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 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 MhuBeanItem 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 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 MhuBeanItem 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 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 MhuBeanItem 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 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 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 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 createBeanPropertyResolver( Object propertyId) { return new PropertyBasedBeanIdResolver(propertyId); } /** * @deprecated As of 7.0, replaced by {@link #addPropertySetChangeListener} **/ @Deprecated @Override public void addListener(Container.PropertySetChangeListener listener) { addPropertySetChangeListener(listener); } @Override public void addPropertySetChangeListener( Container.PropertySetChangeListener listener) { super.addPropertySetChangeListener(listener); } /** * @deprecated As of 7.0, replaced by * #removePropertySetChangeListener(com.vaadin.data.Container.PropertySetChangeListener) **/ @Deprecated @Override public void removeListener(Container.PropertySetChangeListener listener) { removePropertySetChangeListener(listener); } @Override public void removePropertySetChangeListener( Container.PropertySetChangeListener listener) { super.removePropertySetChangeListener(listener); } @Override public boolean addContainerProperty(Object propertyId, Class type, Object defaultValue) throws UnsupportedOperationException { throw new UnsupportedOperationException( "Use addNestedContainerProperty(String) to add container properties to a " + getClass().getSimpleName()); } /** * Merge all the beans from a {@link Collection} in one operation using the * bean item identifier resolver. * * A bean id resolver must be set before calling this method. * * * @param collection * The collection of beans to add. Must not be null. * @param removeOverlapping * Remove beans out of the list which not included in the collection. * @param comparator * Comparator to compare bean ids. Can be null to use default and maybe faster contains() method. * @return true if the list was modified by the merge * @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 boolean mergeAll(Collection collection, boolean removeOverlapping, Comparator comparator) throws IllegalStateException, IllegalArgumentException { boolean modified = false; LinkedList negativeList = null; if (removeOverlapping) { negativeList = new LinkedList(); negativeList.addAll(getAllItemIds()); } 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) { continue; } if (negativeList != null) negativeList.remove(itemId); if (comparator != null) { boolean found = false; for (IDTYPE id : getAllItemIds()) if (comparator.compare(id, itemId) == 0) { found = true; break; } if (found) continue; } else { if (getAllItemIds().contains(itemId)) { continue; } } if (internalAddItemAtEnd(itemId, createBeanItem(bean), false) != null) { modified = true; } } if (negativeList != null && negativeList.size() > 0) { modified = true; for (IDTYPE id : negativeList) removeItem(id); } if (modified) { // Filter the contents when all items have been added if (isFiltered()) { filterAll(); } else { fireItemSetChange(); } } return modified; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy