summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/data
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/data')
-rw-r--r--server/src/com/vaadin/data/Container.java58
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java1045
-rw-r--r--server/src/com/vaadin/data/fieldgroup/FieldGroup.java3
-rw-r--r--server/src/com/vaadin/data/sort/Sort.java153
-rw-r--r--server/src/com/vaadin/data/sort/SortOrder.java106
-rw-r--r--server/src/com/vaadin/data/util/AbstractBeanContainer.java20
-rw-r--r--server/src/com/vaadin/data/util/AbstractInMemoryContainer.java155
-rw-r--r--server/src/com/vaadin/data/util/GeneratedPropertyContainer.java724
-rw-r--r--server/src/com/vaadin/data/util/IndexedContainer.java34
-rw-r--r--server/src/com/vaadin/data/util/PropertyValueGenerator.java100
10 files changed, 2367 insertions, 31 deletions
diff --git a/server/src/com/vaadin/data/Container.java b/server/src/com/vaadin/data/Container.java
index 8e99bac541..fb7a93e832 100644
--- a/server/src/com/vaadin/data/Container.java
+++ b/server/src/com/vaadin/data/Container.java
@@ -582,6 +582,64 @@ public interface Container extends Serializable {
public Item addItemAt(int index, Object newItemId)
throws UnsupportedOperationException;
+ /**
+ * An <code>Event</code> object specifying information about the added
+ * items.
+ *
+ * @since 7.4
+ */
+ public interface ItemAddEvent extends ItemSetChangeEvent {
+
+ /**
+ * Gets the item id of the first added item.
+ *
+ * @return item id of the first added item
+ */
+ public Object getFirstItemId();
+
+ /**
+ * Gets the index of the first added item.
+ *
+ * @return index of the first added item
+ */
+ public int getFirstIndex();
+
+ /**
+ * Gets the number of the added items.
+ *
+ * @return the number of added items.
+ */
+ public int getAddedItemsCount();
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the removed
+ * items.
+ *
+ * @since 7.4
+ */
+ public interface ItemRemoveEvent extends ItemSetChangeEvent {
+ /**
+ * Gets the item id of the first removed item.
+ *
+ * @return item id of the first removed item
+ */
+ public Object getFirstItemId();
+
+ /**
+ * Gets the index of the first removed item.
+ *
+ * @return index of the first removed item
+ */
+ public int getFirstIndex();
+
+ /**
+ * Gets the number of the removed items.
+ *
+ * @return the number of removed items
+ */
+ public int getRemovedItemsCount();
+ }
}
/**
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
new file mode 100644
index 0000000000..5da95c3b5c
--- /dev/null
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -0,0 +1,1045 @@
+/*
+ * Copyright 2000-2014 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.data;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.gwt.thirdparty.guava.common.collect.BiMap;
+import com.google.gwt.thirdparty.guava.common.collect.HashBiMap;
+import com.vaadin.data.Container.Indexed;
+import com.vaadin.data.Container.Indexed.ItemAddEvent;
+import com.vaadin.data.Container.Indexed.ItemRemoveEvent;
+import com.vaadin.data.Container.ItemSetChangeEvent;
+import com.vaadin.data.Container.ItemSetChangeListener;
+import com.vaadin.data.Container.ItemSetChangeNotifier;
+import com.vaadin.data.Property.ValueChangeEvent;
+import com.vaadin.data.Property.ValueChangeListener;
+import com.vaadin.data.Property.ValueChangeNotifier;
+import com.vaadin.data.util.converter.Converter;
+import com.vaadin.data.util.converter.Converter.ConversionException;
+import com.vaadin.server.AbstractExtension;
+import com.vaadin.server.ClientConnector;
+import com.vaadin.server.KeyMapper;
+import com.vaadin.shared.data.DataProviderRpc;
+import com.vaadin.shared.data.DataRequestRpc;
+import com.vaadin.shared.ui.grid.GridState;
+import com.vaadin.shared.ui.grid.Range;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.Grid.CellReference;
+import com.vaadin.ui.Grid.CellStyleGenerator;
+import com.vaadin.ui.Grid.Column;
+import com.vaadin.ui.Grid.RowReference;
+import com.vaadin.ui.Grid.RowStyleGenerator;
+import com.vaadin.ui.renderer.Renderer;
+
+import elemental.json.Json;
+import elemental.json.JsonArray;
+import elemental.json.JsonObject;
+import elemental.json.JsonValue;
+
+/**
+ * Provides Vaadin server-side container data source to a
+ * {@link com.vaadin.client.ui.grid.GridConnector}. This is currently
+ * implemented as an Extension hardcoded to support a specific connector type.
+ * This will be changed once framework support for something more flexible has
+ * been implemented.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class RpcDataProviderExtension extends AbstractExtension {
+
+ /**
+ * ItemId to Key to ItemId mapper.
+ * <p>
+ * This class is used when transmitting information about items in container
+ * related to Grid. It introduces a consistent way of mapping ItemIds and
+ * its container to a String that can be mapped back to ItemId.
+ * <p>
+ * <em>Technical note:</em> This class also keeps tabs on which indices are
+ * being shown/selected, and is able to clean up after itself once the
+ * itemId &lrarr; key mapping is not needed anymore. In other words, this
+ * doesn't leak memory.
+ */
+ public class DataProviderKeyMapper implements Serializable {
+ private final BiMap<Integer, Object> indexToItemId = HashBiMap.create();
+ private final BiMap<Object, String> itemIdToKey = HashBiMap.create();
+ private Set<Object> pinnedItemIds = new HashSet<Object>();
+ private Range activeRange = Range.withLength(0, 0);
+ private long rollingIndex = 0;
+
+ private DataProviderKeyMapper() {
+ // private implementation
+ }
+
+ void setActiveRange(Range newActiveRange) {
+ final Range[] removed = activeRange.partitionWith(newActiveRange);
+ final Range[] added = newActiveRange.partitionWith(activeRange);
+
+ removeActiveRows(removed[0]);
+ removeActiveRows(removed[2]);
+ addActiveRows(added[0]);
+ addActiveRows(added[2]);
+
+ activeRange = newActiveRange;
+ }
+
+ private void removeActiveRows(final Range deprecated) {
+ for (int i = deprecated.getStart(); i < deprecated.getEnd(); i++) {
+ final Integer ii = Integer.valueOf(i);
+ final Object itemId = indexToItemId.get(ii);
+
+ if (!isPinned(itemId)) {
+ itemIdToKey.remove(itemId);
+ indexToItemId.remove(ii);
+ }
+ }
+ }
+
+ private void addActiveRows(Range added) {
+ if (added.isEmpty()) {
+ // Some container.getItemIds() implementations just might be
+ // expensive even for an empty range, so bail out early
+ return;
+ }
+
+ List<?> newItemIds = container.getItemIds(added.getStart(),
+ added.length());
+ Integer index = added.getStart();
+ for (Object itemId : newItemIds) {
+ /*
+ * We might be in a situation we have an index <-> itemId entry
+ * already. This happens when something was selected, scrolled
+ * out of view and now we're scrolling it back into view. It's
+ * unnecessary to overwrite it in that case.
+ *
+ * Fun thought: considering branch prediction, it _might_ even
+ * be a bit faster to simply always run the code inside this
+ * if-state. But it sounds too stupid (and most often too
+ * insignificant) to try out.
+ */
+ if (!indexToItemId.containsKey(index)) {
+ /*
+ * We might be in a situation where we have an itemId <->
+ * key entry already, but no index for it. This happens when
+ * something that is out of view is selected
+ * programmatically. In that case, we only want to add an
+ * index for that entry, and not overwrite the key.
+ */
+ if (!itemIdToKey.containsKey(itemId)) {
+ itemIdToKey.put(itemId, nextKey());
+ }
+
+ indexToItemId.forcePut(index, itemId);
+ }
+ index++;
+ }
+ }
+
+ private String nextKey() {
+ return String.valueOf(rollingIndex++);
+ }
+
+ String getKey(Object itemId) {
+ String key = itemIdToKey.get(itemId);
+ if (key == null) {
+ key = nextKey();
+ itemIdToKey.put(itemId, key);
+ }
+ return key;
+ }
+
+ /**
+ * Gets keys for a collection of item ids.
+ * <p>
+ * If the itemIds are currently cached, the existing keys will be used.
+ * Otherwise new ones will be created.
+ *
+ * @param itemIds
+ * the item ids for which to get keys
+ * @return keys for the {@code itemIds}
+ */
+ public List<String> getKeys(Collection<Object> itemIds) {
+ if (itemIds == null) {
+ throw new IllegalArgumentException("itemIds can't be null");
+ }
+
+ ArrayList<String> keys = new ArrayList<String>(itemIds.size());
+ for (Object itemId : itemIds) {
+ keys.add(getKey(itemId));
+ }
+ return keys;
+ }
+
+ /**
+ * Gets the registered item id based on its key.
+ * <p>
+ * A key is used to identify a particular row on both a server and a
+ * client. This method can be used to get the item id for the row key
+ * that the client has sent.
+ *
+ * @param key
+ * the row key for which to retrieve an item id
+ * @return the item id corresponding to {@code key}
+ * @throws IllegalStateException
+ * if the key mapper does not have a record of {@code key} .
+ */
+ public Object getItemId(String key) throws IllegalStateException {
+ Object itemId = itemIdToKey.inverse().get(key);
+ if (itemId != null) {
+ return itemId;
+ } else {
+ throw new IllegalStateException("No item id for key " + key
+ + " found.");
+ }
+ }
+
+ /**
+ * Gets corresponding item ids for each of the keys in a collection.
+ *
+ * @param keys
+ * the keys for which to retrieve item ids
+ * @return a collection of item ids for the {@code keys}
+ * @throws IllegalStateException
+ * if one or more of keys don't have a corresponding item id
+ * in the cache
+ */
+ public Collection<Object> getItemIds(Collection<String> keys)
+ throws IllegalStateException {
+ if (keys == null) {
+ throw new IllegalArgumentException("keys may not be null");
+ }
+
+ ArrayList<Object> itemIds = new ArrayList<Object>(keys.size());
+ for (String key : keys) {
+ itemIds.add(getItemId(key));
+ }
+ return itemIds;
+ }
+
+ /**
+ * Pin an item id to be cached indefinitely.
+ * <p>
+ * Normally when an itemId is not an active row, it is discarded from
+ * the cache. Pinning an item id will make sure that it is kept in the
+ * cache.
+ * <p>
+ * In effect, while an item id is pinned, it always has the same key.
+ *
+ * @param itemId
+ * the item id to pin
+ * @throws IllegalStateException
+ * if {@code itemId} was already pinned
+ * @see #unpin(Object)
+ * @see #isPinned(Object)
+ * @see #getItemIds(Collection)
+ */
+ public void pin(Object itemId) throws IllegalStateException {
+ if (isPinned(itemId)) {
+ throw new IllegalStateException("Item id " + itemId
+ + " was pinned already");
+ }
+ pinnedItemIds.add(itemId);
+ }
+
+ /**
+ * Unpin an item id.
+ * <p>
+ * This cancels the effect of pinning an item id. If the item id is
+ * currently inactive, it will be immediately removed from the cache.
+ *
+ * @param itemId
+ * the item id to unpin
+ * @throws IllegalStateException
+ * if {@code itemId} was not pinned
+ * @see #pin(Object)
+ * @see #isPinned(Object)
+ * @see #getItemIds(Collection)
+ */
+ public void unpin(Object itemId) throws IllegalStateException {
+ if (!isPinned(itemId)) {
+ throw new IllegalStateException("Item id " + itemId
+ + " was not pinned");
+ }
+
+ pinnedItemIds.remove(itemId);
+ final Integer index = indexToItemId.inverse().get(itemId);
+ if (index == null || !activeRange.contains(index.intValue())) {
+ itemIdToKey.remove(itemId);
+ indexToItemId.remove(index);
+ }
+ }
+
+ /**
+ * Checks whether an item id is pinned or not.
+ *
+ * @param itemId
+ * the item id to check for pin status
+ * @return {@code true} iff the item id is currently pinned
+ */
+ public boolean isPinned(Object itemId) {
+ return pinnedItemIds.contains(itemId);
+ }
+
+ Object itemIdAtIndex(int index) {
+ return indexToItemId.get(Integer.valueOf(index));
+ }
+ }
+
+ /**
+ * A helper class that handles the client-side Escalator logic relating to
+ * making sure that whatever is currently visible to the user, is properly
+ * initialized and otherwise handled on the server side (as far as
+ * required).
+ * <p>
+ * This bookeeping includes, but is not limited to:
+ * <ul>
+ * <li>listening to the currently visible {@link com.vaadin.data.Property
+ * Properties'} value changes on the server side and sending those back to
+ * the client; and
+ * <li>attaching and detaching {@link com.vaadin.ui.Component Components}
+ * from the Vaadin Component hierarchy.
+ * </ul>
+ */
+ private class ActiveRowHandler implements Serializable {
+ /**
+ * A map from itemId to the value change listener used for all of its
+ * properties
+ */
+ private final Map<Object, GridValueChangeListener> valueChangeListeners = new HashMap<Object, GridValueChangeListener>();
+
+ /**
+ * The currently active range. Practically, it's the range of row
+ * indices being cached currently.
+ */
+ private Range activeRange = Range.withLength(0, 0);
+
+ /**
+ * A hook for making sure that appropriate data is "active". All other
+ * rows should be "inactive".
+ * <p>
+ * "Active" can mean different things in different contexts. For
+ * example, only the Properties in the active range need
+ * ValueChangeListeners. Also, whenever a row with a Component becomes
+ * active, it needs to be attached (and conversely, when inactive, it
+ * needs to be detached).
+ *
+ * @param firstActiveRow
+ * the first active row
+ * @param activeRowCount
+ * the number of active rows
+ */
+ public void setActiveRows(int firstActiveRow, int activeRowCount) {
+
+ final Range newActiveRange = Range.withLength(firstActiveRow,
+ activeRowCount);
+
+ // TODO [[Components]] attach and detach components
+
+ /*-
+ * Example
+ *
+ * New Range: [3, 4, 5, 6, 7]
+ * Old Range: [1, 2, 3, 4, 5]
+ * Result: [1, 2][3, 4, 5] []
+ */
+ final Range[] depractionPartition = activeRange
+ .partitionWith(newActiveRange);
+ removeValueChangeListeners(depractionPartition[0]);
+ removeValueChangeListeners(depractionPartition[2]);
+
+ /*-
+ * Example
+ *
+ * Old Range: [1, 2, 3, 4, 5]
+ * New Range: [3, 4, 5, 6, 7]
+ * Result: [] [3, 4, 5][6, 7]
+ */
+ final Range[] activationPartition = newActiveRange
+ .partitionWith(activeRange);
+ addValueChangeListeners(activationPartition[0]);
+ addValueChangeListeners(activationPartition[2]);
+
+ activeRange = newActiveRange;
+
+ assert valueChangeListeners.size() == newActiveRange.length() : "Value change listeners not set up correctly!";
+ }
+
+ private void addValueChangeListeners(Range range) {
+ for (int i = range.getStart(); i < range.getEnd(); i++) {
+
+ final Object itemId = container.getIdByIndex(i);
+ final Item item = container.getItem(itemId);
+
+ if (valueChangeListeners.containsKey(itemId)) {
+ /*
+ * This might occur when items are removed from above the
+ * viewport, the escalator scrolls up to compensate, but the
+ * same items remain in the view: It looks as if one row was
+ * scrolled, when in fact the whole viewport was shifted up.
+ */
+ continue;
+ }
+
+ GridValueChangeListener listener = new GridValueChangeListener(
+ itemId, item);
+ valueChangeListeners.put(itemId, listener);
+ }
+ }
+
+ private void removeValueChangeListeners(Range range) {
+ for (int i = range.getStart(); i < range.getEnd(); i++) {
+ final Object itemId = container.getIdByIndex(i);
+ final GridValueChangeListener listener = valueChangeListeners
+ .remove(itemId);
+
+ if (listener != null) {
+ listener.removeListener();
+ }
+ }
+ }
+
+ /**
+ * Manages removed columns in active rows.
+ * <p>
+ * This method does <em>not</em> send data again to the client.
+ *
+ * @param removedColumns
+ * the columns that have been removed from the grid
+ */
+ public void columnsRemoved(Collection<Column> removedColumns) {
+ if (removedColumns.isEmpty()) {
+ return;
+ }
+
+ for (GridValueChangeListener listener : valueChangeListeners
+ .values()) {
+ listener.removeColumns(removedColumns);
+ }
+ }
+
+ /**
+ * Manages added columns in active rows.
+ * <p>
+ * This method sends the data for the changed rows to client side.
+ *
+ * @param addedColumns
+ * the columns that have been added to the grid
+ */
+ public void columnsAdded(Collection<Column> addedColumns) {
+ if (addedColumns.isEmpty()) {
+ return;
+ }
+
+ for (GridValueChangeListener listener : valueChangeListeners
+ .values()) {
+ listener.addColumns(addedColumns);
+ }
+ }
+
+ /**
+ * Handles the insertion of rows.
+ * <p>
+ * This method's responsibilities are to:
+ * <ul>
+ * <li>shift the internal bookkeeping by <code>count</code> if the
+ * insertion happens above currently active range
+ * <li>ignore rows inserted below the currently active range
+ * <li>shift (and deactivate) rows pushed out of view
+ * <li>activate rows that are inserted in the current viewport
+ * </ul>
+ *
+ * @param firstIndex
+ * the index of the first inserted rows
+ * @param count
+ * the number of rows inserted at <code>firstIndex</code>
+ */
+ public void insertRows(int firstIndex, int count) {
+ if (firstIndex < activeRange.getStart()) {
+ activeRange = activeRange.offsetBy(count);
+ } else if (firstIndex < activeRange.getEnd()) {
+ final Range deprecatedRange = Range.withLength(
+ activeRange.getEnd(), count);
+ removeValueChangeListeners(deprecatedRange);
+
+ final Range freshRange = Range.withLength(firstIndex, count);
+ addValueChangeListeners(freshRange);
+ } else {
+ // out of view, noop
+ }
+ }
+
+ /**
+ * Handles the removal of rows.
+ * <p>
+ * This method's responsibilities are to:
+ * <ul>
+ * <li>shift the internal bookkeeping by <code>count</code> if the
+ * removal happens above currently active range
+ * <li>ignore rows removed below the currently active range
+ * </ul>
+ *
+ * @param firstIndex
+ * the index of the first removed rows
+ * @param count
+ * the number of rows removed at <code>firstIndex</code>
+ */
+ public void removeRows(int firstIndex, int count) {
+ int lastRemoved = firstIndex + count;
+ if (lastRemoved < activeRange.getStart()) {
+ /* firstIndex < lastIndex < start */
+ activeRange = activeRange.offsetBy(-count);
+ } else if (firstIndex < activeRange.getEnd()) {
+ final Range deprecated = Range.between(
+ Math.max(activeRange.getStart(), firstIndex),
+ Math.min(activeRange.getEnd(), lastRemoved + 1));
+ for (int i = deprecated.getStart(); i < deprecated.getEnd(); ++i) {
+ Object itemId = keyMapper.itemIdAtIndex(i);
+ // Item doesn't exist anymore.
+ valueChangeListeners.remove(itemId);
+ }
+
+ activeRange = Range.withLength(activeRange.getStart(),
+ activeRange.length() - deprecated.length());
+ } else {
+ /* end <= firstIndex, no need to do anything */
+ }
+ }
+ }
+
+ /**
+ * A class to listen to changes in property values in the Container added
+ * with {@link Grid#setContainerDatasource(Container.Indexed)}, and notifies
+ * the data source to update the client-side representation of the modified
+ * item.
+ * <p>
+ * One instance of this class can (and should) be reused for all the
+ * properties in an item, since this class will inform that the entire row
+ * needs to be re-evaluated (in contrast to a property-based change
+ * management)
+ * <p>
+ * Since there's no Container-wide possibility to listen to any kind of
+ * value changes, an instance of this class needs to be attached to each and
+ * every Item's Property in the container.
+ *
+ * @see Grid#addValueChangeListener(Container, Object, Object)
+ * @see Grid#valueChangeListeners
+ */
+ private class GridValueChangeListener implements ValueChangeListener {
+ private final Object itemId;
+ private final Item item;
+
+ public GridValueChangeListener(Object itemId, Item item) {
+ /*
+ * Using an assert instead of an exception throw, just to optimize
+ * prematurely
+ */
+ assert itemId != null : "null itemId not accepted";
+ this.itemId = itemId;
+ this.item = item;
+
+ internalAddColumns(getGrid().getColumns());
+ }
+
+ @Override
+ public void valueChange(ValueChangeEvent event) {
+ updateRowData(itemId);
+ }
+
+ public void removeListener() {
+ removeColumns(getGrid().getColumns());
+ }
+
+ public void addColumns(Collection<Column> addedColumns) {
+ internalAddColumns(addedColumns);
+ updateRowData(itemId);
+ }
+
+ private void internalAddColumns(Collection<Column> addedColumns) {
+ for (final Column column : addedColumns) {
+ final Property<?> property = item.getItemProperty(column
+ .getPropertyId());
+ if (property instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) property)
+ .addValueChangeListener(this);
+ }
+ }
+ }
+
+ public void removeColumns(Collection<Column> removedColumns) {
+ for (final Column column : removedColumns) {
+ final Property<?> property = item.getItemProperty(column
+ .getPropertyId());
+ if (property instanceof ValueChangeNotifier) {
+ ((ValueChangeNotifier) property)
+ .removeValueChangeListener(this);
+ }
+ }
+ }
+ }
+
+ private final Indexed container;
+
+ private final ActiveRowHandler activeRowHandler = new ActiveRowHandler();
+
+ private DataProviderRpc rpc;
+
+ private final ItemSetChangeListener itemListener = new ItemSetChangeListener() {
+ @Override
+ public void containerItemSetChange(ItemSetChangeEvent event) {
+
+ if (event instanceof ItemAddEvent) {
+ ItemAddEvent addEvent = (ItemAddEvent) event;
+ int firstIndex = addEvent.getFirstIndex();
+ int count = addEvent.getAddedItemsCount();
+ insertRowData(firstIndex, count);
+ }
+
+ else if (event instanceof ItemRemoveEvent) {
+ ItemRemoveEvent removeEvent = (ItemRemoveEvent) event;
+ int firstIndex = removeEvent.getFirstIndex();
+ int count = removeEvent.getRemovedItemsCount();
+ removeRowData(firstIndex, count);
+ }
+
+ else {
+
+ /*
+ * Clear everything we have in view, and let the client
+ * re-request for whatever it needs.
+ *
+ * Why this shortcut? Well, since anything could've happened, we
+ * don't know what has happened. There are a lot of use-cases we
+ * can cover at once with this carte blanche operation:
+ *
+ * 1) Grid is scrolled somewhere in the middle and all the
+ * rows-inview are removed. We need a new pageful.
+ *
+ * 2) Grid is scrolled somewhere in the middle and none of the
+ * visible rows are removed. We need no new rows.
+ *
+ * 3) Grid is scrolled all the way to the bottom, and the last
+ * rows are being removed. Grid needs to scroll up and request
+ * for more rows at the top.
+ *
+ * 4) Grid is scrolled pretty much to the bottom, and the last
+ * rows are being removed. Grid needs to be aware that some
+ * scrolling is needed, but not to compensate for all the
+ * removed rows. And it also needs to request for some more rows
+ * to the top.
+ *
+ * 5) Some ranges of rows are removed from view. We need to
+ * collapse the gaps with existing rows and load the missing
+ * rows.
+ *
+ * 6) The ultimate use case! Grid has 1.5 pages of rows and
+ * scrolled a bit down. One page of rows is removed. We need to
+ * make sure that new rows are loaded, but not all old slots are
+ * occupied, since the page can't be filled with new row data.
+ * It also needs to be scrolled to the top.
+ *
+ * So, it's easier (and safer) to do the simple thing instead of
+ * taking all the corner cases into account.
+ */
+
+ Map<Object, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners;
+ for (GridValueChangeListener listener : listeners.values()) {
+ listener.removeListener();
+ }
+ listeners.clear();
+ activeRowHandler.activeRange = Range.withLength(0, 0);
+ keyMapper.setActiveRange(Range.withLength(0, 0));
+ keyMapper.indexToItemId.clear();
+ rpc.resetDataAndSize(event.getContainer().size());
+ }
+ }
+ };
+
+ private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper();
+
+ private KeyMapper<Object> columnKeys;
+
+ /* Has client been initialized */
+ private boolean clientInitialized = false;
+
+ private RowReference rowReference;
+ private CellReference cellReference;
+
+ private Set<Object> updatedItemIds = new HashSet<Object>();
+
+ /**
+ * Creates a new data provider using the given container.
+ *
+ * @param container
+ * the container to make available
+ */
+ public RpcDataProviderExtension(Indexed container) {
+ this.container = container;
+ rpc = getRpcProxy(DataProviderRpc.class);
+
+ registerRpc(new DataRequestRpc() {
+ @Override
+ public void requestRows(int firstRow, int numberOfRows,
+ int firstCachedRowIndex, int cacheSize) {
+
+ pushRowData(firstRow, numberOfRows, firstCachedRowIndex,
+ cacheSize);
+ }
+
+ @Override
+ public void setPinned(String key, boolean isPinned) {
+ Object itemId = keyMapper.getItemId(key);
+ if (isPinned) {
+ // Row might already be pinned if it was selected from the
+ // server
+ if (!keyMapper.isPinned(itemId)) {
+ keyMapper.pin(itemId);
+ }
+ } else {
+ keyMapper.unpin(itemId);
+ }
+ }
+ });
+
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container)
+ .addItemSetChangeListener(itemListener);
+ }
+
+ }
+
+ @Override
+ public void beforeClientResponse(boolean initial) {
+ if (initial) {
+ clientInitialized = true;
+
+ /*
+ * Push initial set of rows, assuming Grid will initially be
+ * rendered scrolled to the top and with a decent amount of rows
+ * visible. If this guess is right, initial data can be shown
+ * without a round-trip and if it's wrong, the data will simply be
+ * discarded.
+ */
+ int size = container.size();
+ rpc.resetDataAndSize(size);
+
+ int numberOfRows = Math.min(40, size);
+ pushRowData(0, numberOfRows, 0, 0);
+ }
+
+ for (Object itemId : updatedItemIds) {
+ internalUpdateRowData(itemId);
+ }
+ updatedItemIds.clear();
+
+ super.beforeClientResponse(initial);
+ }
+
+ private void pushRowData(int firstRowToPush, int numberOfRows,
+ int firstCachedRowIndex, int cacheSize) {
+ Range active = Range.withLength(firstRowToPush, numberOfRows);
+ if (cacheSize != 0) {
+ Range cached = Range.withLength(firstCachedRowIndex, cacheSize);
+ active = active.combineWith(cached);
+ }
+
+ keyMapper.setActiveRange(active);
+
+ List<?> itemIds = container.getItemIds(firstRowToPush, numberOfRows);
+ JsonArray rows = Json.createArray();
+ for (int i = 0; i < itemIds.size(); ++i) {
+ rows.set(i, getRowData(getGrid().getColumns(), itemIds.get(i)));
+ }
+ rpc.setRowData(firstRowToPush, rows);
+
+ activeRowHandler.setActiveRows(active.getStart(), active.length());
+ }
+
+ private JsonValue getRowData(Collection<Column> columns, Object itemId) {
+ Item item = container.getItem(itemId);
+
+ JsonObject rowData = Json.createObject();
+
+ Grid grid = getGrid();
+
+ for (Column column : columns) {
+ Object propertyId = column.getPropertyId();
+
+ Object propertyValue = item.getItemProperty(propertyId).getValue();
+ JsonValue encodedValue = encodeValue(propertyValue,
+ column.getRenderer(), column.getConverter(),
+ grid.getLocale());
+
+ rowData.put(columnKeys.key(propertyId), encodedValue);
+ }
+
+ final JsonObject rowObject = Json.createObject();
+ rowObject.put(GridState.JSONKEY_DATA, rowData);
+ rowObject.put(GridState.JSONKEY_ROWKEY, keyMapper.getKey(itemId));
+
+ rowReference.set(itemId);
+
+ CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator();
+ if (cellStyleGenerator != null) {
+ setGeneratedCellStyles(cellStyleGenerator, rowObject, columns);
+ }
+ RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator();
+ if (rowStyleGenerator != null) {
+ setGeneratedRowStyles(rowStyleGenerator, rowObject);
+ }
+
+ return rowObject;
+ }
+
+ private void setGeneratedCellStyles(CellStyleGenerator generator,
+ JsonObject rowObject, Collection<Column> columns) {
+ JsonObject cellStyles = null;
+ for (Column column : columns) {
+ Object propertyId = column.getPropertyId();
+ cellReference.set(propertyId);
+ String style = generator.getStyle(cellReference);
+ if (style != null) {
+ if (cellStyles == null) {
+ cellStyles = Json.createObject();
+ }
+
+ String columnKey = columnKeys.key(propertyId);
+ cellStyles.put(columnKey, style);
+ }
+ }
+ if (cellStyles != null) {
+ rowObject.put(GridState.JSONKEY_CELLSTYLES, cellStyles);
+ }
+
+ }
+
+ private void setGeneratedRowStyles(RowStyleGenerator generator,
+ JsonObject rowObject) {
+ String rowStyle = generator.getStyle(rowReference);
+ if (rowStyle != null) {
+ rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle);
+ }
+ }
+
+ /**
+ * Makes the data source available to the given {@link Grid} component.
+ *
+ * @param component
+ * the remote data grid component to extend
+ */
+ public void extend(Grid component, KeyMapper<Object> columnKeys) {
+ this.columnKeys = columnKeys;
+ super.extend(component);
+ }
+
+ /**
+ * Informs the client side that new rows have been inserted into the data
+ * source.
+ *
+ * @param index
+ * the index at which new rows have been inserted
+ * @param count
+ * the number of rows inserted at <code>index</code>
+ */
+ private void insertRowData(int index, int count) {
+ if (clientInitialized) {
+ rpc.insertRowData(index, count);
+ }
+
+ activeRowHandler.insertRows(index, count);
+ }
+
+ /**
+ * Informs the client side that rows have been removed from the data source.
+ *
+ * @param firstIndex
+ * the index of the first row removed
+ * @param count
+ * the number of rows removed
+ * @param firstItemId
+ * the item id of the first removed item
+ */
+ private void removeRowData(int firstIndex, int count) {
+ if (clientInitialized) {
+ rpc.removeRowData(firstIndex, count);
+ }
+
+ activeRowHandler.removeRows(firstIndex, count);
+ }
+
+ /**
+ * Informs the client side that data of a row has been modified in the data
+ * source.
+ *
+ * @param itemId
+ * the item Id the row that was updated
+ */
+ public void updateRowData(Object itemId) {
+ if (updatedItemIds.isEmpty()) {
+ // At least one new item will be updated. Mark as dirty to actually
+ // update before response to client.
+ markAsDirty();
+ }
+
+ updatedItemIds.add(itemId);
+ }
+
+ private void internalUpdateRowData(Object itemId) {
+ int index = container.indexOfId(itemId);
+ if (index >= 0) {
+ JsonValue row = getRowData(getGrid().getColumns(), itemId);
+ JsonArray rowArray = Json.createArray();
+ rowArray.set(0, row);
+ rpc.setRowData(index, rowArray);
+ }
+ }
+
+ /**
+ * Pushes a new version of all the rows in the active cache range.
+ */
+ public void refreshCache() {
+ if (!clientInitialized) {
+ return;
+ }
+
+ int firstRow = activeRowHandler.activeRange.getStart();
+ int numberOfRows = activeRowHandler.activeRange.length();
+
+ pushRowData(firstRow, numberOfRows, firstRow, numberOfRows);
+ }
+
+ @Override
+ public void setParent(ClientConnector parent) {
+ super.setParent(parent);
+ if (parent == null) {
+ // We're detached, release various listeners
+
+ activeRowHandler
+ .removeValueChangeListeners(activeRowHandler.activeRange);
+
+ if (container instanceof ItemSetChangeNotifier) {
+ ((ItemSetChangeNotifier) container)
+ .removeItemSetChangeListener(itemListener);
+ }
+
+ } else if (parent instanceof Grid) {
+ Grid grid = (Grid) parent;
+ rowReference = new RowReference(grid);
+ cellReference = new CellReference(rowReference);
+ } else {
+ throw new IllegalStateException(
+ "Grid is the only accepted parent type");
+ }
+ }
+
+ /**
+ * Informs this data provider that given columns have been removed from
+ * grid.
+ *
+ * @param removedColumns
+ * a list of removed columns
+ */
+ public void columnsRemoved(List<Column> removedColumns) {
+ activeRowHandler.columnsRemoved(removedColumns);
+ }
+
+ /**
+ * Informs this data provider that given columns have been added to grid.
+ *
+ * @param addedColumns
+ * a list of added columns
+ */
+ public void columnsAdded(List<Column> addedColumns) {
+ activeRowHandler.columnsAdded(addedColumns);
+ }
+
+ public DataProviderKeyMapper getKeyMapper() {
+ return keyMapper;
+ }
+
+ protected Grid getGrid() {
+ return (Grid) getParent();
+ }
+
+ /**
+ * Converts and encodes the given data model property value using the given
+ * converter and renderer. This method is public only for testing purposes.
+ *
+ * @param renderer
+ * the renderer to use
+ * @param converter
+ * the converter to use
+ * @param modelValue
+ * the value to convert and encode
+ * @param locale
+ * the locale to use in conversion
+ * @return an encoded value ready to be sent to the client
+ */
+ public static <T> JsonValue encodeValue(Object modelValue,
+ Renderer<T> renderer, Converter<?, ?> converter, Locale locale) {
+ Class<T> presentationType = renderer.getPresentationType();
+ T presentationValue;
+
+ if (converter == null) {
+ try {
+ presentationValue = presentationType.cast(modelValue);
+ } catch (ClassCastException e) {
+ ConversionException ee = new Converter.ConversionException(
+ "Unable to convert value of type "
+ + modelValue.getClass().getName()
+ + " to presentation type "
+ + presentationType.getName()
+ + ". No converter is set and the types are not compatible.");
+ if (presentationType == String.class) {
+ // We don't want to throw an exception for the default cause
+ // when one column can't be rendered. Just log the exception
+ // and let the column be empty
+ presentationValue = (T) "";
+ getLogger().log(Level.SEVERE, ee.getMessage(), ee);
+ } else {
+ throw ee;
+ }
+ }
+ } else {
+ assert presentationType.isAssignableFrom(converter
+ .getPresentationType());
+ @SuppressWarnings("unchecked")
+ Converter<T, Object> safeConverter = (Converter<T, Object>) converter;
+ presentationValue = safeConverter.convertToPresentation(modelValue,
+ safeConverter.getPresentationType(), locale);
+ }
+
+ JsonValue encodedValue = renderer.encode(presentationValue);
+
+ return encodedValue;
+ }
+
+ private static Logger getLogger() {
+ return Logger.getLogger(RpcDataProviderExtension.class.getName());
+ }
+
+}
diff --git a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
index 4790d786e7..069cb2e153 100644
--- a/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
+++ b/server/src/com/vaadin/data/fieldgroup/FieldGroup.java
@@ -344,7 +344,8 @@ public class FieldGroup implements Serializable {
.getWrappedProperty();
}
- if (fieldDataSource == getItemProperty(propertyId)) {
+ if (getItemDataSource() != null
+ && fieldDataSource == getItemProperty(propertyId)) {
if (null != wrapper) {
wrapper.detachFromProperty();
}
diff --git a/server/src/com/vaadin/data/sort/Sort.java b/server/src/com/vaadin/data/sort/Sort.java
new file mode 100644
index 0000000000..81e0d08c73
--- /dev/null
+++ b/server/src/com/vaadin/data/sort/Sort.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2000-2014 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.data.sort;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.vaadin.shared.data.sort.SortDirection;
+
+/**
+ * Fluid Sort API. Provides a convenient, human-readable way of specifying
+ * multi-column sort order.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class Sort implements Serializable {
+
+ private final Sort previous;
+ private final SortOrder order;
+
+ /**
+ * Initial constructor, called by the static by() methods.
+ *
+ * @param propertyId
+ * a property ID, corresponding to a property in the data source
+ * @param direction
+ * a sort direction value
+ */
+ private Sort(Object propertyId, SortDirection direction) {
+ previous = null;
+ order = new SortOrder(propertyId, direction);
+ }
+
+ /**
+ * Chaining constructor, called by the non-static then() methods. This
+ * constructor links to the previous Sort object.
+ *
+ * @param previous
+ * the sort marker that comes before this one
+ * @param propertyId
+ * a property ID, corresponding to a property in the data source
+ * @param direction
+ * a sort direction value
+ */
+ private Sort(Sort previous, Object propertyId, SortDirection direction) {
+ this.previous = previous;
+ order = new SortOrder(propertyId, direction);
+
+ Sort s = previous;
+ while (s != null) {
+ if (s.order.getPropertyId() == propertyId) {
+ throw new IllegalStateException(
+ "Can not sort along the same property (" + propertyId
+ + ") twice!");
+ }
+ s = s.previous;
+ }
+
+ }
+
+ /**
+ * Start building a Sort order by sorting a provided column in ascending
+ * order.
+ *
+ * @param propertyId
+ * a property id, corresponding to a data source property
+ * @return a sort object
+ */
+ public static Sort by(Object propertyId) {
+ return by(propertyId, SortDirection.ASCENDING);
+ }
+
+ /**
+ * Start building a Sort order by sorting a provided column.
+ *
+ * @param propertyId
+ * a property id, corresponding to a data source property
+ * @param direction
+ * a sort direction value
+ * @return a sort object
+ */
+ public static Sort by(Object propertyId, SortDirection direction) {
+ return new Sort(propertyId, direction);
+ }
+
+ /**
+ * Continue building a Sort order. The provided property is sorted in
+ * ascending order if the previously added properties have been evaluated as
+ * equals.
+ *
+ * @param propertyId
+ * a property id, corresponding to a data source property
+ * @return a sort object
+ */
+ public Sort then(Object propertyId) {
+ return then(propertyId, SortDirection.ASCENDING);
+ }
+
+ /**
+ * Continue building a Sort order. The provided property is sorted in
+ * specified order if the previously added properties have been evaluated as
+ * equals.
+ *
+ * @param propertyId
+ * a property id, corresponding to a data source property
+ * @param direction
+ * a sort direction value
+ * @return a sort object
+ */
+ public Sort then(Object propertyId, SortDirection direction) {
+ return new Sort(this, propertyId, direction);
+ }
+
+ /**
+ * Build a sort order list, ready to be passed to Grid
+ *
+ * @return a sort order list.
+ */
+ public List<SortOrder> build() {
+
+ int count = 1;
+ Sort s = this;
+ while (s.previous != null) {
+ s = s.previous;
+ ++count;
+ }
+
+ List<SortOrder> order = new ArrayList<SortOrder>(count);
+
+ s = this;
+ do {
+ order.add(0, s.order);
+ s = s.previous;
+ } while (s != null);
+
+ return order;
+ }
+}
diff --git a/server/src/com/vaadin/data/sort/SortOrder.java b/server/src/com/vaadin/data/sort/SortOrder.java
new file mode 100644
index 0000000000..2c419f88b7
--- /dev/null
+++ b/server/src/com/vaadin/data/sort/SortOrder.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2000-2014 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.data.sort;
+
+import java.io.Serializable;
+
+import com.vaadin.shared.data.sort.SortDirection;
+
+/**
+ * Sort order descriptor. Links together a {@link SortDirection} value and a
+ * Vaadin container property ID.
+ *
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public class SortOrder implements Serializable {
+
+ private final Object propertyId;
+ private final SortDirection direction;
+
+ /**
+ * Create a SortOrder object. Both arguments must be non-null.
+ *
+ * @param propertyId
+ * id of the data source property to sort by
+ * @param direction
+ * value indicating whether the property id should be sorted in
+ * ascending or descending order
+ */
+ public SortOrder(Object propertyId, SortDirection direction) {
+ if (propertyId == null) {
+ throw new IllegalArgumentException("Property ID can not be null!");
+ }
+ if (direction == null) {
+ throw new IllegalArgumentException(
+ "Direction value can not be null!");
+ }
+ this.propertyId = propertyId;
+ this.direction = direction;
+ }
+
+ /**
+ * Returns the property ID.
+ *
+ * @return a property ID
+ */
+ public Object getPropertyId() {
+ return propertyId;
+ }
+
+ /**
+ * Returns the {@link SortDirection} value.
+ *
+ * @return a sort direction value
+ */
+ public SortDirection getDirection() {
+ return direction;
+ }
+
+ @Override
+ public String toString() {
+ return propertyId + " " + direction;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + direction.hashCode();
+ result = prime * result + propertyId.hashCode();
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (obj == null) {
+ return false;
+ } else if (getClass() != obj.getClass()) {
+ return false;
+ }
+
+ SortOrder other = (SortOrder) obj;
+ if (direction != other.direction) {
+ return false;
+ } else if (!propertyId.equals(other.propertyId)) {
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/server/src/com/vaadin/data/util/AbstractBeanContainer.java b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
index fad0934e53..6dcfbb2b84 100644
--- a/server/src/com/vaadin/data/util/AbstractBeanContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractBeanContainer.java
@@ -226,6 +226,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
@Override
public boolean removeAllItems() {
int origSize = size();
+ IDTYPE firstItem = getFirstVisibleItem();
internalRemoveAllItems();
@@ -238,7 +239,7 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
// fire event only if the visible view changed, regardless of whether
// filtered out items were removed or not
if (origSize != 0) {
- fireItemSetChange();
+ fireItemsRemoved(0, firstItem, origSize);
}
return true;
@@ -683,6 +684,8 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
protected void addAll(Collection<? extends BEANTYPE> collection)
throws IllegalStateException, IllegalArgumentException {
boolean modified = false;
+ int origSize = size();
+
for (BEANTYPE bean : collection) {
// TODO skipping invalid beans - should not allow them in javadoc?
if (bean == null
@@ -703,13 +706,22 @@ public abstract class AbstractBeanContainer<IDTYPE, BEANTYPE> extends
if (modified) {
// Filter the contents when all items have been added
if (isFiltered()) {
- filterAll();
- } else {
- fireItemSetChange();
+ doFilterContainer(!getFilters().isEmpty());
+ }
+ if (visibleNewItemsWasAdded(origSize)) {
+ // fire event about added items
+ int firstPosition = origSize;
+ IDTYPE firstItemId = getVisibleItemIds().get(firstPosition);
+ int affectedItems = size() - origSize;
+ fireItemsAdded(firstPosition, firstItemId, affectedItems);
}
}
}
+ private boolean visibleNewItemsWasAdded(int origSize) {
+ return size() > origSize;
+ }
+
/**
* Use the bean resolver to get the identifier for a bean.
*
diff --git a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
index 27168694e6..f7b1a4b0d8 100644
--- a/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
+++ b/server/src/com/vaadin/data/util/AbstractInMemoryContainer.java
@@ -15,8 +15,10 @@
*/
package com.vaadin.data.util;
+import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
+import java.util.EventObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -146,6 +148,87 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
}
}
+ private static abstract class BaseItemAddOrRemoveEvent extends EventObject
+ implements Serializable {
+ protected Object itemId;
+ protected int index;
+ protected int count;
+
+ public BaseItemAddOrRemoveEvent(Container source, Object itemId,
+ int index, int count) {
+ super(source);
+ this.itemId = itemId;
+ this.index = index;
+ this.count = count;
+ }
+
+ public Container getContainer() {
+ return (Container) getSource();
+ }
+
+ public Object getFirstItemId() {
+ return itemId;
+ }
+
+ public int getFirstIndex() {
+ return index;
+ }
+
+ public int getAffectedItemsCount() {
+ return count;
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the added
+ * items.
+ *
+ * <p>
+ * This class provides information about the first added item and the number
+ * of added items.
+ * </p>
+ *
+ * @since 7.4
+ */
+ protected static class BaseItemAddEvent extends BaseItemAddOrRemoveEvent
+ implements Container.Indexed.ItemAddEvent {
+
+ public BaseItemAddEvent(Container source, Object itemId, int index,
+ int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getAddedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
+ /**
+ * An <code>Event</code> object specifying information about the removed
+ * items.
+ *
+ * <p>
+ * This class provides information about the first removed item and the
+ * number of removed items.
+ * </p>
+ *
+ * @since 7.4
+ */
+ protected static class BaseItemRemoveEvent extends BaseItemAddOrRemoveEvent
+ implements Container.Indexed.ItemRemoveEvent {
+
+ public BaseItemRemoveEvent(Container source, Object itemId, int index,
+ int count) {
+ super(source, itemId, index, count);
+ }
+
+ @Override
+ public int getRemovedItemsCount() {
+ return getAffectedItemsCount();
+ }
+ }
+
/**
* Get an item even if filtered out.
*
@@ -897,33 +980,45 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
/**
* Notify item set change listeners that an item has been added to the
* container.
- * <p>
- * Unless subclasses specify otherwise, the default notification indicates a
- * full refresh.
*
* @since 7.4
*
- * @param postion
- * position of the added item in the view (if visible)
+ * @param position
+ * position of the added item in the view
* @param itemId
* id of the added item
* @param item
* the added item
*/
protected void fireItemAdded(int position, ITEMIDTYPE itemId, ITEMCLASS item) {
- fireItemSetChange();
+ fireItemsAdded(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been added to the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible added item in the view
+ * @param firstItemId
+ * id of the first visible added item
+ * @param numberOfItems
+ * the number of visible added items
+ */
+ protected void fireItemsAdded(int firstPosition, ITEMIDTYPE firstItemId,
+ int numberOfItems) {
+ BaseItemAddEvent addEvent = new BaseItemAddEvent(this, firstItemId,
+ firstPosition, numberOfItems);
+ fireItemSetChange(addEvent);
}
/**
* Notify item set change listeners that an item has been removed from the
* container.
- * <p>
- * Unless subclasses specify otherwise, the default notification indicates a
- * full refresh.
*
* @since 7.4
*
- * @param postion
+ * @param position
* position of the removed item in the view prior to removal (if
* was visible)
* @param itemId
@@ -931,7 +1026,28 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
* {@link Container#removeItem(Object)} API
*/
protected void fireItemRemoved(int position, Object itemId) {
- fireItemSetChange();
+ fireItemsRemoved(position, itemId, 1);
+ }
+
+ /**
+ * Notify item set change listeners that items has been removed from the
+ * container.
+ *
+ * @param firstPosition
+ * position of the first visible removed item in the view prior
+ * to removal
+ * @param firstItemId
+ * id of the first visible removed item, of type {@link Object}
+ * to satisfy {@link Container#removeItem(Object)} API
+ * @param numberOfItems
+ * the number of removed visible items
+ *
+ */
+ protected void fireItemsRemoved(int firstPosition, Object firstItemId,
+ int numberOfItems) {
+ BaseItemRemoveEvent removeEvent = new BaseItemRemoveEvent(this,
+ firstItemId, firstPosition, numberOfItems);
+ fireItemSetChange(removeEvent);
}
// visible and filtered item identifier lists
@@ -950,6 +1066,23 @@ public abstract class AbstractInMemoryContainer<ITEMIDTYPE, PROPERTYIDCLASS, ITE
}
/**
+ * Returns the item id of the first visible item after filtering. 'Null' is
+ * returned if there is no visible items.
+ * <p>
+ * For internal use only.
+ *
+ * @since 7.4
+ *
+ * @return item id of the first visible item
+ */
+ protected ITEMIDTYPE getFirstVisibleItem() {
+ if (!getVisibleItemIds().isEmpty()) {
+ return getVisibleItemIds().get(0);
+ }
+ return null;
+ }
+
+ /**
* Returns true is the container has active filters.
*
* @return true if the container is currently filtered
diff --git a/server/src/com/vaadin/data/util/GeneratedPropertyContainer.java b/server/src/com/vaadin/data/util/GeneratedPropertyContainer.java
new file mode 100644
index 0000000000..fd2ce609b8
--- /dev/null
+++ b/server/src/com/vaadin/data/util/GeneratedPropertyContainer.java
@@ -0,0 +1,724 @@
+/*
+ * Copyright 2000-2014 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.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.google.gwt.thirdparty.guava.common.collect.Sets;
+import com.vaadin.data.Container;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.sort.SortOrder;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+import com.vaadin.shared.data.sort.SortDirection;
+
+/**
+ * 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
+ */
+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
+ */
+ 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.
+ */
+ protected 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<?> wrappedProperties = asSet(wrappedItem.getItemPropertyIds());
+ return Sets.union(
+ Sets.difference(wrappedProperties, removedProperties),
+ propertyGenerators.keySet());
+ }
+
+ @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");
+ }
+ };
+
+ /**
+ * 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.
+ */
+ 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;
+ }
+ };
+
+ 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();
+ }
+ }
+
+ 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
+ */
+ 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);
+ }
+
+ private static <T> LinkedHashSet<T> asSet(Collection<T> collection) {
+ if (collection instanceof LinkedHashSet) {
+ return (LinkedHashSet<T>) collection;
+ } else {
+ return new LinkedHashSet<T>(collection);
+ }
+ }
+
+ /* 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) {
+ 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) {
+ new UnsupportedOperationException(
+ "Wrapped container is not Sortable");
+ }
+
+ 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);
+ return createGeneratedPropertyItem(newItemId, item);
+ }
+
+ @Override
+ public Item addItem(Object itemId) throws UnsupportedOperationException {
+ Item item = wrappedContainer.addItem(itemId);
+ return createGeneratedPropertyItem(itemId, item);
+ }
+
+ @Override
+ public Item addItemAt(int index, Object newItemId)
+ throws UnsupportedOperationException {
+ Item item = wrappedContainer.addItemAt(index, newItemId);
+ return createGeneratedPropertyItem(newItemId, item);
+ }
+
+ @Override
+ public Item getItem(Object itemId) {
+ Item item = wrappedContainer.getItem(itemId);
+ 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<?> wrappedProperties = asSet(wrappedContainer
+ .getContainerPropertyIds());
+ return Sets.union(
+ Sets.difference(wrappedProperties, removedProperties),
+ propertyGenerators.keySet());
+ }
+
+ /**
+ * 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;
+ }
+}
diff --git a/server/src/com/vaadin/data/util/IndexedContainer.java b/server/src/com/vaadin/data/util/IndexedContainer.java
index 68960335d7..b851baf674 100644
--- a/server/src/com/vaadin/data/util/IndexedContainer.java
+++ b/server/src/com/vaadin/data/util/IndexedContainer.java
@@ -226,6 +226,7 @@ public class IndexedContainer extends
@Override
public boolean removeAllItems() {
int origSize = size();
+ Object firstItem = getFirstVisibleItem();
internalRemoveAllItems();
@@ -235,16 +236,17 @@ public class IndexedContainer extends
// filtered out items were removed or not
if (origSize != 0) {
// Sends a change event
- fireItemSetChange();
+ fireItemsRemoved(0, firstItem, origSize);
}
return true;
}
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.data.Container#addItem()
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
*/
@Override
public Object addItem() {
@@ -362,10 +364,11 @@ public class IndexedContainer extends
new IndexedContainerItem(newItemId), true);
}
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.data.Container.Ordered#addItemAfter(java.lang.Object)
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
*/
@Override
public Object addItemAfter(Object previousItemId) {
@@ -391,10 +394,11 @@ public class IndexedContainer extends
newItemId), true);
}
- /*
- * (non-Javadoc)
- *
- * @see com.vaadin.data.Container.Indexed#addItemAt(int)
+ /**
+ * {@inheritDoc}
+ * <p>
+ * The item ID is generated from a sequence of Integers. The id of the first
+ * added item is 1.
*/
@Override
public Object addItemAt(int index) {
@@ -620,8 +624,7 @@ public class IndexedContainer extends
@Override
protected void fireItemAdded(int position, Object itemId, Item item) {
if (position >= 0) {
- fireItemSetChange(new IndexedContainer.ItemSetChangeEvent(this,
- position));
+ super.fireItemAdded(position, itemId, item);
}
}
@@ -1211,4 +1214,5 @@ public class IndexedContainer extends
public Collection<Filter> getContainerFilters() {
return super.getContainerFilters();
}
+
}
diff --git a/server/src/com/vaadin/data/util/PropertyValueGenerator.java b/server/src/com/vaadin/data/util/PropertyValueGenerator.java
new file mode 100644
index 0000000000..453e45b1db
--- /dev/null
+++ b/server/src/com/vaadin/data/util/PropertyValueGenerator.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2000-2014 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.data.util;
+
+import java.io.Serializable;
+
+import com.vaadin.data.Container.Filter;
+import com.vaadin.data.Item;
+import com.vaadin.data.Property;
+import com.vaadin.data.sort.SortOrder;
+import com.vaadin.data.util.filter.UnsupportedFilterException;
+
+/**
+ * PropertyValueGenerator for GeneratedPropertyContainer.
+ *
+ * @param <T>
+ * Property data type
+ * @since 7.4
+ * @author Vaadin Ltd
+ */
+public abstract class PropertyValueGenerator<T> implements Serializable {
+
+ /**
+ * Returns value for given Item. Used by GeneratedPropertyContainer when
+ * generating new properties.
+ *
+ * @param item
+ * currently handled item
+ * @param itemId
+ * item id for currently handled item
+ * @param propertyId
+ * id for this property
+ * @return generated value
+ */
+ public abstract T getValue(Item item, Object itemId, Object propertyId);
+
+ /**
+ * Return Property type for this generator. This function is called when
+ * {@link Property#getType()} is called for generated property.
+ *
+ * @return type of generated property
+ */
+ public abstract Class<T> getType();
+
+ /**
+ * Translates sorting of the generated property in a specific direction to a
+ * set of property ids and directions in the underlying container.
+ *
+ * SortOrder is similar to (or the same as) the SortOrder already defined
+ * for Grid.
+ *
+ * The default implementation of this method returns an empty array, which
+ * means that the property will not be included in
+ * getSortableContainerPropertyIds(). Attempting to sort by that column
+ * throws UnsupportedOperationException.
+ *
+ * Returning null is not allowed.
+ *
+ * @param order
+ * a sort order for this property
+ * @return an array of sort orders describing how this property is sorted
+ */
+ public SortOrder[] getSortProperties(SortOrder order) {
+ return new SortOrder[] {};
+ }
+
+ /**
+ * Return an updated filter that should be compatible with the underlying
+ * container.
+ *
+ * This function is called when setting a filter for this generated
+ * property. Returning null from this function causes
+ * GeneratedPropertyContainer to discard the filter and not use it.
+ *
+ * By default this function throws UnsupportedFilterException.
+ *
+ * @param filter
+ * original filter for this property
+ * @return modified filter that is compatible with the underlying container
+ * @throws UnsupportedFilterException
+ */
+ public Filter modifyFilter(Filter filter) throws UnsupportedFilterException {
+ throw new UnsupportedFilterException("Filter" + filter
+ + " is not supported");
+ }
+
+}