package com.revolsys.ui.web.config;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
public final class SiteNode implements Comparable, Cloneable {
private static final Logger log = Logger.getLogger(SiteNode.class);
/** The controller for the node. */
private SiteNodeController controller;
/** The map of node names to nodes. */
private final Map nodeMap = new TreeMap();
/** The direct child nodes. */
private final Collection nodes = new ArrayList();
/** The parent node. */
private SiteNode parent;
/** The path to the node. */
private String path;
/** The map from regular expression to site node. */
private final Map regexNodes = new HashMap();
/** The ordered set of site node patterns. */
private final Set regexPatterns = new LinkedHashSet();
/**
* Construct a new root SiteNode.
*/
public SiteNode() {
}
/**
* Construct a deep copy of the node. Does not retain the reference to the
* parent node.
*
* @param node The node to copy.
*/
public SiteNode(final SiteNode node) {
this.path = node.path;
if (node.hasController()) {
this.controller = (SiteNodeController)node.controller.clone();
}
setNodes(node.getNodes());
}
/**
* Construct a new SiteNode for the controller.
*
* @param controller The controller.
*/
public SiteNode(final SiteNodeController controller) {
this.controller = controller;
}
/**
* Construct a new site node with the specified path.
*
* @param path The path.
*/
public SiteNode(final String path) {
this.path = path;
}
/**
* Add the node as a child of this node.
*
* @param node The node.
*/
protected void addNode(final SiteNode node) {
node.setParent(this);
this.nodeMap.put(node.getPath(), node);
this.nodes.add(node);
}
/**
* Add the path for the parent node and this node to the string buffer.
*
* @param buffer The buffer to add the path to.
*/
private void addPath(final StringBuilder buffer) {
if (hasParent()) {
this.parent.addPath(buffer);
}
if (buffer.length() == 0 || buffer.charAt(buffer.length() - 1) != '/') {
buffer.append("/");
}
if (this.path != null) {
buffer.append(getPath());
}
}
/**
* Construct a new deep copy of this node. Does not retain the reference to the
* parent node.
*
* @return The cloned node.
*/
@Override
protected Object clone() {
return new SiteNode(this);
}
/**
* Compare this node to another node. The comparison is performed on the path.
*
* @param o The object to compare.
* @return -1 (less than), 0 (equal) or 1 (greater than).
*/
@Override
public int compareTo(final Object o) {
if (o instanceof SiteNode) {
final SiteNode node = (SiteNode)o;
if (this.path == null) {
return -1;
} else if (node.path == null) {
return 1;
} else {
return this.path.compareTo(node.path);
}
}
return -1;
}
/**
* Find the site node matching the path.
*
* @param path The path.
* @return The site node.
*/
public SiteNode findSiteNode(final String path) {
SiteNode node = null;
final int slashIndex = path.indexOf('/');
if (slashIndex == 0) {
final String childPath = path.substring(slashIndex + 1);
node = findSiteNode(childPath);
} else if (slashIndex != -1) {
final String childName = path.substring(0, slashIndex);
final String childPath = path.substring(slashIndex + 1);
final SiteNode childNode = findSiteNode(childName);
if (childNode != null) {
node = childNode.findSiteNode(childPath);
}
} else {
node = getNode(path);
}
if (node == null) {
for (final Iterator nodeIter = this.regexPatterns.iterator(); nodeIter.hasNext();) {
final Pattern pattern = (Pattern)nodeIter.next();
if (pattern.matcher(path).matches()) {
return (SiteNode)this.regexNodes.get(pattern.pattern());
}
}
return null;
} else {
return node;
}
}
/**
* Get the controller for this node.
*
* @return The controller for this node.
*/
public SiteNodeController getController() {
return this.controller;
}
/**
* Get the controller for the node specified by the path (see
* {@link #findSiteNode(String)} for a description how the path is processed.
*
* @param path The path.
* @return The controller.
*/
public SiteNodeController getController(final String path) {
final SiteNode pageNode = findSiteNode(path);
if (pageNode != null) {
return pageNode.getController();
} else {
return null;
}
}
/**
* Get the full path to the node, including the path's of all parents.
*
* @return The path.
*/
public String getFullPath() {
final StringBuilder path = new StringBuilder();
addPath(path);
return path.toString();
}
/**
* Get the child site node with the specified name. The name cannot contain
* the '/' character, see ({@link #findSiteNode(String)} to get a child node
* using a path.
*
* @param name The child node name.
* @return The site node or null if not found.
*/
public SiteNode getNode(final String name) {
return (SiteNode)this.nodeMap.get(name);
}
/**
* Get the collection of child nodes.
*
* @return The collection of nodes.
*/
public Collection getNodes() {
return this.nodes;
}
/**
* @return Returns the parent.
*/
public SiteNode getParent() {
return this.parent;
}
/**
* Get the path for the node relative to the parenr node.
*
* @return The path.
*/
public String getPath() {
if (this.path != null) {
return this.path;
} else if (this.parent == null) {
return null;
} else if (this.controller != null) {
return this.controller.getPath();
} else {
return null;
}
}
/**
* Check to see if this node has a controller.
*
* @return True if the node has a controller.
*/
public boolean hasController() {
return this.controller != null;
}
/**
* Check to see if this node has a parent.
*
* @return True if the node has a parent.
*/
public boolean hasParent() {
return this.parent != null;
}
/**
* Merge the values of the specified node with this node.
*
* @param node The node to merge from.
*/
protected void mergeNode(final SiteNode node) {
setNodes(node.getNodes());
final SiteNodeController controller = node.getController();
if (controller != null) {
setController(controller);
}
}
/**
* Set the controller for this node.
*
* @param controller The controller.
*/
public void setController(final SiteNodeController controller) {
this.controller = controller;
}
/**
* Set the controller for the node specified by the path name. If the
* controller's node already has a parent node, clone the controller so that
* changing the values on the controller on one path does not affect the
* other. See {@link #setNode(String, SiteNode)} for details on how the
* controller for the node is set.
*
* @param path The path.
* @param controller The controller.
*/
public void setController(final String path, final SiteNodeController controller) {
SiteNode controllerNode = controller.getNode();
// If the controller's node already has a parent clone it.
if (controllerNode.hasParent()) {
final SiteNodeController newController = (SiteNodeController)controller.clone();
controllerNode = newController.getNode();
}
setNode(path, controllerNode);
}
/**
* Set the node for the specified path. A node will be created for each
* element in the path if one does not exist. If the last element does not
* exist the node will be set as the node for that element. If it does exist
* the values for the existing node will be merged from the new node. *
*
* @param path The path.
* @param node The node.
*/
public void setNode(final String path, final SiteNode node) {
if (path != null && path.trim().length() > 0) {
if (path.startsWith("regex:")) {
addNode(node);
final String regex = path.substring(6);
this.regexNodes.put(regex, node);
this.regexPatterns.add(Pattern.compile(regex));
} else {
final String[] names = path.split("/");
SiteNode currentNode = this;
for (int i = 0; i < names.length; i++) {
final String name = names[i].trim();
if (name.length() > 0) {
SiteNode childNode = currentNode.getNode(name);
if (childNode == null) {
if (i == names.length - 1) {
// If the last node doesn't exist add the new node as that node
if (node.hasParent()) {
childNode = (SiteNode)node.clone();
} else {
childNode = node;
}
childNode.setPath(name);
} else {
// Otherwise Construct a new new node
childNode = new SiteNode(name);
}
currentNode.addNode(childNode);
} else {
if (i == names.length - 1) {
// If the last node already exists merge in the values from the
// new node
mergeNode(node);
}
}
currentNode = childNode;
}
}
}
} else {
mergeNode(node);
}
}
/**
* Set the child site nodes. The collection can contain {@link SiteNode} or
* {@link SiteNodeController} instances. If the node's path contains a '/'
* character a node will be created for each element of the path. For
* {@link SiteNodeController} instances the node from the controller will be
* merged with the existing node for the path if a node with that path already
* existed.
*
* @param nodes The site nodes
*/
public void setNodes(final Collection nodes) {
for (final Iterator nodeIter = nodes.iterator(); nodeIter.hasNext();) {
Object element = nodeIter.next();
String path = null;
boolean clone = false;
if (element instanceof BeanReference) {
final BeanReference ref = (BeanReference)element;
element = ref.getReferencedBean();
clone = true;
path = ref.getName();
}
if (element instanceof SiteNode) {
SiteNode siteNode = (SiteNode)element;
if (clone) {
siteNode = (SiteNode)siteNode.clone();
}
if (path == null) {
path = siteNode.getPath();
}
setNode(path, siteNode);
} else if (element instanceof SiteNodeController) {
SiteNodeController controller = (SiteNodeController)element;
if (clone) {
controller = (SiteNodeController)controller.clone();
}
if (path == null) {
path = controller.getPath();
}
setController(path, controller);
}
}
}
/**
* @param parent The parent to set.
*/
public void setParent(final SiteNode parent) {
this.parent = parent;
}
/**
* @param path The path to set.
*/
public void setPath(final String path) {
this.path = path;
}
/**
* Get the string representation of the node.
*
* @return The string representation.
*/
@Override
public String toString() {
return getFullPath();
}
}