/*
* Copyright 2000-2016 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.
*/
package com.vaadin.v7.data.util;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import com.vaadin.data.provider.DataProvider;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.v7.data.Container;
import com.vaadin.v7.data.Item;
import com.vaadin.v7.data.Property;
import com.vaadin.v7.data.sort.SortOrder;
import com.vaadin.v7.data.util.filter.UnsupportedFilterException;
/**
* Container wrapper that adds support for generated properties. This container
* only supports adding new generated properties. Adding new normal properties
* should be done for the wrapped container.
*
* <p>
* Removing properties from this container does not remove anything from the
* wrapped container but instead only hides them from the results. These
* properties can be returned to this container by calling
* {@link #addContainerProperty(Object, Class, Object)} with same property id
* which was removed.
*
* <p>
* If wrapped container is Filterable and/or Sortable it should only be handled
* through this container as generated properties need to be handled in a
* specific way when sorting/filtering.
*
* <p>
* Items returned by this container do not support adding or removing
* properties. Generated properties are always read-only. Trying to make them
* editable throws an exception.
*
* @since 7.4
* @author Vaadin Ltd
*
* @deprecated As of 8.0, replaced by {@link DataProvider}
*/
@Deprecated
public class GeneratedPropertyContainer extends AbstractContainer
implements Container.Indexed, Container.Sortable, Container.Filterable,
Container.PropertySetChangeNotifier, Container.ItemSetChangeNotifier {
private final Container.Indexed wrappedContainer;
private final Map<Object, PropertyValueGenerator<?>> propertyGenerators;
private final Map<Filter, List<Filter>> activeFilters;
private Sortable sortableContainer = null;
private Filterable filterableContainer = null;
/* Removed properties which are hidden but not actually removed */
private final Set<Object> removedProperties = new HashSet<Object>();
/**
* Property implementation for generated properties
*/
@Deprecated
protected static class GeneratedProperty<T> implements Property<T> {
private Item item;
private Object itemId;
private Object propertyId;
private PropertyValueGenerator<T> generator;
public GeneratedProperty(Item item, Object propertyId, Object itemId,
PropertyValueGenerator<T> generator) {
this.item = item;
this.itemId = itemId;
this.propertyId = propertyId;
this.generator = generator;
}
@Override
public T getValue() {
return generator.getValue(item, itemId, propertyId);
}
@Override
public void setValue(T newValue) throws ReadOnlyException {
throw new ReadOnlyException("Generated properties are read only");
}
@Override
public Class<? extends T> getType() {
return generator.getType();
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean newStatus) {
if (newStatus) {
// No-op
return;
}
throw new UnsupportedOperationException(
"Generated properties are read only");
}
}
/**
* Item implementation for generated properties, used to wrap the Item that
* belongs to the wrapped container. To reach that Item use
* {@link #getWrappedItem()}
*/
@Deprecated
public class GeneratedPropertyItem implements Item {
private Item wrappedItem;
private Object itemId;
protected GeneratedPropertyItem(Object itemId, Item item) {
this.itemId = itemId;
wrappedItem = item;
}
@Override
public Property getItemProperty(Object id) {
if (propertyGenerators.containsKey(id)) {
return createProperty(wrappedItem, id, itemId,
propertyGenerators.get(id));
}
return wrappedItem.getItemProperty(id);
}
@Override
public Collection<?> getItemPropertyIds() {
Set<Object> wrappedProperties = new LinkedHashSet<Object>(
wrappedItem.getItemPropertyIds());
wrappedProperties.removeAll(removedProperties);
wrappedProperties.addAll(propertyGenerators.keySet());
return wrappedProperties;
}
@Override
public boolean addItemProperty(Object id, Property property)
throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"GeneratedPropertyItem does not support adding properties");
}
@Override
public boolean removeItemProperty(Object id)
throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"GeneratedPropertyItem does not support removing properties");
}
/**
* Tests if the given object is the same as the this object. Two Items
* from the same 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 (this == obj) {
return true;
}
if (obj == null
|| !obj.getClass().equals(GeneratedPropertyItem.class)) {
return false;
}
final GeneratedPropertyItem li = (GeneratedPropertyItem) obj;
return getContainer() == li.getContainer()
&& itemId.equals(li.itemId);
}
@Override
public int hashCode() {
return itemId.hashCode();
}
private GeneratedPropertyContainer getContainer() {
return GeneratedPropertyContainer.this;
}
/**
* Returns the wrapped Item that belongs to the wrapped container
*
* @return wrapped item.
* @since 7.6.8
*/
public Item getWrappedItem() {
return wrappedItem;
}
};
/**
* Base implementation for item add or remove events. This is used when an
* event is fired from wrapped container and needs to be reconstructed to
* act like it actually came from this container.
*/
@Deprecated
protected abstract class GeneratedItemAddOrRemoveEvent
implements Serializable {
private Object firstItemId;
private int firstIndex;
private int count;
protected GeneratedItemAddOrRemoveEvent(Object itemId, int first,
int count) {
firstItemId = itemId;
firstIndex = first;
this.count = count;
}
public Container getContainer() {
return GeneratedPropertyContainer.this;
}
public Object getFirstItemId() {
return firstItemId;
}
public int getFirstIndex() {
return firstIndex;
}
public int getAffectedItemsCount() {
return count;
}
};
@Deprecated
protected class GeneratedItemRemoveEvent
extends GeneratedItemAddOrRemoveEvent implements ItemRemoveEvent {
protected GeneratedItemRemoveEvent(ItemRemoveEvent event) {
super(event.getFirstItemId(), event.getFirstIndex(),
event.getRemovedItemsCount());
}
@Override
public int getRemovedItemsCount() {
return super.getAffectedItemsCount();
}
}
@Deprecated
protected class GeneratedItemAddEvent extends GeneratedItemAddOrRemoveEvent
implements ItemAddEvent {
protected GeneratedItemAddEvent(ItemAddEvent event) {
super(event.getFirstItemId(), event.getFirstIndex(),
event.getAddedItemsCount());
}
@Override
public int getAddedItemsCount() {
return super.getAffectedItemsCount();
}
}
/**
* Constructor for GeneratedPropertyContainer.
*
* @param container
* underlying indexed container
*/
@Deprecated
public GeneratedPropertyContainer(Container.Indexed container) {
wrappedContainer = container;
propertyGenerators = new HashMap<Object, PropertyValueGenerator<?>>();
if (wrappedContainer instanceof Sortable) {
sortableContainer = (Sortable) wrappedContainer;
}
if (wrappedContainer instanceof Filterable) {
activeFilters = new HashMap<Filter, List<Filter>>();
filterableContainer = (Filterable) wrappedContainer;
} else {
activeFilters = null;
}
// ItemSetChangeEvents
if (wrappedContainer instanceof ItemSetChangeNotifier) {
((ItemSetChangeNotifier) wrappedContainer)
.addItemSetChangeListener(new ItemSetChangeListener() {
@Override
public void containerItemSetChange(
ItemSetChangeEvent event) {
if (event instanceof ItemAddEvent) {
final ItemAddEvent addEvent = (ItemAddEvent) event;
fireItemSetChange(
new GeneratedItemAddEvent(addEvent));
} else if (event instanceof ItemRemoveEvent) {
final ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
fireItemSetChange(new GeneratedItemRemoveEvent(
removeEvent));
} else {
fireItemSetChange();
}
}
});
}
// PropertySetChangeEvents
if (wrappedContainer instanceof PropertySetChangeNotifier) {
((PropertySetChangeNotifier) wrappedContainer)
.addPropertySetChangeListener(
new PropertySetChangeListener() {
@Override
public void containerPropertySetChange(
PropertySetChangeEvent event) {
fireContainerPropertySetChange();
}
});
}
}
/* Functions related to generated properties */
/**
* Add a new PropertyValueGenerator with given property id. This will
* override any existing properties with the same property id. Fires a
* PropertySetChangeEvent.
*
* @param propertyId
* property id
* @param generator
* a property value generator
*/
public void addGeneratedProperty(Object propertyId,
PropertyValueGenerator<?> generator) {
propertyGenerators.put(propertyId, generator);
fireContainerPropertySetChange();
}
/**
* Removes any possible PropertyValueGenerator with given property id. Fires
* a PropertySetChangeEvent.
*
* @param propertyId
* property id
*/
public void removeGeneratedProperty(Object propertyId) {
if (propertyGenerators.containsKey(propertyId)) {
propertyGenerators.remove(propertyId);
fireContainerPropertySetChange();
}
}
private Item createGeneratedPropertyItem(final Object itemId,
final Item item) {
return new GeneratedPropertyItem(itemId, item);
}
private <T> Property<T> createProperty(final Item item,
final Object propertyId, final Object itemId,
final PropertyValueGenerator<T> generator) {
return new GeneratedProperty<T>(item, propertyId, itemId, generator);
}
/* Listener functionality */
@Override
public void addItemSetChangeListener(ItemSetChangeListener listener) {
super.addItemSetChangeListener(listener);
}
@Override
public void addListener(ItemSetChangeListener listener) {
super.addListener(listener);
}
@Override
public void removeItemSetChangeListener(ItemSetChangeListener listener) {
super.removeItemSetChangeListener(listener);
}
@Override
public void removeListener(ItemSetChangeListener listener) {
super.removeListener(listener);
}
@Override
public void addPropertySetChangeListener(
PropertySetChangeListener listener) {
super.addPropertySetChangeListener(listener);
}
@Override
public void addListener(PropertySetChangeListener listener) {
super.addListener(listener);
}
@Override
public void removePropertySetChangeListener(
PropertySetChangeListener listener) {
super.removePropertySetChangeListener(listener);
}
@Override
public void removeListener(PropertySetChangeListener listener) {
super.removeListener(listener);
}
/* Filtering functionality */
@Override
public void addContainerFilter(Filter filter)
throws UnsupportedFilterException {
if (filterableContainer == null) {
throw new UnsupportedOperationException(
"Wrapped container is not filterable");
}
List<Filter> addedFilters = new ArrayList<Filter>();
for (Entry<?, PropertyValueGenerator<?>> entry : propertyGenerators
.entrySet()) {
Object property = entry.getKey();
if (filter.appliesToProperty(property)) {
// Have generated property modify filter to fit the original
// data in the container.
Filter modifiedFilter = entry.getValue().modifyFilter(filter);
filterableContainer.addContainerFilter(modifiedFilter);
// Keep track of added filters
addedFilters.add(modifiedFilter);
}
}
if (addedFilters.isEmpty()) {
// No generated property modified this filter, use it as is
addedFilters.add(filter);
filterableContainer.addContainerFilter(filter);
}
// Map filter to actually added filters
activeFilters.put(filter, addedFilters);
}
@Override
public void removeContainerFilter(Filter filter) {
if (filterableContainer == null) {
throw new UnsupportedOperationException(
"Wrapped container is not filterable");
}
if (activeFilters.containsKey(filter)) {
for (Filter f : activeFilters.get(filter)) {
filterableContainer.removeContainerFilter(f);
}
activeFilters.remove(filter);
}
}
@Override
public void removeAllContainerFilters() {
if (filterableContainer == null) {
throw new UnsupportedOperationException(
"Wrapped container is not filterable");
}
filterableContainer.removeAllContainerFilters();
activeFilters.clear();
}
@Override
public Collection<Filter> getContainerFilters() {
if (filterableContainer == null) {
throw new UnsupportedOperationException(
"Wrapped container is not filterable");
}
return Collections.unmodifiableSet(activeFilters.keySet());
}
/* Sorting functionality */
@Override
public void sort(Object[] propertyId, boolean[] ascending) {
if (sortableContainer == null) {
throw new UnsupportedOperationException(
"Wrapped container is not Sortable");
}
if (propertyId.length == 0) {
sortableContainer.sort(propertyId, ascending);
return;
}
List<Object> actualSortProperties = new ArrayList<Object>();
List<Boolean> actualSortDirections = new ArrayList<Boolean>();
for (int i = 0; i < propertyId.length; ++i) {
Object property = propertyId[i];
SortDirection direction;
boolean isAscending = i < ascending.length ? ascending[i] : true;
if (isAscending) {
direction = SortDirection.ASCENDING;
} else {
direction = SortDirection.DESCENDING;
}
if (propertyGenerators.containsKey(property)) {
// Sorting by a generated property. Generated property should
// modify sort orders to work with original properties in the
// container.
for (SortOrder s : propertyGenerators.get(property)
.getSortProperties(
new SortOrder(property, direction))) {
actualSortProperties.add(s.getPropertyId());
actualSortDirections
.add(s.getDirection() == SortDirection.ASCENDING);
}
} else {
actualSortProperties.add(property);
actualSortDirections.add(isAscending);
}
}
boolean[] actualAscending = new boolean[actualSortDirections.size()];
for (int i = 0; i < actualAscending.length; ++i) {
actualAscending[i] = actualSortDirections.get(i);
}
sortableContainer.sort(actualSortProperties.toArray(), actualAscending);
}
@Override
public Collection<?> getSortableContainerPropertyIds() {
if (sortableContainer == null) {
return Collections.emptySet();
}
Set<Object> sortablePropertySet = new HashSet<Object>(
sortableContainer.getSortableContainerPropertyIds());
for (Entry<?, PropertyValueGenerator<?>> entry : propertyGenerators
.entrySet()) {
Object property = entry.getKey();
SortOrder order = new SortOrder(property, SortDirection.ASCENDING);
if (entry.getValue().getSortProperties(order).length > 0) {
sortablePropertySet.add(property);
} else {
sortablePropertySet.remove(property);
}
}
return sortablePropertySet;
}
/* Item related overrides */
@Override
public Item addItemAfter(Object previousItemId, Object newItemId)
throws UnsupportedOperationException {
Item item = wrappedContainer.addItemAfter(previousItemId, newItemId);
if (item == null) {
return null;
}
return createGeneratedPropertyItem(newItemId, item);
}
@Override
public Item addItem(Object itemId) throws UnsupportedOperationException {
Item item = wrappedContainer.addItem(itemId);
if (item == null) {
return null;
}
return createGeneratedPropertyItem(itemId, item);
}
@Override
public Item addItemAt(int index, Object newItemId)
throws UnsupportedOperationException {
Item item = wrappedContainer.addItemAt(index, newItemId);
if (item == null) {
return null;
}
return createGeneratedPropertyItem(newItemId, item);
}
@Override
public Item getItem(Object itemId) {
Item item = wrappedContainer.getItem(itemId);
if (item == null) {
return null;
}
return createGeneratedPropertyItem(itemId, item);
}
/* Property related overrides */
@Override
public Property<?> getContainerProperty(Object itemId, Object propertyId) {
if (propertyGenerators.keySet().contains(propertyId)) {
return getItem(itemId).getItemProperty(propertyId);
} else if (!removedProperties.contains(propertyId)) {
return wrappedContainer.getContainerProperty(itemId, propertyId);
}
return null;
}
/**
* Returns a list of propety ids available in this container. This
* collection will contain properties for generated properties. Removed
* properties will not show unless there is a generated property overriding
* those.
*/
@Override
public Collection<?> getContainerPropertyIds() {
Set<Object> wrappedProperties = new LinkedHashSet<Object>(
wrappedContainer.getContainerPropertyIds());
wrappedProperties.removeAll(removedProperties);
wrappedProperties.addAll(propertyGenerators.keySet());
return wrappedProperties;
}
/**
* Adds a previously removed property back to GeneratedPropertyContainer.
* Adding a property that is not previously removed causes an
* UnsupportedOperationException.
*/
@Override
public boolean addContainerProperty(Object propertyId, Class<?> type,
Object defaultValue) throws UnsupportedOperationException {
if (!removedProperties.contains(propertyId)) {
throw new UnsupportedOperationException(
"GeneratedPropertyContainer does not support adding properties.");
}
removedProperties.remove(propertyId);
fireContainerPropertySetChange();
return true;
}
/**
* Marks the given property as hidden. This property from wrapped container
* will be removed from {@link #getContainerPropertyIds()} and is no longer
* be available in Items retrieved from this container.
*/
@Override
public boolean removeContainerProperty(Object propertyId)
throws UnsupportedOperationException {
if (wrappedContainer.getContainerPropertyIds().contains(propertyId)
&& removedProperties.add(propertyId)) {
fireContainerPropertySetChange();
return true;
}
return false;
}
/* Type related overrides */
@Override
public Class<?> getType(Object propertyId) {
if (propertyGenerators.containsKey(propertyId)) {
return propertyGenerators.get(propertyId).getType();
} else {
return wrappedContainer.getType(propertyId);
}
}
/* Unmodified functions */
@Override
public Object nextItemId(Object itemId) {
return wrappedContainer.nextItemId(itemId);
}
@Override
public Object prevItemId(Object itemId) {
return wrappedContainer.prevItemId(itemId);
}
@Override
public Object firstItemId() {
return wrappedContainer.firstItemId();
}
@Override
public Object lastItemId() {
return wrappedContainer.lastItemId();
}
@Override
public boolean isFirstId(Object itemId) {
return wrappedContainer.isFirstId(itemId);
}
@Override
public boolean isLastId(Object itemId) {
return wrappedContainer.isLastId(itemId);
}
@Override
public Object addItemAfter(Object previousItemId)
throws UnsupportedOperationException {
return wrappedContainer.addItemAfter(previousItemId);
}
@Override
public Collection<?> getItemIds() {
return wrappedContainer.getItemIds();
}
@Override
public int size() {
return wrappedContainer.size();
}
@Override
public boolean containsId(Object itemId) {
return wrappedContainer.containsId(itemId);
}
@Override
public Object addItem() throws UnsupportedOperationException {
return wrappedContainer.addItem();
}
@Override
public boolean removeItem(Object itemId)
throws UnsupportedOperationException {
return wrappedContainer.removeItem(itemId);
}
@Override
public boolean removeAllItems() throws UnsupportedOperationException {
return wrappedContainer.removeAllItems();
}
@Override
public int indexOfId(Object itemId) {
return wrappedContainer.indexOfId(itemId);
}
@Override
public Object getIdByIndex(int index) {
return wrappedContainer.getIdByIndex(index);
}
@Override
public List<?> getItemIds(int startIndex, int numberOfItems) {
return wrappedContainer.getItemIds(startIndex, numberOfItems);
}
@Override
public Object addItemAt(int index) throws UnsupportedOperationException {
return wrappedContainer.addItemAt(index);
}
/**
* Returns the original underlying container.
*
* @return the original underlying container
*/
public Container.Indexed getWrappedContainer() {
return wrappedContainer;
}
}