/FileExtraction/Java_unseen/X0Ken_FreeMind/plugins/collaboration/socket/SocketBasics.java
/*FreeMind - A Program for creating and viewing Mindmaps
 *Copyright (C) 2000-2008  Joerg Mueller, Daniel Polansky, Christian Foltin 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.
 *
 * Created on 28.12.2008
 */

package plugins.collaboration.socket;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.WindowConstants;

import com.jgoodies.forms.builder.DefaultFormBuilder;
import com.jgoodies.forms.factories.ButtonBarFactory;
import com.jgoodies.forms.layout.FormLayout;

import freemind.common.NumberProperty;
import freemind.common.PropertyBean;
import freemind.common.PropertyControl;
import freemind.controller.Controller;
import freemind.controller.MapModuleManager.MapTitleContributor;
import freemind.controller.actions.generated.instance.CollaborationUserInformation;
import freemind.controller.actions.generated.instance.CompoundAction;
import freemind.controller.actions.generated.instance.HookNodeAction;
import freemind.controller.actions.generated.instance.XmlAction;
import freemind.main.Resources;
import freemind.main.Tools;
import freemind.modes.MindMap;
import freemind.modes.MindMapNode;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.mindmapmode.actions.xml.ActionFilter.FirstActionFilter;
import freemind.modes.mindmapmode.actions.xml.ActionPair;
import freemind.modes.mindmapmode.hooks.MindMapNodeHookAdapter;
import freemind.view.MapModule;

