Package org.twodividedbyzero.idea.findbugs.gui.tree.model

Source Code of org.twodividedbyzero.idea.findbugs.gui.tree.model.AbstractTreeModel

/*
* Copyright 2008-2013 Andre Pfeiler
*
* This file is part of FindBugs-IDEA.
*
* FindBugs-IDEA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* FindBugs-IDEA 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 Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FindBugs-IDEA.  If not, see <http://www.gnu.org/licenses/>.
*/

package org.twodividedbyzero.idea.findbugs.gui.tree.model;

import org.twodividedbyzero.idea.findbugs.common.EventDispatchThreadHelper;

import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.EventListener;
import java.util.Vector;


/**
* An abstract implementation of the TreeModel interface. This implementation is
* partly copied from the <code>DefaultTreeModel</code> implementation
* (version 6) as supplied with the JVM. However, the type of nodes has been
* left out, as well as a mechanism to insert, update or delete nodes. This is
* up to subclasses.
*
* @author Andre Pfeiler<andrep@twodividedbyzero.org>
* @param <N> the type of objects that can be used as nodes.
* @see DefaultTreeModel
*/
public abstract class AbstractTreeModel<N> implements Serializable, TreeModel {

  /** Root of the tree. */
  protected N _root;

  /** Listeners. */
  protected final EventListenerList _treeModelListeners = new EventListenerList();
  //protected ArrayList<TreeModelListener> _treeModelListener = new ArrayList<TreeModelListener>();


  /**
   * Creates an empty tree in which any node can have children. Initially, no
   * root is set.
   */
  public AbstractTreeModel() {
    this(null);
  }


  /**
   * Creates a tree in which any node can have children.
   *
   * @param root a R object that is the root of the tree
   */
  public AbstractTreeModel(final N root) {
    setRoot(root);
  }


  /**
   * Adds a listener for the TreeModelEvent posted after the tree changes.
   *
   * @param l the listener to add
   * @see #removeTreeModelListener(TreeModelListener)
   */
  public final void addTreeModelListener(final TreeModelListener l) {
    _treeModelListeners.add(TreeModelListener.class, l);
  }


  /**
   * Returns the value returned from <code>getChildNode(parent, index)</code>.
   *
   * @param parent a node in the tree, obtained from this data source
   * @return the child of <I>parent</I> at index <I>index</I>
   * @see #getChildNode(Object, int)
   */
  @SuppressWarnings("unchecked")
  public final N getChild(final Object parent, final int index) {
    return getChildNode((N) parent, index);
  }


  /**
   * Returns the value from <code>getChildNodeCount(parent)</code>.
   *
   * @param parent a node in the tree, obtained from this data source
   * @return the number of children of the node <I>parent</I>
   */
  @SuppressWarnings("unchecked")
  public final int getChildCount(final Object parent) {
    return getChildNodeCount((N) parent);
  }


  /**
   * Returns the child of <I>parent</I> at index <I>index</I> in the
   * parent's child array. <I>parent</I> must be a node previously obtained
   * from this data source. This should not return null if <i>index</i> is a
   * valid index for <i>parent</i> (that is <i>index</i> >= 0 && <i>index</i> <
   * getChildCount(<i>parent</i>)).
   *
   * @param parent a node in the tree, obtained from this data source
   * @param index
   * @return the child of <I>parent</I> at index <I>index</I>
   */
  public abstract N getChildNode(N parent, int index);


  /**
   * Returns the number of children of <I>parent</I>. Returns 0 if the node
   * is a leaf or if it has no children. <I>parent</I> must be a node
   * previously obtained from this data source.
   *
   * @param parent a node in the tree, obtained from this data source
   * @return the number of children of the node <I>parent</I>
   */
  public abstract int getChildNodeCount(N parent);


  /**
   * Returns the value returned from
   * <code>getIndexOfChildNode(parent, child)</code>.
   *
   * @param parent a note in the tree, obtained from this data source
   * @param child  the node we are interested in
   * @return the index of the child in the parent, or -1 if either the parent
   *         or the child is <code>null</code>
   */
  @SuppressWarnings("unchecked")
  public final int getIndexOfChild(final Object parent, final Object child) {
    return getIndexOfChildNode((N) parent, (N) child);
  }


  /**
   * Returns the index of child in parent. If either the parent or child is
   * <code>null</code>, returns -1.
   *
   * @param parent a note in the tree, obtained from this data source
   * @param child  the node we are interested in
   * @return the index of the child in the parent, or -1 if either the parent
   *         or the child is <code>null</code>
   */
  public abstract int getIndexOfChildNode(N parent, N child);


