001: /*
002: * $Id: AbstractPageableView.java 460586 2006-05-10 15:16:14Z ivaynberg $
003: * $Revision: 460586 $
004: * $Date: 2006-05-10 17:16:14 +0200 (Wed, 10 May 2006) $
005: *
006: * ====================================================================
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019: package wicket.extensions.markup.html.repeater.pageable;
020:
021: import java.util.Iterator;
022: import java.util.NoSuchElementException;
023:
024: import wicket.extensions.markup.html.repeater.refreshing.RefreshingView;
025: import wicket.markup.html.navigation.paging.IPageable;
026: import wicket.model.IModel;
027: import wicket.version.undo.Change;
028:
029: /**
030: * An abstract repeater view that provides paging functionality to its
031: * subclasses.
032: * <p>
033: * The view is populated by overriding the
034: * <code>getItemModels(int offset, int count)</code> method and providing an
035: * iterator that returns models for items in the current page. The
036: * AbstractPageableView builds the items that will be rendered by looping over
037: * the models and calling the
038: * <code>newItem(String id, int index, IModel model)</code> to generate the
039: * child item container followed by <code>populateItem(Component item)</code>
040: * to let the user populate the newly created item container with with custom
041: * components.
042: * </p>
043: *
044: * @see wicket.extensions.markup.html.repeater.refreshing.RefreshingView
045: * @see wicket.markup.html.navigation.paging.IPageable
046: *
047: * @author Igor Vaynberg (ivaynberg)
048: *
049: */
050: public abstract class AbstractPageableView extends RefreshingView
051: implements IPageable
052:
053: {
054: /**
055: * Keeps track of the number of items we show per page. The default is
056: * Integer.MAX_VALUE which effectively disables paging.
057: */
058: private int itemsPerPage = Integer.MAX_VALUE;
059:
060: /**
061: * Keeps track of the current page number.
062: */
063: private int currentPage;
064:
065: /**
066: * <code>cachedItemCount</code> is used to cache the call to
067: * <code>internalGetItemCount()</code> for the duration of the request
068: * because that call can potentially be expensive ( a select count query )
069: * and so we do not want to execute it multiple times.
070: */
071: private int cachedItemCount;
072:
073: /** @see wicket.Component#Component(String, IModel) */
074: public AbstractPageableView(String id, IModel model) {
075: super (id, model);
076: clearCachedItemCount();
077: }
078:
079: /** @see wicket.Component#Component(String) */
080: public AbstractPageableView(String id) {
081: super (id);
082: clearCachedItemCount();
083: }
084:
085: /**
086: * This method retrieves the subset of models for items in the current page
087: * and allows RefreshingView to generate items.
088: *
089: * @return iterator over models for items in the current page
090: */
091: protected Iterator getItemModels() {
092: int offset = getViewOffset();
093: int size = getViewSize();
094:
095: Iterator models = getItemModels(offset, size);
096:
097: models = new CappedIteratorAdapter(models, size);
098:
099: return models;
100: }
101:
102: protected void internalOnDetach() {
103: super .internalOnDetach();
104: clearCachedItemCount();
105: }
106:
107: /**
108: * Returns an iterator over models for items in the current page
109: *
110: * @param offset
111: * index of first item in this page
112: * @param size
113: * number of items that will be showin in the current page
114: * @return an iterator over models for items in the current page
115: */
116: protected abstract Iterator getItemModels(int offset, int size);
117:
118: // /////////////////////////////////////////////////////////////////////////
119: // ITEM COUNT CACHE
120: // /////////////////////////////////////////////////////////////////////////
121:
122: private void clearCachedItemCount() {
123: cachedItemCount = -1;
124: }
125:
126: private void setCachedItemCount(int itemCount) {
127: cachedItemCount = itemCount;
128: }
129:
130: private int getCachedItemCount() {
131: if (cachedItemCount < 0) {
132: throw new IllegalStateException(
133: "getItemCountCache() called when cache was not set");
134: }
135: return cachedItemCount;
136: }
137:
138: private boolean isItemCountCached() {
139: return cachedItemCount >= 0;
140: }
141:
142: // /////////////////////////////////////////////////////////////////////////
143: // PAGING
144: // /////////////////////////////////////////////////////////////////////////
145:
146: /**
147: * @return maximum number of items that will be shown per page
148: */
149: protected final int internalGetRowsPerPage() {
150: return itemsPerPage;
151: }
152:
153: /**
154: * Sets the maximum number of items to show per page. The current page will
155: * also be set to zero
156: *
157: * @param items
158: */
159: protected final void internalSetRowsPerPage(int items) {
160: if (items < 1) {
161: throw new IllegalArgumentException(
162: "Argument [itemsPerPage] cannot be less then 1");
163: }
164:
165: if (itemsPerPage != items) {
166: if (isVersioned()) {
167: addStateChange(new Change() {
168: private static final long serialVersionUID = 1L;
169:
170: final int old = itemsPerPage;
171:
172: public void undo() {
173: itemsPerPage = old;
174: }
175:
176: public String toString() {
177: return "ItemsPerPageChange[component: "
178: + getPath() + ", itemsPerPage: " + old
179: + "]";
180: }
181: });
182: }
183: }
184:
185: itemsPerPage = items;
186:
187: // because items per page can effect the total number of pages we always
188: // reset the current page back to zero
189: setCurrentPage(0);
190: }
191:
192: /**
193: * @return total item count
194: */
195: protected abstract int internalGetItemCount();
196:
197: /**
198: * @return total item count
199: */
200: public final int getRowCount() {
201: if (!isVisibleInHierarchy()) {
202: return 0;
203: }
204:
205: if (isItemCountCached()) {
206: return getCachedItemCount();
207: }
208:
209: int count = internalGetItemCount();
210:
211: setCachedItemCount(count);
212:
213: return count;
214: }
215:
216: /**
217: * @see wicket.markup.html.navigation.paging.IPageable#getCurrentPage()
218: */
219: public final int getCurrentPage() {
220: int page = currentPage;
221:
222: /*
223: * trim current page if its out of bounds this can happen if items are
224: * added/deleted between requests
225: */
226:
227: if (page >= getPageCount()) {
228: page = Math.max(getPageCount() - 1, 0);
229: setCurrentPage(page);
230: return page;
231: }
232:
233: return page;
234: }
235:
236: /**
237: * @see wicket.markup.html.navigation.paging.IPageable#setCurrentPage(int)
238: */
239: public final void setCurrentPage(int page) {
240: if (page < 0 || (page >= getPageCount() && getPageCount() > 0)) {
241: throw new IndexOutOfBoundsException("argument [page]="
242: + page + ", must be 0<=page<" + getPageCount());
243: }
244:
245: if (currentPage != page) {
246: if (isVersioned()) {
247: addStateChange(new Change() {
248: private static final long serialVersionUID = 1L;
249:
250: private final int old = currentPage;
251:
252: public void undo() {
253: currentPage = old;
254: }
255:
256: public String toString() {
257: return "CurrentPageChange[component: "
258: + getPath() + ", currentPage: " + old
259: + "]";
260: }
261: });
262:
263: }
264: }
265: currentPage = page;
266: }
267:
268: /**
269: * @see wicket.markup.html.navigation.paging.IPageable#getPageCount()
270: */
271: public final int getPageCount() {
272: int total = getRowCount();
273: int page = internalGetRowsPerPage();
274: int count = total / page;
275:
276: if (page * count < total) {
277: count++;
278: }
279:
280: return count;
281:
282: }
283:
284: /**
285: * @return the index of the first visible item
286: */
287: protected int getViewOffset() {
288: return getCurrentPage() * internalGetRowsPerPage();
289: }
290:
291: /**
292: * @return the number of items visible
293: */
294: protected int getViewSize() {
295: return Math.min(internalGetRowsPerPage(), getRowCount()
296: - getViewOffset());
297: }
298:
299: // /////////////////////////////////////////////////////////////////////////
300: // HELPER CLASSES
301: // /////////////////////////////////////////////////////////////////////////
302:
303: /**
304: * Iterator adapter that makes sure only the specified max number of items
305: * can be accessed from its delegate.
306: */
307: private static class CappedIteratorAdapter implements Iterator {
308: private int max;
309: private int index;
310: private Iterator delegate;
311:
312: /**
313: * Constructor
314: *
315: * @param delegate
316: * delegate iterator
317: * @param max
318: * maximum number of items that can be accessed.
319: */
320: public CappedIteratorAdapter(Iterator delegate, int max) {
321: this .delegate = delegate;
322: this .max = max;
323: }
324:
325: /**
326: * @see java.util.Iterator#remove()
327: */
328: public void remove() {
329: throw new UnsupportedOperationException();
330: }
331:
332: /**
333: * @see java.util.Iterator#hasNext()
334: */
335: public boolean hasNext() {
336: return (index < max) && delegate.hasNext();
337: }
338:
339: /**
340: * @see java.util.Iterator#next()
341: */
342: public Object next() {
343: if (index >= max) {
344: throw new NoSuchElementException();
345: }
346: index++;
347: return delegate.next();
348: }
349:
350: };
351:
352: }
|