blob: 4eeeb039de829b6307862a0d584801907a9ce3ea [file] [log] [blame]
/*
* 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);
}
}