summaryrefslogtreecommitdiffstats
path: root/server/src/com/vaadin/data/RpcDataProviderExtension.java
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/com/vaadin/data/RpcDataProviderExtension.java')
-rw-r--r--server/src/com/vaadin/data/RpcDataProviderExtension.java958
1 files changed, 165 insertions, 793 deletions
diff --git a/server/src/com/vaadin/data/RpcDataProviderExtension.java b/server/src/com/vaadin/data/RpcDataProviderExtension.java
index b3c7972b52..78c87ab23d 100644
--- a/server/src/com/vaadin/data/RpcDataProviderExtension.java
+++ b/server/src/com/vaadin/data/RpcDataProviderExtension.java
@@ -23,14 +23,9 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Set;
-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.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
import com.google.gwt.thirdparty.guava.common.collect.Maps;
import com.google.gwt.thirdparty.guava.common.collect.Sets;
@@ -43,31 +38,23 @@ 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.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.DetailsConnectorChange;
import com.vaadin.shared.ui.grid.GridClientRpc;
import com.vaadin.shared.ui.grid.GridState;
import com.vaadin.shared.ui.grid.Range;
-import com.vaadin.shared.util.SharedUtil;
import com.vaadin.ui.Component;
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.DetailsGenerator;
import com.vaadin.ui.Grid.RowReference;
-import com.vaadin.ui.Grid.RowStyleGenerator;
-import com.vaadin.ui.renderers.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
@@ -82,445 +69,84 @@ import elemental.json.JsonValue;
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.
+ * Class for keeping track of current items and ValueChangeListeners.
+ *
+ * @since 7.6
*/
- public class DataProviderKeyMapper implements Serializable {
- private final BiMap<Object, String> itemIdToKey = HashBiMap.create();
- private Set<Object> pinnedItemIds = new HashSet<Object>();
- private long rollingIndex = 0;
-
- private DataProviderKeyMapper() {
- // private implementation
- }
-
- /**
- * Sets the currently active rows. This will purge any unpinned rows
- * from cache.
- *
- * @param itemIds
- * collection of itemIds to map to row keys
- */
- void setActiveRows(Collection<?> itemIds) {
- Set<Object> itemSet = new HashSet<Object>(itemIds);
- Set<Object> itemsRemoved = new HashSet<Object>();
- for (Object itemId : itemIdToKey.keySet()) {
- if (!itemSet.contains(itemId) && !isPinned(itemId)) {
- itemsRemoved.add(itemId);
- }
- }
-
- for (Object itemId : itemsRemoved) {
- detailComponentManager.destroyDetails(itemId);
- itemIdToKey.remove(itemId);
- }
-
- for (Object itemId : itemSet) {
- itemIdToKey.put(itemId, getKey(itemId));
- if (visibleDetails.contains(itemId)) {
- detailComponentManager.createDetails(itemId,
- indexOf(itemId));
- }
- }
- }
-
- private String nextKey() {
- return String.valueOf(rollingIndex++);
- }
+ private class ActiveItemHandler implements Serializable, DataGenerator {
- /**
- * Gets the key for a given item id. Creates a new key mapping if no
- * existing mapping was found for the given item id.
- *
- * @since 7.5.0
- * @param itemId
- * the item id to get the key for
- * @return the key for the given item id
- */
- public String getKey(Object itemId) {
- String key = itemIdToKey.get(itemId);
- if (key == null) {
- key = nextKey();
- itemIdToKey.put(itemId, key);
- }
- return key;
- }
+ private final Map<Object, GridValueChangeListener> activeItemMap = new HashMap<Object, GridValueChangeListener>();
+ private final KeyMapper<Object> keyMapper = new KeyMapper<Object>();
+ private final Set<Object> droppedItems = new HashSet<Object>();
/**
- * Gets keys for a collection of item ids.
+ * Registers ValueChangeListeners for given item ids.
* <p>
- * If the itemIds are currently cached, the existing keys will be used.
- * Otherwise new ones will be created.
+ * Note: This method will clean up any unneeded listeners and key
+ * mappings
*
* @param itemIds
- * the item ids for which to get keys
- * @return keys for the {@code itemIds}
+ * collection of new active item ids
*/
- 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());
+ public void addActiveItems(Collection<?> itemIds) {
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");
+ if (!activeItemMap.containsKey(itemId)) {
+ activeItemMap.put(itemId, new GridValueChangeListener(
+ itemId, container.getItem(itemId)));
+ }
}
- pinnedItemIds.remove(itemId);
+ // Remove still active rows that were "dropped"
+ droppedItems.removeAll(itemIds);
+ internalDropActiveItems(droppedItems);
+ droppedItems.clear();
}
/**
- * Checks whether an item id is pinned or not.
+ * Marks given item id as dropped. Dropped items are cleared when adding
+ * new active items.
*
* @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);
- }
- }
-
- /**
- * 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 index to the value change listener used for all of column
- * properties
- */
- private final Map<Integer, GridValueChangeListener> valueChangeListeners = new HashMap<Integer, 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(Range newActiveRange) {
-
- // 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 (Integer i = range.getStart(); i < range.getEnd(); i++) {
-
- final Object itemId = container.getIdByIndex(i);
- final Item item = container.getItem(itemId);
-
- assert valueChangeListeners.get(i) == null : "Overwriting existing listener";
-
- GridValueChangeListener listener = new GridValueChangeListener(
- itemId, item);
- valueChangeListeners.put(i, listener);
- }
- }
-
- private void removeValueChangeListeners(Range range) {
- for (Integer i = range.getStart(); i < range.getEnd(); i++) {
- final GridValueChangeListener listener = valueChangeListeners
- .remove(i);
-
- assert listener != null : "Trying to remove nonexisting listener";
-
- 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
+ * dropped item id
*/
- public void columnsRemoved(Collection<Column> removedColumns) {
- if (removedColumns.isEmpty()) {
- return;
- }
-
- for (GridValueChangeListener listener : valueChangeListeners
- .values()) {
- listener.removeColumns(removedColumns);
+ public void dropActiveItem(Object itemId) {
+ if (activeItemMap.containsKey(itemId)) {
+ droppedItems.add(itemId);
}
}
- /**
- * 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;
- }
+ private void internalDropActiveItems(Collection<Object> itemIds) {
+ for (Object itemId : droppedItems) {
+ assert activeItemMap.containsKey(itemId) : "Item ID should exist in the activeItemMap";
- for (GridValueChangeListener listener : valueChangeListeners
- .values()) {
- listener.addColumns(addedColumns);
+ activeItemMap.remove(itemId).removeListener();
+ keyMapper.remove(itemId);
}
}
/**
- * 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>
+ * Gets a collection copy of currently active item ids.
*
- * @param firstIndex
- * the index of the first inserted rows
- * @param count
- * the number of rows inserted at <code>firstIndex</code>
+ * @return collection of item ids
*/
- public void insertRows(int firstIndex, int count) {
- if (firstIndex < activeRange.getStart()) {
- moveListeners(activeRange, count);
- activeRange = activeRange.offsetBy(count);
- } else if (firstIndex < activeRange.getEnd()) {
- int end = activeRange.getEnd();
- // Move rows from first added index by count
- Range movedRange = Range.between(firstIndex, end);
- moveListeners(movedRange, count);
- // Remove excess listeners from extra rows
- removeValueChangeListeners(Range.withLength(end, count));
- // Add listeners for new rows
- final Range freshRange = Range.withLength(firstIndex, count);
- addValueChangeListeners(freshRange);
- } else {
- // out of view, noop
- }
+ public Collection<Object> getActiveItemIds() {
+ return new HashSet<Object>(activeItemMap.keySet());
}
/**
- * 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>
+ * Gets a collection copy of currently active ValueChangeListeners.
*
- * @param firstIndex
- * the index of the first removed rows
- * @param count
- * the number of rows removed at <code>firstIndex</code>
+ * @return collection of value change listeners
*/
- public void removeRows(int firstIndex, int count) {
- Range removed = Range.withLength(firstIndex, count);
- if (removed.intersects(activeRange)) {
- final Range[] deprecated = activeRange.partitionWith(removed);
- // Remove the listeners that are no longer existing
- removeValueChangeListeners(deprecated[1]);
-
- // Move remaining listeners to fill the listener map correctly
- moveListeners(deprecated[2], -deprecated[1].length());
- activeRange = Range.withLength(activeRange.getStart(),
- activeRange.length() - deprecated[1].length());
-
- } else {
- if (removed.getEnd() < activeRange.getStart()) {
- /* firstIndex < lastIndex < start */
- moveListeners(activeRange, -count);
- activeRange = activeRange.offsetBy(-count);
- }
- /* else: end <= firstIndex, no need to do anything */
- }
+ public Collection<GridValueChangeListener> getValueChangeListeners() {
+ return new HashSet<GridValueChangeListener>(activeItemMap.values());
}
- /**
- * Moves value change listeners in map with given index range by count
- */
- private void moveListeners(Range movedRange, int diff) {
- if (diff < 0) {
- for (Integer i = movedRange.getStart(); i < movedRange.getEnd(); ++i) {
- moveListener(i, i + diff);
- }
- } else if (diff > 0) {
- for (Integer i = movedRange.getEnd() - 1; i >= movedRange
- .getStart(); --i) {
- moveListener(i, i + diff);
- }
- } else {
- // diff == 0 should not happen. If it does, should be no-op
- return;
- }
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ rowData.put(GridState.JSONKEY_ROWKEY, keyMapper.key(itemId));
}
- private void moveListener(Integer oldIndex, Integer newIndex) {
- assert valueChangeListeners.get(newIndex) == null : "Overwriting existing listener";
-
- GridValueChangeListener listener = valueChangeListeners
- .remove(oldIndex);
- assert listener != null : "Moving nonexisting listener.";
- valueChangeListeners.put(newIndex, listener);
- }
}
/**
@@ -601,7 +227,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
* @since 7.5.0
* @author Vaadin Ltd
*/
- public static final class DetailComponentManager implements Serializable {
+ // TODO this should probably be a static nested class
+ public final class DetailComponentManager implements DataGenerator {
/**
* This map represents all the components that have been requested for
* each item id.
@@ -609,9 +236,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
* Normally this map is consistent with what is displayed in the
* component hierarchy (and thus the DOM). The only time this map is out
* of sync with the DOM is between the any calls to
- * {@link #createDetails(Object, int)} or
- * {@link #destroyDetails(Object)}, and
- * {@link GridClientRpc#setDetailsConnectorChanges(Set)}.
+ * {@link #createDetails(Object)} or {@link #destroyDetails(Object)},
+ * and {@link GridClientRpc#setDetailsConnectorChanges(Set)}.
* <p>
* This is easily checked: if {@link #unattachedComponents} is
* {@link Collection#isEmpty() empty}, then this field is consistent
@@ -620,34 +246,11 @@ public class RpcDataProviderExtension extends AbstractExtension {
private final Map<Object, Component> visibleDetailsComponents = Maps
.newHashMap();
- /** A lookup map for which row contains which details component. */
- private BiMap<Integer, Component> rowIndexToDetails = HashBiMap
- .create();
-
- /**
- * A copy of {@link #rowIndexToDetails} from its last stable state. Used
- * for creating a diff against {@link #rowIndexToDetails}.
- *
- * @see #getAndResetConnectorChanges()
- */
- private BiMap<Integer, Component> prevRowIndexToDetails = HashBiMap
- .create();
-
- /**
- * A set keeping track on components that have been created, but not
- * attached. They should be attached at some later point in time.
- * <p>
- * This isn't strictly requried, but it's a handy explicit log. You
- * could find out the same thing by taking out all the other components
- * and checking whether Grid is their parent or not.
- */
- private final Set<Component> unattachedComponents = Sets.newHashSet();
-
/**
* Keeps tabs on all the details that did not get a component during
- * {@link #createDetails(Object, int)}.
+ * {@link #createDetails(Object)}.
*/
- private final Map<Object, Integer> emptyDetails = Maps.newHashMap();
+ private final Set<Object> emptyDetails = Sets.newHashSet();
private Grid grid;
@@ -661,19 +264,16 @@ public class RpcDataProviderExtension extends AbstractExtension {
* the item id for which to create the details component.
* Assumed not <code>null</code> and that a component is not
* currently present for this item previously
- * @param rowIndex
- * the row index for {@code itemId}
* @throws IllegalStateException
* if the current details generator provides a component
* that was manually attached, or if the same instance has
* already been provided
*/
- public void createDetails(Object itemId, int rowIndex)
- throws IllegalStateException {
+ public void createDetails(Object itemId) throws IllegalStateException {
assert itemId != null : "itemId was null";
- Integer newRowIndex = Integer.valueOf(rowIndex);
- if (visibleDetailsComponents.containsKey(itemId)) {
+ if (visibleDetailsComponents.containsKey(itemId)
+ || emptyDetails.contains(itemId)) {
// Don't overwrite existing components
return;
}
@@ -684,58 +284,26 @@ public class RpcDataProviderExtension extends AbstractExtension {
DetailsGenerator detailsGenerator = grid.getDetailsGenerator();
Component details = detailsGenerator.getDetails(rowReference);
if (details != null) {
- String generatorName = detailsGenerator.getClass().getName();
if (details.getParent() != null) {
- throw new IllegalStateException(generatorName
+ String name = detailsGenerator.getClass().getName();
+ throw new IllegalStateException(name
+ " generated a details component that already "
- + "was attached. (itemId: " + itemId + ", row: "
- + rowIndex + ", component: " + details);
- }
-
- if (rowIndexToDetails.containsValue(details)) {
- throw new IllegalStateException(generatorName
- + " provided a details component that already "
- + "exists in Grid. (itemId: " + itemId + ", row: "
- + rowIndex + ", component: " + details);
+ + "was attached. (itemId: " + itemId
+ + ", component: " + details + ")");
}
visibleDetailsComponents.put(itemId, details);
- rowIndexToDetails.put(newRowIndex, details);
- unattachedComponents.add(details);
- assert !emptyDetails.containsKey(itemId) : "Bookeeping thinks "
+ details.setParent(grid);
+ grid.markAsDirty();
+
+ assert !emptyDetails.contains(itemId) : "Bookeeping thinks "
+ "itemId is empty even though we just created a "
+ "component for it (" + itemId + ")";
} else {
- assert assertItemIdHasNotMovedAndNothingIsOverwritten(itemId,
- newRowIndex);
- emptyDetails.put(itemId, newRowIndex);
- }
-
- /*
- * Don't attach the components here. It's done by
- * GridServerRpc.sendDetailsComponents in a separate roundtrip.
- */
- }
-
- private boolean assertItemIdHasNotMovedAndNothingIsOverwritten(
- Object itemId, Integer newRowIndex) {
-
- Integer oldRowIndex = emptyDetails.get(itemId);
- if (!SharedUtil.equals(oldRowIndex, newRowIndex)) {
-
- assert !emptyDetails.containsKey(itemId) : "Unexpected "
- + "change of empty details row index for itemId "
- + itemId + " from " + oldRowIndex + " to "
- + newRowIndex;
-
- assert !emptyDetails.containsValue(newRowIndex) : "Bookkeeping"
- + " already had another itemId for this empty index "
- + "(index: " + newRowIndex + ", new itemId: " + itemId
- + ")";
+ emptyDetails.add(itemId);
}
- return true;
}
/**
@@ -756,8 +324,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
return;
}
- rowIndexToDetails.inverse().remove(removedComponent);
-
removedComponent.setParent(null);
grid.markAsDirty();
}
@@ -773,81 +339,12 @@ public class RpcDataProviderExtension extends AbstractExtension {
public Collection<Component> getComponents() {
Set<Component> components = new HashSet<Component>(
visibleDetailsComponents.values());
- components.removeAll(unattachedComponents);
return components;
}
- /**
- * Gets information on how the connectors have changed.
- * <p>
- * This method only returns the changes that have been made between two
- * calls of this method. I.e. Calling this method once will reset the
- * state for the next state.
- * <p>
- * Used internally by the Grid object.
- *
- * @return information on how the connectors have changed
- */
- public Set<DetailsConnectorChange> getAndResetConnectorChanges() {
- Set<DetailsConnectorChange> changes = new HashSet<DetailsConnectorChange>();
-
- // populate diff with added/changed
- for (Entry<Integer, Component> entry : rowIndexToDetails.entrySet()) {
- Component component = entry.getValue();
- assert component != null : "rowIndexToDetails contains a null component";
-
- Integer newIndex = entry.getKey();
- Integer oldIndex = prevRowIndexToDetails.inverse().get(
- component);
-
- /*
- * only attach components. Detaching already happened in
- * destroyDetails.
- */
- if (newIndex != null && oldIndex == null) {
- assert unattachedComponents.contains(component) : "unattachedComponents does not contain component for index "
- + newIndex + " (" + component + ")";
- component.setParent(grid);
- unattachedComponents.remove(component);
- }
-
- if (!SharedUtil.equals(oldIndex, newIndex)) {
- changes.add(new DetailsConnectorChange(component, oldIndex,
- newIndex, emptyDetails.containsKey(component)));
- }
- }
-
- // populate diff with removed
- for (Entry<Integer, Component> entry : prevRowIndexToDetails
- .entrySet()) {
- Integer oldIndex = entry.getKey();
- Component component = entry.getValue();
- Integer newIndex = rowIndexToDetails.inverse().get(component);
- if (newIndex == null) {
- changes.add(new DetailsConnectorChange(null, oldIndex,
- null, emptyDetails.containsValue(oldIndex)));
- }
- }
-
- // reset diff map
- prevRowIndexToDetails = HashBiMap.create(rowIndexToDetails);
-
- return changes;
- }
-
public void refresh(Object itemId) {
- Component component = visibleDetailsComponents.get(itemId);
- Integer rowIndex = null;
- if (component != null) {
- rowIndex = rowIndexToDetails.inverse().get(component);
- destroyDetails(itemId);
- } else {
- rowIndex = emptyDetails.remove(itemId);
- }
-
- assert rowIndex != null : "Given itemId does not map to an "
- + "existing detail row (" + itemId + ")";
- createDetails(itemId, rowIndex.intValue());
+ destroyDetails(itemId);
+ createDetails(itemId);
}
void setGrid(Grid grid) {
@@ -856,12 +353,29 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
this.grid = grid;
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @since 7.6
+ */
+ @Override
+ public void generateData(Object itemId, Item item, JsonObject rowData) {
+ if (visibleDetails.contains(itemId)) {
+ // Double check to be sure details component exists.
+ detailComponentManager.createDetails(itemId);
+ Component detailsComponent = visibleDetailsComponents
+ .get(itemId);
+ rowData.put(
+ GridState.JSONKEY_DETAILS_VISIBLE,
+ (detailsComponent != null ? detailsComponent
+ .getConnectorId() : ""));
+ }
+ }
}
private final Indexed container;
- private final ActiveRowHandler activeRowHandler = new ActiveRowHandler();
-
private DataProviderRpc rpc;
private final ItemSetChangeListener itemListener = new ItemSetChangeListener() {
@@ -922,22 +436,10 @@ public class RpcDataProviderExtension extends AbstractExtension {
* taking all the corner cases into account.
*/
- Map<Integer, GridValueChangeListener> listeners = activeRowHandler.valueChangeListeners;
- for (GridValueChangeListener listener : listeners.values()) {
- listener.removeListener();
- }
-
- // Wipe clean all details.
- HashSet<Object> detailItemIds = new HashSet<Object>(
- detailComponentManager.visibleDetailsComponents
- .keySet());
- for (Object itemId : detailItemIds) {
+ for (Object itemId : visibleDetails) {
detailComponentManager.destroyDetails(itemId);
}
- listeners.clear();
- activeRowHandler.activeRange = Range.withLength(0, 0);
-
/* Mark as dirty to push changes in beforeClientResponse */
bareItemSetTriggeredSizeChange = true;
markAsDirty();
@@ -945,16 +447,9 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
};
- private final DataProviderKeyMapper keyMapper = new DataProviderKeyMapper();
-
- private KeyMapper<Object> columnKeys;
-
/** RpcDataProvider should send the current cache again. */
private boolean refreshCache = false;
- private RowReference rowReference;
- private CellReference cellReference;
-
/** Set of updated item ids */
private Set<Object> updatedItemIds = new LinkedHashSet<Object>();
@@ -971,10 +466,15 @@ public class RpcDataProviderExtension extends AbstractExtension {
* This map represents all the details that are user-defined as visible.
* This does not reflect the status in the DOM.
*/
- private Set<Object> visibleDetails = new HashSet<Object>();
+ // TODO this should probably be inside DetailComponentManager
+ private final Set<Object> visibleDetails = new HashSet<Object>();
private final DetailComponentManager detailComponentManager = new DetailComponentManager();
+ private final Set<DataGenerator> dataGenerators = new LinkedHashSet<DataGenerator>();
+
+ private final ActiveItemHandler activeItemHandler = new ActiveItemHandler();
+
/**
* Creates a new data provider using the given container.
*
@@ -989,22 +489,15 @@ public class RpcDataProviderExtension extends AbstractExtension {
@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);
+ public void dropRows(JsonArray rowKeys) {
+ for (int i = 0; i < rowKeys.length(); ++i) {
+ activeItemHandler.dropActiveItem(getKeyMapper().get(
+ rowKeys.getString(i)));
}
}
});
@@ -1014,6 +507,8 @@ public class RpcDataProviderExtension extends AbstractExtension {
.addItemSetChangeListener(itemListener);
}
+ addDataGenerator(activeItemHandler);
+ addDataGenerator(detailComponentManager);
}
/**
@@ -1045,16 +540,11 @@ public class RpcDataProviderExtension extends AbstractExtension {
// Send current rows again if needed.
if (refreshCache) {
- int firstRow = activeRowHandler.activeRange.getStart();
- int numberOfRows = activeRowHandler.activeRange.length();
-
- pushRowData(firstRow, numberOfRows, firstRow, numberOfRows);
+ updatedItemIds.addAll(activeItemHandler.getActiveItemIds());
}
}
- for (Object itemId : updatedItemIds) {
- internalUpdateRowData(itemId);
- }
+ internalUpdateRows(updatedItemIds);
// Clear all changes.
rowChanges.clear();
@@ -1076,7 +566,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
List<?> itemIds = container.getItemIds(fullRange.getStart(),
fullRange.length());
- keyMapper.setActiveRows(itemIds);
JsonArray rows = Json.createArray();
@@ -1088,83 +577,25 @@ public class RpcDataProviderExtension extends AbstractExtension {
for (int i = 0; i < newRange.length() && i + diff < itemIds.size(); ++i) {
Object itemId = itemIds.get(i + diff);
+
rows.set(i, getRowData(getGrid().getColumns(), itemId));
}
rpc.setRowData(firstRowToPush, rows);
- activeRowHandler.setActiveRows(fullRange);
+ activeItemHandler.addActiveItems(itemIds);
}
- private JsonValue getRowData(Collection<Column> columns, Object itemId) {
+ private JsonObject 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));
-
- if (visibleDetails.contains(itemId)) {
- rowObject.put(GridState.JSONKEY_DETAILS_VISIBLE, true);
- }
-
- rowReference.set(itemId);
-
- CellStyleGenerator cellStyleGenerator = grid.getCellStyleGenerator();
- if (cellStyleGenerator != null) {
- setGeneratedCellStyles(cellStyleGenerator, rowObject, columns);
- }
- RowStyleGenerator rowStyleGenerator = grid.getRowStyleGenerator();
- if (rowStyleGenerator != null) {
- setGeneratedRowStyles(rowStyleGenerator, rowObject);
+ for (DataGenerator dg : dataGenerators) {
+ dg.generateData(itemId, item, 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 && !style.isEmpty()) {
- 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 && !rowStyle.isEmpty()) {
- rowObject.put(GridState.JSONKEY_ROWSTYLE, rowStyle);
- }
- }
-
/**
* Makes the data source available to the given {@link Grid} component.
*
@@ -1173,13 +604,38 @@ public class RpcDataProviderExtension extends AbstractExtension {
* @param columnKeys
* the key mapper for columns
*/
- public void extend(Grid component, KeyMapper<Object> columnKeys) {
- this.columnKeys = columnKeys;
+ public void extend(Grid component) {
detailComponentManager.setGrid(component);
super.extend(component);
}
/**
+ * Adds a {@link DataGenerator} for this {@code RpcDataProviderExtension}.
+ * DataGenerators are called when sending row data to client. If given
+ * DataGenerator is already added, this method does nothing.
+ *
+ * @since 7.6
+ * @param generator
+ * generator to add
+ */
+ public void addDataGenerator(DataGenerator generator) {
+ dataGenerators.add(generator);
+ }
+
+ /**
+ * Removes a {@link DataGenerator} from this
+ * {@code RpcDataProviderExtension}. If given DataGenerator is not added to
+ * this data provider, this method does nothing.
+ *
+ * @since 7.6
+ * @param generator
+ * generator to remove
+ */
+ public void removeDataGenerator(DataGenerator generator) {
+ dataGenerators.remove(generator);
+ }
+
+ /**
* Informs the client side that new rows have been inserted into the data
* source.
*
@@ -1205,8 +661,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.insertRowData(index, count);
}
});
-
- activeRowHandler.insertRows(index, count);
}
/**
@@ -1231,8 +685,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
rpc.removeRowData(index, count);
}
});
-
- activeRowHandler.removeRows(index, count);
}
/**
@@ -1252,18 +704,20 @@ public class RpcDataProviderExtension extends AbstractExtension {
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);
+ private void internalUpdateRows(Set<Object> itemIds) {
+ if (itemIds.isEmpty()) {
+ return;
+ }
- if (isDetailsVisible(itemId)) {
- detailComponentManager.createDetails(itemId, index);
+ JsonArray rowData = Json.createArray();
+ int i = 0;
+ for (Object itemId : itemIds) {
+ if (activeItemHandler.getActiveItemIds().contains(itemId)) {
+ JsonObject row = getRowData(getGrid().getColumns(), itemId);
+ rowData.set(i++, row);
}
}
+ rpc.updateRowData(rowData);
}
/**
@@ -1280,20 +734,15 @@ public class RpcDataProviderExtension extends AbstractExtension {
public void setParent(ClientConnector parent) {
if (parent == null) {
// We're being detached, release various listeners
-
- activeRowHandler
- .removeValueChangeListeners(activeRowHandler.activeRange);
+ activeItemHandler.internalDropActiveItems(activeItemHandler
+ .getActiveItemIds());
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 {
+ } else if (!(parent instanceof Grid)) {
throw new IllegalStateException(
"Grid is the only accepted parent type");
}
@@ -1308,7 +757,13 @@ public class RpcDataProviderExtension extends AbstractExtension {
* a list of removed columns
*/
public void columnsRemoved(List<Column> removedColumns) {
- activeRowHandler.columnsRemoved(removedColumns);
+ for (GridValueChangeListener l : activeItemHandler
+ .getValueChangeListeners()) {
+ l.removeColumns(removedColumns);
+ }
+
+ // No need to resend unchanged data. Client will remember the old
+ // columns until next set of rows is sent.
}
/**
@@ -1318,11 +773,17 @@ public class RpcDataProviderExtension extends AbstractExtension {
* a list of added columns
*/
public void columnsAdded(List<Column> addedColumns) {
- activeRowHandler.columnsAdded(addedColumns);
+ for (GridValueChangeListener l : activeItemHandler
+ .getValueChangeListeners()) {
+ l.addColumns(addedColumns);
+ }
+
+ // Resend all rows to contain new data.
+ refreshCache();
}
- public DataProviderKeyMapper getKeyMapper() {
- return keyMapper;
+ public KeyMapper<Object> getKeyMapper() {
+ return activeItemHandler.keyMapper;
}
protected Grid getGrid() {
@@ -1330,62 +791,6 @@ public class RpcDataProviderExtension extends AbstractExtension {
}
/**
- * 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) {
- if (presentationType == String.class) {
- // If there is no converter, just fallback to using
- // toString().
- // modelValue can't be null as Class.cast(null) will always
- // succeed
- presentationValue = (T) modelValue.toString();
- } else {
- throw 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.");
- }
- }
- } 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());
- }
-
- /**
* Marks a row's details to be visible or hidden.
* <p>
* If that row is currently in the client side's cache, this information
@@ -1399,37 +804,21 @@ public class RpcDataProviderExtension extends AbstractExtension {
* hide
*/
public void setDetailsVisible(Object itemId, boolean visible) {
- final boolean modified;
-
if (visible) {
- modified = visibleDetails.add(itemId);
+ visibleDetails.add(itemId);
/*
- * We don't want to create the component here, since the component
- * might be out of view, and thus we don't know where the details
- * should end up on the client side. This is also a great thing to
- * optimize away, so that in case a lot of things would be opened at
- * once, a huge chunk of data doesn't get sent over immediately.
+ * This might be an issue with a huge number of open rows, but as of
+ * now this works in most of the cases.
*/
-
+ detailComponentManager.createDetails(itemId);
} else {
- modified = visibleDetails.remove(itemId);
+ visibleDetails.remove(itemId);
- /*
- * Here we can try to destroy the component no matter what. The
- * component has been removed and should be detached from the
- * component hierarchy. The details row will be closed on the client
- * side automatically.
- */
detailComponentManager.destroyDetails(itemId);
}
- int rowIndex = indexOf(itemId);
- boolean modifiedRowIsActive = activeRowHandler.activeRange
- .contains(rowIndex);
- if (modified && modifiedRowIsActive) {
- updateRowData(itemId);
- }
+ updateRowData(itemId);
}
/**
@@ -1454,18 +843,10 @@ public class RpcDataProviderExtension extends AbstractExtension {
public void refreshDetails() {
for (Object itemId : ImmutableSet.copyOf(visibleDetails)) {
detailComponentManager.refresh(itemId);
+ updateRowData(itemId);
}
}
- private int indexOf(Object itemId) {
- /*
- * It would be great if we could optimize this method away, since the
- * normal usage of Grid doesn't need any indices to be known. It was
- * already optimized away once, maybe we can do away with these as well.
- */
- return container.indexOfId(itemId);
- }
-
/**
* Gets the detail component manager for this data provider
*
@@ -1475,13 +856,4 @@ public class RpcDataProviderExtension extends AbstractExtension {
public DetailComponentManager getDetailComponentManager() {
return detailComponentManager;
}
-
- @Override
- public void detach() {
- for (Object itemId : ImmutableSet.copyOf(visibleDetails)) {
- detailComponentManager.destroyDetails(itemId);
- }
-
- super.detach();
- }
}