/FileExtraction/Java_unseen/X0Ken_FreeMind/accessories/plugins/ClonePlugin.java
/*FreeMind - A Program for creating and viewing Mindmaps
 *Copyright (C) 2000-2011 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others.
 *
 *See COPYING for Details
 *
 *This program is free software; you can redistribute it and/or
 *modify it under the terms of the GNU General Public License
 *as published by the Free Software Foundation; either version 2
 *of the License, or (at your option) any later version.
 *
 *This program is distributed in the hope that it will be useful,
 *but WITHOUT ANY WARRANTY; without even the implied warranty of
 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *GNU General Public License for more details.
 *
 *You should have received a copy of the GNU General Public License
 *along with this program; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package accessories.plugins;

import java.awt.EventQueue;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;

import accessories.plugins.ClonePasteAction.CloneProperties;
import accessories.plugins.ClonePasteAction.ClonePropertiesObserver;
import accessories.plugins.ClonePasteAction.Registration;
import freemind.extensions.PermanentNodeHook;
import freemind.main.Tools;
import freemind.main.XMLElement;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController.NodeLifetimeListener;
import freemind.modes.mindmapmode.hooks.PermanentMindMapNodeHookAdapter;

public class ClonePlugin extends PermanentMindMapNodeHookAdapter implements
		NodeLifetimeListener, ClonePropertiesObserver {

	public static final String CLONE_ITSELF_FALSE = "false";
	public static final String CLONE_ITSELF_TRUE = "true";
	public static final String PLUGIN_LABEL = "accessories/plugins/ClonePlugin.properties";
	public static final String XML_STORAGE_CLONES = "CLONE_IDS";
	public static final String XML_STORAGE_CLONE_ID = "CLONE_ID";
	public static final String XML_STORAGE_CLONE_ITSELF = "CLONE_ITSELF";

	/**
	 * This is the master list. {@link ClonePlugin#mCloneNodes mCloneNodes} is
	 * derived from it. It contains id strings.
	 */
	private HashSet mCloneNodeIds;
	/**
	 * Includes the original node. This is a cached list with the MindMapNodes
	 * belonging to the {@link ClonePlugin#mCloneNodeIds mCloneNodeIds}.
	 */
	private HashSet mCloneNodes;

	private String mCloneId;
	private boolean mDisabled = false;
	private Boolean mCloneItself = null;
	private CloneProperties mClonePropertiesHolder;

	public ClonePlugin() {
	}

	public void invoke(MindMapNode node) {
		super.invoke(node);
		registerPlugin();
	}

	public void addClone(MindMapNode cloneNode) {
		mCloneNodeIds.add(getMindMapController().getNodeID(cloneNode));
		clearCloneCache();
	}

	public void clearCloneCache() {
		mCloneNodes = new HashSet();
	}

	private void disablePlugin() {
		mDisabled = true;
		getMindMapController().getController().errorMessage(
				getMindMapController().getText("clone_plugin_impossible"));
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				if (getHook(getNode()) != null) {
					removeHook();
				}
			}
		});
	}

	/**
	 * double add = remove.
	 * 
	 */
	protected void removeHook() {
		// first deactivate cloning for this node (otherwise, the deactivation will be cloned, too!)
		deregisterCloning();
		Vector selecteds = Tools.getVectorWithSingleElement(getNode());
		getMindMapController()
				.addHook(getNode(), selecteds, PLUGIN_LABEL, null);
	}

	public void save(XMLElement xml) {
		super.save(xml);
		HashMap values = new HashMap();
		values.put(XML_STORAGE_CLONES, getCloneIdsAsString());
		values.put(XML_STORAGE_CLONE_ID, mCloneId);
		String cloneItselfValue = getCloneItselfValue();
		values.put(XML_STORAGE_CLONE_ITSELF, cloneItselfValue);
		logger.finest("Saved mCloneItself to " + cloneItselfValue);
		saveNameValuePairs(values, xml);
		logger.fine("Saved clone plugin");
	}

	protected String getCloneItselfValue() {
		return mClonePropertiesHolder
				.isCloneItself() ? CLONE_ITSELF_TRUE
				: CLONE_ITSELF_FALSE;
	}

	public String getCloneIdsAsString() {
		StringBuffer cloneIds = new StringBuffer();
		for (Iterator it = mCloneNodeIds.iterator(); it.hasNext();) {
			String cloneId = (String) it.next();
			cloneIds.append(cloneId);
			cloneIds.append(",");
		}
		return cloneIds.toString();
	}

	public void loadFrom(XMLElement child) {
		super.loadFrom(child);
		mCloneNodes = null;
		mCloneNodeIds = new HashSet();
		HashMap values = loadNameValuePairs(child);
		String cloneIds = (String) values.get(XML_STORAGE_CLONES);
		if (cloneIds != null) {
			StringTokenizer st = new StringTokenizer(cloneIds, ",");
			while (st.hasMoreTokens()) {
				String cloneId = st.nextToken();
				mCloneNodeIds.add(cloneId);
			}
		}
		mCloneId = (String) values.get(XML_STORAGE_CLONE_ID);
		if (values.containsKey(XML_STORAGE_CLONE_ITSELF)) {
			mCloneItself = Boolean.valueOf(Tools.safeEquals(CLONE_ITSELF_TRUE,
					values.get(XML_STORAGE_CLONE_ITSELF)));
		} else {
			mCloneItself = Boolean.FALSE;
		}
		logger.finest("Loaded mCloneItself to " + mCloneItself);
	}

	public void shutdownMapHook() {
		logger.fine("Shutdown of clones");
		deregisterPlugin();
		super.shutdownMapHook();
	}

	private void registerPlugin() {
		if (mDisabled)
			return;
		/*
		 * test for error cases: - orig is child of clone now - if clone is a
		 * child of clone, this is here not reachable, as the plugin remains
		 * active and is not newly invoked. Hmm, what to do?
		 */
		MindMapNode originalNode = getNode();
		HashSet cloneNodes = getCloneNodes();
		logger.fine("Invoke shadow class with orig: "
				+ printNodeId(originalNode) + " and clones "
				+ printNodeIds(cloneNodes));
		// check for error case that clones are descendant of one another.
		for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
			MindMapNode cloneNode = (MindMapNode) it.next();
			if (originalNode != null && originalNode.isDescendantOf(cloneNode)) {
				disablePlugin();
				return;
			}
		}
		getMindMapController().registerNodeLifetimeListener(this, false);
		Registration registration = getRegistration();
		if (mCloneId == null) {
			// hmm, it seems, that I am the first. Let's generate an id:
			mCloneId = registration.generateNewCloneId(null);
		}
		registration.registerClone(mCloneId, this);
		// the clone list contains itself, too.
		addClone(getNode());
		mClonePropertiesHolder = registration
				.getCloneProperties(mCloneId);
		if (mCloneItself != null) {
			mClonePropertiesHolder.setCloneItself(mCloneItself.booleanValue());
		}
		mClonePropertiesHolder.registerObserver(this);
	}

	protected Registration getRegistration() {
		return (Registration) getPluginBaseClass();
	}

	private void deregisterPlugin() {
		mClonePropertiesHolder.deregisterObserver(this);
		deregisterCloning();
		getMindMapController().deregisterNodeLifetimeListener(this);
		// remove icon
		getNode().setStateIcon(getName(), null);
		getMindMapController().nodeRefresh(getNode());
	}

	protected void deregisterCloning() {
		getRegistration().deregisterClone(mCloneId, this);
	}

	public void onCreateNodeHook(MindMapNode node) {
		HashSet cloneNodes = getCloneNodes();
		for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
			MindMapNode clone = (MindMapNode) it.next();
			for (Iterator it2 = cloneNodes.iterator(); it2.hasNext();) {
				MindMapNode clone2 = (MindMapNode) it2.next();
				if (clone != clone2) {
					checkForChainError(clone, node, clone2);
				}
			}
		}
	}

	public void onPreDeleteNode(MindMapNode node) {
	}

	public void onPostDeleteNode(MindMapNode node, MindMapNode parent) {
	}

	/**
	 * @return a list of {@link MindMapNode}s including the original node!
	 */
	HashSet getCloneNodes() {
		// is list up to date?
		if (mCloneNodes != null) {
			for (Iterator it = mCloneNodes.iterator(); it.hasNext();) {
				MindMapNode cloneNode = (MindMapNode) it.next();
				if (cloneNode.getParentNode() == null) {
					clearCloneCache();
				}
			}
		} else {
			clearCloneCache();
		}
		if (mCloneNodes.isEmpty()) {
			mCloneNodes.add(getNode());
			for (Iterator it = mCloneNodeIds.iterator(); it.hasNext();) {
				String cloneId = (String) it.next();
				try {
					mCloneNodes.add(getMindMapController().getNodeFromID(
							cloneId));
				} catch (IllegalArgumentException e) {
					// freemind.main.Resources.getInstance().logException(e);
					it.remove();
				}
			}
		}
		return mCloneNodes;
	}

	/**
	 * @param pCloneNode
	 * @return
	 */
	private String printNodeId(MindMapNode pCloneNode) {
		try {
			return getMindMapController().getNodeID(pCloneNode) + ": '"
					+ (pCloneNode.getShortText(getMindMapController())) + "'";
		} catch (Exception e) {
			return "NOT FOUND: '" + pCloneNode + "'";
		}
	}

	/**
	 * @param pTargets
	 * @return
	 */
	private String printNodeIds(Collection pTargets) {
		Vector strings = new Vector();
		for (Iterator it = pTargets.iterator(); it.hasNext();) {
			MindMapNode node = (MindMapNode) it.next();
			strings.add(printNodeId(node));
		}
		return "" + strings;
	}

	private void checkForChainError(MindMapNode originalNode, MindMapNode node,
			MindMapNode cloneNode) {
		if (cloneNode.isDescendantOfOrEqual(node)
				&& node.isDescendantOfOrEqual(originalNode)) {
			// orig -> .... -> node -> .. -> clone
			disablePlugin();
		}
	}

	public void removeClone(MindMapNode pCloneNode) {
		String nodeID = getMindMapController().getNodeID(pCloneNode);
		mCloneNodeIds.remove(nodeID);
		clearCloneCache();
		if (mCloneNodeIds.isEmpty()
				|| (mCloneNodeIds.size() == 1 && mCloneNodeIds
						.contains(getNodeId()))) {
			// remove myself
			logger.info("I'm the last clone " + nodeID);
			removeHook();
		}
	}

	public static ClonePlugin getHook(MindMapNode originalNode) {
		if (originalNode == null) {
			return null;
		}
		for (Iterator it2 = originalNode.getActivatedHooks().iterator(); it2
				.hasNext();) {
			PermanentNodeHook hook = (PermanentNodeHook) it2.next();
			if (hook instanceof ClonePlugin) {
				ClonePlugin cloneHook = (ClonePlugin) hook;
				return cloneHook;
			}
		}
		return null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * freemind.extensions.PermanentNodeHookAdapter#processUnfinishedLinks()
	 */
	public void processUnfinishedLinks() {
		super.processUnfinishedLinks();
		if (mDisabled)
			return;
		HashSet cloneNodes = getCloneNodes();
		// activate other clones, if not already activated.
		for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
			MindMapNode cloneNode = (MindMapNode) it.next();
			ClonePlugin hook = getHook(cloneNode);
			if (hook == null && cloneNode != null) {
				// add hook to clone partner:
				Vector selecteds = Tools.getVectorWithSingleElement(cloneNode);
				// Transport the data to the plugin, as this method calls
				// invoke.
				Properties hookProperties = new Properties();
				hookProperties.setProperty(XML_STORAGE_CLONE_ID, mCloneId);
				hookProperties.setProperty(XML_STORAGE_CLONES,
						getCloneIdsAsString());
				hookProperties.setProperty(XML_STORAGE_CLONE_ITSELF, getCloneItselfValue());
				getMindMapController().addHook(cloneNode, selecteds,
						PLUGIN_LABEL, hookProperties);
			}
		}

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see accessories.plugins.ClonePasteAction.ClonePropertiesObserver#
	 * propertiesChanged(accessories.plugins.ClonePasteAction.CloneProperties)
	 */
	public void propertiesChanged(CloneProperties pCloneProperties) {
		mCloneItself = Boolean.valueOf(pCloneProperties.isCloneItself());
	}

	public String getCloneId() {
		return mCloneId;
	}

}
