/** * author: Marcel Genzmehr * 03.11.2011 */ package org.freeplane.plugin.workspace.model.project; import java.io.File; import java.io.IOException; 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.tree.TreeNode; import javax.swing.tree.TreePath; import org.freeplane.core.util.LogUtils; import org.freeplane.plugin.workspace.URIUtils; import org.freeplane.plugin.workspace.WorkspaceController; import org.freeplane.plugin.workspace.event.IWorkspaceNodeActionListener; import org.freeplane.plugin.workspace.event.WorkspaceActionEvent; import org.freeplane.plugin.workspace.model.AWorkspaceTreeNode; import org.freeplane.plugin.workspace.model.WorkspaceModelEvent; import org.freeplane.plugin.workspace.model.WorkspaceModelEvent.WorkspaceModelEventType; import org.freeplane.plugin.workspace.model.WorkspaceModelException; import org.freeplane.plugin.workspace.model.WorkspaceTreeModel; 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 ProjectModel implements WorkspaceTreeModel { private AWorkspaceTreeNode root = null; private final Map<String, AWorkspaceTreeNode> hashStringKeyIndex = new HashMap<String, AWorkspaceTreeNode>(); protected EventListenerList listenerList = new EventListenerList(); private final AWorkspaceProject PROJECT; /*********************************************************************************** * CONSTRUCTORS **********************************************************************************/ public ProjectModel(AWorkspaceProject project) { this.PROJECT = project; } /*********************************************************************************** * METHODS **********************************************************************************/ public AWorkspaceProject getProject() { return this.PROJECT; } public IProjectModelListener[] getTreeModelListeners() { return (IProjectModelListener[]) listenerList.getListeners(IProjectModelListener.class); } protected void fireTreeNodesChanged(Object source, TreePath path, int[] childIndices, Object[] children) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, childIndices, children); try { ((IProjectModelListener) listeners[i + 1]).treeNodesChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeNodeChanged(Object source, TreePath path, int childIndex, Object children, Object oldValue, Object newValue) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent e = new WorkspaceModelEvent(getProject(), source, path, WorkspaceModelEventType.DEFAULT, oldValue, newValue); // 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] == IProjectModelListener.class) { try { ((IProjectModelListener) listeners[i + 1]).treeNodesChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeNodesInserted(Object source, TreePath path, int[] childIndices, Object[] children) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, childIndices, children); try { ((IProjectModelListener) listeners[i + 1]).treeNodesInserted(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeNodesRemoved(Object source, TreePath path, int[] childIndices, Object[] children) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, childIndices, children); try { ((IProjectModelListener) listeners[i + 1]).treeNodesRemoved(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeNodesRemoved(Object source, TreePath path, Object from, Object to) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, WorkspaceModelEventType.DELETED, from, to); try { ((IProjectModelListener) listeners[i + 1]).treeNodesRemoved(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeStructureChanged(Object source, TreePath path, int[] childIndices, Object[] children) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, childIndices, children); try { ((IProjectModelListener) listeners[i + 1]).treeStructureChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeStructureChanged(Object source, TreePath path) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path); try { ((IProjectModelListener) listeners[i + 1]).treeStructureChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeStructureMoved(Object source, TreePath path, Object from, Object to) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, WorkspaceModelEventType.MOVED, from, to); try { ((IProjectModelListener) listeners[i + 1]).treeStructureChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } protected void fireTreeNodeRenamed(Object source, TreePath path, Object from, Object to) { synchronized (this) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); WorkspaceModelEvent 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] == IProjectModelListener.class) { // Lazily create the event: if (e == null) e = new WorkspaceModelEvent(getProject(), source, path, WorkspaceModelEventType.RENAMED, from, to); try { ((IProjectModelListener) listeners[i + 1]).treeNodesChanged(e); } catch (Exception ex) { LogUtils.warn(ex); } } } } } public void reload(AWorkspaceTreeNode node) { if (node != null) { fireTreeStructureChanged(this, node.getTreePath(), null, null); } } public void reload() { reload(root); } 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 nodeChanged(AWorkspaceTreeNode node, Object oldValue, Object newValue) { fireTreeNodeChanged(this, node.getTreePath(), node.getParent().getChildIndex(node), node, oldValue, newValue); } public void nodeMoved(AWorkspaceTreeNode node, Object from, Object to) { fireTreeStructureMoved(this, node.getTreePath(), from, to); } 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); } } public void nodesWereRemoved(AWorkspaceTreeNode node, int[] childIndices, Object[] removedChildren) { if (node != null && childIndices != null) { fireTreeNodesRemoved(this, node.getTreePath(), childIndices, removedChildren); } } 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); } } } 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) { if(node.equals(targetNode)) { return false; } 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()); } } /** * @param node */ private void removeIndexOnlyRecursively(AWorkspaceTreeNode node) { this.hashStringKeyIndex.remove(node.getKey()); if (node.getChildCount() > 0) { for (int i = 0; i < node.getChildCount(); i++) { AWorkspaceTreeNode childNode = node.getChildAt(i); removeIndexOnlyRecursively(childNode); } } } private void addIndexOnlyRecursively(AWorkspaceTreeNode node) { this.hashStringKeyIndex.put(node.getKey(), node); if (node.getChildCount() > 0) { for (int i = 0; i < node.getChildCount(); i++) { AWorkspaceTreeNode childNode = node.getChildAt(i); addIndexOnlyRecursively(childNode); } } } public void changeNodeName(AWorkspaceTreeNode node, String newName) throws WorkspaceModelException { String oldName = node.getName(); 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); removeIndexOnlyRecursively(node); node.setName(newName); addIndexOnlyRecursively(node); fireTreeNodeRenamed(this, node.getTreePath(), oldName, newName); } /** * */ 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).getLinkURI(); if (uri.getPath().endsWith(filter)) { URI absUri = URIUtils.getAbsoluteURI(uri); if(absUri == null) { continue; } set.add(absUri); } } } return Arrays.asList(set.toArray(new URI[] {})); } /*********************************************************************************** * REQUIRED METHODS FOR INTERFACES **********************************************************************************/ public AWorkspaceTreeNode getRoot() { return root; } public void setRoot(AWorkspaceTreeNode newRoot) { AWorkspaceTreeNode oldRoot = this.root; this.root = newRoot; if(this.root != null) { this.root.setModel(this); if(oldRoot == null) { fireTreeNodesInserted(this, null, new int[]{}, new Object[]{root}); } else { fireTreeNodesChanged(this, null, new int[]{}, new Object[]{root}); } } else { if(oldRoot != null) { fireTreeNodesRemoved(this, null, new int[]{}, new Object[]{oldRoot}); } } } 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 addProjectModelListener(IProjectModelListener listener) { if(listener == null) { return; } listenerList.add(IProjectModelListener.class, listener); } public void removeProjectModelListener(IProjectModelListener listener) { if(listener == null) { return; } listenerList.remove(IProjectModelListener.class, listener); } public void requestSave() { try { WorkspaceController.getCurrentModeExtension().getProjectLoader().storeProject(WorkspaceController.getCurrentModel().getProject(this)); } catch (IOException e) { LogUtils.severe(e); } } }