> itemMap = new HashMap<>();
private PropertyExtractor super T> propertyExtractor;
// Constructors
/**
* Constructor.
*
*
* After using this constructor, subsequent invocations of {@link #setPropertyExtractor setPropertyExtractor()}
* and {@link #setProperties setProperties()} are required to define the properties of this container
* and how to extract them.
*/
protected AbstractSimpleContainer() {
this((PropertyExtractor super T>)null);
}
/**
* Constructor.
*
*
* After using this constructor, a subsequent invocation of {@link #setProperties setProperties()} is required
* to define the properties of this container.
*
* @param propertyExtractor used to extract properties from the underlying Java objects;
* may be null but then container is not usable until one is configured via
* {@link #setPropertyExtractor setPropertyExtractor()}
*/
protected AbstractSimpleContainer(PropertyExtractor super T> propertyExtractor) {
this(propertyExtractor, null);
}
/**
* Constructor.
*
*
* After using this constructor, a subsequent invocation of {@link #setPropertyExtractor setPropertyExtractor()} is required
* to define how to extract the properties of this container; alternately, subclasses can override
* {@link #getPropertyValue getPropertyValue()}.
*
* @param propertyDefs container property definitions; null is treated like the empty set
*/
protected AbstractSimpleContainer(Collection extends PropertyDef>> propertyDefs) {
this(null, propertyDefs);
}
/**
* Constructor.
*
* @param propertyExtractor used to extract properties from the underlying Java objects;
* may be null but then container is not usable until one is configured via
* {@link #setPropertyExtractor setPropertyExtractor()}
* @param propertyDefs container property definitions; null is treated like the empty set
*/
protected AbstractSimpleContainer(PropertyExtractor super T> propertyExtractor,
Collection extends PropertyDef>> propertyDefs) {
this.setItemSorter(new SimpleItemSorter());
this.setPropertyExtractor(propertyExtractor);
this.setProperties(propertyDefs);
}
/**
* Constructor.
*
*
* Properties will be determined by the {@link ProvidesProperty @ProvidesProperty} and
* {@link ProvidesPropertySort @ProvidesPropertySort} annotated methods in the given class.
*
* @param type class to introspect for annotated methods
* @throws IllegalArgumentException if {@code type} is null
* @throws IllegalArgumentException if {@code type} has two {@link ProvidesProperty @ProvidesProperty}
* or {@link ProvidesPropertySort @ProvidesPropertySort} annotated methods for the same property
* @throws IllegalArgumentException if a {@link ProvidesProperty @ProvidesProperty}-annotated method with no
* {@linkplain ProvidesProperty#value property name specified} has a name which cannot be interpreted as a bean
* property "getter" method
* @see ProvidesProperty
* @see ProvidesPropertySort
* @see ProvidesPropertyScanner
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected AbstractSimpleContainer(Class super T> type) {
// Why the JLS forces this stupid cast:
// http://stackoverflow.com/questions/4902723/why-cant-a-java-type-parameter-have-a-lower-bound
final ProvidesPropertyScanner super T> propertyReader
= (ProvidesPropertyScanner super T>)new ProvidesPropertyScanner(type);
this.setItemSorter(new SimpleItemSorter());
this.setPropertyExtractor(propertyReader.getPropertyExtractor());
this.setProperties(propertyReader.getPropertyDefs());
}
// Public methods
/**
* Get the configured {@link PropertyExtractor} for this container.
*
* @return configured {@link PropertyExtractor}
*/
public PropertyExtractor super T> getPropertyExtractor() {
return this.propertyExtractor;
}
/**
* Change the configured {@link PropertyExtractor} for this container.
* Invoking this method does not result in any container notifications.
*
* @param propertyExtractor used to extract properties from the underlying Java objects;
* may be null but the container is not usable without one
*/
public void setPropertyExtractor(PropertyExtractor super T> propertyExtractor) {
this.propertyExtractor = propertyExtractor;
}
/**
* Read the value of the property defined by {@code propertyDef} from the given object.
*
*
* The implementation in {@link AbstractSimpleContainer} just delegates to the {@linkplain #setPropertyExtractor configured}
* {@link PropertyExtractor}; subclasses may override to customize property extraction.
*
* @param obj Java object
* @param propertyDef definition of which property to read
* @throws NullPointerException if either parameter is null
* @throws IllegalStateException if no {@link PropertyExtractor} is configured for this container
*/
@Override
public V getPropertyValue(T obj, PropertyDef propertyDef) {
if (this.propertyExtractor == null)
throw new IllegalStateException("no PropertyExtractor is configured for this container");
return this.propertyExtractor.getPropertyValue(obj, propertyDef);
}
/**
* Change the configured properties of this container.
*
* @param propertyDefs container property definitions; null is treated like the empty set
* @throws IllegalArgumentException if {@code propertyDefs} contains a property with a duplicate name
*/
public void setProperties(Collection extends PropertyDef>> propertyDefs) {
if (propertyDefs == null)
propertyDefs = Collections.>emptySet();
this.propertyMap.clear();
for (PropertyDef> propertyDef : propertyDefs) {
if (this.propertyMap.put(propertyDef.getName(), propertyDef) != null)
throw new IllegalArgumentException("duplicate property name `" + propertyDef.getName() + "'");
}
this.fireContainerPropertySetChange();
}
/**
* Add or replace a configured property of this container.
*
* @param propertyDef new container property definitions
* @throws IllegalArgumentException if {@code propertyDef} is null
*/
public void setProperty(PropertyDef> propertyDef) {
if (propertyDef == null)
throw new IllegalArgumentException("null propertyDef");
this.propertyMap.put(propertyDef.getName(), propertyDef);
this.fireContainerPropertySetChange();
}
/**
* Change this container's contents.
*
* @param contents new container contents
* @throws IllegalArgumentException if {@code contents} or any item in {@code contents} is null
*/
public void load(Iterable extends T> contents) {
this.load(contents.iterator());
}
/**
* Change this container's contents.
*
* @param contents new container contents
* @throws IllegalArgumentException if {@code contents} or any item in {@code contents} is null
*/
public void load(Iterator extends T> contents) {
// Sanity check
if (contents == null)
throw new IllegalArgumentException("null contents");
// Reset item IDs
this.resetItemIds();
this.internalRemoveAllItems();
// Bulk load and register items with id's 0, 1, 2, ...
int index = 0;
while (contents.hasNext()) {
final T obj = contents.next();
if (obj == null)
throw new IllegalArgumentException("null item in contents at index " + index);
final BackedItem item = this.createBackedItem(obj, this.propertyMap.values(), this);
this.internalAddItemAtEnd(this.generateItemId(obj), item, false);
index++;
}
// Apply filters
this.doFilterContainer(!this.getFilters().isEmpty());
// Notify subclass
this.afterReload();
// Fire event
this.fireItemSetChange();
}
@Override
protected void internalRemoveAllItems() {
super.internalRemoveAllItems();
this.itemMap.clear();
}
/**
* Get the container item ID corresponding to the given underlying Java object which is wrapped by this container.
* Objects are tested for equality using {@link Object#equals Object.equals()}.
*
*
* The implementation in {@link AbstractSimpleContainer} requires a linear search of the container.
* Some subclasses may provide a more efficient implementation.
*
*
* This method is not used by this class but is defined as a convenience for subclasses.
*
*
* Note: items that are filtered out will not be found.
*
* @param obj underlying container object
* @return item ID corresponding to {@code object}, or null if {@code object} is not found in this container
* @throws IllegalArgumentException if {@code object} is null
* @see #getItemIdForSame
*/
public I getItemIdFor(T obj) {
if (obj == null)
throw new IllegalArgumentException("null object");
for (I itemId : this.getItemIds()) {
T candidate = this.getJavaObject(itemId);
if (obj.equals(candidate))
return itemId;
}
return null;
}
/**
* Get the container item ID corresponding to the given underlying Java object which is wrapped by this container.
* Objects are tested for equality using object equality, not {@link Object#equals Object.equals()}.
*
*
* The implementation in {@link AbstractSimpleContainer} requires a linear search of the container.
* Some subclasses may provide a more efficient implementation.
*
*
* This method is not used by this class but is defined as a convenience for subclasses.
*
*
* Note: items that are filtered out will not be found.
*
* @param obj underlying container object
* @return item ID corresponding to {@code object}, or null if {@code object} is not found in this container
* @throws IllegalArgumentException if {@code object} is null
* @see #getItemIdFor
*/
public I getItemIdForSame(T obj) {
if (obj == null)
throw new IllegalArgumentException("null object");
for (I itemId : this.getItemIds()) {
T candidate = this.getJavaObject(itemId);
if (obj == candidate)
return itemId;
}
return null;
}
// Connectable
/**
* Connect this instance to non-Vaadin resources.
*
*
* The implementation in {@link AbstractSimpleContainer} does nothing.
*
* @throws IllegalStateException if there is no {@link com.vaadin.server.VaadinSession} associated with the current thread
*/
@Override
public void connect() {
}
/**
* Disconnect this instance from non-Vaadin resources.
*
*
* The implementation in {@link AbstractSimpleContainer} does nothing.
*
* @throws IllegalStateException if there is no {@link com.vaadin.server.VaadinSession} associated with the current thread
*/
@Override
public void disconnect() {
}
// Container and superclass required methods
// Workaround for http://dev.vaadin.com/ticket/8856
@Override
@SuppressWarnings("unchecked")
public List getItemIds() {
return (List)super.getItemIds();
}
@Override
public Set getContainerPropertyIds() {
return Collections.unmodifiableSet(this.propertyMap.keySet());
}
@Override
public Property> getContainerProperty(Object itemId, Object propertyId) {
final BackedItem entityItem = this.getItem(itemId);
if (entityItem == null)
return null;
return entityItem.getItemProperty(propertyId);
}
@Override
public Class> getType(Object propertyId) {
final PropertyDef> propertyDef = this.propertyMap.get(propertyId);
return propertyDef != null ? propertyDef.getType() : null;
}
@Override
public BackedItem getUnfilteredItem(Object itemId) {
final T obj = this.getJavaObject(itemId);
if (obj == null)
return null;
BackedItem item = this.itemMap.get(itemId);
if (item == null) {
item = this.createBackedItem(obj, this.propertyMap.values(), this);
this.itemMap.put(itemId, item);
}
return item;
}
// Subclass methods
/**
* Get the underlying Java object corresponding to the given item ID.
* This method ignores any filtering (i.e., filtered-out objects are still accessible).
*
* @param itemId item ID
* @return the corresponding Java object, or null if not found
*/
public abstract T getJavaObject(Object itemId);
/**
* Subclass hook invoked prior to each reload. The subclass should reset its state (e.g., issued item IDs) as required.
*/
protected abstract void resetItemIds();
/**
* Create a new, unique item ID for the given object. This method is invoked during a {@linkplain #load reload operation},
* once for each container object. Both visible and filtered objects will be passed to this method.
*
*
* The returned item ID must be unique, i.e., not returned by this method since the most recent invocation of
* {@link #resetItemIds}.
*
* @param obj underlying container object, never null
* @return item ID, never null
*/
protected abstract I generateItemId(T obj);
/**
* Subclass hook invoked after each reload but prior to invoking {@link #fireItemSetChange}.
*
*
* The implementation in {@link AbstractSimpleContainer} does nothing.
*/
protected void afterReload() {
}
/**
* Create a {@link BackedItem} for the given backing Java object.
*
*
* The implementation in {@link AbstractSimpleContainer} returns
* {@code new SimpleItem(object, propertyDefs, propertyExtractor)}.
*
* @param object underlying Java object
* @param propertyDefs property definitions
* @param propertyExtractor extracts the property value from {@code object}
* @return new {@link BackedItem}
* @throws IllegalArgumentException if any parameter is null
*/
protected BackedItem createBackedItem(T object, Collection> propertyDefs,
PropertyExtractor super T> propertyExtractor) {
return new SimpleItem<>(object, propertyDefs, propertyExtractor);
}
// Container methods
@Override
public void sort(Object[] propertyId, boolean[] ascending) {
super.sortContainer(propertyId, ascending);
}
@Override
public void addContainerFilter(Object propertyId, String filterString, boolean ignoreCase, boolean onlyMatchPrefix) {
try {
this.addFilter(new SimpleStringFilter(propertyId, filterString, ignoreCase, onlyMatchPrefix));
} catch (UnsupportedFilterException e) {
// the filter instance created here is always valid for in-memory containers
throw new RuntimeException("unexpected exception", e);
}
}
@Override
public Collection getContainerFilters() {
return super.getContainerFilters();
}
@Override
public void removeAllContainerFilters() {
this.removeAllFilters();
}
@Override
public void removeContainerFilters(Object propertyId) {
this.removeFilters(propertyId);
}
@Override
public void addContainerFilter(Filter filter) {
this.addFilter(filter);
}
@Override
public void removeContainerFilter(Filter filter) {
this.removeFilter(filter);
}
@Override
public Collection> getSortableContainerPropertyIds() {
final ArrayList propertyIds = new ArrayList<>(this.propertyMap.size());
for (Map.Entry> entry : this.propertyMap.entrySet()) {
if (this.propertyExtractor instanceof SortingPropertyExtractor) {
final SortingPropertyExtractor super T> sortingPropertyExtractor
= (SortingPropertyExtractor super T>)this.propertyExtractor;
if (sortingPropertyExtractor.canSort(entry.getValue())) {
propertyIds.add(entry.getKey());
continue;
}
}
if (entry.getValue().isSortable())
propertyIds.add(entry.getKey());
}
return propertyIds;
}
// ItemSorter class
/**
* {@link com.vaadin.data.util.ItemSorter} implementation used by {@link AbstractSimpleContainer}.
*/
private class SimpleItemSorter extends DefaultItemSorter {
@Override
@SuppressWarnings("unchecked")
protected int compareProperty(Object propertyId, boolean ascending, Item item1, Item item2) {
// Get property definition
final PropertyDef> propertyDef = AbstractSimpleContainer.this.propertyMap.get(propertyId);
if (propertyDef == null)
return super.compareProperty(propertyId, ascending, item1, item2);
// Ask SortingPropertyExtractor if we have one
if (AbstractSimpleContainer.this.propertyExtractor instanceof SortingPropertyExtractor) {
final SortingPropertyExtractor super T> sortingPropertyExtractor
= (SortingPropertyExtractor super T>)AbstractSimpleContainer.this.propertyExtractor;
if (sortingPropertyExtractor.canSort(propertyDef)) {
final T obj1 = ((BackedItem)item1).getObject();
final T obj2 = ((BackedItem)item2).getObject();
final int diff = sortingPropertyExtractor.sort(propertyDef, obj1, obj2);
return ascending ? diff : -diff;
}
}
// Ask property definition
if (propertyDef.isSortable()) {
final int diff = this.sort(propertyDef, item1, item2);
return ascending ? diff : -diff;
}
// Defer to superclass
return super.compareProperty(propertyId, ascending, item1, item2);
}
// This method exists only to allow the generic parameter to be bound
private int sort(PropertyDef propertyDef, Item item1, Item item2) {
return propertyDef.sort(propertyDef.read(item1), propertyDef.read(item2));
}
}
}