/**
* License Agreement.
*
* Rich Faces - Natural Ajax for Java Server Faces (JSF)
*
* Copyright (C) 2007 Exadel, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License version 2.1 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.richfaces.model;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.w3c.dom.NamedNodeMap;
/**
* That is intended for internal use
*
* @author Nick Belaevski mailto:nbelaevski@exadel.com created 25.07.2007
*
*/
public class StackingTreeModel extends AbstractTreeDataModel {
//ctor arguments
private String id;
private String var;
private StackingTreeModelDataProvider dataProvider;
//structural elements
private StackingTreeModel parent;
private Map<String, StackingTreeModel> models = new LinkedHashMap<String, StackingTreeModel>();
private Object rowKey;
private class StackEntry {
private Object modelKey;
private Object varObject;
private StackingTreeModel model;
private Object rowData;
public StackEntry(Object varObject, Object modelKey, Object rowData, StackingTreeModel model) {
super();
this.varObject = varObject;
this.modelKey = modelKey;
this.rowData = rowData;
this.model = model;
}
}
// private StackingTreeModel stackingTreeModel;
private LinkedList<StackEntry> stackEntries = new LinkedList<StackEntry>();
public ExtendedDataModel getDataModel() {
Object data = dataProvider.getData();
ExtendedDataModel dataModel;
if (data instanceof Map || data instanceof NamedNodeMap) {
dataModel = new MapDataModel();
} else {
dataModel = new SequenceDataModel();
}
dataModel.setWrappedData(data);
return dataModel;
}
protected StackingTreeModel getCurrentModel() {
if (this.rowKey == null) {
return this;
}
if (isRowAvailable()) {
return ((StackEntry) stackEntries.getLast()).model;
}
throw new IllegalStateException(
"No tree element available or row key not set!");
}
public boolean isEmpty() {
//TODO optimize that
return getDataModel().getRowCount() == 0;
}
private void leaveModel(Iterator<StackEntry> iterator, StackEntry currentEntry, FacesContext context) {
if (iterator == null) {
return ;
}
LinkedList<StackEntry> stack = new LinkedList<StackEntry>();
StackingTreeModel lastModel = null;
if (currentEntry != null) {
iterator.remove();
stack.addFirst(currentEntry);
lastModel = currentEntry.model;
}
while (iterator.hasNext()) {
StackEntry entry = (StackEntry) iterator.next();
if (entry.model != lastModel) {
//always true for non-recursive models
lastModel = entry.model;
stack.addFirst(entry);
}
iterator.remove();
}
for (Iterator<StackEntry> iterator2 = stack.iterator(); iterator2.hasNext();) {
StackEntry stackEntry = (StackEntry) iterator2.next();
stackEntry.model.setupVariable(stackEntry.varObject, context);
}
}
protected StackingTreeModel doSetupKey(Iterator<StackingTreeModelKey> keyIterator, Iterator<StackEntry> entriesIterator, FacesContext context, Object modelKey) {
if (modelKey != null) {
if (!setupModel(modelKey, context)) {
//no key is available
leaveModel(getRoot().stackEntries.iterator(), null, context);
return null;
}
//TODO what's here?
}
if (keyIterator != null && keyIterator.hasNext()) {
StackingTreeModelKey key = keyIterator.next();
StackingTreeModel stackingTreeModel = this.getInternalModelById(key.modelId);
Iterator<StackEntry> nextEntriesIterator = null;
Object nextModelKey = key.modelKey;
if (entriesIterator != null && entriesIterator.hasNext()) {
StackEntry entry = entriesIterator.next();
if (!entry.model.equals(stackingTreeModel) || !entry.modelKey.equals(nextModelKey)) {
leaveModel(entriesIterator, entry, context);
} else {
//continue iterating entries, they still lead us by key path
nextEntriesIterator = entriesIterator;
nextModelKey = null;
}
}
//should not be called when nextEntriesIterator & nextModelKey are both valid
return stackingTreeModel.doSetupKey(keyIterator, nextEntriesIterator, context, nextModelKey);
} else {
leaveModel(entriesIterator, null, context);
return this;
}
}
protected StackingTreeModel setupKey(Object key, FacesContext context) {
if (key == this.rowKey) {
if (stackEntries.isEmpty()) {
return this;
} else {
return (stackEntries.getLast()).model;
}
} else {
Iterator<StackingTreeModelKey> keyIterator = null;
if (key != null) {
keyIterator = ((ListRowKey<StackingTreeModelKey>) key).iterator();
}
StackingTreeModel model = doSetupKey(keyIterator, stackEntries.iterator(), context, null);
this.rowKey = key;
return model;
}
}
public StackingTreeModel(String id, String var, StackingTreeModelDataProvider dataProvider) {
super();
this.id = id;
this.var = var;
this.dataProvider = dataProvider;
}
public StackingTreeModel() {
this(null, null, null);
}
private Object setupVariable(Object variable, FacesContext context) {
if (var != null) {
Map<String, Object> map = context.getExternalContext().getRequestMap();
return map.put(var, variable);
}
return null;
}
public boolean setupModel(Object key, FacesContext facesContext) {
ExtendedDataModel dataModel = getDataModel();
dataModel.setRowKey(key);
if (dataModel.isRowAvailable()) {
Object rowData = dataModel.getRowData();
//System.out.println("StackingTreeModel.setupModel() " + rowData);
Object varObject = setupVariable(rowData, facesContext);
getRoot().stackEntries.add(new StackEntry(varObject, key, rowData, this));
return true;
}
return false;
}
public void setParent(StackingTreeModel parent) {
this.parent = parent;
}
/*
* (non-Javadoc)
*
* @see org.richfaces.model.AbstractTreeDataModel#getTreeNode()
*/
public TreeNode getTreeNode() {
if (isRowAvailable()) {
return null;
}
throw new IllegalStateException(
"No tree element available or row key not set!");
}
/*
* (non-Javadoc)
*
* @see org.richfaces.model.AbstractTreeDataModel#isLeaf()
*/
public boolean isLeaf() {
if (isRowAvailable()) {
StackEntry lastEntry = (StackEntry) stackEntries.getLast();
for (Iterator<StackingTreeModel> iterator = lastEntry.model.getInternalModelsIterator(); iterator.hasNext();) {
StackingTreeModel stackingTreeModel = iterator.next();
if (!stackingTreeModel.isEmpty()) {
return false;
}
}
return true;
}
throw new IllegalStateException(
"No tree element available or row key not set!");
}
protected StackingTreeModel getRoot() {
if (parent != null) {
return parent.getRoot();
}
return this;
}
protected void doWalk(FacesContext context, DataVisitor dataVisitor,
Range range, ListRowKey argumentKey, Object argument,
boolean last) throws IOException {
TreeRange treeRange = (TreeRange) range;
if (treeRange == null || treeRange.processNode(argumentKey)) {
StackingTreeModel rootModel = getRoot();
if (argumentKey != null) {
processElement(context, dataVisitor, argument, argumentKey, last);
}
final ShiftingDataVisitor shiftingDataVisitor = new ShiftingDataVisitor(
new Visitor1(dataVisitor));
if (treeRange == null || treeRange.processChildren(argumentKey)) {
Object savedRowKey = rootModel.getRowKey();
//setup key in order for nested components to initialize data models
rootModel.setRowKey(context, argumentKey);
Iterator<StackingTreeModel> iterator = this.getInternalModelsIterator();
rootModel.setRowKey(context, savedRowKey);
while (iterator.hasNext()) {
final StackingTreeModel model = iterator.next();
savedRowKey = rootModel.getRowKey();
rootModel.setRowKey(context, argumentKey);
final ExtendedDataModel scalarModel = model.getDataModel();
rootModel.setRowKey(context, savedRowKey);
Argument argument2 = new Argument();
argument2.listRowKey = argumentKey;
argument2.argument = argument;
// setup current model
argument2.model = model;
argument2.range = range;
scalarModel.walk(context, new DataVisitor() {
public void process(FacesContext context,
Object rowKey, Object argument)
throws IOException {
Object key = scalarModel.getRowKey();
scalarModel.setRowKey(rowKey);
Object data = scalarModel.getRowData();
Object variable = model.setupVariable(data, context);
boolean activeData = model.isActiveData();
model.setupVariable(variable, context);
scalarModel.setRowKey(key);
if (activeData) {
shiftingDataVisitor.process(context, rowKey, argument);
}
}
}, null, argument2);
}
}
shiftingDataVisitor.end(context);
}
}
private StackingTreeModel getInternalModelById(String id) {
StackingTreeModel model = getModelById(id);
if (model.isActive()) {
return model;
}
throw new IllegalStateException();
}
public StackingTreeModel getModelById(String id) {
return (StackingTreeModel) models.get(id);
}
private Iterator<StackingTreeModel> getInternalModelsIterator() {
return new FilterIterator(getModelsIterator(), ACTIVE_MODEL_PREDICATE);
}
public Iterator<StackingTreeModel> getModelsIterator() {
return models.values().iterator();
}
public void walk(FacesContext context, DataVisitor dataVisitor,
Range range, Object rowKey, Object argument,
boolean last) throws IOException {
StackingTreeModel rootModel = getRoot();
if (rowKey != null) {
ListRowKey listRowKey = (ListRowKey) rowKey;
StackingTreeModel treeModel = rootModel.setupKey(listRowKey, context);
treeModel.doWalk(context, dataVisitor, range, listRowKey, argument,
last);
} else {
doWalk(context, dataVisitor, range,
(ListRowKey) rowKey, argument, last);
}
}
private class Argument {
private ListRowKey listRowKey;
private StackingTreeModel model;
private Range range;
private Object argument;
}
private class Visitor1 implements DataVisitor, LastElementAware {
private DataVisitor dataVisitor;
private boolean theLast;
public Visitor1(DataVisitor dataVisitor) {
super();
this.dataVisitor = dataVisitor;
}
public void process(FacesContext context, Object rowKey, Object argument)
throws IOException {
Argument a = (Argument) argument;
ListRowKey listRowKey = new ListRowKey(a.listRowKey, new StackingTreeModelKey(
a.model.id, rowKey));
//System.out.println(".walk() " + (theLast ? " * " : "") + listRowKey);
a.model.doWalk(context, dataVisitor, a.range, listRowKey, a.argument,
theLast);
}
public void resetLastElement() {
theLast = false;
}
public void setLastElement() {
theLast = true;
}
}
private static class ShiftingDataVisitor implements DataVisitor {
private DataVisitor dataVisitor;
public ShiftingDataVisitor(DataVisitor dataVisitor) {
super();
this.dataVisitor = dataVisitor;
}
private Object rowKey;
private Object argument;
private boolean shifted = false;
public void process(FacesContext context, Object rowKey, Object argument)
throws IOException {
if (!shifted) {
this.rowKey = rowKey;
this.argument = argument;
this.shifted = true;
} else {
dataVisitor.process(context, this.rowKey, this.argument);
this.rowKey = rowKey;
this.argument = argument;
}
}
public void end(FacesContext context) throws IOException {
if (shifted) {
try {
((LastElementAware) dataVisitor).setLastElement();
dataVisitor.process(context, this.rowKey, argument);
} finally {
((LastElementAware) dataVisitor).resetLastElement();
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.richfaces.model.AbstractTreeDataModel#walkModel(javax.faces.context.FacesContext,
* org.ajax4jsf.model.DataVisitor, org.ajax4jsf.model.Range,
* java.lang.Object, java.lang.Object, boolean)
*/
public void walkModel(FacesContext facesContext, DataVisitor visitor,
Range range, Object key, Object argument, boolean last)
throws IOException {
walk(facesContext, visitor, range, key, argument, last);
}
/*
* (non-Javadoc)
*
* @see org.ajax4jsf.model.ExtendedDataModel#getRowKey()
*/
public Object getRowKey() {
return rowKey;
}
public void setRowKey(Object key) {
setRowKey(FacesContext.getCurrentInstance(), key);
}
public void setRowKey(FacesContext context, Object key) {
setupKey(key, context);
}
public void addStackingModel(StackingTreeModel model) {
this.models.put(model.id, model);
model.setParent(this);
}
public void removeStackingModel(StackingTreeModel model) {
this.models.remove(model.id);
model.setParent(null);
}
/*
* (non-Javadoc)
*
* @see javax.faces.model.DataModel#getRowData()
*/
public Object getRowData() {
if (isRowAvailable()) {
StackEntry lastEntry = (StackEntry) stackEntries.getLast();
return lastEntry.rowData;
}
throw new IllegalStateException(
"No tree element available or row key not set!");
}
/*
* (non-Javadoc)
*
* @see javax.faces.model.DataModel#isRowAvailable()
*/
public boolean isRowAvailable() {
return !stackEntries.isEmpty();
}
public StackingTreeModel getParent() {
return parent;
}
protected boolean isActiveData() {
return true;
}
protected boolean isActive() {
return true;
}
private final static Predicate ACTIVE_MODEL_PREDICATE = new Predicate() {
public boolean evaluate(Object object) {
StackingTreeModel model = (StackingTreeModel) object;
if (model == null) {
return false;
}
return model.isActive();
}
};
@Override
public Object getWrappedData() {
throw new UnsupportedOperationException();
}
@Override
public void setWrappedData(Object data) {
throw new UnsupportedOperationException();
}
@Override
public Object convertToKey(FacesContext context, String keyString,
UIComponent component, Converter converter) {
//force model leave
setRowKey(context, null);
String[] strings = ListRowKey.fromString(keyString);
int l = strings.length / 2;
List<Object> list = new ArrayList<Object>(l);
StackingTreeModel model = getRoot();
for (int i = 0; i < l; i++) {
int idx = i*2;
String modelId = strings[idx];
model = model.getModelById(modelId);
if (model.isActive()) {
Object key = model.convert(context, strings[idx + 1], component, converter);
if (key == null) {
return null;
}
list.add(new StackingTreeModelKey(modelId, key));
if (!model.setupModel(key, context) || !model.isActiveData()) {
return null;
}
} else {
return null;
}
}
return new ListRowKey<Object>(list);
}
protected Object convert(FacesContext context, String string,
UIComponent component, Converter converter) {
ConvertableKeyModel convertable = (ConvertableKeyModel) getDataModel();
return convertable.getKeyAsObject(context, string, component, converter);
}
public String getId() {
return id;
}
}