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

net.sf.javagimmicks.swing.model.ListTreeNode Maven / Gradle / Ivy

package net.sf.javagimmicks.swing.model;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;

import javax.swing.event.TreeModelEvent;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;

/**
 * A very powerful implementation of {@link TypedTreeNode} (and it's child
 * interfaces) dedicated to act as a leaf, intermediate or root node within a
 * {@link ListTreeModel} (but works as well for other {@link TreeModel}s) that
 * allows easy {@link List}-style and fluent API access to child nodes and
 * values.
 * 

* Nevertheless - it is only suitable, if all nodes within the {@link TreeModel} * carry the same type of values (like in a {@link ListTreeModel}). This is * basically no problem, because you can choose {@link Object} as value type - * but you might have many type-casts then. * * @param * the type of values that ALL nodes within the represented * {@link TreeModel} or {@link ListTreeModel} can carry */ public class ListTreeNode implements TypedTreeNode, TypedChildTreeNode>, TypedParentTreeNode> { protected ArrayList> _children; protected ChildrenListView _childrenListView; protected ChildrenValueListView _childrenValueListView; protected ListTreeModel _model; protected ListTreeNode _parent; protected E _value; /** * Creates a new instance with the given value and {@link #isDedicatedLeaf() * dedicated leaf mode} setting. * * @param value * the value that this node should carry * @param leaf * if this node is a {@link #isDedicatedLeaf() dedicated leaf} or * not */ public ListTreeNode(final E value, final boolean leaf) { this(null, null, leaf, value); } /** * Creates a new instance with the given value in {@link #isDedicatedLeaf() * non-dedicated leaf mode}. * * @param value * the value that this node should carry */ public ListTreeNode(final E value) { this(value, false); } protected ListTreeNode(final ListTreeModel model, final boolean leaf, final E value) { this(model, null, leaf, value); } protected ListTreeNode(final ListTreeNode parent, final boolean leaf, final E value) { this(parent._model, parent, leaf, value); } private ListTreeNode(final ListTreeModel model, final ListTreeNode parent, final boolean leaf, final E value) { _model = model; _parent = parent; setDedicatedLeaf(leaf); _value = value; _childrenListView = new ChildrenListView(); _childrenValueListView = new ChildrenValueListView(); } /** * Adds and returns a new child {@link ListTreeNode} for the given value at * the given position and with the given {@link #isDedicatedLeaf() dedicated * leaf mode} setting. * * @param index * the index within the internal child list where to add the new * child {@link ListTreeNode} * @param value * the value to set for the new child {@link ListTreeNode} * @param leaf * the {@link #isDedicatedLeaf() dedicated leaf mode} setting for * the new child {@link ListTreeNode} * @return the new child {@link ListTreeNode} */ public ListTreeNode addChildAt(final int index, final E value, final boolean leaf) { final ListTreeNode result = new ListTreeNode(value, leaf); getChildList().add(index, result); return result; } /** * Adds and returns a new child {@link ListTreeNode} for the given value at * the given position in {@link #isDedicatedLeaf() non-leaf} mode. * * @param index * the index within the internal child list where to add the new * child {@link ListTreeNode} * @param value * the value to set for the new child {@link ListTreeNode} * @return the new child {@link ListTreeNode} */ public ListTreeNode addChildAt(final int index, final E value) { return addChildAt(index, value, false); } /** * Appends and returns a new child {@link ListTreeNode} for the given value * with the given {@link #isDedicatedLeaf() dedicated leaf mode} setting. * * @param value * the value to set for the new child {@link ListTreeNode} * @param leaf * the {@link #isDedicatedLeaf() dedicated leaf mode} setting for * the new child {@link ListTreeNode} * @return the new child {@link ListTreeNode} */ public ListTreeNode addChild(final E value, final boolean leaf) { return addChildAt(getChildCount(), value, leaf); } /** * Appends and returns a new child {@link ListTreeNode} for the given value * in {@link #isDedicatedLeaf() non-dedicated leaf mode}. * * @param value * the value to set for the new child {@link ListTreeNode} * @return the new child {@link ListTreeNode} */ public ListTreeNode addChild(final E value) { return addChild(value, false); } /** * Removes the child {@link ListTreeNode} at the given position, * {@link #detach() detaches} and returns it. * * @param index * the index of the child {@link ListTreeNode} to remove * @return the removed and {@link #isDetached() detached} * {@link ListTreeNode} */ public ListTreeNode removeChildAt(final int index) { return getChildList().remove(index); } /** * Returns a {@link List} view of the internal child list of * {@link ListTreeNode}s that takes care about proper state * checking/manipulation upon any called operation on it. *

* This means the following: *

    *
  • Upon add operations (including new nodes added via * {@link List#set(int, Object) set()}) *
      *
    • Make a consistency check, if the new {@link ListTreeNode} is fully * {@link #isDetached() detached} - if not, throw an * {@link IllegalArgumentException}
    • *
    • Attach the new {@link ListTreeNode} - i.e. set it's parent to this * {@link ListTreeNode} and apply recursively our internal * {@link ListTreeModel} if existing
    • *
    • Fire respective {@link TreeModelEvent}s if this instance is internally * attached to a {@link ListTreeModel}
    • *
    *
  • *
  • Upon remove operations (including old nodes removed via * {@link List#set(int, Object) set()}) *
      *
    • {@link #detach() Detach} the removed {@link ListTreeNode}
    • *
    • Fire respective {@link TreeModelEvent}s if this instance is internally * attached to a {@link ListTreeModel}
    • *
    *
  • *
  • Upon all operations except {@link List#isEmpty() isEmpty()} and * {@link List#size() size()} *
      *
    • Throw an {@link IllegalStateException} if the current instance is in * {@link #isDedicatedLeaf() dedicated leaf mode}.
    • *
    *
  • *
* * @return the {@link List} view all child {@link ListTreeNode}s */ public List> getChildList() { return _childrenListView; } /** * Returns a {@link List} view to the values of the internally managed child * {@link ListTreeNode}s whose operations mostly behave like that of * {@link #getChildList()}. *

* The {@link List} view is completely backed - so for example any * {@code add()} operation will actually add a new {@link ListTreeNode} with * the given value to the internal list of {@link ListTreeNode}s, and so on. * The only exception is the {@link List#set(int, Object) set()} operation * which does not remove the existing {@link ListTreeNode} and add a new one * - instead simply the value of the existing {@link ListTreeNode} is * {@link ListTreeNode#setValue(Object) updated}. */ @Override public List getChildValues() { return _childrenValueListView; } @Override public E getValue() { return _value; } /** * Updates the internal value of this node - eventually firing a respective * {@link TreeModelEvent} if there is a surrounding {@link ListTreeModel}. * * @param value * the new value to apply */ public void setValue(final E value) { _value = value; if (_model != null) { _model.fireNodeChanged(_parent, _parent == null ? 0 : _parent._childrenListView.indexOf(this)); } } @Override public boolean isLeaf() { return isDedicatedLeaf() || _children.isEmpty(); } /** * Returns if this instance is a dedicated leaf which means that it is not * allowed to add children (see {@link #getAllowsChildren()}). * * @return if the instance is a dedicated leaf * @see #getAllowsChildren() */ public boolean isDedicatedLeaf() { return _children == null; } /** * Set the given dedicated leaf mode on this instance. * * @param leaf * if the instance is a dedicated leaf or not */ public void setDedicatedLeaf(final boolean leaf) { if (isDedicatedLeaf() == leaf) { return; } if (!leaf) { _children = new ArrayList>(); } else if (_children.isEmpty()) { _children = null; } else { throw new IllegalStateException("Node still has children. Remove them before setting the node to leaf mode."); } } @Override @SuppressWarnings("unchecked") public Enumeration> children() { return Collections.enumeration(isLeaf() ? Collections.EMPTY_LIST : _children); } @Override public boolean getAllowsChildren() { return !isDedicatedLeaf(); } @Override public ListTreeNode getChildAt(final int childIndex) { if (isDedicatedLeaf()) { throw new ArrayIndexOutOfBoundsException("Node allows no children!"); } return _children.get(childIndex); } @Override public int getChildCount() { return isLeaf() ? 0 : _children.size(); } @Override public int getIndex(final TreeNode node) { return isDedicatedLeaf() ? -1 : _children.indexOf(node); } @Override public ListTreeNode getParent() { return _parent; } @Override public E getParentValue() { if (_parent == null) { throw new IllegalStateException("Node has no parent!"); } return _parent.getValue(); } /** * Returns if this instance is fully detached, which means it has no link to * a surrounding {@link ListTreeModel} and also has no parent * {@link ListTreeNode}. *

* A fully detached {@link ListTreeNode} can be added as a child to an * existing {@link ListTreeNode} via it's {@link #getChildList() child list * view} *

* A node can be fully detached using {@link #detach()} * * @return if the current instance is fully detached * @see #detach() */ public boolean isDetached() { return _parent == null && _model == null; } /** * Fully detaches this node, which removes the internal links from and to * it's parent {@link ListTreeNode} (if existing) and removes the link to the * surrounding {@link ListTreeModel} (if existing) eventually firing a * respective {@link TreeModelEvent}. * * @throws IllegalStateException * if the node is already fully detached */ public void detach() throws IllegalStateException { if (isDetached()) { throw new IllegalStateException("This node is already fully detached!"); } // If there is a parent, remove this node via its child list view - it // will take care of everything if (_parent != null) { _parent._childrenListView.remove(this); } // We had no parent but a model means, that we were the root node else if (_model != null) { final ListTreeModel model = updateModel(null); model._root = null; model.fireNodesRemoved(null, 0, Collections.singleton(this)); } } @Override public String toString() { return _value == null ? null : _value.toString(); } protected ListTreeModel updateModel(final ListTreeModel model) { final ListTreeModel result = _model; _model = model; if (!isLeaf()) { for (final ListTreeNode child : _children) { child.updateModel(model); } } return result; } protected class ChildrenListView extends AbstractList> { @Override public boolean addAll(final int index, final Collection> c) { checkLeaf(); for (final ListTreeNode newChild : c) { preProcessAdd(newChild); } _children.addAll(index, c); if (_model != null) { _model.fireNodesInserted(ListTreeNode.this, index, c); } return true; } @Override public boolean addAll(final Collection> c) { return addAll(_children.size(), c); } @Override public void add(final int index, final ListTreeNode element) { addAll(index, Collections.singleton(element)); } @Override public void clear() { super.clear(); /* * The following implementation of the clear() operation whould * recursively clear child nodes! I think this would break the contract * of List where clear() is the same as a buld remove() */ // checkLeaf(); // clear(true); } @Override public ListTreeNode get(final int index) { checkLeaf(); return _children.get(index); } @Override public ListTreeNode remove(final int index) { checkLeaf(); final ListTreeNode removedNode = _children.remove(index); postProcessRemove(removedNode); if (_model != null) { _model.fireNodesRemoved(ListTreeNode.this, index, Collections.singleton(removedNode)); } return removedNode; } @Override public ListTreeNode set(final int index, final ListTreeNode element) { checkLeaf(); preProcessAdd(element); final ListTreeNode removedNode = _children.set(index, element); postProcessRemove(removedNode); if (_model != null) { _model.fireNodeChanged(ListTreeNode.this, index); } return removedNode; } @Override public int size() { if (isLeaf()) { return 0; } return _children.size(); } @SuppressWarnings("unused") private void clear(final boolean isTop) { if (isLeaf()) { return; } for (final ListTreeNode newChild : _children) { newChild._childrenListView.clear(false); newChild._model = null; newChild._parent = null; } if (isTop && _model != null) { final ArrayList> oldChildren = new ArrayList>(_children); _children.clear(); _model.fireNodesRemoved(ListTreeNode.this, 0, oldChildren); } else { _children.clear(); } } private void preProcessAdd(final ListTreeNode newChild) { if (!newChild.isDetached()) { throw new IllegalArgumentException("Can only add fully deatched nodes!"); } newChild._parent = ListTreeNode.this; newChild.updateModel(_model); } private void postProcessRemove(final ListTreeNode removedNode) { removedNode.updateModel(null); removedNode._parent = null; } private void checkLeaf() { if (isDedicatedLeaf()) { throw new IllegalStateException("Node is a dedicated leaf!"); } } } protected class ChildrenValueListView extends AbstractList { @Override public void add(final int index, final E element) { final ListTreeNode newNode = new ListTreeNode(element, false); _childrenListView.add(index, newNode); } @Override public boolean addAll(final Collection c) { return addAll(_childrenListView.size(), c); } @Override public boolean addAll(final int index, final Collection c) { final ArrayList> newNodeCollection = new ArrayList>(c.size()); for (final E element : c) { newNodeCollection.add(new ListTreeNode(element, false)); } return _childrenListView.addAll(index, newNodeCollection); } @Override public void clear() { _childrenListView.clear(); } @Override public E get(final int index) { return _childrenListView.get(index).getValue(); } @Override public E remove(final int index) { return _childrenListView.remove(index).getValue(); } @Override public E set(final int index, final E element) { final ListTreeNode childNode = _childrenListView.get(index); final E oldValue = childNode.getValue(); childNode.setValue(element); return oldValue; } @Override public int size() { return _childrenListView.size(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy