| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.cocoon.forms.binding; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.cocoon.forms.formmodel.Repeater.RepeaterRow; |
| |
| import org.apache.commons.jxpath.JXPathContext; |
| |
| /** |
| * Implements a collection that takes care about removed, updated and inserted |
| * elements, obtaining from a {@link RepeaterAdapter} all the needed objects. |
| * |
| * @version $Id$ |
| */ |
| public class RepeaterJXPathCollection { |
| |
| private JXPathContext storageContext; |
| |
| private Map updatedRows; |
| private Set deletedRows; |
| private List insertedRows; |
| |
| private int collectionSize; |
| |
| private RepeaterSorter sorter = null; |
| private RepeaterFilter filter = null; |
| private RepeaterAdapter adapter = null; |
| |
| private List itemsCache = new ArrayList(); |
| |
| public void init(JXPathContext storageContext, String rowpath, RepeaterAdapter adapter) { |
| this.storageContext = storageContext; |
| collectionSize = 0; |
| Object value = storageContext.getValue(rowpath); |
| if (value != null) { |
| if (value instanceof Collection) { |
| collectionSize = ((Collection) value).size(); |
| } else { |
| collectionSize = ((Double) storageContext.getValue("count(" + rowpath + ")")).intValue(); |
| } |
| } |
| |
| this.updatedRows = new HashMap(); |
| this.deletedRows = new HashSet(); |
| this.insertedRows = new ArrayList(); |
| this.adapter = adapter; |
| this.sorter = adapter.sortBy(null); |
| } |
| |
| private int getStartIndex(int start) { |
| int i = start; |
| RepeaterItem item = adapter.getItem(i); |
| // In case start is after the end of the collection try to go back |
| // until a valid item is found |
| while (item == null && i > 0) { |
| i--; |
| item = adapter.getItem(i); |
| } |
| if (item == null) { |
| return 0; |
| } |
| // Now move the index ahead of one for each deleted item "before" |
| // the desired one |
| for (Iterator iter = deletedRows.iterator(); iter.hasNext();) { |
| RepeaterItem delitem = (RepeaterItem) iter.next(); |
| if (sorter.compare(delitem, item) < 0) { |
| i++; |
| } |
| } |
| // And move it backward for each inserted row before the actual index |
| for (Iterator iter = insertedRows.iterator(); iter.hasNext();) { |
| RepeaterItem insitem = (RepeaterItem) iter.next(); |
| if (sorter.compare(insitem, item) < 0) { |
| i--; |
| } |
| } |
| if (i < 0) { |
| return 0; |
| } |
| // Now we should have the correct start |
| return i; |
| } |
| |
| public List getItems(int start, int length) { |
| List ret = new ArrayList(); |
| int rlength = length; |
| int rstart = getStartIndex(start); |
| RepeaterItem startItem = null; |
| if (rstart > 0) { |
| // Try to fetch one element before, so that we can distinguish |
| // where we started after inferring added elements. |
| startItem = getItem(rstart - 1); |
| } |
| if (startItem != null) { |
| ret.add(startItem); |
| } |
| int i = rstart; |
| RepeaterItem item; |
| while (length > 0) { |
| item = getItem(i); |
| if (item == null) { |
| break; |
| } |
| // skip deleted items |
| while (isDeleted(item)) { |
| i++; |
| item = getItem(i); |
| if (item == null) { |
| break; |
| } |
| } |
| if (filter != null) { |
| while (!filter.shouldDisplay(item)) { |
| i++; |
| item = getItem(i); |
| if (item == null) { |
| break; |
| } |
| } |
| } |
| if (item == null) { |
| break; |
| } |
| ret.add(item); |
| i++; |
| length--; |
| } |
| // Infer the inserted rows. |
| if (this.insertedRows.size() > 0) { |
| if (filter != null) { |
| for (Iterator iter = this.insertedRows.iterator(); iter.hasNext();) { |
| RepeaterItem acitem = (RepeaterItem) iter.next(); |
| if (filter.shouldDisplay(acitem)) { |
| ret.add(acitem); |
| } |
| } |
| } else { |
| ret.addAll(this.insertedRows); |
| } |
| Collections.sort(ret, this.sorter); |
| } |
| if (startItem != null) { |
| // Now get from the element after our start element. |
| int pos = ret.indexOf(startItem); |
| for (int j = 0; j <= pos; j++) { |
| ret.remove(0); |
| } |
| } |
| while (ret.size() > rlength) { |
| ret.remove(ret.size() - 1); |
| } |
| |
| this.itemsCache.clear(); |
| this.itemsCache.addAll(ret); |
| return ret; |
| } |
| |
| public List getCachedItems() { |
| return this.itemsCache; |
| } |
| |
| public void flushCachedItems() { |
| this.itemsCache.clear(); |
| } |
| |
| private RepeaterItem getItem(int i) { |
| // Take the element from the original collection and check if it was updated |
| RepeaterItem item = this.adapter.getItem(i); |
| if (item == null) { |
| return null; |
| } |
| if (isUpdated(item)) { |
| item = (RepeaterItem) this.updatedRows.get(item.getHandle()); |
| } |
| return item; |
| } |
| |
| public void updateRow(RepeaterItem item) { |
| if (!isInserted(item) && !isDeleted(item)) { |
| this.updatedRows.put(item.getHandle(), item); |
| } |
| } |
| |
| public void deleteRow(RepeaterItem item) { |
| if (isInserted(item)) { |
| this.insertedRows.remove(item); |
| return; |
| } else if (isUpdated(item)) { |
| this.updatedRows.remove(item); |
| } |
| this.deletedRows.add(item); |
| } |
| |
| public void addRow(RepeaterItem item) { |
| this.insertedRows.add(item); |
| } |
| |
| public int getOriginalCollectionSize() { |
| return collectionSize; |
| } |
| |
| public int getActualCollectionSize() { |
| return getOriginalCollectionSize() - this.deletedRows.size() + this.insertedRows.size(); |
| } |
| |
| /* |
| * convenience methods to search the cache |
| */ |
| |
| private boolean isUpdated(RepeaterItem item) { |
| return this.updatedRows.containsKey(item.getHandle()); |
| } |
| |
| private boolean isDeleted(RepeaterItem item) { |
| return this.deletedRows.contains(item); |
| } |
| |
| private boolean isInserted(RepeaterItem item) { |
| return this.insertedRows.contains(item); |
| } |
| |
| public JXPathContext getStorageContext() { |
| return storageContext; |
| } |
| |
| public List getDeletedRows() { |
| // FIXME we should sort by natural order |
| List ret = new ArrayList(this.deletedRows); |
| Collections.sort(ret, this.sorter); |
| Collections.reverse(ret); |
| return ret; |
| } |
| |
| public List getInsertedRows() { |
| return insertedRows; |
| } |
| |
| public Collection getUpdatedRows() { |
| return updatedRows.values(); |
| } |
| |
| public RepeaterAdapter getAdapter() { |
| return this.adapter; |
| } |
| |
| public void addRow(RepeaterRow row) { |
| RepeaterItem item = this.adapter.generateItem(row); |
| this.addRow(item); |
| } |
| |
| public void sortBy(String field) { |
| this.sorter = this.adapter.sortBy(field); |
| } |
| |
| public void filter(String field, Object value) { |
| if (filter == null) { |
| filter = this.adapter.getFilter(); |
| } |
| filter.setFilter(field, value); |
| } |
| } |