package org.freeplane.plugin.workspace.model;
import java.awt.Toolkit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.core.util.LogUtils;
import org.freeplane.plugin.workspace.WorkspaceController;
import org.freeplane.plugin.workspace.dnd.WorkspaceTransferable;
import org.freeplane.plugin.workspace.event.IWorkspaceNodeActionListener;
import org.freeplane.plugin.workspace.event.WorkspaceActionEvent;
import org.freeplane.plugin.workspace.model.WorkspaceModelEvent.WorkspaceModelEventType;
import org.freeplane.plugin.workspace.model.project.AWorkspaceProject;
import org.freeplane.plugin.workspace.model.project.IProjectModelListener;
import org.freeplane.plugin.workspace.model.project.ProjectModel;
import org.freeplane.plugin.workspace.nodes.WorkspaceRootNode;
public abstract class WorkspaceModel implements TreeModel {
private final static Map<String, AWorkspaceProject> projectCacheIndex = new HashMap<String, AWorkspaceProject>();
protected List<AWorkspaceProject> projects = new ArrayList<AWorkspaceProject>();
protected final List<WorkspaceModelListener> listeners = new ArrayList<WorkspaceModelListener>();
protected WorkspaceRootNode root;
protected IProjectModelListener projectModelListener;
public void addProject(AWorkspaceProject project) {
if(project == null) {
return;
}
synchronized (projects) {
if(!projects.contains(project)) {
unindexProject(project.getProjectID());
indexProject(project);
projects.add(project);
project.getModel().addProjectModelListener(getTreeModelListener());
fireProjectInserted(project);
}
}
}
public void removeProject(AWorkspaceProject project) {
if(project == null) {
return;
}
synchronized (projects) {
int index = getProjectIndex(project);
if(index > -1) {
projects.remove(project);
project.getModel().removeProjectModelListener(getTreeModelListener());
fireProjectRemoved(project, index);
}
}
}
private WorkspaceModelEvent validatedModelEvent(WorkspaceModelEvent event) {
WorkspaceModelEvent evt = event;
if(event.getTreePath() == null) {
int[] indices;
int index = getProjectIndex(event.getProject());
if(index < 0) {
indices = new int[]{};
}
else {
indices = new int[]{index};
}
evt = new WorkspaceModelEvent(event.getProject(), event.getSource(), getRoot().getTreePath(), indices, event.getChildren());
}
return evt;
}
public int getProjectIndex(AWorkspaceProject project) {
synchronized (projects) {
int index = 0;
for (AWorkspaceProject prj : projects) {
if(prj.equals(project)) {
return index;
}
index++;
}
}
return -1;
}
protected void resetClipboard() {
if(Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null) instanceof WorkspaceTransferable) {
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(null, null);
LogUtils.info("clipboard has been reset");
}
}
protected IProjectModelListener getTreeModelListener() {
if(projectModelListener == null) {
projectModelListener = new IProjectModelListener() {
public void treeStructureChanged(WorkspaceModelEvent event) {
resetClipboard();
event = validatedModelEvent(event);
synchronized (listeners) {
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).treeStructureChanged(event);
}
catch (Exception ex) {
LogUtils.warn(ex);
}
}
}
}
public void treeNodesRemoved(WorkspaceModelEvent event) {
event = validatedModelEvent(event);
synchronized (listeners) {
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).treeNodesRemoved(event);
}
catch (Exception ex) {
LogUtils.warn(ex);
}
}
}
}
public void treeNodesInserted(WorkspaceModelEvent event) {
event = validatedModelEvent(event);
synchronized (listeners) {
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).treeNodesInserted(event);
}
catch (Exception ex) {
LogUtils.warn(ex);
}
}
}
}
public void treeNodesChanged(WorkspaceModelEvent event) {
event = validatedModelEvent(event);
synchronized (listeners) {
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).treeNodesChanged(event);
}
catch (Exception ex) {
LogUtils.warn(ex);
}
}
}
}
};
}
return projectModelListener;
}
protected void fireProjectRemoved(AWorkspaceProject project, int index) {
synchronized (listeners) {
project.unload();
WorkspaceModelEvent event = new WorkspaceModelEvent(project, this, new Object[]{getRoot()}, new int[]{index}, new Object[]{project.getModel().getRoot()});
for (int i = listeners.size()-1; i >= 0; i--) {
WorkspaceModelListener listener = listeners.get(i);
try {
listener.projectRemoved(event);
}
catch (Exception e) {
LogUtils.warn(e);
}
}
}
}
protected void fireProjectInserted(AWorkspaceProject project) {
synchronized (listeners) {
WorkspaceModelEvent event = new WorkspaceModelEvent(project, this, new Object[]{getRoot()}, new int[]{getProjectIndex(project)}, new Object[]{project.getModel().getRoot()});;
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).projectAdded(event);
}
catch (Exception e) {
LogUtils.warn(e);
}
}
}
}
protected void fireWorkspaceRenamed(String oldName, String newName) {
synchronized (listeners) {
TreeModelEvent event = new WorkspaceModelEvent(null, this, new Object[]{getRoot()}, new int[]{}, new Object[]{});
for (int i = listeners.size()-1; i >= 0; i--) {
try {
listeners.get(i).treeNodesChanged(event);
}
catch (Exception e) {
LogUtils.warn(e);
}
}
}
}
public AWorkspaceTreeNode getRoot() {
if(root == null) {
root = new WorkspaceRootNode();
root.setModel(new DefaultWorkspaceTreeModel());
}
return root;
}
public Object getChild(Object parent, int index) {
if(parent == getRoot()) {
int offset = getRoot().getChildCount()-projects.size();
if(index < offset) {
return getRoot().getChildAt(index);
}
else {
synchronized (projects) {
AWorkspaceTreeNode node = projects.get(index-offset).getModel().getRoot();
return node;
}
}
}
else {
return ((AWorkspaceTreeNode) parent).getChildAt(index);
}
}
public int getChildCount(Object parent) {
if(parent == getRoot()) {
return getRoot().getChildCount();
}
else {
return ((AWorkspaceTreeNode) parent).getChildCount();
}
}
public boolean isLeaf(Object node) {
if(node == getRoot()) {
return false;
}
else {
return ((TreeNode) node).isLeaf();
}
}
public void valueForPathChanged(TreePath path, Object newValue) {
if(path == null || getRoot().equals(path.getLastPathComponent())) {
return;
}
AWorkspaceTreeNode node = (AWorkspaceTreeNode) path.getLastPathComponent();
if (node instanceof IWorkspaceNodeActionListener) {
((IWorkspaceNodeActionListener) node).handleAction(new WorkspaceActionEvent(node, WorkspaceActionEvent.WSNODE_CHANGED, newValue));
((ProjectModel) node.getModel()).nodeChanged(node);
}
else {
node.setName(newValue.toString());
}
}
public int getIndexOfChild(Object parent, Object child) {
if(parent == getRoot()) {
int offset = getRoot().getChildCount();
if(offset > 0) {
int index = getRoot().getChildIndex((AWorkspaceTreeNode) child);
if( index > -1) {
return index;
}
}
synchronized (projects) {
int index = 0;
for (AWorkspaceProject project : projects) {
if(child.equals(project.getModel().getRoot())) {
return index+offset;
}
index++;
}
return -1;
}
}
else {
return ((AWorkspaceTreeNode) parent).getIndex((TreeNode) child);
}
}
public void addTreeModelListener(TreeModelListener l) {
if(l == null) {
return;
}
WorkspaceModelListener listener = new WrappedTreeModelListener(l);
synchronized (listeners) {
if(listeners.contains(listener)) {
return;
}
listeners.add(listener);
}
}
public void addWorldModelListener(WorkspaceModelListener l) {
if(l == null) {
return;
}
synchronized (listeners) {
if(listeners.contains(l)) {
return;
}
listeners.add(l);
}
}
public void removeTreeModelListener(TreeModelListener l) {
if(l == null) {
return;
}
WorkspaceModelListener listener = new WrappedTreeModelListener(l);
synchronized (listeners) {
listeners.remove(listener);
}
}
public List<AWorkspaceProject> getProjects() {
return Collections.unmodifiableList(this.projects);
}
public static WorkspaceModel createDefaultModel() {
return new WorkspaceModel() {
};
}
public AWorkspaceProject getProject(WorkspaceTreeModel model) {
synchronized (this.projects) {
for(AWorkspaceProject project : this.projects) {
if (project.getModel().equals(model)) {
return project;
}
}
}
return null;
}
public AWorkspaceProject getProject(String projectID) {
synchronized (this.projects) {
for(AWorkspaceProject project : this.projects) {
if (project.getProjectID().equals(projectID)) {
return project;
}
}
}
return null;
}
public void indexProject(AWorkspaceProject project) {
if(project == null) {
return;
}
synchronized (projectCacheIndex) {
if (!projectCacheIndex.containsKey(project.getProjectID())) {
projectCacheIndex.put(project.getProjectID(), project);
}
}
}
public void unindexProject(String projectID) {
if(projectID == null) {
return;
}
synchronized (projectCacheIndex) {
projectCacheIndex.remove(projectID);
}
}
public void clearProjectPathIndex() {
synchronized (projectCacheIndex) {
projectCacheIndex.clear();
}
}
public AWorkspaceProject getCachedProjectByID(String projectID) {
if(projectID == null) {
return null;
}
synchronized (projectCacheIndex) {
return projectCacheIndex.get(projectID);
}
}
/**********************************************************************
* NESTED CLASSES
**********************************************************************/
/**
*
* @author mag
*
*/
protected final class DefaultWorkspaceTreeModel implements WorkspaceTreeModel {
public DefaultWorkspaceTreeModel() {
}
public void removeNodeFromParent(AWorkspaceTreeNode node) {
if(node == null) {
return;
}
if(getRoot().equals(node.getParent())) {
//forbidden: use removeProject
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
AWorkspaceTreeNode parent = node.getParent();
int index = parent.getChildIndex(node);
parent.removeChild(node);
node.disassociateReferences();
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, parent.getTreePath(), new int[]{index}, new Object[]{node});
getTreeModelListener().treeNodesRemoved(event);
}
else {
tModel.removeNodeFromParent(node);
}
}
}
public void removeAllElements(AWorkspaceTreeNode node) {
if(node == null) {
return;
}
if(getRoot().equals(node)) {
//forbidden: use removeProject
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
int[] indices = new int[node.getChildCount()];
Object[] childArray = new Object[node.getChildCount()];
Enumeration<AWorkspaceTreeNode> children = node.children();
AWorkspaceTreeNode child = null;
int i = 0;
while (children.hasMoreElements()) {
child = children.nextElement();
child.disassociateReferences();
indices[i] = i;
childArray[i] = child;
//fireTreeNodesRemoved(this, node.getTreePath(), null, new Object[] { child });
i++;
}
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, node.getTreePath(), indices, childArray);
getTreeModelListener().treeNodesRemoved(event);
node.removeAllChildren();
}
else {
tModel.removeAllElements(node);
}
}
}
public void reload(AWorkspaceTreeNode node) {
if(node == null) {
return;
}
resetClipboard();
if(getRoot().equals(node)) {
for (AWorkspaceProject project : getProjects()) {
project.getModel().reload();
}
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, node.getTreePath(), null, null);
getTreeModelListener().treeStructureChanged(event);
//fireTreeStructureChanged(this, node.getTreePath(), null, null);
}
else {
tModel.reload(node);
}
}
}
public void nodeMoved(AWorkspaceTreeNode node, Object from, Object to) {
if(getRoot().equals(node)) {
//forbidden for root node
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, node.getTreePath(), WorkspaceModelEventType.MOVED, from, to);
getTreeModelListener().treeStructureChanged(event);
}
else {
tModel.nodeMoved(node, from, to);
}
}
}
public boolean insertNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode, int atPos, boolean allowRenaming) {
if(getRoot().equals(targetNode)) {
//forbidden: only allowed via addProject()
return false;
}
else {
WorkspaceTreeModel tModel = targetNode.getModel();
if(tModel == this) {
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);
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, targetNode.getTreePath(), new int[]{atPos}, new Object[]{node});
getTreeModelListener().treeNodesInserted(event);
return true;
}
else {
return tModel.insertNodeTo(node, targetNode, atPos, allowRenaming);
}
}
}
public AWorkspaceTreeNode getRoot() {
return WorkspaceModel.this.getRoot();
}
public boolean containsNode(String key) {
for (AWorkspaceProject project : getProjects()) {
if(project.getModel().containsNode(key)) {
return true;
}
}
if(getRoot().getChildCount() > 0) {
if(getChildByKey(getRoot(), key) != null) {
return true;
}
}
return false;
}
private AWorkspaceTreeNode getChildByKey(AWorkspaceTreeNode parent, String key) {
if(key == null || key.isEmpty()) {
return null;
}
Enumeration<AWorkspaceTreeNode> children = parent.children();
AWorkspaceTreeNode child = null;
while (children.hasMoreElements()) {
child = children.nextElement();
String childKey = child.getKey();
if(key.startsWith(childKey)) {
if(key.equals(childKey)) {
return child;
}
else {
return getChildByKey(child, key);
}
}
}
return null;
}
public AWorkspaceTreeNode getNode(String key) {
if(getRoot().getKey().equals(key)) {
return getRoot();
}
AWorkspaceTreeNode node = null;
for (AWorkspaceProject project : getProjects()) {
ProjectModel pModel = project.getModel();
String pKey = pModel.getRoot().getKey();
if(pKey.equals(key)) {
node = pModel.getRoot();
break;
}
else {
node = pModel.getNode(key);
if(node != null) {
break;
}
}
}
if(node == null && getRoot().getChildCount() > 0) {
node = getChildByKey(getRoot(), key);
}
return node;
}
public void cutNodeFromParent(AWorkspaceTreeNode node) {
if(getRoot().equals(node.getParent())) {
//forbidden: use removeProject
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
AWorkspaceTreeNode parent = node.getParent();
parent.removeChild(node);
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, parent.getTreePath(), null, new Object[]{node});
getTreeModelListener().treeNodesRemoved(event);
}
else {
tModel.cutNodeFromParent(node);
}
}
}
public void changeNodeName(AWorkspaceTreeNode node, String newName) throws WorkspaceModelException {
if(getRoot().equals(node)) {
String oldName = node.getName();
node.setName(newName);
fireWorkspaceRenamed(oldName, newName);
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
String oldName = node.getName();
node.setName(newName);
if (containsNode(node.getKey())) {
node.setName(oldName);
throw new WorkspaceModelException("A Node with the name '" + newName + "' already exists.");
}
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, node.getTreePath(), WorkspaceModelEventType.RENAMED, oldName, newName);
getTreeModelListener().treeNodesChanged(event);
}
else {
tModel.changeNodeName(node, newName);
}
}
}
public boolean addNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode, boolean allowRenaming) {
return insertNodeTo(node, targetNode, targetNode.getChildCount(), allowRenaming);
}
public boolean addNodeTo(AWorkspaceTreeNode node, AWorkspaceTreeNode targetNode) {
return addNodeTo(node, targetNode, true);
}
public void requestSave() {
WorkspaceController.save();
}
public void nodeChanged(AWorkspaceTreeNode node, Object oldValue, Object newValue) {
if(getRoot().equals(node)) {
//should not happen
}
else {
WorkspaceTreeModel tModel = node.getModel();
if(tModel == this) {
WorkspaceModelEvent event = new WorkspaceModelEvent(null, this, node.getTreePath(), WorkspaceModelEventType.DEFAULT, oldValue, newValue);
getTreeModelListener().treeNodesChanged(event);
} else {
node.getModel().nodeChanged(node, oldValue, newValue);
}
}
}
}
/**
*
* @author mag
*
*/
public class WrappedTreeModelListener implements WorkspaceModelListener {
private final TreeModelListener wrappedListener;
public WrappedTreeModelListener(TreeModelListener l) {
if(l == null) {
throw new IllegalArgumentException();
}
this.wrappedListener = l;
}
public void treeNodesChanged(TreeModelEvent e) {
wrappedListener.treeNodesChanged(e);
}
public void treeNodesInserted(TreeModelEvent e) {
wrappedListener.treeNodesInserted(e);
}
public void treeNodesRemoved(TreeModelEvent e) {
wrappedListener.treeNodesRemoved(e);
}
public void treeStructureChanged(TreeModelEvent e) {
wrappedListener.treeStructureChanged(e);
}
public void projectAdded(WorkspaceModelEvent event) {
//do nth.
}
public void projectRemoved(WorkspaceModelEvent event) {
treeNodesRemoved(event);
}
public boolean equals(Object obj) {
return super.equals(obj);
}
}
}