  /**
   * Returns an array of all the objects currently registered as
   * <code><em>Foo</em>Listener</code>s upon this model.
   * <code><em>Foo</em>Listener</code>s are registered using the
   * <code>add<em>Foo</em>Listener</code> method.
   * <p/>
   * <p/>
   * <p/>
   * You can specify the <code>listenerType</code> argument with a class
   * literal, such as <code><em>Foo</em>Listener.class</code>. For
   * example, you can query a
   * <code>AbstractTreeModelFrom6</code> <code>m</code> for its tree model
   * listeners with the following code:
   * <p/>
   * <pre>
   * TreeModelListener[] tmls = (TreeModelListener[]) (m
   *   .getListeners(TreeModelListener.class));
   * </pre>
   * <p/>
   * If no such listeners exist, this method returns an empty array.
   *
   * @param listenerType the type of listeners requested; this parameter should
   *                     specify an interface that descends from
   *                     <code>java.util.EventListener</code>
   * @return an array of all objects registered as
   *         <code><em>Foo</em>Listener</code>s on this component, or an
   *         empty array if no such listeners have been added
   * @throws ClassCastException if <code>listenerType</code> doesn't specify a class
   *                            or interface that implements
   *                            <code>java.util.EventListener</code>
   * @see #getTreeModelListeners
   * @since 1.3
   */
  public final <T extends EventListener> T[] getListeners(final Class<T> listenerType) {
    return _treeModelListeners.getListeners(listenerType);
  }


  /**
   * Obtain the parent of a node.
   *
   * @param child a node
   * @return the parent of the child (or null if child has no parent).
   */
  public abstract N getParentNode(N child);


  /**
   * Builds the parents of node up to and including the root node, where the
   * original node is the last element in the returned array. The length of
   * the returned array gives the node's depth in the tree.
   *
   * @param aNode the R to get the path for
   * @return
   */
  public final N[] getPathToRoot(final N aNode) {
    return getPathToRoot(aNode, 0);
  }


  /**
   * Returns the root of the tree. Returns null only if the tree has no nodes.
   *
   * @return the root of the tree
   */
  public final N getRoot() {
    return _root;
  }


  /**
   * Returns an array of all the tree model listeners registered on this
   * model.
   *
   * @return all of this model's <code>TreeModelListener</code>s or an
   *         empty array if no tree model listeners are currently registered
   * @see #addTreeModelListener
   * @see #removeTreeModelListener
   */
  public final TreeModelListener[] getTreeModelListeners() {
    return _treeModelListeners.getListeners(TreeModelListener.class);
  }


  /**
   * Returns whether the specified node is a leaf node.
   *
   * @param node the node to check
   * @return true if the node has no child nodes
   */
  @SuppressWarnings("unchecked")
  public final boolean isLeaf(final Object node) {
    return getChildCount(node) == 0;
  }


  /*public void valueForPathChanged(final TreePath path, final Object newValue) {
    final MutableTreeNode node = (MutableTreeNode) path.getLastPathComponent();

    node.setUserObject(newValue);
    nodeChanged(path.getLastPathComponent());
  }*/


  /**
   * Invoke this method after you've changed how node is to be represented in
   * the tree.
   *
   * @param node
   */
  public final void nodeChanged(final N node) {
    if (_treeModelListeners != null && node != null) {
      final N parent = getParentNode(node);

      if (parent != null) {
        final int anIndex = getIndexOfChildNode(parent, node);
        if (anIndex != -1) {
          final int[] cIndexs = new int[1];

          cIndexs[0] = anIndex;
          nodesChanged(parent, cIndexs);
        }
      } else if (node == getRoot()) {
        nodesChanged(node, null);
      }
    }
  }


  /**
   * Invoke this method after you've changed how the children identified by
   * childIndicies are to be represented in the tree.
   *
   * @param node
   * @param childIndices
   */
  public final void nodesChanged(final N node, final int[] childIndices) {
    if (node != null) {
      if (childIndices != null) {
        final int cCount = childIndices.length;

        if (cCount > 0) {
          final Object[] cChildren = new Object[cCount];

          for (int counter = 0; counter < cCount; counter++) {
            cChildren[counter] = getChildNode(node, childIndices[counter]);
          }
          fireTreeNodesChanged(this, getPathToRoot(node), childIndices, cChildren);
        }
      } else if (node == getRoot()) {
        fireTreeNodesChanged(this, getPathToRoot(node), null, null);
      }
    }
  }


  /**
   * Message this to remove node from its parent. This will message
   * nodesWereRemoved to create the appropriate event. This is the
   * preferred way to remove a node as it handles the event creation
   * for you.
   */
  /*public void removeNodeFromParent(final N node) {
    final N parent = getParentNode(node);

    if (parent == null) {
      throw new IllegalArgumentException("node does not have a parent.");
    }

    final int[] childIndex = new int[1];
    final Object[] removedArray = new Object[1];


    childIndex[0] = getIndexOfChildNode(parent, node);
    //parent.remove(childIndex[0]);

    removedArray[0] = node;
    nodesWereRemoved(parent, childIndex, removedArray);
  }*/