public abstract class SocketBasics extends MindMapNodeHookAdapter implements
		MapTitleContributor, FirstActionFilter {

	/**
	 * 
	 */
	private static final String PLUGINS_COLLABORATION_SOCKET = "plugins/collaboration/socket/";
	public final static String MASTER_HOOK_LABEL = PLUGINS_COLLABORATION_SOCKET
			+ "socket_master_plugin";
	public final static String SLAVE_HOOK_LABEL = PLUGINS_COLLABORATION_SOCKET
			+ "socket_slave_plugin";
	public final static String SLAVE_STARTER_LABEL = PLUGINS_COLLABORATION_SOCKET
			+ "socket_slave_starter_plugin";
	protected static final Integer ROLE_MASTER = Integer.valueOf(0);
	protected static final Integer ROLE_SLAVE = Integer.valueOf(1);
	private static final String PORT_PROPERTY = "plugins.collaboration.socket.port";
	private static final String SOCKET_BASICS_CLASS = "plugins.collaboration.socket.SocketBasics";

	protected static final String PASSWORD = SOCKET_BASICS_CLASS + ".password";
	protected static final String PASSWORD_DESCRIPTION = SOCKET_BASICS_CLASS
			+ ".password.description";

	protected static final String PASSWORD_VERIFICATION = SOCKET_BASICS_CLASS
			+ ".password_verification";
	protected static final String PASSWORD_VERIFICATION_DESCRIPTION = SOCKET_BASICS_CLASS
			+ ".password_verification_description";

	protected static final String HOST = SOCKET_BASICS_CLASS + ".host";
	protected static final String HOST_DESCRIPTION = SOCKET_BASICS_CLASS
			+ ".host.description";

	protected static final String PORT = SOCKET_BASICS_CLASS + ".port";
	protected static final String PORT_DESCRIPTION = PORT + ".description";

	protected static final String TITLE = SOCKET_BASICS_CLASS + ".title";
	protected static final String UNKNWON_HOST_EXCEPTION_MESSAGE = SOCKET_BASICS_CLASS
			+ ".unknown_host_exception";
	protected static final String CONNECT_EXCEPTION_MESSAGE = SOCKET_BASICS_CLASS
			+ ".connection_exception";
	protected static final String SOCKET_CREATION_EXCEPTION_MESSAGE = SOCKET_BASICS_CLASS
			+ ".socket_creation_exception";;

	protected static java.util.logging.Logger logger = null;

	protected String mPassword;
	protected boolean mFilterEnabled = true;
	private String mUserName;

	public SocketBasics() {
		super();
		mUserName = Tools.getUserName();
	}

	/**
	 * @return ROLE_MASTER OR ROLE_SLAVE
	 */
	public abstract Integer getRole();

	public void startupMapHook() {
		super.startupMapHook();
		if (logger == null) {
			logger = freemind.main.Resources.getInstance().getLogger(
					this.getClass().getName());
		}
		getMindMapController().getController()
				.registerMapTitleContributor(this);
	}

	public void shutdownMapHook() {
		Controller controller = getMindMapController().getController();
		controller.deregisterMapTitleContributor(this);
		controller.setTitle();
		super.shutdownMapHook();
	}

	public static void togglePermanentHook(MindMapController controller,
			final String hookName) {
		MindMapNode rootNode = controller.getRootNode();
		List selecteds = Arrays.asList(new MindMapNode[] { rootNode });
		controller.addHook(rootNode, selecteds, hookName, null);
	}

	protected void setPortProperty(final NumberProperty portProperty) {
		getMindMapController().getFrame().setProperty(PORT_PROPERTY,
				portProperty.getValue());
	}

	protected NumberProperty getPortProperty() {
		final NumberProperty portProperty = new NumberProperty(
				PORT_DESCRIPTION, PORT, 1024, 32767, 1);
		// fill values:
		portProperty.setValue(""
				+ getMindMapController().getFrame().getIntProperty(
						PORT_PROPERTY, 9001));
		return portProperty;
	}

	public static abstract class FormDialogValidator {
		/**
		 * @return true, if ok should be enabled.
		 */
		public abstract boolean isValid();
	}

	public static class FormDialog extends JDialog implements
			PropertyChangeListener {
		private final MindMapController mController2;
		private boolean mSuccess = false;
		private JButton mOkButton;
		private FormDialogValidator mFormDialogValidator;

		public boolean isSuccess() {
			return mSuccess;
		}

		public FormDialog(MindMapController pController) {
			super(pController.getFrame().getJFrame());
			mController2 = pController;
		}

		public void setUp(Vector controls) {
			setUp(controls, new FormDialogValidator() {

				public boolean isValid() {
					return true;
				}
			});
		}

		public void setUp(Vector controls, FormDialogValidator pValidator) {
			mFormDialogValidator = pValidator;
			setModal(true);
			getContentPane().setLayout(new BorderLayout());
			FormLayout formLayout = new FormLayout(
					"right:max(40dlu;p), 4dlu, 80dlu, 7dlu", "");
			DefaultFormBuilder builder = new DefaultFormBuilder(formLayout);
			builder.setDefaultDialogBorder();
			for (Iterator it = controls.iterator(); it.hasNext();) {
				PropertyControl prop = (PropertyControl) it.next();
				prop.layout(builder, mController2);
				PropertyBean bean = (PropertyBean) prop;
				bean.addPropertyChangeListener(this);
			}
			getContentPane().add(builder.getPanel(), BorderLayout.CENTER);
			JButton cancelButton = new JButton(getText("Cancel"));
			cancelButton.addActionListener(new ActionListener() {

				public void actionPerformed(ActionEvent arg0) {
					closeWindow();
				}

			});
			mOkButton = new JButton(getText("OK"));
			mOkButton.addActionListener(new ActionListener() {

				public void actionPerformed(ActionEvent arg0) {
					mSuccess = true;
					closeWindow();
				}

			});
			getRootPane().setDefaultButton(mOkButton);
			getContentPane().add(
					ButtonBarFactory.buildOKCancelBar(cancelButton, mOkButton),
					BorderLayout.SOUTH);
			setTitle("Enter Password Dialog");
			setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
			addWindowListener(new WindowAdapter() {
				public void windowClosing(WindowEvent event) {
					closeWindow();
				}
			});
			Action action = new AbstractAction() {

				public void actionPerformed(ActionEvent arg0) {
					closeWindow();
				}
			};
			Action actionSuccess = new AbstractAction() {

				public void actionPerformed(ActionEvent arg0) {
					mSuccess = true;
					closeWindow();
				}
			};
			Tools.addEscapeActionToDialog(this, action);
			Tools.addKeyActionToDialog(this, actionSuccess, "ENTER",
					"ok_dialog");

			pack();
			setVisible(true);

		}

		private void closeWindow() {
			setVisible(false);
		}

		String getText(String text) {
			return text;
		}

		public void propertyChange(PropertyChangeEvent pEvt) {
			logger.finest("Property change " + pEvt);
			mOkButton.setEnabled(mFormDialogValidator.isValid());
		}

	}

	public abstract int getPort();

	public String getMapTitle(String pOldTitle, MapModule pMapModule,
			MindMap pModel) {
		if (pModel.getModeController() != getMindMapController()) {
			return pOldTitle;
		}
		CollaborationUserInformation userInfo = getMasterInformation();
		if (userInfo == null) {
			return pOldTitle;
		}
		return pOldTitle
				+ Resources.getInstance().format(
						TITLE,
						new Object[] { this.getRole(),
								userInfo.getMasterHostname(),
								userInfo.getMasterIp(),
								new Integer(userInfo.getMasterPort()),
								userInfo.getUserIds() });
	}

	public abstract CollaborationUserInformation getMasterInformation();

	public String getPassword() {
		return mPassword;
	}

	/**
	 * Deep search inside the {@link XmlAction} to find a hook (i.e. myself).
	 * They should not be send over the wire.
	 * 
	 * @param pAction
	 * @param pSearchString
	 * @return
	 */
	private boolean visit(XmlAction pAction, String pSearchString) {
		if (pAction instanceof CompoundAction) {
			CompoundAction compound = (CompoundAction) pAction;
			boolean result = false;
			for (Iterator it = compound.getListChoiceList().iterator(); it
					.hasNext();) {
				XmlAction action = (XmlAction) it.next();
				result |= visit(action, pSearchString);
			}
			return result;
		}
		if (pAction instanceof HookNodeAction) {
			HookNodeAction hookNodeAction = (HookNodeAction) pAction;
			if (Tools.safeEquals(hookNodeAction.getHookName(), pSearchString)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Try to lock, send update package to master (perhaps, myself), execute
	 * action and unlock
	 */
	public ActionPair filterAction(ActionPair pPair) {
		if (pPair == null || !mFilterEnabled)
			return pPair;
		// Don't send any hook instantiations to others.
		if (visit(pPair.getDoAction(), SocketConnectionHook.SLAVE_HOOK_LABEL)) {
			return pPair;
		}
		if (visit(pPair.getDoAction(), MindMapMaster.MASTER_HOOK_LABEL)) {
			return pPair;
		}
		String doAction = getMindMapController().marshall(pPair.getDoAction());
		String undoAction = getMindMapController().marshall(
				pPair.getUndoAction());
		logger.info("Require lock for command: " + doAction);
		try {
			String lockId = lock(getUserName());
			/*
			 * Blocking broadcast call: Client: send to master (who broadcasts
			 * the command afterwards), Master: send to all clients.
			 */
			broadcastCommand(doAction, undoAction, lockId);
		} catch (UnableToGetLockException e) {
			freemind.main.Resources.getInstance().logException(e);
			return getEmptyActionPair();
		} catch (Exception e) {
			freemind.main.Resources.getInstance().logException(e);
			return getEmptyActionPair();
		} finally {
			unlock();
		}
		return pPair;
	}

	public ActionPair getEmptyActionPair() {
		return new ActionPair(new CompoundAction(), new CompoundAction());
	}

	protected static class UnableToGetLockException extends Exception {

	}

	/**
	 * @param pUserName
	 *            the user the lock belongs to.
	 * @return The id associated with this lock.
	 * @throws UnableToGetLockException
	 * @throws InterruptedException
	 */
	protected abstract String lock(String pUserName)
			throws UnableToGetLockException, InterruptedException;

	/**
	 * @return the user's name (to acquire a named lock)
	 */
	protected String getUserName() {
		return mUserName;
	}

	/**
	 * Should send the command to the master, or, if the master itself, sends it
	 * to the clients.
	 * 
	 * @throws Exception
	 */
	protected abstract void broadcastCommand(String pDoAction,
			String pUndoAction, String pLockId) throws Exception;

	/**
	 * Unlocks the previous lock
	 */
	protected abstract void unlock();

	protected void registerFilter() {
		logger.info("Registering filter");
		getMindMapController().getActionFactory().registerFilter(this);
	}

	protected void deregisterFilter() {
		logger.info("Deregistering filter");
		getMindMapController().getActionFactory().deregisterFilter(this);
	}

	protected void executeTransaction(final ActionPair pair) {
		mFilterEnabled = false;
		try {
			getMindMapController().doTransaction("update", pair);
		} finally {
			mFilterEnabled = true;
		}
	}

	/**
	 * Closes the connection.
	 */
	public abstract void shutdown();

}
