com.google.gwt.view.client.ListDataProvider.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwt.view.client.ListDataProvider.java

Source

/*
 * 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.
 *
 * <p>
 * <h3>Example</h3>
 * {@example com.google.gwt.examples.view.ListDataProviderExample}
 * </p>
 *
 * @param <T> the data type of the list
 */
public class ListDataProvider<T> extends AbstractDataProvider<T> {

    /**
     * A wrapper around a list that updates the model on any change.
     */
    private class ListWrapper implements List<T> {

        /**
         * A wrapped ListIterator.
         */
        private final class WrappedListIterator implements ListIterator<T> {

            /**
             * 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;
            }

            public void add(T o) {
                if (last < 0) {
                    throw new IllegalStateException(IMPERMEABLE_EXCEPTION);
                }
                ListWrapper.this.add(i++, o);
                last = -1;
            }

            public boolean hasNext() {
                return i < ListWrapper.this.size();
            }

            public boolean hasPrevious() {
                return i > 0;
            }

            public T next() {
                if (!hasNext()) {
                    throw new NoSuchElementException();
                }
                return ListWrapper.this.get(last = i++);
            }

            public int nextIndex() {
                return i;
            }

            public T previous() {
                if (!hasPrevious()) {
                    throw new NoSuchElementException();
                }
                return ListWrapper.this.get(last = --i);
            }

            public int previousIndex() {
                return i - 1;
            }

            public void remove() {
                if (last < 0) {
                    throw new IllegalStateException(IMPERMEABLE_EXCEPTION);
                }
                ListWrapper.this.remove(last);
                i = last;
                last = -1;
            }

            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() {
            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<T> 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<T> 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<T> list, ListWrapper delegate, int offset) {
            this.list = list;
            this.delegate = delegate;
            this.offset = offset;
        }

        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());
            }
        }

        public boolean add(T e) {
            boolean toRet = list.add(e);
            minModified = Math.min(minModified, size() - 1);
            maxModified = size();
            modified = true;
            flush();
            return toRet;
        }

        public boolean addAll(Collection<? extends T> c) {
            minModified = Math.min(minModified, size());
            boolean toRet = list.addAll(c);
            maxModified = size();
            modified = true;
            flush();
            return toRet;
        }

        public boolean addAll(int index, Collection<? extends T> 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());
            }
        }

        public void clear() {
            list.clear();
            minModified = maxModified = 0;
            modified = true;
            flush();
        }

        public boolean contains(Object o) {
            return list.contains(o);
        }

        public boolean containsAll(Collection<?> c) {
            return list.containsAll(c);
        }

        @Override
        public boolean equals(Object o) {
            return list.equals(o);
        }

        public T get(int index) {
            return list.get(index);
        }

        @Override
        public int hashCode() {
            return list.hashCode();
        }

        public int indexOf(Object o) {
            return list.indexOf(o);
        }

        public boolean isEmpty() {
            return list.isEmpty();
        }

        public Iterator<T> iterator() {
            return listIterator();
        }

        public int lastIndexOf(Object o) {
            return list.lastIndexOf(o);
        }

        public ListIterator<T> listIterator() {
            return new WrappedListIterator();
        }

        public ListIterator<T> listIterator(int index) {
            return new WrappedListIterator(index);
        }

        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());
            }
        }

        public boolean remove(Object o) {
            int index = indexOf(o);
            if (index == -1) {
                return false;
            }
            remove(index);
            return true;
        }

        public boolean removeAll(Collection<?> c) {
            boolean toRet = list.removeAll(c);
            minModified = 0;
            maxModified = size();
            modified = true;
            flush();
            return toRet;
        }

        public boolean retainAll(Collection<?> c) {
            boolean toRet = list.retainAll(c);
            minModified = 0;
            maxModified = size();
            modified = true;
            flush();
            return toRet;
        }

        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;
        }

        public int size() {
            return list.size();
        }

        public List<T> subList(int fromIndex, int toIndex) {
            return new ListWrapper(list.subList(fromIndex, toIndex), this, fromIndex);
        }

        public Object[] toArray() {
            return list.toArray();
        }

        public <C> 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<T>(), null);
    }

    /**
     * Creates a list model that wraps the given list. Changes to the
     * wrapped list must be made via this model in order to be correctly applied
     * to displays.
     * 
     * @param listToWrap the List to be wrapped
     */
    public ListDataProvider(List<T> listToWrap) {
        this(listToWrap, null);
    }

    /**
     * Creates an empty list model that wraps the given collection.
     *
     * @param keyProvider an instance of ProvidesKey<T>, or null if the record
     *        object should act as its own key
     */
    public ListDataProvider(ProvidesKey<T> keyProvider) {
        this(new ArrayList<T>(), keyProvider);
    }

    /**
     * Creates a list model that wraps the given list. Changes to the
     * wrapped list must be made via this model in order to be correctly applied
     * to displays.
     * 
     * @param listToWrap the List to be wrapped
     * @param keyProvider an instance of ProvidesKey<T>, or null if the record
     *        object should act as its own key
     */
    public ListDataProvider(List<T> listToWrap, ProvidesKey<T> 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.
     *
     * @return the list
     *
     * @see #setList(List)
     */
    public List<T> getList() {
        return listWrapper;
    }

    /**
     * Refresh all of the displays listening to this adapter.
     */
    public void refresh() {
        updateRowData(0, listWrapper);
    }

    /**
     * Replace this model's list.
     *
     * @param listToWrap the model's new list
     *
     * @see #getList()
     */
    public void setList(List<T> listToWrap) {
        listWrapper = new ListWrapper(listToWrap);
        listWrapper.minModified = 0;
        listWrapper.maxModified = listWrapper.size();
        listWrapper.modified = true;
        flush();
    }

    @Override
    protected void onRangeChanged(HasData<T> display) {
        int size = listWrapper.size();
        if (size > 0) {
            // Do not push data if the data set is empty.
            updateRowData(display, 0, listWrapper);
        }
    }
}