001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.forms.binding;
018:
019: import java.util.ArrayList;
020: import java.util.Collection;
021: import java.util.Collections;
022: import java.util.HashMap;
023: import java.util.HashSet;
024: import java.util.Iterator;
025: import java.util.List;
026: import java.util.Map;
027: import java.util.Set;
028:
029: import org.apache.cocoon.forms.formmodel.Repeater.RepeaterRow;
030:
031: import org.apache.commons.jxpath.JXPathContext;
032:
033: /**
034: * Implements a collection that takes care about removed, updated and inserted
035: * elements, obtaining from a {@link RepeaterAdapter} all the needed objects.
036: *
037: * @version $Id: RepeaterJXPathCollection.java 479295 2006-11-26 06:15:25Z antonio $
038: */
039: public class RepeaterJXPathCollection {
040:
041: private JXPathContext storageContext;
042:
043: private Map updatedRows;
044: private Set deletedRows;
045: private List insertedRows;
046:
047: private int collectionSize;
048:
049: private RepeaterSorter sorter = null;
050: private RepeaterFilter filter = null;
051: private RepeaterAdapter adapter = null;
052:
053: private List itemsCache = new ArrayList();
054:
055: public void init(JXPathContext storageContext, String rowpath,
056: RepeaterAdapter adapter) {
057: this .storageContext = storageContext;
058: collectionSize = 0;
059: Object value = storageContext.getValue(rowpath);
060: if (value != null) {
061: if (value instanceof Collection) {
062: collectionSize = ((Collection) value).size();
063: } else {
064: collectionSize = ((Double) storageContext
065: .getValue("count(" + rowpath + ")")).intValue();
066: }
067: }
068:
069: this .updatedRows = new HashMap();
070: this .deletedRows = new HashSet();
071: this .insertedRows = new ArrayList();
072: this .adapter = adapter;
073: this .sorter = adapter.sortBy(null);
074: }
075:
076: private int getStartIndex(int start) {
077: int i = start;
078: RepeaterItem item = adapter.getItem(i);
079: // In case start is after the end of the collection try to go back
080: // until a valid item is found
081: while (item == null && i > 0) {
082: i--;
083: item = adapter.getItem(i);
084: }
085: if (item == null)
086: return 0;
087: // Now move the index ahead of one for each deleted item "before"
088: // the desired one
089: for (Iterator iter = deletedRows.iterator(); iter.hasNext();) {
090: RepeaterItem delitem = (RepeaterItem) iter.next();
091: if (sorter.compare(delitem, item) < 0) {
092: i++;
093: }
094: }
095: // And move it backward for each inserted row before the actual index
096: for (Iterator iter = insertedRows.iterator(); iter.hasNext();) {
097: RepeaterItem insitem = (RepeaterItem) iter.next();
098: if (sorter.compare(insitem, item) < 0) {
099: i--;
100: }
101: }
102: if (i < 0)
103: return 0;
104: // Now we should have the correct start
105: return i;
106: }
107:
108: public List getItems(int start, int length) {
109: List ret = new ArrayList();
110: int rlength = length;
111: int rstart = getStartIndex(start);
112: RepeaterItem startItem = null;
113: if (rstart > 0) {
114: // Try to fetch one element before, so that we can distinguish
115: // where we started after inferring added elements.
116: startItem = getItem(rstart - 1);
117: }
118: if (startItem != null) {
119: ret.add(startItem);
120: }
121: int i = rstart;
122: RepeaterItem item;
123: while (length > 0) {
124: item = getItem(i);
125: if (item == null)
126: break;
127: // skip deleted items
128: while (isDeleted(item)) {
129: i++;
130: item = getItem(i);
131: if (item == null)
132: break;
133: }
134: if (filter != null) {
135: while (!filter.shouldDisplay(item)) {
136: i++;
137: item = getItem(i);
138: if (item == null)
139: break;
140: }
141: }
142: if (item == null)
143: break;
144: ret.add(item);
145: i++;
146: length--;
147: }
148: // Infer the inserted rows.
149: if (this .insertedRows.size() > 0) {
150: if (filter != null) {
151: for (Iterator iter = this .insertedRows.iterator(); iter
152: .hasNext();) {
153: RepeaterItem acitem = (RepeaterItem) iter.next();
154: if (filter.shouldDisplay(acitem)) {
155: ret.add(acitem);
156: }
157: }
158: } else {
159: ret.addAll(this .insertedRows);
160: }
161: Collections.sort(ret, this .sorter);
162: }
163: if (startItem != null) {
164: // Now get from the element after our start element.
165: int pos = ret.indexOf(startItem);
166: for (int j = 0; j <= pos; j++) {
167: ret.remove(0);
168: }
169: }
170: while (ret.size() > rlength)
171: ret.remove(ret.size() - 1);
172:
173: this .itemsCache.clear();
174: this .itemsCache.addAll(ret);
175: return ret;
176: }
177:
178: public List getCachedItems() {
179: return this .itemsCache;
180: }
181:
182: public void flushCachedItems() {
183: this .itemsCache.clear();
184: }
185:
186: private RepeaterItem getItem(int i) {
187: // Take the element from the original collection and check if it was updated
188: RepeaterItem item = this .adapter.getItem(i);
189: if (item == null)
190: return null;
191: if (isUpdated(item)) {
192: item = (RepeaterItem) this .updatedRows
193: .get(item.getHandle());
194: }
195: return item;
196: }
197:
198: public void updateRow(RepeaterItem item) {
199: if (!isInserted(item) && !isDeleted(item)) {
200: this .updatedRows.put(item.getHandle(), item);
201: }
202: }
203:
204: public void deleteRow(RepeaterItem item) {
205: if (isInserted(item)) {
206: this .insertedRows.remove(item);
207: return;
208: } else if (isUpdated(item)) {
209: this .updatedRows.remove(item);
210: }
211: this .deletedRows.add(item);
212: }
213:
214: public void addRow(RepeaterItem item) {
215: this .insertedRows.add(item);
216: }
217:
218: public int getOriginalCollectionSize() {
219: return collectionSize;
220: }
221:
222: public int getActualCollectionSize() {
223: return getOriginalCollectionSize() - this .deletedRows.size()
224: + this .insertedRows.size();
225: }
226:
227: /*
228: * convenience methods to search the cache
229: */
230:
231: private boolean isUpdated(RepeaterItem item) {
232: return this .updatedRows.containsKey(item.getHandle());
233: }
234:
235: private boolean isDeleted(RepeaterItem item) {
236: return this .deletedRows.contains(item);
237: }
238:
239: private boolean isInserted(RepeaterItem item) {
240: return this .insertedRows.contains(item);
241: }
242:
243: public JXPathContext getStorageContext() {
244: return storageContext;
245: }
246:
247: public List getDeletedRows() {
248: // FIXME we should sort by natural order
249: List ret = new ArrayList(this .deletedRows);
250: Collections.sort(ret, this .sorter);
251: Collections.reverse(ret);
252: return ret;
253: }
254:
255: public List getInsertedRows() {
256: return insertedRows;
257: }
258:
259: public Collection getUpdatedRows() {
260: return updatedRows.values();
261: }
262:
263: public RepeaterAdapter getAdapter() {
264: return this .adapter;
265: }
266:
267: public void addRow(RepeaterRow row) {
268: RepeaterItem item = this .adapter.generateItem(row);
269: this .addRow(item);
270: }
271:
272: public void sortBy(String field) {
273: this .sorter = this .adapter.sortBy(field);
274: }
275:
276: public void filter(String field, Object value) {
277: if (filter == null) {
278: filter = this.adapter.getFilter();
279: }
280: filter.setFilter(field, value);
281: }
282: }
|