Author: tchemit Date: 2010-06-22 18:53:04 +0200 (Tue, 22 Jun 2010) New Revision: 1979 Url: http://nuiton.org/repositories/revision/jaxx/1979 Log: - Finalize new tree api - Improve javadoc Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/AbstractJaxxTreeCellRenderer.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/DataProvider.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNode.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNodeChildLoador.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxTreeHelper.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/package.html Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/AbstractJaxxTreeCellRenderer.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/AbstractJaxxTreeCellRenderer.java 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/AbstractJaxxTreeCellRenderer.java 2010-06-22 16:53:04 UTC (rev 1979) @@ -78,6 +78,10 @@ renderCache.clear(); } + public void invalidateCache(N node) { + renderCache.remove(node); + } + @Override protected void finalize() throws Throwable { super.finalize(); Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/DataProvider.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/DataProvider.java 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/DataProvider.java 2010-06-22 16:53:04 UTC (rev 1979) @@ -25,7 +25,10 @@ package jaxx.runtime.swing.tree; /** - * Contract of provider of data from their ids. + * Contract of provider of data. + * <p/> + * This object is used by {@link JaxxNodeChildLoador} to populate childs of node + * and by {@link AbstractJaxxTreeCellRenderer} to render nodes. * * @author tchemit <chemit@codelutin.com> * @since 2.1 Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNode.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNode.java 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNode.java 2010-06-22 16:53:04 UTC (rev 1979) @@ -32,7 +32,45 @@ import java.util.Enumeration; /** - * Definition of a node with no child loador. + * Definition of a node with a optional {@link #childLoador} to build childs of + * node. + * <p/> + * A node is identified by an {@link #id} of an associated data of type + * {@link #internalClass}. + * <p/> + * <b>Note:</b> + * <p><i> While using a {@link #childLoador}, we can not know before node + * was loaded the exact count of his childs. As a matter of facts, real leaf + * nodes appears at the beginning in ui as a not leaf (there is a root handler). + * When node was loaded, a leaf node will be then displayed as required. + * </i></p> + * <p/> + * <h2>Internal states</h2> + * <ul> + * <li><b>internalClass</b> : the type of data associated with the node</li> + * <li><b>context</b> : an optinal context to distinguish different types of + * node with same {@code internalclass}</li> + * <li><b>id</b> : id of the data associated with the node</li> + * <li><b>dirty</b> : flag sets to {@code true} when node render MUST be recomputed</li> + * <li><b>loaded</b> : flag sets to {@code true} when node was loaded</li> + * <li><b>childLoador</b> : optional loador of childs</li> + * </ul> + * <h2>Static nodes</h2> + * Some nodes do not need auto-loading, we call them {@code static nodes}. + * The method {@link #isStaticNode()} gives this state. + * <p/> + * <b>Note:</b> A static node has no {@link #childLoador}. + * <h2>Node loading</h2> + * Initialy node has no data child nodes, ({@link #isLoaded()} equals + * {@code false}). + * when model requires node's childs, it can load them via method + * {@link #populateNode(DefaultTreeModel, DataProvider, boolean)} + * and {@link #populateChilds(DefaultTreeModel, DataProvider)} methods. + * <h2>Node rendering</h2 + * the {@link AbstractJaxxTreeCellRenderer} looks the {@link #dirty} state to + * know when render should be (re-)compute and set back the state to {@code false}. + * <p/> + * Each time, a node is modified, the {@link #dirty} should be set to {@code true}. * * @author tchemit <chemit@codelutin.com> * @since 2.1 @@ -44,25 +82,25 @@ private static final long serialVersionUID = 1L; - /** le type de l'objet encapsule dans le noeud */ + /** Type of data associated with the node */ protected final Class<?> internalClass; /** - * un nom de context optionnel de context pour pouvoir gérer plusieurs - * types de noeud ayant la même {@link #internalClass}. + * Optinal context to distinguish different nodes with same + * {@link #internalClass}. */ protected final String context; - /** l'id de l'objet encapsule dans le noeud */ + /** Id of the data associated with the node. */ protected final String id; - /** un drapeau pour savoir quand il faut recalculer les données du noeud */ + /** Flag to know when renderer should (re-)compute render of the node. */ protected boolean dirty = true; - /** un drapeau a faux tant que les fils n'ont pas ete chargés */ + /** Flag to know when the none static node was loaded. */ protected boolean loaded; - /** l'objet (optionnel) pour créer les noeuds fils */ + /** Optional child loador to lazy create childs of the node. */ protected final JaxxNodeChildLoador<?, N> childLoador; protected JaxxNode(String id) { @@ -138,13 +176,13 @@ // 1 - when the node is static, then can directly use his number of child // to determine if node is a leaf (no child) // 2 - when the node is dynamic, then ALWAYS says the node is NOT a leaf until - // it is loaded, otherwise the WillExpand listener will not load the childs... + // it was loaded, otherwise the WillExpand listener will not load the childs... // Once the node is loaded, use back the normal behaviour (count number of childs) - return isStaticNode() ? getChildCount() == 0 : isLoaded() && getChildCount() == 0; + return isStaticNode() ? super.isLeaf() : isLoaded() && getChildCount() == 0; } /** - * Convinient method to known if we are on a String typed node. + * Convinient method to known if the node is a {@code String} typed. * * @return {@code true} if the type of node if */ @@ -165,7 +203,7 @@ } /** - * Get the first node form this one to the root which has a none + * Gets the first node form this one to the root which has a none * {@code String} type. * * @return the first concrete node type @@ -189,8 +227,8 @@ /** * Changes the {@link #dirty} state. * <p/> - * As a side effect, when a render will use this node, it will force to reload - * the datas from the {@link DataProvider}. + * As a side effect, when a renderer will use this node, it will force to + * reload the render from the {@link DataProvider}. * * @param dirty the new dirty value */ @@ -198,9 +236,21 @@ this.dirty = dirty; } + /** + * Given an {@code id}, obtain the child with matching id. + * <p/> + * If node is NOT {@code loaded}, then first loads it (method + * {@link #populateChilds(DefaultTreeModel, DataProvider)}) then do search + * on direct childs of the node. + * + * @param id the id of the researched node + * @param model model owner of nodes + * @param provider data provider + * @return the found node or {@code null} if not found + */ public N findNodeById(String id, DefaultTreeModel model, - DataProvider source) { + DataProvider provider) { if (id == null) { // id null ? donc rien a faire @@ -215,7 +265,7 @@ if (!isLoaded()) { // il faut charger les fils du noeud pour effectuer la recherche - populateChilds(model, source); + populateChilds(model, provider); } if (isLeaf()) { @@ -228,7 +278,7 @@ Enumeration<N> enumeration = children(); while (enumeration.hasMoreElements()) { N node = enumeration.nextElement(); - N nodeById = node.findNodeById(id, model, source); + N nodeById = node.findNodeById(id, model, provider); if (nodeById != null) { return nodeById; } @@ -239,10 +289,11 @@ } /** - * Pour charger un noeud et optionnellement ces fils. + * To populate the node. A side-effect of this method is to set {@code dirty} + * the node (renderer will recompute the render of the node). * <p/> - * Tous les noeuds modifiés (ou chargés) passent à l'état {@code dirty}, et - * donc les rendus seront recalculés. + * If {@code populateChilds} is set to {@code true}, then also populate + * childs of the node using the given {@code dataProvider}. * * @param model le modèles content le noeud * @param provider le provider de données @@ -263,12 +314,13 @@ } /** - * Pour charger les fils d'un noeud en utilisant le {@link #childLoador}. + * To populate childs of the node (only when a none static node). + * A side-effect of this method is to set {@code loaded} of the node. * <p/> - * Si le noeud est static (i.e n'a pas de childLoador), on ne fait rien. + * For a static node, do nothing. * - * @param model le modèle contenant le noeud - * @param provider le provider de données + * @param model model owner of the node + * @param provider data provider */ public void populateChilds(DefaultTreeModel model, DataProvider provider) { if (isStaticNode()) { @@ -294,7 +346,7 @@ @Override public String toString() { - return System.identityHashCode(this) + " : " + id; + return System.identityHashCode(this) + "-" + id; } } Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNodeChildLoador.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNodeChildLoador.java 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxNodeChildLoador.java 2010-06-22 16:53:04 UTC (rev 1979) @@ -24,6 +24,7 @@ */ package jaxx.runtime.swing.tree; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,10 +33,23 @@ import java.util.List; /** - * Un object pour charger les fils d'un noeud. + * Object to load childs of a node. + * <p/> + * It uses {@link DataProvider} in method + * {@link #loadChilds(DefaultTreeModel, JaxxNode, DataProvider)} to obtain datas + * then build childs nodes. + * <p/> + * A factory of such objects can be found in {@link JaxxTreeHelper} to make + * them reusable in other places than inside a {@link JaxxNode} to auto-load + * childs. + * <p/> + * For example when you want to creat by hand a new node, always prefer to reuse + * a such object rathen than duplicate same code in helper... * * @author tchemit <chemit@codelutin.com> - * @since 1.4 + * @see JaxxTreeHelper + * @see JaxxNode + * @since 2.1 */ public abstract class JaxxNodeChildLoador<O, N extends JaxxNode<N>> implements Serializable { @@ -65,11 +79,11 @@ /** * Hook to create a child node given his {@code data}. * - * @param parentNode the parent node * @param data the data of the node to create * @return the created node */ - public abstract N createNode(N parentNode, O data); + public abstract N createNode(O data); +// public abstract N createNode(N parentNode, O data); public Class<O> getBeanType() { return beanType; @@ -92,15 +106,12 @@ List<O> datas; if (containerNode == null) { - // pas d'ancetre, il doit s'agir d'un premier neoud de données depuis le noeud root + // pas d'ancetre, il doit s'agir d'un premier neoud de données + // depuis le noeud root // recuperation des objets fils (sans connaitre de parent) - datas = getData(null, - null, - source); + datas = getData(null, null, source); - // on considere que le container est le parent -// containerNode = parentNode; } else { if (log.isDebugEnabled()) { log.debug("search data for " + containerNode.getInternalClass() + @@ -111,22 +122,16 @@ datas = getData(containerNode.getInternalClass(), containerNode.getId(), source); - } + if (!CollectionUtils.isEmpty(datas)) { - if (datas != null && !datas.isEmpty()) { - // on charge les fils addChildNodes(parentNode, datas); } // notifie le modele d'un ajout de noeuds - int[] indices = new int[parentNode.getChildCount()]; - for (int i = 0; i < indices.length; i++) { - indices[i] = i; - } - model.nodesWereInserted(parentNode, indices); + JaxxTreeHelper.notifyChildNodesInserted(model, parentNode); } protected void addChildNodes(N parentNode, List<O> datas) { @@ -135,16 +140,9 @@ if (log.isInfoEnabled()) { log.info("[" + parentNode + "] Will add child node for " + o); } - N node = createNode(parentNode, o); + N node = createNode(o); parentNode.add(node); } } - protected void notifyChildChildAdded(DefaultTreeModel model, N parentNode) { - // notifie le modele d'un ajout de noeuds - for (int i = 0; i < parentNode.getChildCount(); i++) { - N at = parentNode.getChildAt(i); - model.nodesWereInserted(at, new int[]{0}); - } - } } \ No newline at end of file Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxTreeHelper.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxTreeHelper.java 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/JaxxTreeHelper.java 2010-06-22 16:53:04 UTC (rev 1979) @@ -36,7 +36,6 @@ import javax.swing.event.TreeWillExpandListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeCellRenderer; -import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import java.util.ArrayList; @@ -49,8 +48,79 @@ /** * Tree helper to deal with the build of trees and other usefull operations. + * <p/> + * A helper acts as an handler for a {@code tree}. It owns the {@link #model} of + * the {@link #tree}. + * <p/> + * <b>Note:</b> A helper can NOT be used to manage multi-trees. + * <h2>Internal states</h2 + * <h3>Internal model</h3> + * To create the model, use method {@link #createModel(JaxxNode)} given a + * root node. + * <p/> + * To obtain the model, use method {@link #getModel()}. + * <p/> + * <b>Note:</b> The helper internal model can be different from the tree model, + * but must be the <b>lowest</b> model, other models must listen nicely this + * model to obtain model modification and selection notifications. + * <h3>Internal tree</h3> + * As said before, a helper matches exactly one tree. + * <p/> + * To register the tree, use method {@link #setTree(JTree, boolean, TreeSelectionListener)}. + * <p/> + * To obtain the tree, use method {@link #getTree()}. + * <h3>Internal data provider</h3> + * To populate childs nodes and render nodes, we use a {@link DataProvider}. + * <p/> + * To register the data provider, use method {@link #setDataProvider(DataProvider)}. + * <p/> + * To obtain the data provider, use method {@link #getDataProvider()}. + * <h2>Internal listeners</h2> + * Several listeners are used to manage the auto-loading of nodes in model : + * <h3>{@link #expandListener}</h3> + * This listener will load node's childs before node expands if the node is not loaded. + * <p/> + * See the {@link JaxxNode#isLoaded()} method. + * <h3>{@link #treeModelListener}</h3> + * To listen modification of the model, it will mainly repopulate nodes when + * required. + * <p/> + * See the method {@link #populateNode(JaxxNode, Object[], boolean)}. + * <h3>{@link #selectionListener}</h3> + * To listen modification of the selection, it will mainly expand paths if required. + * <p/> + * This is a requirement, since childs of a node should NOT be loaded, so when + * selects a node, always check the path from root to selected node are all fully + * loaded. + * <h2>Model methods</h2> + * The helper offers some methods to modify and query the internal tree model. + * <h3>Model modification</h3> + * <ul> + * <li>{@link #createModel(JaxxNode)}</li> + * <li>{@link #insertNode(JaxxNode, JaxxNode)}</li> + * <li>{@link #removeNode(JaxxNode)}</li> + * <li>{@link #moveNode(JaxxNode, JaxxNode, int)}</li> + * <li>{@link #refreshNode(JaxxNode, boolean)}</li> + * <p/> + * </ul> + * <h3>Model selection modification</h3> + * <ul> + * <li>{@link #selectNode(JaxxNode)}</li> + * <li>{@link #selectNode(String...)}</li> + * <li>{@link #selectParentNode()}</li> + * </ul> + * <h3>Model query</h3> + * <ul> + * <li>{@link #findNode(JaxxNode, String...)}</li> + * </ul> + * <h3>Child loadors factory</h3> + * The class offers a factory of {@link JaxxNodeChildLoador}, use the method + * {@link #getChildLoador(Class)} to obtain the correct child loador given his type. * * @author tchemit <chemit@codelutin.com> + * @see JaxxNode + * @see JaxxNodeChildLoador + * @see AbstractJaxxTreeCellRenderer * @since 2.1 */ public class JaxxTreeHelper<N extends JaxxNode<N>> { @@ -58,18 +128,43 @@ /** Logger */ static private final Log log = LogFactory.getLog(JaxxTreeHelper.class); - /** le modèle de l'arbre */ + /** + * The shared instance of tree model. + * <p/> + * A helper deals with only ONE model (this one), becuase we add some + * listeners on it, we prefer always to keep ONE instance (any way this is + * a good thing). + * <p/> + * If you want to create a new model, just creates the good root node and + * push it in this model. + * <p/> + * <b>Note:</b> The model of the registred {@link #tree} can be different + * from this one. + * <p/> + * For example, if you wrap the shared model with a filter model... Anyway, all + * listeners of this helper apply always of THIs model. + */ protected DefaultTreeModel model; - /** l'arbre utilisation le model */ + /** + * The shared instance of tree. + * <p/> + * A helper deleas with only ONE tree (this one), becuase we add some listeners + * on it, we prefer always to kepp ONE safe instance. + * <p/> + * If you need to work with more than one helper, please instanciat a new + * helper for each tree. + */ protected JTree tree; - /** data provider */ + /** The shared data provider used to obtain datas to populate nodes and render them. */ protected DataProvider dataProvider; /** - * pour charger les fils d'un noeud lorsqu'on l'ouvre. Cela permet de - * ne pas a voir a construire tout l'arbre à démarrage. + * A {@link TreeWillExpandListener} used to listen when tree should expand. + * <p/> + * If so, the listener will load selected node childs if required + * (says when the {@link JaxxNode#isLoaded()} is sets to {@code false}). */ protected TreeWillExpandListener expandListener; @@ -85,9 +180,7 @@ */ protected TreeModelListener treeModelListener; - /** - * Cache of child loadors. - */ + /** Cache of child loadors. */ protected static Set<? super JaxxNodeChildLoador<?, ?>> childLoadors; protected static Set<? super JaxxNodeChildLoador<?, ?>> getChildLoadors() { @@ -97,91 +190,177 @@ return childLoadors; } - public static <L extends JaxxNodeChildLoador<?, ?>> L getChildLoador(Class<L> loadorType) { + /** + * Obtains the {@link JaxxNodeChildLoador} of the given {@code type} from + * internal cache. + * <p/> + * <b>Note:</b> The loador will be instanciated if not found, and push in cache. + * + * @param type the type of loador to get + * @param <L> the type of loador to get + * @return the loador from cache + */ + public static <L extends JaxxNodeChildLoador<?, ?>> L getChildLoador(Class<L> type) { Set<? super JaxxNodeChildLoador<?, ?>> cache = getChildLoadors(); - L result = null; + JaxxNodeChildLoador<?, ?> result = null; for (Object loador : cache) { - if (loadorType.equals(loador.getClass())) { - result = (L) loador; + if (type.equals(loador.getClass())) { + result = (JaxxNodeChildLoador<?, ?>) loador; break; } } if (result == null) { // add it in cache try { - result = loadorType.newInstance(); + result = type.newInstance(); cache.add(result); + if (log.isDebugEnabled()) { + log.debug("Add " + result + " in loadors cache (new size:" + cache.size() + ")."); + } } catch (Exception e) { - throw new IllegalArgumentException("Could not instanciate loador [" + loadorType.getName() + "]", e); + throw new IllegalArgumentException("Could not instanciate loador [" + type.getName() + "]", e); } } - return result; + return (L) result; } + /** + * Notifies the given {@code model} that all childs nodes of {@code node} were + * inserted. + * <p/> + * <b>Note:</b> The method recurses on childs (always notify parent before child) + * + * @param model model to notify + * @param node node where all childs where inserted + */ + public static void notifyChildNodesInserted(DefaultTreeModel model, + JaxxNode<?> node) { + int count = node.getChildCount(); + if (count < 1) { + if (log.isDebugEnabled()) { + log.debug("Skip for leaf node : " + node); + } + return; + } + if (log.isDebugEnabled()) { + log.debug("Notify for node : " + node + ", " + count + " child(s) inserted."); + } + int[] indices = new int[count]; + for (int i = 0; i < count; i++) { + indices[i] = i; + } + model.nodesWereInserted(node, indices); + + // recurse notify on childs + for (Enumeration<? extends JaxxNode<?>> childs = node.children(); + childs.hasMoreElements();) { + JaxxNode<?> child = childs.nextElement(); + notifyChildNodesInserted(model, child); + } + } + + /** + * Notifies the given {@code model} that the {@code node} was inserted. + * <p/> + * <b>Note:</b> The method recurses on childs (always notify parent before child) + * + * @param model model to notify + * @param node node inserted + */ + public static void notifyNodeInserted(DefaultTreeModel model, + JaxxNode<?> node) { + JaxxNode<?> parent = node.getParent(); + int indice = parent.getIndex(node); + if (log.isDebugEnabled()) { + log.debug("Notify for node : " + node + ", for parent [" + parent + "] child " + indice + " inserted."); + } + model.nodesWereInserted(parent, new int[]{indice}); + notifyChildNodesInserted(model, node); + } + public JaxxTreeHelper() { selectionListener = new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { - TreeSelectionModel source = (TreeSelectionModel) e.getSource(); - if (source.isSelectionEmpty()) { + if (!checkModel()) { return; } - if (e.getPath() == null) { - return; - } - JaxxNode<?> node = - (JaxxNode<?>) e.getPath().getLastPathComponent(); - if (log.isDebugEnabled()) { - log.debug("for node " + node); - } - if (node == null) { - // pas de noeud selectionne + TreeSelectionModel source = (TreeSelectionModel) e.getSource(); + + if (source.isSelectionEmpty()) { + + // empty selection + if (log.isDebugEnabled()) { + log.debug("Selection is empty."); + } return; } - if (!node.isLeaf()) { + boolean debugEnabled = log.isDebugEnabled(); + boolean traceEnabled = log.isTraceEnabled(); + for (TreePath path : e.getPaths()) { - // on ouvre le chemin si necessaire - for (TreePath path : e.getPaths()) { - if (e.isAddedPath(path) && - !tree.isExpanded(path)) { - log.info("expand node [" + path + "]"); - // will expand the node - tree.expandPath(path); + JaxxNode<?> node = getNode(path); + if (node == null) { + + // pas de noeud selectionne + if (debugEnabled) { + log.debug("Skip for null node."); } + continue; } + + boolean isAdded = e.isAddedPath(path); + + TreePath pathToExpand = new TreePath(node.getPath()); + boolean pathExpanded = tree.isExpanded(pathToExpand); + + + if (traceEnabled || isAdded && debugEnabled) { + log.debug("==== Node selection ===================================="); + log.debug("node ? " + node); + log.debug("is added ? " + isAdded); + log.debug("is path expanded ? " + pathExpanded); + log.debug("is node static ? " + node.isStaticNode()); + log.debug("is node loaded ? " + node.isLoaded()); + log.debug("is node leaf ? " + node.isLeaf()); + log.debug("node nb childs ? " + node.getChildCount()); + } + + if (isAdded && !pathExpanded) { + + // ask to expand path + log.info("expand node [" + pathToExpand + "]"); + tree.expandPath(pathToExpand); + } } - } }; expandListener = new TreeWillExpandListener() { @Override public void treeWillExpand(TreeExpansionEvent event) { - if (model == null) { + if (!checkModel()) { + // no model + return; + } - // no model set, - log.warn("No model set in " + this); + JaxxNode<?> source = getNode(event.getPath()); + + if (source.isLoaded()) { + + // node is already loaded, nothing to do return; } - JaxxNode<?> source = (JaxxNode<?>) - event.getPath().getLastPathComponent(); if (log.isDebugEnabled()) { - log.debug("will expand node " + source); + log.debug("will load childs of node [" + source + "]"); } - - if (!source.isLoaded()) { - - if (log.isDebugEnabled()) { - log.debug("will load childs of " + source); - } - // on charge les fils de l'arbre - source.populateChilds(model, getDataProvider()); - } + // populate childs of node + source.populateChilds(model, getDataProvider()); } @Override @@ -189,115 +368,211 @@ } }; + treeModelListener = new TreeModelListener() { @Override - public void treeNodesChanged(TreeModelEvent e) { - if (model == null) { - - // no model set, - log.warn("No model set in " + this); + public void treeNodesInserted(TreeModelEvent e) { + if (!checkModel()) { + // no model return; } - - JaxxNode<?> source = getNode(e); - DataProvider dataProvider = getDataProvider(); - source.populateNode(model, dataProvider, false); + JaxxNode<?> source = getNode(e.getTreePath()); + Object[] children = e.getChildren(); if (log.isDebugEnabled()) { - String message = getLogMessage(e, source); - log.debug(message); + log.debug(getMessage("inserted ", source, children)); } - Object[] children = e.getChildren(); - if (children != null) { - for (Object o : children) { - JaxxNode<?> child = (JaxxNode<?>) o; - child.populateNode(model, dataProvider, false); - } - } + + // ask to populate children nodes + populateNode(null, children, false); } @Override - public void treeNodesInserted(TreeModelEvent e) { - if (model == null) { - - // no model set, - log.warn("No model set in " + this); + public void treeNodesRemoved(TreeModelEvent e) { + if (!checkModel()) { + // no model return; } - JaxxNode<?> source = getNode(e); - DataProvider dataProvider = getDataProvider(); - source.populateNode(model, dataProvider, false); + JaxxNode<?> source = getNode(e.getTreePath()); + Object[] children = e.getChildren(); if (log.isDebugEnabled()) { - String message = getLogMessage(e, source); - log.debug(message); + log.debug(getMessage("removed ", source, children)); } - Object[] children = e.getChildren(); - if (children != null) { - for (Object o : children) { - JaxxNode<?> child = (JaxxNode<?>) o; - child.populateNode(model, dataProvider, false); + + // Invalidates nodes in renderer cache (if any) + AbstractJaxxTreeCellRenderer<N> renderer = getTreeCellRenderer(); + if (children != null && renderer != null) { + for (Object child : children) { + renderer.invalidateCache((N) child); } } } @Override - public void treeNodesRemoved(TreeModelEvent e) { - if (model == null) { - - // no model set, - log.warn("No model set in " + this); + public void treeNodesChanged(TreeModelEvent e) { + if (!checkModel()) { + // no model return; } - JaxxNode<?> source = getNode(e); + + JaxxNode<?> source = getNode(e.getTreePath()); + Object[] children = e.getChildren(); if (log.isDebugEnabled()) { - String message = getLogMessage(e, source); - log.debug(message); + log.debug(getMessage("changed ", source, children)); } + + // ask to populate modified child nodes + populateNode(null, children, false); } @Override public void treeStructureChanged(TreeModelEvent e) { - if (model == null) { - - // no model set, - log.warn("No model set in " + this); + if (!checkModel()) { + // no model return; } - JaxxNode<?> source = getNode(e); - DataProvider dataProvider = getDataProvider(); - source.populateNode(model, dataProvider, false); + JaxxNode<?> source = getNode(e.getTreePath()); + Object[] children = e.getChildren(); if (log.isDebugEnabled()) { - String message = getLogMessage(e, source); - log.debug(message); + log.debug(getMessage("structure changed", source, children)); } - Object[] children = e.getChildren(); + + // ask to populate structure modified node and nodes recursively + populateNode(source, children, true); + } + + protected String getMessage(String action, JaxxNode<?> source, Object[] children) { + StringBuilder sb = new StringBuilder(); + sb.append("==== Nodes "); + sb.append(action); + sb.append(" ================="); + sb.append("\nsource : ").append(source); + sb.append("\nnb nodes : "); + sb.append(children == null ? 0 : children.length); if (children != null) { - for (Object o : children) { - JaxxNode<?> child = (JaxxNode<?>) o; - child.populateNode(model, dataProvider, true); + int i = 0; + for (Object child : children) { + sb.append("\n ["); + sb.append(i++); + sb.append("] - "); + sb.append(child); } } + return sb.toString(); } - - protected String getLogMessage(TreeModelEvent e, JaxxNode<?> source) { - String message = source.getInternalClass() + " - " + - source.getId() + " : " + source.getUserObject() + - "\n" + "children indices : " + - Arrays.toString(e.getChildIndices()); - return message; - } - - - protected JaxxNode<?> getNode(TreeModelEvent e) { - JaxxNode<?> source = (JaxxNode<?>) e.getTreePath().getLastPathComponent(); - return source; - } }; } + /** + * Obtains the attached data provider used to populate and render nodes. + * + * @return the attached data provider + */ protected DataProvider getDataProvider() { return dataProvider; } + /** + * Obtains the registred tree. + * + * @return the registred tree for this helper or {@code null} if none was registred + */ + public JTree getTree() { + return tree; + } + + /** + * Obtains the internal tree model. + * + * @return the internal tree model or {@code null} if none was created. + */ + public DefaultTreeModel getModel() { + return model; + } + + /** + * Obtains the {@link AbstractJaxxTreeCellRenderer} renderer of the + * registred tree. + * + * @return the renderer of the registred tree or null if no tree was + * registred nor the renderer is a {@link AbstractJaxxTreeCellRenderer}. + */ + public AbstractJaxxTreeCellRenderer<N> getTreeCellRenderer() { + JTree t = getTree(); + if (t == null) { + return null; + } + TreeCellRenderer r = t.getCellRenderer(); + if (r instanceof AbstractJaxxTreeCellRenderer<?>) { + return (AbstractJaxxTreeCellRenderer<N>) r; + } + return null; + } + + /** + * Obtains the selected node of the registred tree. + * + * @return the selected tree or {@code null} if no registred tree nor + * selection empty. + */ + public N getSelectedNode() { + JTree tree = getTree(); + if (tree == null) { + return null; + } + TreePath path = tree.getSelectionPath(); + N node = null; + if (path != null) { + node = (N) getNode(path); + } + return node; + } + + /** + * Obtains the path of ids fro the root node to the selected node on the + * registred tree. + * + * @return the array of ids from root node to selected node. + */ + public String[] getSelectedIds() { + List<String> result = new ArrayList<String>(); + JaxxNode<?> selectedNode = getSelectedNode(); + while (selectedNode != null && !selectedNode.isRoot()) { + + result.add(selectedNode.getId()); + selectedNode = selectedNode.getParent(); + } + Collections.reverse(result); + return result.toArray(new String[result.size()]); + } + + /** + * Registers the {@code dataProvider} for the helper. + * <p/> + * <b>Node:</b> As a side-effect, the provider will be propagate to the + * renderer of the registred tree (if any). + * + * @param dataProvider the data provider to use + */ + public void setDataProvider(DataProvider dataProvider) { + this.dataProvider = dataProvider; + AbstractJaxxTreeCellRenderer<N> renderer = getTreeCellRenderer(); + if (renderer != null) { + + // dispatch provider to renderer + renderer.setDataProvider(dataProvider); + } + } + + /** + * Registers the given {@code tree} for this helper. + * <p/> + * <b>Note:</b> as a side-effect, it will register (if required) the + * {@link #expandListener} listener and the {@link #selectionListener}. + * + * @param tree the tree to register + * @param addExpandTreeListener a flag to add expand listener + * @param listener the optional selection listener to add + */ public void setTree(JTree tree, boolean addExpandTreeListener, TreeSelectionListener listener) { @@ -311,35 +586,63 @@ this.tree.getSelectionModel().addTreeSelectionListener(selectionListener); } - public AbstractJaxxTreeCellRenderer<N> getTreeCellRenderer() { - JTree t = getTree(); - if (t == null) { - return null; - } - TreeCellRenderer r = t.getCellRenderer(); - if (r instanceof AbstractJaxxTreeCellRenderer<?>) { - return (AbstractJaxxTreeCellRenderer<N>) r; - } - return null; + /** + * Inserts the given node to the given {@code parentNode}. + * <p/> + * The node will be added to his parent, then creation listeners will be + * fired. + * + * @param parentNode the parent node where to insert the new node * + * @param newNode the node to insert + */ + public void insertNode(JaxxNode<?> parentNode, JaxxNode<?> newNode) { + parentNode.add(newNode); + notifyNodeInserted(model, newNode); } - public DefaultTreeModel getModel() { - return model; + /** + * Removes the given {@code node} from the registred tree model and returns + * his parent. + * + * @param node the node to remove + * @return the parent node of the removed node. + */ + public N removeNode(N node) { + N parentNode = node.getParent(); + model.removeNodeFromParent(node); + return parentNode; } /** - * Demande une opération de repaint sur un noeud de l'arbre de navigation. + * Moves the given {@code node} to the new {@code position}. + * + * @param parentNode the parent node + * @param node the node to move + * @param position the new position of the node + */ + public void moveNode(N parentNode, N node, int position) { + parentNode.remove(node); + parentNode.insert(node, position); + model.nodeStructureChanged(parentNode); + } + + /** + * Refreshs the given {@code node}. * <p/> - * <b>Note:</b> La descendance du noeud est repainte si le paramètre - * <code>deep</code> est à <code>true</code>. + * If flag {@code deep} is set to {@code true}, then it will refresh + * recursively children nodes. + * <p/> + * <b>Note:</b>As a side-effect, evvery node involved will become + * {@code dirty}. * - * @param node le noeud à repaindre + * @param node the node to refresh * @param deep un flag pour activer la repainte de la descendance du * noeud + * @see JaxxNode#isDirty() */ public void refreshNode(N node, boolean deep) { if (log.isDebugEnabled()) { - log.debug(node); + log.debug("Will refresh (deep ? " + deep + ") node " + node); } model.nodeChanged(node); if (deep) { @@ -352,8 +655,14 @@ } } - /** Sélection du parent du noeud selectionne dans l'arbre de navigation. */ - public void selectParentNode() { + /** + * Selects the parent of the currently selected node. + * <p/> + * <b>Note:</> If selection is empty, then throws a NPE. + * + * @throws NullPointerException if selection is empty + */ + public void selectParentNode() throws NullPointerException { N node = getSelectedNode(); @@ -366,38 +675,44 @@ selectNode(node); } - public N getSelectedNode() { - JTree tree = getTree(); - TreePath path = tree.getSelectionPath(); - N node = null; - if (path != null) { - node = (N) path.getLastPathComponent(); + /** + * Selects the given {@code node} in the registred tree. + * + * @param node the node to select + */ + public void selectNode(N node) { + if (!checkModel()) { + + // no model + return; } - return node; - } + if (log.isDebugEnabled()) { + log.debug("try to select node [" + node + "]"); + } + TreePath path = new TreePath(model.getPathToRoot(node)); - public String[] getSelectedIds() { - List<String> result = new ArrayList<String>(); - JaxxNode<?> selectedNode = getSelectedNode(); - while (selectedNode != null && !selectedNode.isRoot()) { - - result.add(selectedNode.getId()); - selectedNode = selectedNode.getParent(); - } - Collections.reverse(result); - return result.toArray(new String[result.size()]); + tree.setSelectionPath(path); + tree.scrollPathToVisible(path); } /** - * Sélection d'un noeud dans l'arbre de navigation à partir de son path. + * Selects the node described by his given {@code path} of ids. * - * @param path le path absolue du noeud dans l'arbre + * @param path the absolute path of ids from root node to node to select. */ public void selectNode(String... path) { + if (!checkModel()) { + + // no model + return; + } + if (log.isDebugEnabled()) { + log.debug("try to select node from ids " + Arrays.toString(path)); + } N root = (N) model.getRoot(); N node = findNode(root, path); if (log.isDebugEnabled()) { - log.debug(Arrays.toString(path) + " :: " + node); + log.debug("selected node [" + node + "]"); } if (node != null) { selectNode(node); @@ -405,22 +720,19 @@ } /** - * Sélection d'un noeud dans l'arbre de navigation. + * Finds a node from the given root {@code node}, applying the path given + * by {@code ids}. * - * @param node le noeud à sélectionner dans l'arbre + * @param node the starting node + * @param ids the path of ids to apply on the node. + * @return the find node or {@code null} if no node matchs. */ - public void selectNode(N node) { + public N findNode(N node, String... ids) { + if (!checkModel()) { - if (log.isDebugEnabled()) { - log.debug(node); + // no model + return null; } - TreePath path = new TreePath(model.getPathToRoot(node)); - - tree.setSelectionPath(path); - tree.scrollPathToVisible(path); - } - - public N findNode(N node, String... ids) { N result = null; for (String id : ids) { @@ -436,51 +748,83 @@ return result; } - public N removeChildNode(N node) { - N parentNode = node.getParent(); - model.removeNodeFromParent(node); - return parentNode; - } - - public void moveNode(N parentNode, N node, int position) { - parentNode.remove(node); - parentNode.insert(node, position); - model.nodeStructureChanged(parentNode); - } - - protected JTree getTree() { - return tree; - } - + /** + * Creates a new registred tree model from thei given root {@code node}. + * <p/> + * <b>Note:</b> As a side-effect, the model will be keep in field {@link #model} + * and the {@link #treeModelListener} will be registred on this model. + * + * @param node the root node of the new model + * @return the new model + */ protected DefaultTreeModel createModel(N node) { model = new DefaultTreeModel(node); model.addTreeModelListener(treeModelListener); return model; } - public void reloadModel(JTree tree) { - DefaultTreeModel model = (DefaultTreeModel) tree.getModel(); - TreeNode node = (TreeNode) model.getRoot(); - reload(model, node); - } - protected void reload(DefaultTreeModel model, TreeNode node) { - model.nodeChanged(node); - Enumeration<?> enumeration = node.children(); - while (enumeration.hasMoreElements()) { - TreeNode treeNode = (TreeNode) enumeration.nextElement(); - model.nodeChanged(treeNode); - reload(model, treeNode); + /** + * Checks if internal model was created. + * + * @return {@code true} if model was created + * (should be done via {@link #createModel(JaxxNode)} method), + * {@code false} otherwise. + */ + protected boolean checkModel() { + if (model == null) { + + // no model set, + log.warn("No model set in " + this); + return false; } + // model is set + return true; } - public void setDataProvider(DataProvider dataProvider) { - this.dataProvider = dataProvider; - AbstractJaxxTreeCellRenderer<N> renderer = getTreeCellRenderer(); - if (renderer != null) { - - // dispatch provider to renderer - renderer.setDataProvider(dataProvider); + /** + * Populates nodes. + * <p/> + * If {@code node} is not {@code null}, then populate it. + * <p/> + * If {@code children} is not {@code null}, then populate them, moreover + * if {@code recurse} is set to {@code true} then do a recurse refresh on + * children. + * + * @param node the parent node to populate (optional) + * @param children the child nodes to populate (optional) + * @param recurse flag sets to {@code true} if should do recurse refresh on + * given {@code children} nodes. + */ + protected void populateNode(JaxxNode<?> node, + Object[] children, + boolean recurse) { + DataProvider dataProvider = getDataProvider(); + if (node != null) { + if (log.isDebugEnabled()) { + log.debug("Will populate node : " + node); + } + node.populateNode(model, dataProvider, false); } + if (children != null) { + for (Object o : children) { + JaxxNode<?> child = (JaxxNode<?>) o; + if (log.isDebugEnabled()) { + log.debug("Will populate child node : " + child); + } + child.populateNode(model, dataProvider, recurse); + } + } } + + /** + * Convinient method to objet the casted node of a {@link TreePath}. + * + * @param path the path contaning the node. + * @return the casted node from the path. + */ + protected N getNode(TreePath path) { + N result = (N) path.getLastPathComponent(); + return result; + } } Modified: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/package.html =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/package.html 2010-06-21 13:28:01 UTC (rev 1978) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/tree/package.html 2010-06-22 16:53:04 UTC (rev 1979) @@ -8,5 +8,43 @@ Replace the previous framework from package <code>jaxx.runtime.swing.navigation</code> </p> + +<h1>Why this api ?</h1> + +<p> + The main goal of this api is to offer an auto-loading system of tree + model. +</p> + +<p> + While previous api we had to load all the model in memory, now we can build + a + tree model with no data. +</p> + +<p> + When the tree will need to expand a node, it will ask first in childs of + node + were loaded, if not, load them then give by hand to system. +</p> + +<h1>Api</h1> + +<h2>DataProvider</h2> +Contract of objet responsible of acquiring data. + +<h2>JaxxNode</h2> +An override of DefaultMutableTreeNode customized for our purpose (loaded, dirty +states,...) + +<h2>JaxxNodeChildLoador</h2> +Object to load childs of a node using DataProvider. + +<h2>JaxxTreeHelper</h2> +Helper to manage a tree using auto-loading nodes. + +<h2>AbstractJaxxTreeCellRenderer</h2> +Abstract renderer using DataProvider to acquire node render. + </body> </html>
participants (1)
-
tchemit@users.nuiton.org