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

com.google.gwt.view.client.ListDataProvider Maven / Gradle / Ivy

Go to download

A mocked implementation for GWT-2.5.1 that can run in the JVM to allow integration testing

There is a newer version: 0.1.7
Show newest version
/*
 * Copyright 2010 Google Inc.
 *
 * 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.google.gwt.view.client;

import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;

/**
 * A concrete subclass of {@link AbstractDataProvider} that is backed by an
 * in-memory list.
 * 
 * 

* Modifications (inserts, removes, sets, etc.) to the list returned by * {@link #getList()} will be reflected in the model. However, mutations to the * items contained within the list will NOT be reflected in the model. You must * call {@link List#set(int, Object)} to update the item within the list and * push the change to the display, or call {@link #refresh()} to push all rows * to the displays. {@link List#set(int, Object)} performs better because it * allows the data provider to push only those rows which have changed, and * usually allows the display to re-render only a subset of the rows. *

* *

*

Example

{@example * com.google.gwt.examples.view.ListDataProviderExample} *

* * @param the data type of the list */ public class ListDataProvider extends AbstractDataProvider { /** * A wrapper around a list that updates the model on any change. */ private class ListWrapper implements List { /** * A wrapped ListIterator. */ private final class WrappedListIterator implements ListIterator { /** * The error message when {@link #add(Object)} or {@link #remove()} is * called more than once per call to {@link #next()} or * {@link #previous()}. */ private static final String IMPERMEABLE_EXCEPTION = "Cannot call add/remove more than once per call to next/previous."; /** * The index of the object that will be returned by {@link #next()}. */ private int i = 0; /** * The index of the last object accessed through {@link #next()} or * {@link #previous()}. */ private int last = -1; private WrappedListIterator() { } private WrappedListIterator(int start) { int size = ListWrapper.this.size(); if (start < 0 || start > size) { throw new IndexOutOfBoundsException( "Index: " + start + ", Size: " + size); } i = start; } @Override public void add(T o) { if (last < 0) { throw new IllegalStateException(IMPERMEABLE_EXCEPTION); } ListWrapper.this.add(i++, o); last = -1; } @Override public boolean hasNext() { return i < ListWrapper.this.size(); } @Override public boolean hasPrevious() { return i > 0; } @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); } return ListWrapper.this.get(last = i++); } @Override public int nextIndex() { return i; } @Override public T previous() { if (!hasPrevious()) { throw new NoSuchElementException(); } return ListWrapper.this.get(last = --i); } @Override public int previousIndex() { return i - 1; } @Override public void remove() { if (last < 0) { throw new IllegalStateException(IMPERMEABLE_EXCEPTION); } ListWrapper.this.remove(last); i = last; last = -1; } @Override public void set(T o) { if (last == -1) { throw new IllegalStateException(); } ListWrapper.this.set(last, o); } } /** * The current size of the list. */ private int curSize = 0; /** * The delegate wrapper. */ private final ListWrapper delegate; /** * Set to true if the pending flush has been canceled. */ private boolean flushCancelled; /** * We wait until the end of the current event loop before flushing changes * so that we don't spam the displays. This also allows users to clear and * replace all of the data without forcing the display back to page 0. */ private ScheduledCommand flushCommand = new ScheduledCommand() { @Override public void execute() { flushPending = false; if (flushCancelled) { flushCancelled = false; return; } flushNow(); } }; /** * Set to true if a flush is pending. */ private boolean flushPending; /** * The list that backs the wrapper. */ private List list; /** * If this is a sublist, the offset it the index relative to the main list. */ private final int offset; /** * If modified is true, the smallest modified index. */ private int maxModified = Integer.MIN_VALUE; /** * If modified is true, one past the largest modified index. */ private int minModified = Integer.MAX_VALUE; /** * True if the list data has been modified. */ private boolean modified; public ListWrapper(List list) { this(list, null, 0); // Initialize the data size based on the size of the input list. updateRowCount(list.size(), true); } /** * Construct a new {@link ListWrapper} that delegates flush calls to the * specified delegate. * * @param list the list to wrap * @param delegate the delegate * @param offset the offset of this list */ private ListWrapper(List list, ListWrapper delegate, int offset) { this.list = list; this.delegate = delegate; this.offset = offset; } @Override public void add(int index, T element) { try { list.add(index, element); minModified = Math.min(minModified, index); maxModified = size(); modified = true; flush(); } catch (IndexOutOfBoundsException e) { throw new IndexOutOfBoundsException(e.getMessage()); } } @Override public boolean add(T e) { boolean toRet = list.add(e); minModified = Math.min(minModified, size() - 1); maxModified = size(); modified = true; flush(); return toRet; } @Override public boolean addAll(Collection c) { minModified = Math.min(minModified, size()); boolean toRet = list.addAll(c); maxModified = size(); modified = true; flush(); return toRet; } @Override public boolean addAll(int index, Collection c) { try { boolean toRet = list.addAll(index, c); minModified = Math.min(minModified, index); maxModified = size(); modified = true; flush(); return toRet; } catch (IndexOutOfBoundsException e) { throw new IndexOutOfBoundsException(e.getMessage()); } } @Override public void clear() { list.clear(); minModified = maxModified = 0; modified = true; flush(); } @Override public boolean contains(Object o) { return list.contains(o); } @Override public boolean containsAll(Collection c) { return list.containsAll(c); } @Override public boolean equals(Object o) { return list.equals(o); } @Override public T get(int index) { return list.get(index); } @Override public int hashCode() { return list.hashCode(); } @Override public int indexOf(Object o) { return list.indexOf(o); } @Override public boolean isEmpty() { return list.isEmpty(); } @Override public Iterator iterator() { return listIterator(); } @Override public int lastIndexOf(Object o) { return list.lastIndexOf(o); } @Override public ListIterator listIterator() { return new WrappedListIterator(); } @Override public ListIterator listIterator(int index) { return new WrappedListIterator(index); } @Override public T remove(int index) { try { T toRet = list.remove(index); minModified = Math.min(minModified, index); maxModified = size(); modified = true; flush(); return toRet; } catch (IndexOutOfBoundsException e) { throw new IndexOutOfBoundsException(e.getMessage()); } } @Override public boolean remove(Object o) { int index = indexOf(o); if (index == -1) { return false; } remove(index); return true; } @Override public boolean removeAll(Collection c) { boolean toRet = list.removeAll(c); minModified = 0; maxModified = size(); modified = true; flush(); return toRet; } @Override public boolean retainAll(Collection c) { boolean toRet = list.retainAll(c); minModified = 0; maxModified = size(); modified = true; flush(); return toRet; } @Override public T set(int index, T element) { T toRet = list.set(index, element); minModified = Math.min(minModified, index); maxModified = Math.max(maxModified, index + 1); modified = true; flush(); return toRet; } @Override public int size() { return list.size(); } @Override public List subList(int fromIndex, int toIndex) { return new ListWrapper(list.subList(fromIndex, toIndex), this, fromIndex); } @Override public Object[] toArray() { return list.toArray(); } @Override public C[] toArray(C[] a) { return list.toArray(a); } /** * Flush the data to the model. */ private void flush() { // Defer to the delegate. if (delegate != null) { delegate.minModified = Math.min( minModified + offset, delegate.minModified); delegate.maxModified = Math.max( maxModified + offset, delegate.maxModified); delegate.modified = modified || delegate.modified; delegate.flush(); return; } flushCancelled = false; if (!flushPending) { flushPending = true; Scheduler.get().scheduleFinally(flushCommand); } } /** * Flush pending list changes to the displays. By default, */ private void flushNow() { // Cancel any pending flush command. if (flushPending) { flushCancelled = true; } // Early exit if this list has been replaced in the data provider. if (listWrapper != this) { return; } int newSize = list.size(); if (curSize != newSize) { curSize = newSize; updateRowCount(curSize, true); } if (modified) { updateRowData(minModified, list.subList(minModified, maxModified)); modified = false; } minModified = Integer.MAX_VALUE; maxModified = Integer.MIN_VALUE; } } /** * The wrapper around the actual list. */ private ListWrapper listWrapper; /** * Creates an empty model. */ public ListDataProvider() { this(new ArrayList(), null); } /** * Creates a list model that wraps the given list. * *

* The wrapped list should no longer be modified as the data provider cannot * detect changes to the wrapped list. Instead, call {@link #getList()} to * retrieve a wrapper that can be modified and will correctly forward changes * to displays. * * @param listToWrap the List to be wrapped */ public ListDataProvider(List listToWrap) { this(listToWrap, null); } /** * Creates an empty list model that wraps the given collection. * * @param keyProvider an instance of ProvidesKey, or null if the record * object should act as its own key */ public ListDataProvider(ProvidesKey keyProvider) { this(new ArrayList(), keyProvider); } /** * Creates a list model that wraps the given list. * *

* The wrapped list should no longer be modified as the data provider cannot * detect changes to the wrapped list. Instead, call {@link #getList()} to * retrieve a wrapper that can be modified and will correctly forward changes * to displays. * * @param listToWrap the List to be wrapped * @param keyProvider an instance of ProvidesKey, or null if the record * object should act as its own key */ public ListDataProvider(List listToWrap, ProvidesKey keyProvider) { super(keyProvider); listWrapper = new ListWrapper(listToWrap); } /** * Flush pending list changes to the displays. By default, displays are * informed of modifications to the underlying list at the end of the current * event loop, which makes it possible to perform multiple operations * synchronously without repeatedly refreshing the displays. This method can * be called to flush the changes immediately instead of waiting until the end * of the current event loop. */ public void flush() { listWrapper.flushNow(); } /** * Get the list that backs this model. Changes to the list will be reflected * in the model. * *

* NOTE: Mutations to the items contained within the list will NOT be * reflected in the model. You must call {@link List#set(int, Object)} to * update the item within the list and push the change to the display, or call * {@link #refresh()} to push all rows to the displays. * {@link List#set(int, Object)} performs better because it allows the data * provider to push only those rows which have changed, and usually allows the * display to re-render only a subset of the rows. * * @return the list * * @see #setList(List) */ public List getList() { return listWrapper; } /** * Refresh all of the displays listening to this adapter. * *

* Use {@link #refresh()} to push mutations to the underlying data items * contained within the list. The data provider cannot detect changes to data * objects within the list, so you must call this method if you modify items. * *

* This is a shortcut for calling {@link List#set(int, Object)} on every item * that you modify, but note that calling {@link List#set(int, Object)} * performs better because the data provider knows which rows were modified * and can push only the modified rows the the displays. */ public void refresh() { updateRowData(0, listWrapper); } /** * Replace this model's list with the specified list. * *

* The wrapped list should no longer be modified as the data provider cannot * detect changes to the wrapped list. Instead, call {@link #getList()} to * retrieve a wrapper that can be modified and will correctly forward changes * to displays. * * @param listToWrap the model's new list * * @see #getList() */ public void setList(List listToWrap) { listWrapper = new ListWrapper(listToWrap); listWrapper.minModified = 0; listWrapper.maxModified = listWrapper.size(); listWrapper.modified = true; flush(); } @Override protected void onRangeChanged(HasData display) { int size = listWrapper.size(); if (size > 0) { // Do not push data if the data set is empty. updateRowData(display, 0, listWrapper); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy