| /******************************************************************************* |
| * Copyright (c) 2007, 2009 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| package org.eclipse.jpt.utility.internal.model.value.swing; |
| |
| import java.util.ArrayList; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| |
| import javax.swing.event.TreeModelListener; |
| import javax.swing.tree.TreePath; |
| |
| import org.eclipse.jpt.utility.internal.StringTools; |
| import org.eclipse.jpt.utility.internal.model.listener.awt.AWTListChangeListenerWrapper; |
| import org.eclipse.jpt.utility.internal.model.listener.awt.AWTPropertyChangeListenerWrapper; |
| import org.eclipse.jpt.utility.internal.model.listener.awt.AWTStateChangeListenerWrapper; |
| import org.eclipse.jpt.utility.internal.model.value.StaticPropertyValueModel; |
| import org.eclipse.jpt.utility.model.event.ListAddEvent; |
| import org.eclipse.jpt.utility.model.event.ListChangeEvent; |
| import org.eclipse.jpt.utility.model.event.ListClearEvent; |
| import org.eclipse.jpt.utility.model.event.ListEvent; |
| import org.eclipse.jpt.utility.model.event.ListMoveEvent; |
| import org.eclipse.jpt.utility.model.event.ListRemoveEvent; |
| import org.eclipse.jpt.utility.model.event.ListReplaceEvent; |
| import org.eclipse.jpt.utility.model.event.PropertyChangeEvent; |
| import org.eclipse.jpt.utility.model.event.StateChangeEvent; |
| import org.eclipse.jpt.utility.model.listener.ListChangeListener; |
| import org.eclipse.jpt.utility.model.listener.PropertyChangeListener; |
| import org.eclipse.jpt.utility.model.listener.StateChangeListener; |
| import org.eclipse.jpt.utility.model.value.ListValueModel; |
| import org.eclipse.jpt.utility.model.value.PropertyValueModel; |
| import org.eclipse.jpt.utility.model.value.TreeNodeValueModel; |
| |
| /** |
| * This javax.swing.tree.TreeModel can be used to keep a TreeModelListener |
| * (e.g. a JTree) in synch with a tree of TreeNodeValueModel objects. Unlike |
| * javax.swing.tree.DefaultTreeModel, you do not add and remove nodes with |
| * methods implemented here. You can add and remove nodes by adding and |
| * removing them directly to/from the nodes (or, more typically, the domain |
| * objects the nodes are wrapping and listening to). |
| * |
| * Due to limitations in JTree, the root of the tree can never be null, |
| * which, typically, should not be a problem. (If you want to display an empty |
| * tree you can set the JTree's treeModel to null.) |
| */ |
| public class TreeModelAdapter<T> |
| extends AbstractTreeModel |
| { |
| /** |
| * A value model on the underlying tree's root node and its |
| * corresponding listener. This allows clients to swap out |
| * the entire tree. Due to limitations in JTree, the root should |
| * never be set to null while we have listeners. |
| */ |
| private final PropertyValueModel<TreeNodeValueModel<T>> rootHolder; |
| private final PropertyChangeListener rootListener; |
| |
| /** |
| * A listener that notifies us when a node's internal |
| * "state" changes (as opposed to the node's value or list of |
| * children), allowing us to forward notification to our listeners. |
| */ |
| private final StateChangeListener nodeStateListener; |
| |
| /** |
| * A listener that notifies us when a node's "value" |
| * changes (as opposed to the node's state or list of |
| * children), allowing us to forward notification to our listeners. |
| * Typically, this will only happen with nodes that hold |
| * primitive data. |
| */ |
| private final PropertyChangeListener nodeValueListener; |
| |
| /** |
| * A listener that notifies us when an underlying node's |
| * "list" of children changes, allowing us to keep our |
| * internal tree in synch with the underlying tree model. |
| */ |
| private final ListChangeListener childrenListener; |
| |
| /* these attributes make up our internal tree */ |
| /** |
| * The root cannot be null while we have listeners, which is |
| * most of the time. The root is cached so we can disengage |
| * from it when it has been swapped out. |
| */ |
| private TreeNodeValueModel<T> root; |
| |
| /** |
| * Map the nodes to their lists of children. |
| * We cache these so we can swap out the entire list of children |
| * when we receive a #listChanged() event (which does not include |
| * the items that were affected). |
| * @see EventChangePolicy#rebuildChildren() |
| */ |
| final IdentityHashMap<TreeNodeValueModel<T>, List<TreeNodeValueModel<T>>> childrenLists; |
| |
| /** |
| * Map the children models to their parents. |
| * We cache these so we can figure out the "real" source of the |
| * list change events (the parent). |
| * @see EventChangePolicy#parent() |
| */ |
| final IdentityHashMap<ListValueModel<TreeNodeValueModel<T>>, TreeNodeValueModel<T>> parents; |
| |
| |
| // ********** constructors ********** |
| |
| /** |
| * Construct a tree model for the specified root. |
| */ |
| public TreeModelAdapter(PropertyValueModel<TreeNodeValueModel<T>> rootHolder) { |
| super(); |
| if (rootHolder == null) { |
| throw new NullPointerException(); |
| } |
| this.rootHolder = rootHolder; |
| this.rootListener = this.buildRootListener(); |
| this.nodeStateListener = this.buildNodeStateListener(); |
| this.nodeValueListener = this.buildNodeValueListener(); |
| this.childrenListener = this.buildChildrenListener(); |
| this.childrenLists = new IdentityHashMap<TreeNodeValueModel<T>, List<TreeNodeValueModel<T>>>(); |
| this.parents = new IdentityHashMap<ListValueModel<TreeNodeValueModel<T>>, TreeNodeValueModel<T>>(); |
| } |
| |
| /** |
| * Construct a tree model for the specified root. |
| */ |
| public TreeModelAdapter(TreeNodeValueModel<T> root) { |
| this(new StaticPropertyValueModel<TreeNodeValueModel<T>>(root)); |
| } |
| |
| |
| // ********** initialization ********** |
| |
| protected PropertyChangeListener buildRootListener() { |
| return new AWTPropertyChangeListenerWrapper(this.buildRootListener_()); |
| } |
| |
| protected PropertyChangeListener buildRootListener_() { |
| return new PropertyChangeListener() { |
| public void propertyChanged(PropertyChangeEvent event) { |
| TreeModelAdapter.this.rootChanged(); |
| } |
| @Override |
| public String toString() { |
| return "root listener"; //$NON-NLS-1$ |
| } |
| }; |
| } |
| |
| protected PropertyChangeListener buildNodeValueListener() { |
| return new AWTPropertyChangeListenerWrapper(this.buildNodeValueListener_()); |
| } |
| |
| protected PropertyChangeListener buildNodeValueListener_() { |
| return new PropertyChangeListener() { |
| @SuppressWarnings("unchecked") |
| public void propertyChanged(PropertyChangeEvent event) { |
| TreeModelAdapter.this.nodeChanged((TreeNodeValueModel<T>) event.getSource()); |
| } |
| @Override |
| public String toString() { |
| return "node value listener"; //$NON-NLS-1$ |
| } |
| }; |
| } |
| |
| protected StateChangeListener buildNodeStateListener() { |
| return new AWTStateChangeListenerWrapper(this.buildNodeStateListener_()); |
| } |
| |
| protected StateChangeListener buildNodeStateListener_() { |
| return new StateChangeListener() { |
| @SuppressWarnings("unchecked") |
| public void stateChanged(StateChangeEvent event) { |
| TreeModelAdapter.this.nodeChanged((TreeNodeValueModel<T>) event.getSource()); |
| } |
| @Override |
| public String toString() { |
| return "node state listener"; //$NON-NLS-1$ |
| } |
| }; |
| } |
| |
| protected ListChangeListener buildChildrenListener() { |
| return new AWTListChangeListenerWrapper(this.buildChildrenListener_()); |
| } |
| |
| protected ListChangeListener buildChildrenListener_() { |
| return new ListChangeListener() { |
| public void itemsAdded(ListAddEvent event) { |
| new AddEventChangePolicy(event).addChildren(); |
| } |
| public void itemsRemoved(ListRemoveEvent event) { |
| new RemoveEventChangePolicy(event).removeChildren(); |
| } |
| public void itemsReplaced(ListReplaceEvent event) { |
| new ReplaceEventChangePolicy(event).replaceChildren(); |
| } |
| public void itemsMoved(ListMoveEvent event) { |
| new MoveEventChangePolicy(event).moveChildren(); |
| } |
| public void listCleared(ListClearEvent event) { |
| new ClearEventChangePolicy(event).clearChildren(); |
| } |
| public void listChanged(ListChangeEvent event) { |
| new ChangeEventChangePolicy(event).rebuildChildren(); |
| } |
| @Override |
| public String toString() { |
| return "children listener"; //$NON-NLS-1$ |
| } |
| }; |
| } |
| |
| |
| // ********** TreeModel implementation ********** |
| |
| public Object getRoot() { |
| return this.root; |
| } |
| |
| @SuppressWarnings("unchecked") |
| public Object getChild(Object parent, int index) { |
| return ((TreeNodeValueModel<T>) parent).child(index); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public int getChildCount(Object parent) { |
| return ((TreeNodeValueModel<T>) parent).childrenSize(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public boolean isLeaf(Object node) { |
| return ((TreeNodeValueModel<T>) node).isLeaf(); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public void valueForPathChanged(TreePath path, Object newValue) { |
| ((TreeNodeValueModel<T>) path.getLastPathComponent()).setValue((T) newValue); |
| } |
| |
| @SuppressWarnings("unchecked") |
| public int getIndexOfChild(Object parent, Object child) { |
| return ((TreeNodeValueModel<T>) parent).indexOfChild((TreeNodeValueModel<T>) child); |
| } |
| |
| /** |
| * Extend to start listening to the underlying model if necessary. |
| */ |
| @Override |
| public void addTreeModelListener(TreeModelListener l) { |
| if (this.hasNoTreeModelListeners()) { |
| this.engageModel(); |
| } |
| super.addTreeModelListener(l); |
| } |
| |
| /** |
| * Extend to stop listening to the underlying model if appropriate. |
| */ |
| @Override |
| public void removeTreeModelListener(TreeModelListener l) { |
| super.removeTreeModelListener(l); |
| if (this.hasNoTreeModelListeners()) { |
| this.disengageModel(); |
| } |
| } |
| |
| |
| // ********** behavior ********** |
| |
| /** |
| * Listen to the root and all the other nodes |
| * in the underlying tree model. |
| */ |
| private void engageModel() { |
| this.rootHolder.addPropertyChangeListener(PropertyValueModel.VALUE, this.rootListener); |
| this.root = this.rootHolder.getValue(); |
| if (this.root == null) { |
| throw new NullPointerException(); // the root cannot be null while we have listeners |
| } |
| this.engageNode(this.root); |
| this.addRoot(); |
| } |
| |
| /** |
| * Add the root and all of the nodes to the underlying tree. |
| */ |
| private void addRoot() { |
| this.addNode(0, this.root); |
| } |
| |
| /** |
| * Stop listening to the root and all the other |
| * nodes in the underlying tree model. |
| */ |
| private void disengageModel() { |
| this.removeRoot(); |
| this.disengageNode(this.root); |
| this.root = null; |
| this.rootHolder.removePropertyChangeListener(PropertyValueModel.VALUE, this.rootListener); |
| } |
| |
| /** |
| * Remove the root and all of the nodes from the underlying tree. |
| */ |
| private void removeRoot() { |
| this.removeNode(0, this.root); |
| } |
| |
| /** |
| * The root has been swapped. |
| * This method is a bit gnarly because the API for notifying listeners |
| * that the root has changed is a bit inconsistent with that used for |
| * non-root nodes. |
| */ |
| void rootChanged() { |
| TreeNodeValueModel<T> newRoot = this.rootHolder.getValue(); |
| if (newRoot == null) { |
| throw new NullPointerException(); // the root cannot be null while we have listeners |
| } |
| // remove all the current root's children from the tree |
| // and remove the it from the internal tree |
| this.removeRoot(); |
| |
| // save the old root and swap in the new root |
| TreeNodeValueModel<T> oldRoot = this.root; |
| this.root = newRoot; |
| |
| // we must be listening to both the old and new roots when we fire the event |
| // because their values can be affected by whether they have listeners |
| this.engageNode(this.root); |
| this.fireTreeRootReplaced(this.root); |
| // now we can stop listening to the old root |
| this.disengageNode(oldRoot); |
| |
| // add the new root to the internal tree and |
| // add all its children to the tree also |
| this.addRoot(); |
| } |
| |
| /** |
| * Either the "value" or the "state" of the specified node has changed, |
| * forward notification to our listeners. |
| */ |
| void nodeChanged(TreeNodeValueModel<T> node) { |
| TreeNodeValueModel<T> parent = node.parent(); |
| if (parent == null) { |
| this.fireTreeRootChanged(node); |
| } else { |
| this.fireTreeNodeChanged(parent.path(), parent.indexOfChild(node), node); |
| } |
| } |
| |
| /** |
| * Listen to the nodes, notify our listeners that the nodes were added, |
| * and then add the nodes to our internal tree. |
| * We must listen to the nodes before notifying anybody, because |
| * adding a listener can change the value of a node. |
| */ |
| void addChildren(TreeNodeValueModel<T>[] path, int[] childIndices, TreeNodeValueModel<T>[] children) { |
| int len = childIndices.length; |
| for (int i = 0; i < len; i++) { |
| this.engageNode(children[i]); |
| } |
| this.fireTreeNodesInserted(path, childIndices, children); |
| for (int i = 0; i < len; i++) { |
| this.addNode(childIndices[i], children[i]); |
| } |
| } |
| |
| /** |
| * Listen to the node and its children model. |
| */ |
| private void engageNode(TreeNodeValueModel<T> node) { |
| node.addStateChangeListener(this.nodeStateListener); |
| node.addPropertyChangeListener(PropertyValueModel.VALUE, this.nodeValueListener); |
| node.childrenModel().addListChangeListener(ListValueModel.LIST_VALUES, this.childrenListener); |
| } |
| |
| /** |
| * Add the node to our internal tree; |
| * then recurse down through the node's children, |
| * adding them to the internal tree also. |
| */ |
| private void addNode(int index, TreeNodeValueModel<T> node) { |
| this.addNodeToInternalTree(node.parent(), index, node, node.childrenModel()); |
| new NodeChangePolicy(node).addChildren(); |
| } |
| |
| /** |
| * Add the specified node to our internal tree. |
| */ |
| private void addNodeToInternalTree(TreeNodeValueModel<T> parent, int index, TreeNodeValueModel<T> node, ListValueModel<TreeNodeValueModel<T>> childrenModel) { |
| List<TreeNodeValueModel<T>> siblings = this.childrenLists.get(parent); |
| if (siblings == null) { |
| siblings = new ArrayList<TreeNodeValueModel<T>>(); |
| this.childrenLists.put(parent, siblings); |
| } |
| siblings.add(index, node); |
| |
| this.parents.put(childrenModel, node); |
| } |
| |
| /** |
| * Remove nodes from our internal tree, notify our listeners that the |
| * nodes were removed, then stop listening to the nodes. |
| * We must listen to the nodes until after notifying anybody, because |
| * removing a listener can change the value of a node. |
| */ |
| void removeChildren(TreeNodeValueModel<T>[] path, int[] childIndices, TreeNodeValueModel<T>[] children) { |
| int len = childIndices.length; |
| for (int i = 0; i < len; i++) { |
| // the indices slide down a notch each time we remove a child |
| this.removeNode(childIndices[i] - i, children[i]); |
| } |
| this.fireTreeNodesRemoved(path, childIndices, children); |
| for (int i = 0; i < len; i++) { |
| this.disengageNode(children[i]); |
| } |
| } |
| |
| /** |
| * First, recurse down through the node's children, |
| * removing them from our internal tree; |
| * then remove the node itself from our internal tree. |
| */ |
| private void removeNode(int index, TreeNodeValueModel<T> node) { |
| new NodeChangePolicy(node).removeChildren(); |
| this.removeNodeFromInternalTree(node.parent(), index, node, node.childrenModel()); |
| } |
| |
| /** |
| * Remove the specified node from our internal tree. |
| */ |
| private void removeNodeFromInternalTree(TreeNodeValueModel<T> parent, int index, TreeNodeValueModel<T> node, ListValueModel<TreeNodeValueModel<T>> childrenModel) { |
| this.parents.remove(childrenModel); |
| |
| List<TreeNodeValueModel<T>> siblings = this.childrenLists.get(parent); |
| siblings.remove(index); |
| if (siblings.isEmpty()) { |
| this.childrenLists.remove(parent); |
| } |
| } |
| |
| /** |
| * Stop listening to the node and its children model. |
| */ |
| private void disengageNode(TreeNodeValueModel<T> node) { |
| node.childrenModel().removeListChangeListener(ListValueModel.LIST_VALUES, this.childrenListener); |
| node.removePropertyChangeListener(PropertyValueModel.VALUE, this.nodeValueListener); |
| node.removeStateChangeListener(this.nodeStateListener); |
| } |
| |
| void moveChildren(TreeNodeValueModel<T> parent, int targetIndex, int sourceIndex, int length) { |
| List<TreeNodeValueModel<T>> childrenList = this.childrenLists.get(parent); |
| ArrayList<TreeNodeValueModel<T>> temp = new ArrayList<TreeNodeValueModel<T>>(length); |
| for (int i = 0; i < length; i++) { |
| temp.add(childrenList.remove(sourceIndex)); |
| } |
| childrenList.addAll(targetIndex, temp); |
| |
| this.fireTreeStructureChanged(parent.path()); |
| } |
| |
| |
| // ********** standard methods ********** |
| |
| @Override |
| public String toString() { |
| return StringTools.buildToStringFor(this, this.root); |
| } |
| |
| |
| // ********** inner classes ********** |
| |
| /** |
| * Coalesce some of the common change policy behavior. |
| */ |
| abstract class ChangePolicy { |
| |
| ChangePolicy() { |
| super(); |
| } |
| |
| /** |
| * Add the current set of children. |
| */ |
| void addChildren() { |
| TreeModelAdapter.this.addChildren(this.parent().path(), this.childIndices(), this.childArray()); |
| } |
| |
| /** |
| * Remove the current set of children. |
| */ |
| void removeChildren() { |
| TreeModelAdapter.this.removeChildren(this.parent().path(), this.childIndices(), this.childArray()); |
| } |
| |
| /** |
| * Return an array of the indices of the current set of children, |
| * which should be contiguous. |
| */ |
| int[] childIndices() { |
| return this.buildIndices(this.childrenStartIndex(), this.childrenSize()); |
| } |
| |
| /** |
| * Return an array of the current set of children. |
| */ |
| TreeNodeValueModel<T>[] childArray() { |
| return this.buildArray(this.getChildren(), this.childrenSize()); |
| } |
| |
| /** |
| * Build an array to hold the elements in the specified iterator. |
| * If they are different sizes, something is screwed up... |
| */ |
| TreeNodeValueModel<T>[] buildArray(Iterable<TreeNodeValueModel<T>> elements, int size) { |
| @SuppressWarnings("unchecked") |
| TreeNodeValueModel<T>[] array = new TreeNodeValueModel[size]; |
| int i = 0; |
| for (TreeNodeValueModel<T> element : elements) { |
| array[i++] = element; |
| } |
| return array; |
| } |
| |
| /** |
| * Return a set of indices, starting at zero and |
| * continuing for the specified size. |
| */ |
| int[] buildIndices(int size) { |
| return buildIndices(0, size); |
| } |
| |
| /** |
| * Return a set of indices, starting at the specified index and |
| * continuing for the specified size. |
| */ |
| int[] buildIndices(int start, int size) { |
| int[] indices = new int[size]; |
| int index = start; |
| for (int i = 0; i < size; i++) { |
| indices[i] = index++; |
| } |
| return indices; |
| } |
| |
| /** |
| * Return the parent of the current set of children. |
| */ |
| abstract TreeNodeValueModel<T> parent(); |
| |
| /** |
| * Return the starting index for the current set of children. |
| */ |
| abstract int childrenStartIndex(); |
| |
| /** |
| * Return the size of the current set of children. |
| */ |
| abstract int childrenSize(); |
| |
| /** |
| * Return the current set of children. |
| */ |
| abstract Iterable<TreeNodeValueModel<T>> getChildren(); |
| |
| } |
| |
| |
| /** |
| * Wraps a ListEvent for adding, removing, replacing, |
| * and changing children. |
| */ |
| abstract class EventChangePolicy extends ChangePolicy { |
| final ListEvent event; |
| |
| EventChangePolicy(ListEvent event) { |
| super(); |
| this.event = event; |
| } |
| |
| /** |
| * Map the ListChangeEvent's source to the corresponding parent. |
| */ |
| @Override |
| TreeNodeValueModel<T> parent() { |
| return TreeModelAdapter.this.parents.get(this.event.getSource()); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListAddEvent for adding children. |
| */ |
| class AddEventChangePolicy extends EventChangePolicy { |
| |
| AddEventChangePolicy(ListAddEvent event) { |
| super(event); |
| } |
| |
| private ListAddEvent getEvent() { |
| return (ListAddEvent) this.event; |
| } |
| |
| /** |
| * The ListAddEvent's item index is the children start index. |
| */ |
| @Override |
| int childrenStartIndex() { |
| return this.getEvent().getIndex(); |
| } |
| |
| /** |
| * The ListAddEvent's size is the children size. |
| */ |
| @Override |
| int childrenSize() { |
| return this.getEvent().getItemsSize(); |
| } |
| |
| /** |
| * The ListAddEvent's items are the children. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| return (Iterable<TreeNodeValueModel<T>>) this.getEvent().getItems(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListRemoveEvent for adding children. |
| */ |
| class RemoveEventChangePolicy extends EventChangePolicy { |
| |
| RemoveEventChangePolicy(ListRemoveEvent event) { |
| super(event); |
| } |
| |
| private ListRemoveEvent getEvent() { |
| return (ListRemoveEvent) this.event; |
| } |
| |
| /** |
| * The ListRemoveEvent's item index is the children start index. |
| */ |
| @Override |
| int childrenStartIndex() { |
| return this.getEvent().getIndex(); |
| } |
| |
| /** |
| * The ListRemoveEvent's size is the children size. |
| */ |
| @Override |
| int childrenSize() { |
| return this.getEvent().getItemsSize(); |
| } |
| |
| /** |
| * The ListRemoveEvent's items are the children. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| return (Iterable<TreeNodeValueModel<T>>) this.getEvent().getItems(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListReplaceEvent for replacing children. |
| */ |
| class ReplaceEventChangePolicy extends EventChangePolicy { |
| |
| ReplaceEventChangePolicy(ListReplaceEvent event) { |
| super(event); |
| } |
| |
| private ListReplaceEvent getEvent() { |
| return (ListReplaceEvent) this.event; |
| } |
| |
| /** |
| * The ListReplaceEvent's item index is the children start index. |
| */ |
| @Override |
| int childrenStartIndex() { |
| return this.getEvent().getIndex(); |
| } |
| |
| /** |
| * The ListReplaceEvent's size is the children size. |
| */ |
| @Override |
| int childrenSize() { |
| return this.getEvent().getItemsSize(); |
| } |
| |
| /** |
| * The ListReplaceEvent's items are the children. |
| */ |
| @Override |
| @SuppressWarnings("unchecked") |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| return (Iterable<TreeNodeValueModel<T>>) this.getEvent().getNewItems(); |
| } |
| |
| /** |
| * Remove the old nodes and add the new ones. |
| */ |
| void replaceChildren() { |
| TreeNodeValueModel<T>[] parentPath = this.parent().path(); |
| int[] childIndices = this.childIndices(); |
| TreeModelAdapter.this.removeChildren(parentPath, childIndices, this.getOldChildren()); |
| TreeModelAdapter.this.addChildren(parentPath, childIndices, this.childArray()); |
| } |
| |
| TreeNodeValueModel<T>[] getOldChildren() { |
| return this.buildArray(this.getOldItems(), this.getEvent().getItemsSize()); |
| } |
| |
| // minimized scope of suppressed warnings |
| @SuppressWarnings("unchecked") |
| protected Iterable<TreeNodeValueModel<T>> getOldItems() { |
| return (Iterable<TreeNodeValueModel<T>>) this.getEvent().getOldItems(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListMoveEvent for moving children. |
| */ |
| class MoveEventChangePolicy extends EventChangePolicy { |
| |
| MoveEventChangePolicy(ListMoveEvent event) { |
| super(event); |
| } |
| |
| private ListMoveEvent getEvent() { |
| return (ListMoveEvent) this.event; |
| } |
| |
| void moveChildren() { |
| TreeModelAdapter.this.moveChildren(this.parent(), this.getEvent().getTargetIndex(), this.getEvent().getSourceIndex(), this.getEvent().getLength()); |
| } |
| |
| @Override |
| int childrenStartIndex() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| int childrenSize() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListClearEvent for clearing children. |
| */ |
| class ClearEventChangePolicy extends EventChangePolicy { |
| |
| ClearEventChangePolicy(ListClearEvent event) { |
| super(event); |
| } |
| |
| /** |
| * Clear all the nodes. |
| */ |
| void clearChildren() { |
| TreeNodeValueModel<T> parent = this.parent(); |
| TreeNodeValueModel<T>[] parentPath = parent.path(); |
| List<TreeNodeValueModel<T>> childrenList = TreeModelAdapter.this.childrenLists.get(parent); |
| int[] childIndices = this.buildIndices(childrenList.size()); |
| TreeNodeValueModel<T>[] childArray = this.buildArray(childrenList, childrenList.size()); |
| TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray); |
| } |
| |
| @Override |
| int childrenStartIndex() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| int childrenSize() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a ListChangeEvent for clearing children. |
| */ |
| class ChangeEventChangePolicy extends EventChangePolicy { |
| |
| ChangeEventChangePolicy(ListChangeEvent event) { |
| super(event); |
| } |
| |
| /** |
| * Remove all the old nodes and add all the new nodes. |
| */ |
| void rebuildChildren() { |
| TreeNodeValueModel<T> parent = this.parent(); |
| TreeNodeValueModel<T>[] parentPath = parent.path(); |
| List<TreeNodeValueModel<T>> childrenList = TreeModelAdapter.this.childrenLists.get(parent); |
| int[] childIndices = this.buildIndices(childrenList.size()); |
| TreeNodeValueModel<T>[] childArray = this.buildArray(childrenList, childrenList.size()); |
| TreeModelAdapter.this.removeChildren(parentPath, childIndices, childArray); |
| |
| childIndices = this.buildIndices(parent.childrenModel().size()); |
| childArray = this.buildArray(parent.childrenModel(), parent.childrenSize()); |
| TreeModelAdapter.this.addChildren(parentPath, childIndices, childArray); |
| } |
| |
| @Override |
| int childrenStartIndex() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| int childrenSize() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Override |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| throw new UnsupportedOperationException(); |
| } |
| |
| } |
| |
| |
| /** |
| * Wraps a TreeNodeValueModel for adding and removing its children. |
| */ |
| class NodeChangePolicy extends ChangePolicy { |
| private final TreeNodeValueModel<T> node; |
| |
| NodeChangePolicy(TreeNodeValueModel<T> node) { |
| super(); |
| this.node = node; |
| } |
| |
| /** |
| * The node itself is the parent. |
| */ |
| @Override |
| TreeNodeValueModel<T> parent() { |
| return this.node; |
| } |
| |
| /** |
| * Since we will always be dealing with all of the node's |
| * children, the children start index is always zero. |
| */ |
| @Override |
| int childrenStartIndex() { |
| return 0; |
| } |
| |
| /** |
| * Since we will always be dealing with all of the node's |
| * children, the children size is always equal to the size |
| * of the children model. |
| */ |
| @Override |
| int childrenSize() { |
| return this.node.childrenModel().size(); |
| } |
| |
| /** |
| * Since we will always be dealing with all of the node's |
| * children, the children are all the objects held by |
| * the children model. |
| */ |
| @Override |
| Iterable<TreeNodeValueModel<T>> getChildren() { |
| return this.node.childrenModel(); |
| } |
| |
| } |
| |
| } |