| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.viewmodel; |
| |
| import java.beans.PropertyVetoException; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.WeakHashMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.JTree; |
| import javax.swing.SwingUtilities; |
| import javax.swing.event.TreeExpansionEvent; |
| import javax.swing.event.TreeExpansionListener; |
| |
| import org.netbeans.spi.viewmodel.ModelEvent; |
| import org.netbeans.spi.viewmodel.Models; |
| import org.netbeans.spi.viewmodel.Models.TreeFeatures; |
| import org.openide.explorer.ExplorerManager; |
| import org.openide.explorer.view.OutlineView; |
| import org.openide.explorer.view.TreeView; |
| import org.openide.explorer.view.Visualizer; |
| import org.openide.nodes.Children; |
| import org.openide.nodes.Node; |
| import org.openide.util.Exceptions; |
| |
| |
| /** |
| * Implements root node of hierarchy created for given TreeModel. |
| * |
| * @author Jan Jancura |
| */ |
| public class TreeModelRoot { |
| /** generated Serialized Version UID */ |
| static final long serialVersionUID = -1259352660663524178L; |
| |
| |
| // variables ............................................................... |
| |
| private Models.CompoundModel model; |
| private HyperCompoundModel hyperModel; |
| private final ModelChangeListener[] modelListeners; |
| private TreeModelNode rootNode; |
| private final Object rootNodeLock = new Object(); |
| private final WeakHashMap<Object, WeakReference<TreeModelNode>[]> objectToNode = new WeakHashMap<Object, WeakReference<TreeModelNode>[]>(); |
| private DefaultTreeFeatures treeFeatures; |
| private ExplorerManager manager; |
| private OutlineView outlineView; |
| private MessageFormat treeNodeDisplayFormat; |
| |
| /** The children evaluator for view of this root. * |
| private final Map<RequestProcessor, TreeModelNode.LazyEvaluator> childrenEvaluators |
| = new WeakHashMap<RequestProcessor, TreeModelNode.LazyEvaluator>(); |
| /** The values evaluator for view of this root. * |
| private final Map<RequestProcessor, TreeModelNode.LazyEvaluator> valuesEvaluators |
| = new WeakHashMap<RequestProcessor, TreeModelNode.LazyEvaluator>(); |
| */ |
| |
| public TreeModelRoot (Models.CompoundModel model, TreeView treeView) { |
| this.model = model; |
| this.manager = ExplorerManager.find(treeView); |
| this.treeFeatures = new DefaultTreeFeatures(treeView); |
| modelListeners = new ModelChangeListener[] { new ModelChangeListener(model) }; |
| model.addModelListener (modelListeners[0]); |
| } |
| |
| public TreeModelRoot (HyperCompoundModel model, TreeView treeView) { |
| this.hyperModel = model; |
| this.model = model.getMain(); |
| this.manager = ExplorerManager.find(treeView); |
| this.treeFeatures = new DefaultTreeFeatures(treeView); |
| int nl = model.getModels().length; |
| modelListeners = new ModelChangeListener[nl]; |
| for (int i = 0; i < nl; i++) { |
| Models.CompoundModel m = model.getModels()[i]; |
| modelListeners[i] = new ModelChangeListener(m); |
| m.addModelListener(modelListeners[i]); |
| } |
| } |
| |
| public TreeModelRoot (Models.CompoundModel model, OutlineView outlineView) { |
| this.model = model; |
| this.manager = ExplorerManager.find(outlineView); |
| this.treeFeatures = new DefaultTreeFeatures(outlineView); |
| this.outlineView = outlineView; |
| modelListeners = new ModelChangeListener[] { new ModelChangeListener(model) }; |
| model.addModelListener (modelListeners[0]); |
| } |
| |
| public TreeModelRoot (HyperCompoundModel model, OutlineView outlineView) { |
| this.hyperModel = model; |
| this.model = model.getMain(); |
| this.manager = ExplorerManager.find(outlineView); |
| this.treeFeatures = new DefaultTreeFeatures(outlineView); |
| this.outlineView = outlineView; |
| int nl = model.getModels().length; |
| modelListeners = new ModelChangeListener[nl]; |
| for (int i = 0; i < nl; i++) { |
| Models.CompoundModel m = model.getModels()[i]; |
| modelListeners[i] = new ModelChangeListener(m); |
| m.addModelListener(modelListeners[i]); |
| } |
| } |
| |
| public TreeFeatures getTreeFeatures () { |
| return treeFeatures; |
| } |
| |
| public OutlineView getOutlineView() { |
| return outlineView; |
| } |
| |
| public TreeModelNode getRootNode () { |
| synchronized (rootNodeLock) { |
| TreeModelNode rn = rootNode; |
| if (rn == null) { |
| if (hyperModel != null) { |
| rn = new TreeModelHyperNode (hyperModel, this, model.getRoot ()); |
| } else { |
| rn = new TreeModelNode (model, this, model.getRoot ()); |
| } |
| rootNode = rn; |
| } |
| return rn; |
| } |
| } |
| |
| void registerNode (Object o, TreeModelNode n) { |
| synchronized (objectToNode) { |
| WeakReference<TreeModelNode>[] wrs = objectToNode.get(o); |
| if (wrs == null) { |
| objectToNode.put (o, new WeakReference[] { new WeakReference<TreeModelNode>(n) }); |
| } else { |
| for (int i = 0; i < wrs.length; i++) { |
| WeakReference<TreeModelNode> wr = wrs[i]; |
| TreeModelNode tn = wr.get(); |
| if (tn == n) { |
| return ; |
| } else if (tn == null) { |
| wrs[i] = new WeakReference<TreeModelNode>(n); |
| return ; |
| } |
| } |
| WeakReference<TreeModelNode>[] wrs2 = new WeakReference[wrs.length + 1]; |
| System.arraycopy(wrs, 0, wrs2, 0, wrs.length); |
| wrs2[wrs.length] = new WeakReference<TreeModelNode>(n); |
| objectToNode.put (o, wrs2); |
| } |
| } |
| } |
| |
| void unregisterNode (Object o, TreeModelNode n) { |
| synchronized (objectToNode) { |
| WeakReference<TreeModelNode>[] wrs = objectToNode.get(o); |
| if (wrs != null) { |
| for (int i = 0; i < wrs.length; i++) { |
| WeakReference<TreeModelNode> wr = wrs[i]; |
| TreeModelNode tn = wr.get(); |
| if (tn == n) { |
| if (wrs.length == 1) { // The only item |
| objectToNode.remove(o); |
| } else if (wrs.length == 2) { // Leave only the other item |
| wrs = new WeakReference[] { wrs[(i + 1) % 2] }; |
| objectToNode.put (o, wrs); |
| } else { // Leave only the other items |
| WeakReference[] nwrs = new WeakReference[wrs.length - 1]; |
| if (i > 0) { |
| System.arraycopy(wrs, 0, nwrs, 0, i); |
| } |
| if (i < (wrs.length - 1)) { |
| System.arraycopy(wrs, i+1, nwrs, i, wrs.length - i - 1); |
| } |
| objectToNode.put (o, nwrs); |
| } |
| return ; |
| } |
| } |
| } |
| } |
| } |
| |
| TreeModelNode[] findNode (Object o) { |
| WeakReference<TreeModelNode>[] wrs; |
| synchronized (objectToNode) { |
| wrs = objectToNode.get (o); |
| } |
| TreeModelNode[] tns = null; |
| if (wrs != null) { |
| for (int i = 0; i < wrs.length; i++) { |
| // Suppose that it's unlikely that wrs.length > 1 |
| WeakReference<TreeModelNode> wr = wrs[i]; |
| TreeModelNode tn = wr.get (); |
| if (tn == null) continue; |
| if (tns == null) { |
| tns = new TreeModelNode[] { tn }; |
| } else { |
| TreeModelNode[] ntns = new TreeModelNode[tns.length + 1]; |
| System.arraycopy(tns, 0, ntns, 0, tns.length); |
| ntns[tns.length] = tn; |
| tns = ntns; |
| } |
| } |
| } |
| if (tns == null) { |
| return new TreeModelNode[0]; |
| } else { |
| return tns; |
| } |
| } |
| |
| // public void treeNodeChanged (Object parent) { |
| // final TreeModelNode tmn = findNode (parent); |
| // if (tmn == null) return; |
| // SwingUtilities.invokeLater (new Runnable () { |
| // public void run () { |
| // tmn.refresh (); |
| // } |
| // }); |
| // } |
| |
| /* |
| synchronized TreeModelNode.LazyEvaluator getChildrenEvaluator(RequestProcessor rp) { |
| TreeModelNode.LazyEvaluator childrenEvaluator = childrenEvaluators.get(rp); |
| if (childrenEvaluator == null) { |
| childrenEvaluator = new TreeModelNode.LazyEvaluator(rp); |
| childrenEvaluators.put(rp, childrenEvaluator); |
| } |
| return childrenEvaluator; |
| } |
| |
| synchronized TreeModelNode.LazyEvaluator getValuesEvaluator(RequestProcessor rp) { |
| TreeModelNode.LazyEvaluator valuesEvaluator = valuesEvaluators.get(rp); |
| if (valuesEvaluator == null) { |
| valuesEvaluator = new TreeModelNode.LazyEvaluator(rp); |
| valuesEvaluators.put(rp, valuesEvaluator); |
| } |
| return valuesEvaluator; |
| } |
| */ |
| |
| public void destroy () { |
| boolean doRemoveModelListeners = false; |
| DefaultTreeFeatures tf = null; |
| synchronized (this) { |
| if (model != null) { |
| doRemoveModelListeners = true; |
| model = null; |
| tf = treeFeatures; |
| treeFeatures = null; |
| } |
| if (hyperModel != null) { |
| hyperModel = null; |
| } |
| synchronized (objectToNode) { |
| objectToNode.clear(); |
| } |
| } |
| if (doRemoveModelListeners) { |
| for (ModelChangeListener mchl : modelListeners) { |
| Models.CompoundModel cm = mchl.getModel(); |
| if (cm != null) { |
| cm.removeModelListener (mchl); |
| } |
| } |
| } |
| if (tf != null) { |
| tf.destroy(); |
| } |
| } |
| |
| public synchronized Models.CompoundModel getModel() { |
| return model; |
| } |
| |
| void setTreeNodeDisplayFormat(MessageFormat treeNodeDisplayFormat) { |
| this.treeNodeDisplayFormat = treeNodeDisplayFormat; |
| } |
| |
| MessageFormat getTreeNodeDisplayFormat() { |
| return treeNodeDisplayFormat; |
| } |
| |
| private final class ModelChangeListener implements ModelRootChangeListener { |
| |
| //private final Logger logger = Logger.getLogger(ModelChangeListener.class.getName()); |
| |
| private final Reference<Models.CompoundModel> modelRef; |
| |
| public ModelChangeListener(Models.CompoundModel model) { |
| this.modelRef = new WeakReference<Models.CompoundModel>(model); |
| } |
| |
| Models.CompoundModel getModel() { |
| return modelRef.get(); |
| } |
| |
| public void modelChanged (final ModelEvent event) { |
| //System.err.println("TreeModelRoot.modelChanged("+event.getClass()+") from "+model); |
| //Thread.dumpStack(); |
| //logger.fine("TreeModelRoot.modelChanged("+event+")"); |
| //logger.log(Level.FINE, "Called from ", new IllegalStateException("TEST_MODEL_CHANGED")); |
| SwingUtilities.invokeLater (new Runnable () { |
| public void run () { |
| Models.CompoundModel model = getModel(); |
| if (model == null) |
| return; // already disposed |
| if (event instanceof ModelEvent.TableValueChanged) { |
| ModelEvent.TableValueChanged tvEvent = (ModelEvent.TableValueChanged) event; |
| Object node = tvEvent.getNode(); |
| //System.err.println("TableValueChanged("+node+")"); |
| if (node != null) { |
| TreeModelNode[] tmNodes = findNode(node); |
| //System.err.println(" nodes = "+Arrays.toString(tmNodes)); |
| int change = tvEvent.getChange(); |
| for (TreeModelNode tmNode : tmNodes) { |
| String column = tvEvent.getColumnID(); |
| if (column != null) { |
| tmNode.refreshColumn(column, change); |
| } else { |
| tmNode.refresh(model); |
| } |
| } |
| return ; // We're done |
| } |
| } |
| if (event instanceof ModelEvent.NodeChanged) { |
| ModelEvent.NodeChanged nchEvent = (ModelEvent.NodeChanged) event; |
| Object node = nchEvent.getNode(); |
| //logger.fine("NodeChanged("+node+")"); |
| //System.err.println("NodeChanged("+node+")"); |
| if (node != null) { |
| TreeModelNode[] tmNodes = findNode(node); |
| //System.err.println(" nodes = "+Arrays.toString(tmNodes)); |
| //logger.fine(" nodes = "+Arrays.toString(tmNodes)); |
| for (TreeModelNode tmNode : tmNodes) { |
| tmNode.refresh(model, nchEvent.getChange()); |
| } |
| return ; // We're done |
| } else { // Refresh all nodes |
| List<TreeModelNode> nodes = new ArrayList<TreeModelNode>(objectToNode.size()); |
| for (WeakReference<TreeModelNode>[] wrs : objectToNode.values()) { |
| for (WeakReference<TreeModelNode> wr : wrs) { |
| TreeModelNode tm = wr.get(); |
| if (tm != null) { |
| nodes.add(tm); |
| } |
| } |
| } |
| for (TreeModelNode tmNode : nodes) { |
| tmNode.refresh(model, nchEvent.getChange()); |
| } |
| return ; // We're done |
| } |
| } |
| if (event instanceof ModelEvent.SelectionChanged) { |
| final Object[] nodes = ((ModelEvent.SelectionChanged) event).getNodes(); |
| SwingUtilities.invokeLater(new Runnable() { |
| public void run() { |
| List<TreeModelNode> tmNodes = new ArrayList<TreeModelNode>(nodes.length); |
| for (Object node : nodes) { |
| TreeModelNode[] tmNodesf = findNode(node); |
| for (TreeModelNode tmNode : tmNodesf) { |
| tmNodes.add(tmNode); |
| } |
| } |
| try { |
| manager.setSelectedNodes(tmNodes.toArray(new Node[] {})); |
| } catch (PropertyVetoException ex) { |
| Logger.getLogger(this.getClass().getName()).log(Level.INFO, "Selection of "+Arrays.toString(nodes)+" vetoed.", ex); // NOI18N |
| } |
| } |
| }); |
| return ; |
| } |
| getRootNode().setObject (model, model.getRoot ()); |
| } |
| }); |
| } |
| |
| } |
| |
| /** |
| * Implements set of tree view features. |
| */ |
| private final class DefaultTreeFeatures extends TreeFeatures implements TreeExpansionListener { |
| |
| private TreeView view; |
| private OutlineView outline; |
| |
| private DefaultTreeFeatures (TreeView view) { |
| this.view = view; |
| JTree tree; |
| try { |
| java.lang.reflect.Field treeField = TreeView.class.getDeclaredField("tree"); |
| treeField.setAccessible(true); |
| tree = (JTree) treeField.get(view); |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| return ; |
| } |
| tree.addTreeExpansionListener(this); |
| } |
| |
| private DefaultTreeFeatures (OutlineView view) { |
| this.outline = view; |
| view.addTreeExpansionListener(this); |
| } |
| |
| public void destroy() { |
| if (outline != null) { |
| outline.removeTreeExpansionListener(this); |
| } else { |
| JTree tree; |
| try { |
| java.lang.reflect.Field treeField = TreeView.class.getDeclaredField("tree"); |
| treeField.setAccessible(true); |
| tree = (JTree) treeField.get(view); |
| } catch (Exception ex) { |
| Exceptions.printStackTrace(ex); |
| return ; |
| } |
| tree.removeTreeExpansionListener(this); |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if given node is expanded. |
| * |
| * @param node a node to be checked |
| * @return <code>true</code> if given node is expanded |
| */ |
| public boolean isExpanded ( |
| Object node |
| ) { |
| Node[] ns = findNode (node); |
| if (ns.length == 0) return false; // Something what does not exist is not expanded ;-) |
| if (outline != null) { |
| return outline.isExpanded(ns[0]); |
| } else { |
| return view.isExpanded (ns[0]); |
| } |
| |
| } |
| |
| /** |
| * Expands given list of nodes. |
| * |
| * @param node a list of nodes to be expanded |
| */ |
| public void expandNode ( |
| Object node |
| ) { |
| Node[] ns = findNode (node); |
| for (Node n : ns) { |
| if (outline != null) { |
| outline.expandNode(n); |
| } else { |
| view.expandNode (n); |
| } |
| } |
| } |
| |
| /** |
| * Collapses given node. |
| * |
| * @param node a node to be expanded |
| */ |
| public void collapseNode ( |
| Object node |
| ) { |
| Node[] ns = findNode (node); |
| for (Node n : ns) { |
| if (outline != null) { |
| outline.collapseNode(n); |
| } else { |
| view.collapseNode (n); |
| } |
| } |
| } |
| |
| /** |
| * Called whenever an item in the tree has been expanded. |
| */ |
| public void treeExpanded (TreeExpansionEvent event) { |
| Models.CompoundModel model = getModel(); |
| if (model != null) { |
| model.nodeExpanded (initExpandCollapseNotify(event)); |
| } |
| } |
| |
| /** |
| * Called whenever an item in the tree has been collapsed. |
| */ |
| public void treeCollapsed (TreeExpansionEvent event) { |
| Models.CompoundModel model = getModel(); |
| if (model != null) { |
| model.nodeCollapsed (initExpandCollapseNotify(event)); |
| } |
| } |
| |
| private Object initExpandCollapseNotify(TreeExpansionEvent event) { |
| Node node = Visualizer.findNode(event.getPath ().getLastPathComponent()); |
| Object obj = node.getLookup().lookup(Object.class); |
| Object actOn; |
| node = node.getParentNode(); |
| if (node == null) { |
| actOn = Integer.valueOf(0); |
| } else { |
| Children ch = node.getChildren(); |
| if (ch instanceof TreeModelNode.TreeModelChildren) { |
| actOn = ((TreeModelNode.TreeModelChildren) ch).getTreeDepth(); |
| } else { |
| actOn = ch; |
| } |
| } |
| Models.CompoundModel model = getModel(); |
| if (model != null) { |
| DefaultTreeExpansionManager.get(model).setChildrenToActOn(actOn); |
| } |
| return obj; |
| } |
| |
| } |
| |
| } |
| |