  /**
   * Invoke this method if you've totally changed the children of node and its
   * childrens children... This will post a treeStructureChanged event.
   *
   * @param node
   */
  public final void nodeStructureChanged(final N node) {
    if (node != null) {
      EventDispatchThreadHelper.invokeLater(new Runnable() {
        public void run() {
          fireTreeStructureChanged(this, getPathToRoot(node), null, null);
        }
      });
    }
  }


  /**
   * Invoke this method after you've inserted some TreeNodes into node.
   * childIndices should be the index of the new elements and must be sorted
   * in ascending order.
   *
   * @param node
   * @param childIndices
   */
  public final void nodesWereInserted(final N node, final int[] childIndices) {
    if (_treeModelListeners != null && node != null && childIndices != null && childIndices.length > 0) {
      final int cCount = childIndices.length;
      final Object[] newChildren = new Object[cCount];

      for (int counter = 0; counter < cCount; counter++) {
        newChildren[counter] = getChildNode(node, childIndices[counter]);
      }
      fireTreeNodesInserted(this, getPathToRoot(node), childIndices, newChildren);
    }
  }


  /**
   * Invoke this method after you've removed some TreeNodes from node.
   * childIndices should be the index of the removed elements and must be
   * sorted in ascending order. And removedChildren should be the array of the
   * children objects that were removed.
   *
   * @param node
   * @param childIndices
   * @param removedChildren
   */
  public final void nodesWereRemoved(final N node, final int[] childIndices, final Object[] removedChildren) {
    if (node != null && childIndices != null) {
      fireTreeNodesRemoved(this, getPathToRoot(node), childIndices, removedChildren);
    }
  }


  /**
   * Invoke this method if you've modified the {@code R}s upon which this
   * model depends. The model will notify all of its listeners that the model
   * has changed.
   */
  public final void reload() {
    reload(_root);
  }


  /**
   * Invoke this method if you've modified the {@code R}s upon which this
   * model depends. The model will notify all of its listeners that the model
   * has changed below the given node.
   *
   * @param node the node below which the model has changed
   */
  public final void reload(final N node) {
    if (node != null) {
      fireTreeStructureChanged(this, getPathToRoot(node), null, null);
    }
  }


  /**
   * Removes a listener previously added with
   * <code>addTreeModelListener</code>.
   *
   * @param l the listener to remove
   * @see #addTreeModelListener
   */
  public final void removeTreeModelListener(final TreeModelListener l) {
    _treeModelListeners.remove(TreeModelListener.class, l);
  }


  /**
   * Sets the root to <code>root</code>. A null <code>root</code> implies
   * the tree is to display nothing, and is legal.
   *
   * @param newRoot
   */
  public final void setRoot(final N newRoot) {
    final N oldRoot = _root;
    if (oldRoot != newRoot) {
      if (oldRoot != null) {
        deinstall(oldRoot);
      }
      _root = newRoot;
      if (newRoot != null) {
        install(newRoot);
      }
      if (newRoot == null && oldRoot != null) {
        fireTreeStructureChanged(this, null);
      } else {
        nodeStructureChanged(newRoot);
      }
    }
  }


  @SuppressWarnings("unchecked")
  private N[] createEmptyArray(final int size) {
    return (N[]) Array.newInstance(getNodeClass(), size);
  }


