/**
* author: Marcel Genzmehr
* 03.11.2011
*/
package org.freeplane.plugin.workspace.model;
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import javax.swing.event.EventListenerList;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.freeplane.plugin.workspace.WorkspaceController;
import org.freeplane.plugin.workspace.WorkspaceUtils;
import org.freeplane.plugin.workspace.event.IWorkspaceNodeActionListener;
import org.freeplane.plugin.workspace.event.WorkspaceActionEvent;
import org.freeplane.plugin.workspace.model.WorkspaceTreeModelEvent.WorkspaceTreeModelEventType;
import org.freeplane.plugin.workspace.nodes.AFolderNode;
import org.freeplane.plugin.workspace.nodes.ALinkNode;
import org.freeplane.plugin.workspace.nodes.DefaultFileNode;
import org.freeplane.plugin.workspace.nodes.FolderFileNode;
public class WorkspaceIndexedTreeModel implements TreeModel {
private AWorkspaceTreeNode root = null;
private final Map<String, AWorkspaceTreeNode> hashStringKeyIndex = new HashMap<String, AWorkspaceTreeNode>();
protected EventListenerList listenerList = new EventListenerList();
/***********************************************************************************
* CONSTRUCTORS
**********************************************************************************/
/***********************************************************************************
* METHODS
**********************************************************************************/
public TreeModelListener[] getTreeModelListeners() {
return (TreeModelListener[]) listenerList.getListeners(TreeModelListener.class);
}
/**
* 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
*/
protected void fireTreeNodesChanged(Object source, TreePath path, int[] childIndices, Object[] children) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 void fireTreeNodesInserted(Object source, TreePath path, int[] childIndices, Object[] children) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 void fireTreeNodesRemoved(Object source, TreePath path, int[] childIndices, Object[] children) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 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 void fireTreeNodesRemoved(Object source, TreePath path, Object from, Object to) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 WorkspaceTreeModelEvent(source, path, WorkspaceTreeModelEventType.delete, from, to);
((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 void fireTreeStructureChanged(Object source, TreePath path, int[] childIndices, Object[] children) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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);
}
}
}
/**
* 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
*/
protected void fireTreeStructureChanged(Object source, TreePath path) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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);
}
}
}
/**
* 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
*/
protected void fireTreeStructureMoved(Object source, TreePath path, Object from, Object to) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 WorkspaceTreeModelEvent(source, path, WorkspaceTreeModelEventType.move, from, to);
((TreeModelListener) listeners[i + 1]).treeStructureChanged(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 from
* the old name
* @param path
* the new name
* @see EventListenerList
*/
protected void fireTreeNodeRenamed(Object source, TreePath path, Object from, Object to) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.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 WorkspaceTreeModelEvent(source, path, WorkspaceTreeModelEventType.rename, from, to);
((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
}
}
}
public void reload(AWorkspaceTreeNode node) {
if (node != null) {
fireTreeStructureChanged(this, node.getTreePath(), null, null);
WorkspaceController.getController().getExpansionStateHandler().restoreExpansionStates();
}
}
/**
* Invoke this method if you've modified the TreeNodes upon which this model
* depends. The model will notify all of its listeners that the model has
* changed.
*/
public void reload() {
reload(root);
}
/**
* Invoke this method after you've changed how node is to be represented in
* the tree.
*/
public void nodeChanged(AWorkspaceTreeNode node) {
if (listenerList != null && node != null) {
AWorkspaceTreeNode parent = node.getParent();
if (parent != null) {
int anIndex = parent.getIndex(node);
if (anIndex != -1) {
int[] cIndexs = new int[1];
cIndexs[0] = anIndex;
nodesChanged(parent, cIndexs);
}
}
else if (node == getRoot()) {
nodesChanged(node, null);
}
}
}
public void nodeMoved(AWorkspaceTreeNode node, Object from, Object to) {
fireTreeStructureMoved(this, node.getTreePath(), from, to);
}
/**
* 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.
*/
public void nodesWereInserted(AWorkspaceTreeNode node, int[] childIndices) {
if (listenerList != null && node != null && childIndices != null && childIndices.length > 0) {
int cCount = childIndices.length;
Object[] newChildren = new Object[cCount];
for (int counter = 0; counter < cCount; counter++)
newChildren[counter] = node.getChildAt(childIndices[counter]);
fireTreeNodesInserted(this, node.getTreePath(), 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.
*/
public void nodesWereRemoved(AWorkspaceTreeNode node, int[] childIndices, Object[] removedChildren) {
if (node != null && childIndices != null) {
fireTreeNodesRemoved(this, node.getTreePath(), childIndices, removedChildren);
}
}
/**
* Invoke this method after you've changed how the children identified by
* childIndicies are to be represented in the tree.
*/
public void nodesChanged(AWorkspaceTreeNode node, int[] childIndices) {
if (node != null) {
if (childIndices != null) {
int cCount = childIndices.length;
if (cCount > 0) {
Object[] cChildren = new Object[cCount];
for (int counter = 0; counter < cCount; counter++)
cChildren[counter] = node.getChildAt(childIndices[counter]);
fireTreeNodesChanged(this, node.getTreePath(), childIndices, cChildren);
}
}
else if (node == getRoot()) {
fireTreeNodesChanged(this, node.getTreePath(), null, null);
}
}
}
/**
* Invoke this method if you've totally changed the children of node and its
* childrens children... This will post a treeStructureChanged event.
*/
public void nodeStructureChanged(AWorkspaceTreeNode node) {
if (node != null) {
fireTreeStructureChanged(this, node.getTreePath(), null, null);
}
}
/**
* @param key
* @return
*/
public boolean containsNode(String key) {
return this.hashStringKeyIndex.containsKey(key);
}
/**
* @param key
* @return
*/
public AWorkspaceTreeNode getNode(String key) {
return this.hashStringKeyIndex.get(key);
}
/**
* @param node
* @param targetNode
* @return
*/
public boolean addNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode) {
return addNodeTo(node, targetNode, true);
}
/**
* @param node
* @param targetNode
* @param allowRenaming
* @return
*/
public boolean addNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode, boolean allowRenaming) {
return insertNodeTo(node, targetNode, targetNode.getChildCount(), allowRenaming);
}
/**
* @param node
* @param targetNode
* @param allowRenaming
* @return
*/
public boolean insertNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode, int atPos, boolean allowRenaming) {
node.setParent(targetNode);
// DOCEAR - look for problems that may caused by this change!!!
if (allowRenaming) {
String newNodeName = node.getName();
int nameCount = 0;
while (this.containsNode(node.getKey()) && nameCount++ < 100) {
node.setName(newNodeName + " (" + nameCount + ")");
}
}
if (this.containsNode(node.getKey())) {
return false;
}
targetNode.insertChildNode(node, atPos);
nodesWereInserted(targetNode, new int[] { atPos });
addToIndexRecursively(node, targetNode);
return true;
}
private void addToIndexRecursively(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode) {
this.hashStringKeyIndex.put(node.getKey(), node);
if (node.getChildCount() > 0) {
int[] indices = new int[node.getChildCount()];
for (int i = 0; i < node.getChildCount(); i++) {
AWorkspaceTreeNode childNode = node.getChildAt(i);
addToIndexRecursively(childNode, node);
indices[i] = targetNode.getChildCount() - 1;
}
nodesWereInserted(targetNode, indices);
}
}
/**
* @param node
*/
public void removeAllElements(AWorkspaceTreeNode node) {
Enumeration<AWorkspaceTreeNode> children = node.children();
AWorkspaceTreeNode child = null;
while (children.hasMoreElements()) {
child = children.nextElement();
this.hashStringKeyIndex.remove(child.getKey());
child.disassociateReferences();
fireTreeNodesRemoved(this, node.getTreePath(), null, new Object[] { child });
}
node.removeAllChildren();
}
/**
* @param node
*/
public void removeNodeFromParent(AWorkspaceTreeNode node) {
this.hashStringKeyIndex.remove(node.getKey());
AWorkspaceTreeNode parent = node.getParent();
parent.removeChild(node);
node.disassociateReferences();
fireTreeNodesRemoved(this, parent.getTreePath(), null, new Object[] { node });
}
/**
* @param node
*/
public void cutNodeFromParent(AWorkspaceTreeNode node) {
AWorkspaceTreeNode parent = node.getParent();
removeFromIndexRecursively(node);
parent.removeChild(node);
fireTreeNodesRemoved(this, parent.getTreePath(), null, new Object[] { node });
}
/**
* @param node
*/
private void removeFromIndexRecursively(AWorkspaceTreeNode node) {
List<AWorkspaceTreeNode> removes = new ArrayList<AWorkspaceTreeNode>();
this.hashStringKeyIndex.remove(node.getKey());
if (node.getChildCount() > 0) {
int[] indices = new int[node.getChildCount()];
for (int i = 0; i < node.getChildCount(); i++) {
AWorkspaceTreeNode childNode = node.getChildAt(i);
removeFromIndexRecursively(childNode);
removes.add(childNode);
indices[i] = i;
}
fireTreeNodesRemoved(this, node.getTreePath(), indices, removes.toArray());
}
}
public void changeNodeName(AWorkspaceTreeNode node, String newName) throws WorkspaceModelException {
String oldName = node.getName();
File oldFile = null;
if (node instanceof DefaultFileNode) {
oldFile = new File(((DefaultFileNode) node).getFile().getParent(), oldName);
}
node.setName(newName);
if (this.hashStringKeyIndex.containsKey(node.getKey())) {
node.setName(oldName);
throw new WorkspaceModelException("A Node with the name '" + newName + "' already exists.");
}
node.setName(oldName);
removeFromIndexRecursively(node);
node.setName(newName);
addToIndexRecursively(node, node.getParent());
if (node instanceof DefaultFileNode) {
File newFile = ((DefaultFileNode) node).getFile();
fireTreeNodeRenamed(this, node.getTreePath(), oldFile, newFile);
}
}
/**
*
*/
public void resetIndex() {
this.hashStringKeyIndex.clear();
}
public List<URI> getAllNodesFiltered(String filter) {
HashSet<URI> set = new HashSet<URI>();
Collection<AWorkspaceTreeNode> values = hashStringKeyIndex.values();
for (AWorkspaceTreeNode node : values) {
if (node instanceof AFolderNode || node instanceof FolderFileNode) {
continue;
}
if (node instanceof DefaultFileNode) {
File file = ((DefaultFileNode) node).getFile();
if (file.getName().endsWith(filter)) {
set.add(file.toURI());
}
}
else if (node instanceof ALinkNode) {
URI uri = ((ALinkNode) node).getLinkPath();
if (uri.getPath().endsWith(filter)) {
set.add(WorkspaceUtils.absoluteURI(uri));
}
}
}
return Arrays.asList(set.toArray(new URI[] {}));
}
/***********************************************************************************
* REQUIRED METHODS FOR INTERFACES
**********************************************************************************/
public Object getRoot() {
return root;
}
public void setRoot(AWorkspaceTreeNode object) {
this.root = object;
}
public Object getChild(Object parent, int index) {
return ((AWorkspaceTreeNode) parent).getChildAt(index);
}
public int getChildCount(Object parent) {
return ((AWorkspaceTreeNode) parent).getChildCount();
}
public boolean isLeaf(Object node) {
return ((AWorkspaceTreeNode) node).isLeaf();
}
public void valueForPathChanged(TreePath path, Object newValue) {
AWorkspaceTreeNode node = (AWorkspaceTreeNode) path.getLastPathComponent();
if (node instanceof IWorkspaceNodeActionListener) {
((IWorkspaceNodeActionListener) node).handleAction(new WorkspaceActionEvent(node, WorkspaceActionEvent.WSNODE_CHANGED, newValue));
nodeChanged(node);
}
else {
node.setName(newValue.toString());
}
}
public int getIndexOfChild(Object parent, Object child) {
return ((AWorkspaceTreeNode) parent).getIndex((TreeNode) child);
}
public void addTreeModelListener(TreeModelListener l) {
listenerList.add(TreeModelListener.class, l);
}
public void removeTreeModelListener(TreeModelListener l) {
listenerList.remove(TreeModelListener.class, l);
}
}