All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jaxx.runtime.swing.navigation.NavigationTreeNode Maven / Gradle / Ivy

There is a newer version: 3.0-alpha-6
Show newest version
/*
 * *##% 
 * 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 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 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 getUIClass() { return uIClass; } public void setUIClass(Class uIClass) { this.uIClass = uIClass; } public void setInternalClass(Class internalClass) { this.internalClass = internalClass; } public Class getUIHandlerClass() { return uIHandlerClass; } public void setUIHandlerClass(Class 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