package er.ajax;
import java.util.Enumeration;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation._NSDelegate;
public class AjaxTreeModel {
private Object _rootTreeNode;
private NSMutableSet _expandedTreeNodes;
private NSMutableSet _collapsedTreeNodes;
private String _parentTreeNodeKeyPath;
private String _childrenTreeNodesKeyPath;
private String _isLeafKeyPath;
private boolean _allExpanded;
private boolean _rootExpanded;
private _NSDelegate _delegate;
public AjaxTreeModel() {
_expandedTreeNodes = new NSMutableSet();
_collapsedTreeNodes = new NSMutableSet();
_delegate = new _NSDelegate(AjaxTreeModel.Delegate.class);
}
public void setDelegate(Object delegate) {
_delegate.setDelegate(delegate);
}
public Object delegate() {
return _delegate.delegate();
}
public void setRootExpanded(boolean rootExpanded) {
if (_rootExpanded != rootExpanded) {
_rootExpanded = rootExpanded;
expandRootIfNecessary();
}
}
public boolean isRootExpanded() {
return _rootExpanded;
}
public void setAllExpanded(boolean allExpanded) {
_allExpanded = allExpanded;
}
public boolean isAllExpanded() {
return _allExpanded;
}
public void setParentTreeNodeKeyPath(String parentTreeNodeKeyPath) {
_parentTreeNodeKeyPath = parentTreeNodeKeyPath;
}
public String parentTreeNodeKeyPath() {
return _parentTreeNodeKeyPath;
}
public void setChildrenTreeNodesKeyPath(String childrenTreeNodesKayPath) {
_childrenTreeNodesKeyPath = childrenTreeNodesKayPath;
}
public String childrenTreeNodesKeyPath() {
return _childrenTreeNodesKeyPath;
}
public void setIsLeafKeyPath(String isLeafKeyPath) {
_isLeafKeyPath = isLeafKeyPath;
}
public String isLeafKeyPath() {
return _isLeafKeyPath;
}
public void setRootTreeNode(Object rootTreeNode) {
if (rootTreeNode != _rootTreeNode) {
_rootTreeNode = rootTreeNode;
_expandedTreeNodes.removeAllObjects();
_collapsedTreeNodes.removeAllObjects();
expandRootIfNecessary();
}
}
public Object rootTreeNode() {
return _rootTreeNode;
}
public boolean isExpanded(Object treeNode) {
boolean expanded;
if (_allExpanded) {
expanded = !_collapsedTreeNodes.containsObject(treeNode);
}
else {
expanded = _expandedTreeNodes.containsObject(treeNode);
}
return expanded;
}
public void setExpanded(Object treeNode, boolean expanded) {
if (_rootExpanded && treeNode == _rootTreeNode && !expanded) {
return;
}
if (expanded) {
if (_allExpanded) {
_collapsedTreeNodes.removeObject(treeNode);
}
else {
_expandedTreeNodes.addObject(treeNode);
}
}
else {
if (_allExpanded) {
_collapsedTreeNodes.addObject(treeNode);
}
else {
_expandedTreeNodes.removeObject(treeNode);
}
}
}
public void collapseAll() {
if (_allExpanded) {
_allExpanded = false;
}
clearExpandedAndCollapsed();
}
public void expandAll() {
if (!_allExpanded) {
_allExpanded = true;
}
clearExpandedAndCollapsed();
}
protected void clearExpandedAndCollapsed() {
_collapsedTreeNodes.removeAllObjects();
_expandedTreeNodes.removeAllObjects();
expandRootIfNecessary();
}
protected void expandRootIfNecessary() {
if (_rootExpanded && _rootTreeNode != null) {
setExpanded(_rootTreeNode, true);
}
}
public int level(Object treeNode) {
Object parentTreeNode = treeNode;
int level;
for (level = 0; parentTreeNode != null; parentTreeNode = parentTreeNode(parentTreeNode), level++) {
// do nothing
}
return level - 1;
}
public boolean isLeaf(Object node) {
boolean isLeaf;
if (_isLeafKeyPath == null) {
NSArray childrenTreeNodes = childrenTreeNodes(node);
isLeaf = childrenTreeNodes == null || childrenTreeNodes.count() == 0;
}
else if (_delegate.respondsTo("isLeaf")) {
isLeaf = _delegate.booleanPerform("isLeaf", node);
}
else {
Boolean isLeafBoolean = (Boolean) NSKeyValueCodingAdditions.Utility.valueForKeyPath(node, _isLeafKeyPath);
isLeaf = isLeafBoolean.booleanValue();
}
return isLeaf;
}
public Object parentTreeNode(Object node) {
Object parentTreeNode = null;
if (_delegate.respondsTo("parentTreeNode")) {
parentTreeNode = _delegate.perform("parentTreeNode", node);
}
else if (node != null) {
parentTreeNode = NSKeyValueCodingAdditions.Utility.valueForKeyPath(node, _parentTreeNodeKeyPath);
}
return parentTreeNode;
}
public NSArray childrenTreeNodes(Object node) {
NSArray childrenTreeNodes;
if (_delegate.respondsTo("childrenTreeNodes")) {
childrenTreeNodes = (NSArray) _delegate.perform("childrenTreeNodes", node);
}
else {
childrenTreeNodes = (NSArray) NSKeyValueCodingAdditions.Utility.valueForKeyPath(node, _childrenTreeNodesKeyPath);
}
return childrenTreeNodes;
}
public Enumeration depthFirstEnumeration(Object node, boolean enumeratedClosedNodes) {
return new DepthFirstEnumeration(node, enumeratedClosedNodes);
}
public Enumeration rootDepthFirstEnumeration(boolean enumeratedClosedNodes) {
return new DepthFirstEnumeration(_rootTreeNode, enumeratedClosedNodes);
}
public static interface Delegate {
public boolean isLeaf(Object node);
public Object parentTreeNode(Object node);
public NSArray childrenTreeNodes(Object node);
}
protected class DepthFirstEnumeration implements Enumeration {
private Object _rootNode;
private Enumeration _childrenEnumeration;
private Enumeration _subtreeEnumeration;
private boolean _enumerateClosedNodes;
public DepthFirstEnumeration(Object rootNode, boolean enumerateClosedNodes) {
_rootNode = rootNode;
_enumerateClosedNodes = enumerateClosedNodes;
if (_enumerateClosedNodes || isExpanded(rootNode)) {
_childrenEnumeration = childrenTreeNodes(rootNode).objectEnumerator();
}
_subtreeEnumeration = NSArray.EmptyArray.objectEnumerator();
}
public boolean hasMoreElements() {
return _rootNode != null;
}
public Object nextElement() {
Object retval;
if (_subtreeEnumeration.hasMoreElements()) {
retval = _subtreeEnumeration.nextElement();
}
else if (_childrenEnumeration != null && _childrenEnumeration.hasMoreElements()) {
_subtreeEnumeration = new DepthFirstEnumeration(_childrenEnumeration.nextElement(), _enumerateClosedNodes);
retval = _subtreeEnumeration.nextElement();
}
else {
retval = _rootNode;
_rootNode = null;
}
return retval;
}
}
/**
* WrapperNode is useful if your objects form a
* graph instead of a tree and you want to maintain the unique
* branching to a particular node as the user navigates through
* the tree. isLeaf has a default implementation that you may
* want to overried if you can provide a "smarter"
* implementation.
*
* @author mschrag
*/
public abstract static class WrapperNode {
private WrapperNode _parent;
private Object _userObject;
public WrapperNode(WrapperNode parent, Object userObject) {
_parent = parent;
_userObject = userObject;
}
public Object userObject() {
return _userObject;
}
protected abstract WrapperNode _createChildNode(Object userObject);
protected abstract NSArray _childrenTreeNodes();
public NSArray childrenTreeNodes() {
NSArray childrenTreeNodes = _childrenTreeNodes();
if (childrenTreeNodes != null && childrenTreeNodes.count() > 0) {
NSMutableArray wrappedTreeNodes = new NSMutableArray();
Enumeration childrenTreeNodesEnum = childrenTreeNodes.objectEnumerator();
while (childrenTreeNodesEnum.hasMoreElements()) {
Object obj = childrenTreeNodesEnum.nextElement();
wrappedTreeNodes.addObject(_createChildNode(obj));
}
childrenTreeNodes = wrappedTreeNodes;
}
return childrenTreeNodes;
}
public boolean isLeaf() {
NSArray childrenTreeNodes = _childrenTreeNodes();
boolean isLeaf = childrenTreeNodes == null || childrenTreeNodes.count() == 0;
return isLeaf;
}
public WrapperNode parentTreeNode() {
return _parent;
}
@Override
public int hashCode() {
int hashCode;
if (_userObject == null) {
hashCode = super.hashCode();
}
else {
hashCode = _userObject.hashCode();
}
if (_parent != null) {
hashCode *= _parent.hashCode();
}
return hashCode;
}
@Override
public boolean equals(Object obj) {
boolean equals;
if (obj instanceof WrapperNode) {
WrapperNode wrapperNode = (WrapperNode)obj;
if (_userObject == null) {
equals = (wrapperNode._userObject == null);
}
else {
equals = _userObject.equals(wrapperNode._userObject);
}
if (equals) {
if (_parent == null) {
equals = (wrapperNode._parent == null);
}
else {
equals = _parent.equals(wrapperNode._parent);
}
}
}
else {
equals = false;
}
return equals;
}
}
}