  /**
   * Notifies all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @param source the node where the tree model has changed
   * @param path   the path to the root node
   * @see EventListenerList
   */
  private void fireTreeStructureChanged(final Object source, final TreePath path) {
    // Guaranteed to return a non-null array
    final Object[] listeners = _treeModelListeners.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path);
        }
        ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
      }
    }
  }


  @SuppressWarnings({"unchecked", "UseOfObsoleteCollectionType"})
  private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    final Vector<?> values = (Vector<?>) s.readObject();
    int indexCounter = 0;
    final int maxCounter = values.size();

    if (indexCounter < maxCounter && "root".equals(values.elementAt(indexCounter))) {
      _root = (N) values.elementAt(++indexCounter);
      //indexCounter++;
    }
  }


  @SuppressWarnings({"unchecked", "UseOfObsoleteCollectionType"})
  private void writeObject(final ObjectOutputStream s) throws IOException {
    final Vector values = new Vector();

    s.defaultWriteObject();
    // Save the root, if its Serializable.
    if (_root != null && _root instanceof Serializable) {
      values.addElement("root");
      values.addElement(_root);
    }
    s.writeObject(values);
  }


  /**
   * Will be called prior to removal of the current root node. Sub classes
   * must remove listeners that were added previously by <code>install</code>.
   *
   * @param root the root node that is about to be be removed.
   * @see #install(Object)
   */
  protected abstract void deinstall(N root);


  /**
   * Notifies all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @param source     the node being changed
   * @param path     the path to the root node
   * @param childIndices the indices of the changed elements
   * @param children   the changed elements
   * @see EventListenerList
   */
  protected final void fireTreeNodesChanged(final Object source, final Object[] path, final int[] childIndices, final Object[] children) {
    // Guaranteed to return a non-null array
    final Object[] listeners = _treeModelListeners.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path, childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
      }
    }
  }


  /**
   * Notifies all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @param source     the node where new elements are being inserted
   * @param path     the path to the root node
   * @param childIndices the indices of the new elements
   * @param children   the new elements
   * @see EventListenerList
   */
  protected final void fireTreeNodesInserted(final Object source, final Object[] path, final int[] childIndices, final Object[] children) {
    // Guaranteed to return a non-null array
    final Object[] listeners = _treeModelListeners.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path, childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
      }
    }
  }


  /**
   * Notifies all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @param source     the node where elements are being removed
   * @param path     the path to the root node
   * @param childIndices the indices of the removed elements
   * @param children   the removed elements
   * @see EventListenerList
   */
  protected final void fireTreeNodesRemoved(final Object source, final Object[] path, final int[] childIndices, final Object[] children) {
    // Guaranteed to return a non-null array
    final Object[] listeners = _treeModelListeners.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path, childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
      }
    }
  }


  /**
   * Notifies all listeners that have registered interest for notification on
   * this event type. The event instance is lazily created using the
   * parameters passed into the fire method.
   *
   * @param source     the node where the tree model has changed
   * @param path     the path to the root node
   * @param childIndices the indices of the affected elements
   * @param children   the affected elements
   * @see EventListenerList
   */
  protected final void fireTreeStructureChanged(final Object source, final Object[] path, final int[] childIndices, final Object[] children) {
    // Guaranteed to return a non-null array
    final Object[] listeners = _treeModelListeners.getListenerList();
    TreeModelEvent e = null;
    // Process the listeners last to first, notifying
    // those that are interested in this event
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      if (listeners[i] == TreeModelListener.class) {
        // Lazily create the event:
        if (e == null) {
          e = new TreeModelEvent(source, path, childIndices, children);
        }
        ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
      }
    }
  }


  /**
   * Subclasses must implement this method and return a <code>Class</code>
   * object for the generic type.
   *
   * @return the <code>Class</code> for the generic type
   */
  protected abstract Class<N> getNodeClass();


  /**
   * Builds the parents of node up to and including the root node, where the
   * original node is the last element in the returned array. The length of
   * the returned array gives the node's depth in the tree.
   *
   * @param aNode the R to get the path for
   * @param depth an integer giving the number of steps already taken
   *              towards the root (on recursive calls), used to size the
   *              returned array
   * @return an array of TreeNodes giving the path from the root to the
   *         specified node
   */
  protected final N[] getPathToRoot(final N aNode, int depth) {
    // This method recurses, traversing towards the root in order
    // size the array. On the way back, it fills in the nodes,
    // starting from the root and working back to the original node.

    final N[] retNodes;

    if (aNode == null) {
      if (depth == 0) {
        return createEmptyArray(0);
      } else {
        retNodes = createEmptyArray(depth);
      }
    } else {
      //noinspection AssignmentToMethodParameter
      depth++;
      if (aNode == _root) {
        retNodes = createEmptyArray(depth);
      } else {
        retNodes = getPathToRoot(getParentNode(aNode), depth);
      }
      retNodes[retNodes.length - depth] = aNode;
    }
    return retNodes;
  }


  /**
   * Will be called immediately after setting of the root node. Sub classes
   * may add listeners to the root that enable them to monitor changes to the
   * tree and fire change events accordingly.
   *
   * @param root the root node that is just installed.
   * @see #deinstall(Object)
   */
  protected abstract void install(N root);


  public void valueForPathChanged(final TreePath path, final Object newValue) {
    if (_treeModelListeners.getListenerCount() > 0 && !path.getLastPathComponent().equals(newValue)) {
      final TreePath parentPath = path.getParentPath();
      final int index = getIndexOfChild(parentPath.getLastPathComponent(), newValue);
      if (index != -1) {
        fireTreeNodesChanged(this, path.getPath(), new int[] {index}, new Object[] {newValue});
        //fireTreeEvent(CHANGE, new TreeModelEvent(this, parentPath, new int[] {index}, new Object[] {newValue}));
      } else {
        fireTreeStructureChanged(this, parentPath);
        //fireTreeEvent(STRUCTURE, new TreeModelEvent(this, parentPath));
      }
    }
  }


}
TOP

Related Classes of org.twodividedbyzero.idea.findbugs.gui.tree.model.AbstractTreeModel

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.