001: /**
002: * Copyright 2006 Webmedia Group Ltd.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: **/package org.araneaframework.uilib.list.dataprovider;
016:
017: import java.io.Serializable;
018: import java.util.ArrayList;
019: import java.util.Collections;
020: import java.util.Comparator;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.List;
024: import java.util.Set;
025: import org.apache.commons.lang.exception.NestableRuntimeException;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.araneaframework.backend.list.memorybased.BeanVariableResolver;
029: import org.araneaframework.backend.list.memorybased.ComparatorExpression;
030: import org.araneaframework.backend.list.memorybased.Expression;
031: import org.araneaframework.backend.list.memorybased.ExpressionEvaluationException;
032: import org.araneaframework.backend.list.model.ListItemsData;
033: import org.araneaframework.core.util.ExceptionUtil;
034:
035: /**
036: * This class provides a memory based implementation of the list. It takes care
037: * of the filtering, ordering and returning data to the web components.
038: * Implementations should override method <code>loadData</code> loading the
039: * initial data for the list.
040: * <p>
041: * Note, that all operations on items are made on the list of "processed", that
042: * is ordered and filtered items.
043: *
044: * @author Jevgeni Kabanov (ekabanov <i>at</i> araneaframework <i>dot</i> org)
045: *
046: */
047: public abstract class MemoryBasedListDataProvider extends
048: BaseListDataProvider {
049: private static final Log log = LogFactory
050: .getLog(MemoryBasedListDataProvider.class);
051: private Set dataUpdateListeners = new HashSet(1);
052:
053: // *******************************************************************
054: // FIELDS
055: // *******************************************************************
056:
057: protected Class beanClass;
058:
059: protected List allData = new ArrayList();
060:
061: protected List processedData = new ArrayList();
062:
063: protected BeanFilter currentFilter = null;
064:
065: protected boolean doFilter = true;
066:
067: protected Comparator currentOrder = null;
068:
069: protected boolean doOrder = true;
070:
071: // *********************************************************************
072: // * CONSTRUCTORS
073: // *********************************************************************
074:
075: /**
076: * Creates the class initializing its parameters.
077: *
078: * @param beanClass
079: * Value Object class.
080: */
081: protected MemoryBasedListDataProvider(Class beanClass) {
082: this .beanClass = beanClass;
083: }
084:
085: // *********************************************************************
086: // * PUBLIC METHODS
087: // *********************************************************************
088:
089: /**
090: * Loads the data using the <code>loadData</code> method, initializes the
091: * filtering and ordering and returns the number of items loaded.
092: */
093: public void init() throws Exception {
094: this .allData = loadData();
095: this .processedData.addAll(this .allData);
096: }
097:
098: /**
099: * Empty.
100: */
101: public void destroy() {
102: }
103:
104: /**
105: * Returns the number of items in the processed list.
106: *
107: * @return the number of items in the processed list.
108: */
109: public Long getItemCount() throws Exception {
110: process(this .currentFilter, this .currentOrder, this .allData,
111: this .processedData);
112: return new Long(this .processedData.size());
113: }
114:
115: /**
116: * Returns <code>List</code> of all processed items.
117: *
118: * @return <code>List</code> of all processed items.
119: */
120: public ListItemsData getAllItems() throws Exception {
121: ListItemsData result = new ListItemsData();
122:
123: process(this .currentFilter, this .currentOrder, this .allData,
124: this .processedData);
125: result.setItemRange(this .processedData);
126: result.setTotalCount(getItemCount());
127:
128: return result;
129: }
130:
131: /**
132: * Returns a range of processed items, starting with <code>start</code>
133: * (indexing starts at 0) and <code>count</code> items after it.
134: *
135: * @param start
136: * the start of item range.
137: * @param count
138: * the count of items in the range.
139: */
140: public ListItemsData getItemRange(Long start, Long count)
141: throws Exception {
142: ListItemsData result = new ListItemsData();
143:
144: process(this .currentFilter, this .currentOrder, this .allData,
145: this .processedData);
146: result.setItemRange(getSubList(this .processedData, start
147: .intValue(), count == null ? -1 : start.intValue()
148: + count.intValue() - 1));
149: result.setTotalCount(getItemCount());
150: return result;
151: }
152:
153: /**
154: * Returns a processed item by index.
155: *
156: * @param index
157: * 0-based index of processed item
158: * @return processed item.
159: */
160: public Object getItem(Long index) throws Exception {
161: process(this .currentFilter, this .currentOrder, this .allData,
162: this .processedData);
163: return this .processedData.get(index.intValue());
164: }
165:
166: /**
167: * Sets the list order expression.
168: */
169: public void setOrderExpression(ComparatorExpression orderExpr) {
170: this .doOrder = true;
171: if (orderExpr != null) {
172: this .currentOrder = new BeanOrder(orderExpr);
173: } else {
174: this .currentOrder = null;
175: }
176: notifyDataChangeListeners();
177: }
178:
179: /**
180: * Sets the list filter expression.
181: */
182: public void setFilterExpression(Expression filterExpr) {
183: this .doFilter = true;
184:
185: if (filterExpr != null) {
186: this .currentFilter = new BeanFilter(filterExpr);
187: } else {
188: this .currentFilter = null;
189: }
190: notifyDataChangeListeners();
191: }
192:
193: /**
194: * Refreshes the data, including reordering and refiltering.
195: */
196: public void refreshData() {
197: log.debug("Loading all data");
198: try {
199: this .allData = loadData();
200: } catch (Exception e) {
201: ExceptionUtil.uncheckException(e);
202: }
203: this .doFilter = true;
204: this .doOrder = true;
205:
206: notifyDataChangeListeners();
207: }
208:
209: /** @since 1.1 */
210: protected void notifyDataChangeListeners() {
211: for (Iterator i = dataUpdateListeners.iterator(); i.hasNext();) {
212: DataUpdateListener listener = (DataUpdateListener) i.next();
213: listener.onDataUpdate();
214: }
215: }
216:
217: public void addDataUpdateListener(DataUpdateListener listener) {
218: dataUpdateListeners.add(listener);
219: }
220:
221: public void removeDataUpdateListener(DataUpdateListener listener) {
222: dataUpdateListeners.remove(listener);
223: }
224:
225: // *********************************************************************
226: // * OVERRIDABLE METHODS
227: // *********************************************************************
228:
229: /**
230: * Processes the list items, filtering and ordering them, if there is need.
231: */
232: protected void process(BeanFilter beanFilter, Comparator beanOrder,
233: List all, List processed) {
234: if (this .doFilter) {
235: filter(beanFilter, all, processed);
236:
237: this .doFilter = false;
238: this .doOrder = true;
239: }
240: if (this .doOrder) {
241: order(beanOrder, processed);
242: this .doOrder = false;
243: }
244: }
245:
246: /**
247: * Filters the items.
248: *
249: * @param beanFilter
250: * Bean filter.
251: */
252: protected void filter(BeanFilter beanFilter, List all, List filtered) {
253: log.debug("Filtering list itmes");
254: filtered.clear();
255: if (beanFilter == null) {
256: filtered.addAll(all);
257: return;
258: }
259: for (Iterator i = all.iterator(); i.hasNext();) {
260: Object vo = i.next();
261: if (beanFilter.suits(vo)) {
262: filtered.add(vo);
263: }
264: }
265: }
266:
267: /**
268: * Orders the items.
269: */
270: protected void order(Comparator comparator, List ordered) {
271: log.debug("Ordering list itmes");
272: if (comparator != null) {
273: Collections.sort(ordered, comparator);
274: }
275: }
276:
277: /**
278: * Gets sublist from the list.
279: *
280: * @param records
281: * list where the sublist will be originated from.
282: * @param start
283: * first index of the element to be included to the sublist. List
284: * is 0-based.
285: * @param end
286: * last index of the element to be included to the sublist.
287: *
288: * @return sublist of the given list.
289: */
290: public static List getSubList(List records, int start, int end) {
291: int len = end - start + 1;
292:
293: List subRecords = null;
294: if (start < 0) {
295: start = 0;
296: }
297:
298: end = start + len;
299: if (end < 0) {
300: subRecords = records;
301: } else if (start >= records.size()) {
302: subRecords = new ArrayList();
303: } else {
304: if (end > records.size()) {
305: end = records.size();
306: }
307:
308: List subList = records.subList(start, end);
309: ArrayList tmpRecords = new ArrayList();
310: for (int i = 0; i < subList.size(); i++) {
311: tmpRecords.add(subList.get(i));
312: }
313: subRecords = tmpRecords;
314: }
315:
316: return new ArrayList(subRecords);
317: }
318:
319: // *********************************************************************
320: // * INNER CLASSES
321: // *********************************************************************
322:
323: class BeanOrder implements Comparator, Serializable {
324:
325: private static final long serialVersionUID = 1L;
326:
327: private ComparatorExpression orderExpr;
328: private BeanVariableResolver resolver1;
329: private BeanVariableResolver resolver2;
330:
331: public BeanOrder(ComparatorExpression orderExpr) {
332: this .orderExpr = orderExpr;
333: this .resolver1 = new BeanVariableResolver(
334: MemoryBasedListDataProvider.this .beanClass);
335: this .resolver2 = new BeanVariableResolver(
336: MemoryBasedListDataProvider.this .beanClass);
337: }
338:
339: public int compare(Object o1, Object o2) {
340: this .resolver1.setBean(o1);
341: this .resolver2.setBean(o2);
342: try {
343: return this .orderExpr.compare(this .resolver1,
344: this .resolver2);
345: } catch (ExpressionEvaluationException e) {
346: throw new NestableRuntimeException(e);
347: }
348: }
349: }
350:
351: class BeanFilter implements Serializable {
352:
353: private static final long serialVersionUID = 1L;
354:
355: private Expression filterExpr;
356:
357: private BeanVariableResolver resolver;
358:
359: public BeanFilter(Expression filterExpr) {
360: this .filterExpr = filterExpr;
361: this .resolver = new BeanVariableResolver(
362: MemoryBasedListDataProvider.this .beanClass);
363: }
364:
365: public boolean suits(Object bean) {
366: this .resolver.setBean(bean);
367: try {
368: return ((Boolean) this .filterExpr
369: .evaluate(this .resolver)).booleanValue();
370: } catch (ExpressionEvaluationException e) {
371: throw new NestableRuntimeException(e);
372: }
373: }
374: }
375:
376: // *********************************************************************
377: // * ABSTRACT METHODS
378: // *********************************************************************
379:
380: /**
381: * A callback method, which should load the data and return it as a list of
382: * Beans. It should use for that the <code>params</code> which are
383: * passed to it from web component.
384: *
385: * @return <code>List</code> of Value Objects.
386: */
387: public abstract List loadData() throws Exception;
388: }
|