jaxx.runtime.swing.navigation.NavigationTreeNode Maven / Gradle / Ivy
/*
* *##%
* JAXX Runtime
* Copyright (C) 2008 - 2009 CodeLutin
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program 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 General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* ##%*
*/
package jaxx.runtime.swing.navigation;
import java.util.Enumeration;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import jaxx.runtime.JAXXAction;
import jaxx.runtime.JAXXContext;
import jaxx.runtime.context.JAXXContextEntryDef;
import jaxx.runtime.JAXXObject;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Node of the {@link NavigationTreeModel}.
*
* Each node is associated with :
*
* - a {@code bean} coming from a {@link JAXXContext}
* - a {@code uiClass} to build the associated ui
*
*
* To retrieve the bean from the context, there is a huge logic in the
* method {@link #getBean(JAXXContext)}.
*
* In few words, if the {@link #jaxxContextEntryDef} is defined, it means
* that the object is taken from the {@code context}.
*
* Otherwise, find the first ancestor with an context entry and retrieve from
* here the bean.
*
* Then go down to the initial node by applying the jxpath expressions
* {@link #jaxxContextEntryPath} of each node on road back.
*
*
* To identify easly a node, each node has a {@link #path} and a
* {@link #fullPath} (full path from root node).
*
*
* To display the node we use a {@link NavigationTreeNodeRenderer} which is in
* fact the {@link #userObject}, the object can be synch with the bean via the
* {@link NavigationTreeNodeRenderer#reload(java.lang.Object)} method.
*
* @author chemit
* @see NavigationTreeModel
* @see NavigationTreeNodeRenderer
* @since 1.7.2
*/
public class NavigationTreeNode extends DefaultMutableTreeNode {
private static final long serialVersionUID = 1L;
/**
* Logger
*/
static private final Log log = LogFactory.getLog(NavigationTreeNode.class);
/**
* The path separator used to build the {@link #fullPath}.
*
* @see #path
* @see #fullPath
*/
protected final String pathSeparator;
/**
* The node path.
*
* Used to build the {@link #fullPath} which gives a unique identifier of the
* node.
* @see #pathSeparator
* @see #fullPath
*/
protected String path;
/**
* The full path for the node from the rood node.
*
* This path is build by appending nodes {@link #path} from the root node
* to this node, between each path we introduce the {@link #pathSeparator}.
*
* Exemple :
*
* root
* $root
* `-- child1
* `-- child2
*
* will given {@code $root/child1/child2}.
* @see #pathSeparator
* @see #path
*/
protected String fullPath;
/**
* The UI class associated with the node.
*
* This class can be {@code null}, in that case, the
* {@link NavigationTreeModelBuilder#defaultUIClass} will be used while
* building the model.
*/
protected Class extends JAXXObject> uIClass;
/**
* The UI handler class associated with the node.
*
* This class can be {@code null}, in that case, the
* {@link NavigationTreeModelBuilder#defaultUIHandlerClass} will be used
* while building the model.
*/
protected Class extends JAXXAction> uIHandlerClass;
/**
* The context entry definition to retrieve the bean.
*
* Note: If not set - the {@code bean} will be retrieve on a ancestor
* node.
*/
protected JAXXContextEntryDef> jaxxContextEntryDef;
/**
* The jxPath to process to obtain real {@code bean} from the data retrieve
* in the context.
*
* Note: If not set -no jxpath will be apply on the bean from context.
*/
protected String jaxxContextEntryPath;
/**
* The bean associated with the node ( the value can be obtain via the
* method {@link #getBean(JAXXContext)} or directly set via method
* {@link #setBean(Object)}.
*
* cache of bean associated with bean to improve performance
*/
protected transient Object bean;
/**
* The type of the related bean associated with the node.
*
* Note: This type is here to override the NodeRenderer internalClass, since
* we could need to override this data.
*
* If this property is let to null, then we will use the NodeRenderer one
*/
protected Class> internalClass;
public NavigationTreeNode(String pathSeparator, String navigationPath, Object jaxxContextEntryDef) {
super();
this.pathSeparator = pathSeparator;
this.path = navigationPath;
if (jaxxContextEntryDef instanceof JAXXContextEntryDef>) {
this.jaxxContextEntryDef = ((JAXXContextEntryDef>) jaxxContextEntryDef);
} else if (jaxxContextEntryDef instanceof String) {
this.jaxxContextEntryPath = (String) jaxxContextEntryDef;
} else if (jaxxContextEntryDef != null) {
// wrong context definition type
throw new IllegalArgumentException("to define a context link, must be a String (jxpath) or a " + JAXXContextEntryDef.class + ", but was " + jaxxContextEntryDef);
}
}
public NavigationTreeNode(String pathSeparator, String navigationPath, JAXXContextEntryDef> jaxxContextEntryDef, String jaxxContextEntryPath) {
super();
this.pathSeparator = pathSeparator;
this.path = navigationPath;
this.jaxxContextEntryDef = jaxxContextEntryDef;
this.jaxxContextEntryPath = jaxxContextEntryPath;
}
/**
*
* @return the text node renderer (store in {@link #userObject} property.
*/
public NavigationTreeNodeRenderer getRenderer() {
NavigationTreeNodeRenderer render = null;
Object o = getUserObject();
if (o != null && o instanceof NavigationTreeNodeRenderer) {
render = (NavigationTreeNodeRenderer) o;
}
return render;
}
public void setRenderer(NavigationTreeNodeRenderer renderer) {
// clear all cache
bean = null;
setUserObject(renderer);
}
public String getPathSeparator() {
return pathSeparator;
}
public String getNodePath() {
return path;
}
public void setNodePath(String navigationPath) {
this.path = navigationPath;
}
public Class extends JAXXObject> getUIClass() {
return uIClass;
}
public void setUIClass(Class extends JAXXObject> uIClass) {
this.uIClass = uIClass;
}
public void setInternalClass(Class> internalClass) {
this.internalClass = internalClass;
}
public Class extends JAXXAction> getUIHandlerClass() {
return uIHandlerClass;
}
public void setUIHandlerClass(Class extends JAXXAction> uIHandlerClass) {
this.uIHandlerClass = uIHandlerClass;
}
public JAXXContextEntryDef> getJaxxContextEntryDef() {
return jaxxContextEntryDef;
}
public void setJaxxContextEntryDef(JAXXContextEntryDef> jaxxContextEntryDef) {
this.jaxxContextEntryDef = jaxxContextEntryDef;
}
public String getJaxxContextEntryPath() {
return jaxxContextEntryPath;
}
public void setJaxxContextEntryPath(String jaxxContextEntryPath) {
this.jaxxContextEntryPath = jaxxContextEntryPath;
}
public Class> getInternalClass() {
if (internalClass == null && getRenderer() != null) {
return getRenderer().getInternalClass();
}
return internalClass;
}
/** @return the fully context path of the node from the root node to this. */
public String getFullPath() {
if (fullPath == null) {
StringBuilder sb = new StringBuilder();
for (TreeNode treeNode : getPath()) {
NavigationTreeNode myNode = (NavigationTreeNode) treeNode;
sb.append(pathSeparator).append(myNode.getNodePath());
}
fullPath = sb.substring(1);
}
return fullPath;
}
@Override
public NavigationTreeNode getChildAt(int index) {
return (NavigationTreeNode) super.getChildAt(index);
}
@Override
public NavigationTreeNode getParent() {
return (NavigationTreeNode) super.getParent();
}
/**
* @param path the name of the {@link #path} to be matched in the child of this node.
* @return the child of this node with given {@link #path} value.
*/
public NavigationTreeNode getChild(String path) {
Enumeration> childs = children();
while (childs.hasMoreElements()) {
NavigationTreeNode son = (NavigationTreeNode) childs.nextElement();
if (path.equals(son.getNodePath())) {
return son;
}
}
return null;
}
public Object getBean() {
return bean;
}
public void setBean(Object bean) {
this.bean = bean;
}
public void reload(JAXXContext context) {
// clear bean cache
bean = null;
// clear context navigation cache
fullPath = null;
NavigationTreeNodeRenderer renderer = getRenderer();
if (renderer == null) {
// this can't be !
throw new NullPointerException("could not find the renderer for node " + this);
}
Object b = getBean(context);
renderer.reload(b);
}
/**
* Obtain the associated bean value from context corresponding to node
*
* @param context the context to seek
* @return the value associated in context with the given context path
*/
public Object getBean(JAXXContext context) {
if (bean != null) {
// use cached bean
return bean;
}
Object result;
if (getJaxxContextEntryDef() != null && jaxxContextEntryPath == null) {
// the node maps directly a value in context, with no jxpath resolving
result = getJaxxContextEntryDef().getContextValue(context);
// save in cache
setBean(result);
return result;
}
// find the first ancestor node with a context def
NavigationTreeNode parentNode = getFirstAncestorWithDef();
if (parentNode == null) {
log.warn("could not find a ancestor node with a definition of a context entry from node (" + this + ")");
// todo must be an error
// no parent found
return null;
}
Object parentBean = parentNode.getJaxxContextEntryDef().getContextValue(context);
if (parentBean == null) {
// must be an error no bean found
log.warn("could not find a bean attached in context from context entry definition " + parentNode.getJaxxContextEntryDef());
return null;
}
if (parentNode.jaxxContextEntryPath != null) {
// apply the jxpath on parentBean
JXPathContext jxcontext = JXPathContext.newContext(parentBean);
parentBean = jxcontext.getValue(parentNode.jaxxContextEntryPath);
}
// save in cache
parentNode.setBean(parentBean);
if (this == parentNode) {
// current node is the node matching the context entry value and no jxpath is found
return parentBean;
}
if (jaxxContextEntryPath == null) {
// todo must be an error
log.warn("must find a jaxxContextEntryPath on node (" + this + ")");
return null;
}
String jxpathExpression = computeJXPath(jaxxContextEntryPath, parentNode);
if (jxpathExpression == null) {
/// todo must be an error
log.warn("could not build jxpath from node " + parentNode + " to " + this);
// could not retreave the jxpath...
return null;
}
if (jxpathExpression.startsWith("[")) {
// special case when we want to access a collection
jxpathExpression = '.' + jxpathExpression;
}
if (log.isDebugEnabled()) {
log.debug("jxpath : " + jxpathExpression);
}
JXPathContext jxcontext = JXPathContext.newContext(parentBean);
result = jxcontext.getValue(jxpathExpression);
// save in cache
setBean(result);
return result;
}
/**
* @return the first ancestor with a none null {@link #jaxxContextEntryDef}
* or null
if none find..
*/
protected NavigationTreeNode getFirstAncestorWithDef() {
if (jaxxContextEntryDef != null) {
// find a node with a direct link with the context
return this;
}
// the node is not linked to context
// seek in his parent
NavigationTreeNode ancestor = getParent();
return ancestor == null ? null : ancestor.getFirstAncestorWithDef();
}
protected String computeJXPath(String expr, NavigationTreeNode parentNode) {
if (parentNode == this) {
// reach the parent limit node, return the expr computed
return expr;
}
int firstIndex = expr.indexOf("..");
int lastIndex = expr.lastIndexOf("..");
if (firstIndex == -1) {
// this is a error, since current node is not parent limit node,
// we must find somewhere a way to go up in nodes
throw new IllegalArgumentException(expr + " should contains at least one \"..\"");
}
if (firstIndex != 0) {
// this is a error, the ../ must be at the beginning of the expression
throw new IllegalArgumentException("\"..\" must be at the beginning but was : " + expr);
}
NavigationTreeNode ancestor = getParent();
if (firstIndex == lastIndex) {
// found only one go up, so must be substitute by the parent node context
String newExpr = expr.substring(2);
//String newExpr = expr.substring(expr.startsWith("../") ? 3 : 2);
if (getParent().equals(parentNode)) {
// parent node is the final parent node, so no substitution needed
return newExpr;
}
// ancestor must have a jaxxContextEntryPath
if (ancestor.jaxxContextEntryPath == null) {
throw new IllegalArgumentException("with the expression " + expr + ", the ancestor node (" + ancestor + ") must have a jaxxContextEntryPath definition, but was not ");
}
newExpr = ancestor.jaxxContextEntryPath + newExpr;
return ancestor.computeJXPath(newExpr, parentNode);
}
// have more than one go up, so the ancestor node can not have a jaxxContextEntryPath
if (ancestor.jaxxContextEntryPath != null) {
throw new IllegalArgumentException("with the expression " + expr + ", the ancestor node can not have a jaxxContextEntryPath definition");
}
// substitute the last ..[/] and delegate to ancestor
String newExpr = expr.substring(0, lastIndex - 1) + expr.substring(lastIndex + (expr.charAt(lastIndex + 3) == '/' ? 3 : 2));
return ancestor.computeJXPath(newExpr, parentNode);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy