/* * 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.event.shared.HandlerRegistration; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * A base implementation of a data source for {@link HasData} implementations. * * @param <T> the data type of records in the list */ public abstract class AbstractDataProvider<T> implements ProvidesKey<T> { private Set<HasData<T>> displays = new HashSet<HasData<T>>(); /** * The provider of keys for list items. */ private final ProvidesKey<T> keyProvider; /** * The last row count. */ private int lastRowCount = -1; /** * Indicates whether or not the last row count is exact. */ private boolean lastRowCountExact; /** * A mapping of {@link HasData}s to their handlers. */ private Map<HasData<T>, HandlerRegistration> rangeChangeHandlers = new HashMap<HasData<T>, HandlerRegistration>(); /** * Construct an AbstractDataProvider without a key provider. */ protected AbstractDataProvider() { this.keyProvider = null; } /** * Construct an AbstractDataProvider with a given key provider. * * @param keyProvider a {@link ProvidesKey} object */ protected AbstractDataProvider(ProvidesKey<T> keyProvider) { this.keyProvider = keyProvider; } /** * Adds a data display to this adapter. The current range of interest of the * display will be populated with data. * * @param display a {@link HasData}. */ public void addDataDisplay(final HasData<T> display) { if (display == null) { throw new IllegalArgumentException("display cannot be null"); } else if (displays.contains(display)) { throw new IllegalStateException( "The specified display has already been added to this adapter."); } // Add the display to the set. displays.add(display); // Add a handler to the display. HandlerRegistration handler = display.addRangeChangeHandler( new RangeChangeEvent.Handler() { public void onRangeChange(RangeChangeEvent event) { AbstractDataProvider.this.onRangeChanged(display); } }); rangeChangeHandlers.put(display, handler); // Update the data size in the display. if (lastRowCount >= 0) { display.setRowCount(lastRowCount, lastRowCountExact); } // Initialize the display with the current range. onRangeChanged(display); } /** * Get the set of displays currently assigned to this adapter. * * @return the set of {@link HasData} */ public Set<HasData<T>> getDataDisplays() { return Collections.unmodifiableSet(displays); } /** * Get the key for a list item. The default implementation returns the item * itself. * * @param item the list item * @return the key that represents the item */ public Object getKey(T item) { return keyProvider == null ? item : keyProvider.getKey(item); } /** * Get the {@link ProvidesKey} that provides keys for list items. * * @return the {@link ProvidesKey} */ public ProvidesKey<T> getKeyProvider() { return keyProvider; } /** * Get the current ranges of all displays. * * @return the ranges */ public Range[] getRanges() { Range[] ranges = new Range[displays.size()]; int i = 0; for (HasData<T> display : displays) { ranges[i++] = display.getVisibleRange(); } return ranges; } /** * Remove the given data display. * * @param display a {@link HasData} instance * * @throws IllegalStateException if the display is not present */ public void removeDataDisplay(HasData<T> display) { if (!displays.contains(display)) { throw new IllegalStateException("HasData not present"); } displays.remove(display); // Remove the handler. HandlerRegistration handler = rangeChangeHandlers.remove(display); handler.removeHandler(); } /** * Called when a display changes its range of interest. * * @param display the display whose range has changed */ protected abstract void onRangeChanged(HasData<T> display); /** * Inform the displays of the total number of items that are available. * * @param count the new total row count * @param exact true if the count is exact, false if it is an estimate */ protected void updateRowCount(int count, boolean exact) { lastRowCount = count; lastRowCountExact = exact; for (HasData<T> display : displays) { display.setRowCount(count, exact); } } /** * Inform the displays of the new data. * * @param start the start index * @param values the data values */ protected void updateRowData(int start, List<T> values) { for (HasData<T> display : displays) { updateRowData(display, start, values); } } /** * Informs a single display of new data. * * @param display the display to be updated * @param start the start index * @param values the data values */ protected void updateRowData(HasData<T> display, int start, List<T> values) { int end = start + values.size(); Range range = display.getVisibleRange(); int curStart = range.getStart(); int curLength = range.getLength(); int curEnd = curStart + curLength; if (start == curStart || (curStart < end && curEnd > start)) { // Fire the handler with the data that is in the range. // Allow an empty list that starts on the page start. int realStart = curStart < start ? start : curStart; int realEnd = curEnd > end ? end : curEnd; int realLength = realEnd - realStart; List<T> realValues = values.subList( realStart - start, realStart - start + realLength); display.setRowData(realStart, realValues); } } }