// GeneratorPanel.java
//
// Created: November 2005
//
// This file is a component of the BioMoby project.
// Copyright Martin Senger (martin.senger@gmail.com).
//

package org.biomoby.service.dashboard;

import org.tulsoft.shared.UUtils;
import org.tulsoft.tools.gui.SwingUtils;
import org.tulsoft.tools.gui.JFileChooserWithHistory;
import org.tulsoft.tools.gui.JTextFieldWithHistory;

import org.biomoby.shared.MobyException;

import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JRadioButton;
import javax.swing.ButtonGroup;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JSplitPane;
import javax.swing.JComponent;
import javax.swing.Box;
import javax.swing.SwingConstants;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.tree.TreePath;

import java.awt.GridBagLayout;
import java.awt.Component;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;

import java.io.File;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Map;
import java.util.Iterator;

/**
 * A panel allowing to generate Java code for new services using the
 * MoSeS generators. <p>
 *
 * @author <A HREF="mailto:martin.senger@gmail.com">Martin Senger</A>
 * @version $Id: GeneratorPanel.java,v 1.20 2009/02/03 09:02:28 groscurt Exp $
 */

public class GeneratorPanel
	extends AbstractPanel {

	//private static org.apache.commons.logging.Log log =
	//   org.apache.commons.logging.LogFactory.getLog (GeneratorPanel.class);
	
	// some texts
	protected static final String ANT_ERROR =
	"Sorry, an error happened when running a task.\n\n" +
	
	"The details should be in the console area, or\n" +
	"they might had been reported to a log (file)\n\n";
	
	protected static final String JMOBY_ERROR =
	"Sorry, but in order to use MoSeS Generators\n" +
	"you need to have full jMoby installation, and\n" +
	"you need to start Dashboard from a jMoby directory.\n\n" +
	
	"This application does not allow me to change\n" +
	"directories. Please, close Dashboard, go to\n" +
	"the jMoby directory, and start Dashboard again.\n\n";
	
	protected static final String NOSEL_ERROR =
	"Sorry, you need first to select some services\n" +
	"which you wish Moses generates code for.\n\n" +
	
	"You can select several (or even many) of them\n" +
	"by using CTRL and SHIFT when selecting them.\n" +
	"You can also select one or more authorities\n" +
	"(then code for all their services will be made).\n\n";
	
	protected static final String NODEPL_ERROR =
	"Sorry, you need first to select some services\n" +
	"which you wish to be deployed (or undeployed).\n\n" +
	
	"You can select several (or even many) of them\n" +
	"by using CTRL and SHIFT when selecting them.\n" +
	"You can also select one or more authorities\n" +
	"(it will add all their services to the list).\n\n";
	
	protected static final String NOIMPL_ERROR =
	"Sorry, each service that is going to be deployed\n" +
	"needs to have assigned a class that implements it.\n\n";
	
	// associated models working behind the scene
	AntModel antModel;
	RegistryModel registryModel;
	
	// components that are used from more methods
	CommonConsole console;
	JFileChooserWithHistory outputDir;
	JFileChooserWithHistory javadocDir;
	JFileChooserWithHistory dotLocation;
	JLabel labelDotLocation;
	JButton dtgenButton, sgenButton, bothButton;
	JButton dtclsButton, sclsButton;
	JButton deployButton, undeployButton;
	JLabel aSelectedCount, sSelectedCount;
	JCheckBox dtGenerate, dtCompile, dtJavadoc, dtJar;
	JCheckBox sGenerate, sCompile, sJavadoc, sJar;
	JTextFieldWithHistory pattern;
	DeploymentTable dTable;
	JCheckBox copyBySelect;
	JRadioButton locally;
	
	// shared icons
	protected static Icon genDTIcon, genDTIconDis;
	protected static Icon genSIcon, genSIconDis;
	protected static Icon genIcon, genIconDis;
	protected static Icon trashIcon, trashIconDis;
	protected static Icon deployIcon, deployIconDis;
	protected static Icon undeployIcon, undeployIconDis;
	
	/*********************************************************************
	 * Default constructor.
	 ********************************************************************/
	public GeneratorPanel() {
	super();
	panelIconFileName = "images/bricks.gif";
	}
	
	/*********************************************************************
	 * Load shared icons.
	 ********************************************************************/
	protected void loadIcons() {
	super.loadIcons();
	
	if (genDTIcon == null) genDTIcon = loadIcon ("images/yellowDiamond.gif");
	if (genDTIconDis == null) genDTIconDis = loadIcon ("images/yellowDiamond_dis.gif");
	
	if (genSIcon == null) genSIcon = loadIcon ("images/smallClass.gif");
	if (genSIconDis == null) genSIconDis = loadIcon ("images/smallClass_dis.gif");
	
	if (genIcon == null) genIcon = loadIcon ("images/smallClass2.gif");
	if (genIconDis == null) genIconDis = loadIcon ("images/smallClass2_dis.gif");
	
	if (trashIcon == null) trashIcon = loadIcon ("images/smallTrash.gif");
	if (trashIconDis == null) trashIconDis = loadIcon ("images/smallTrash_dis.gif");
	
	if (deployIcon == null) deployIcon = loadIcon ("images/smallDeploy.gif");
	if (deployIconDis == null) deployIconDis = loadIcon ("images/smallDeploy_dis.gif");
	
	if (undeployIcon == null) undeployIcon = loadIcon ("images/smallUnDeploy.gif");
	if (undeployIconDis == null) undeployIconDis = loadIcon ("images/smallUnDeploy_dis.gif");
	
	}
	
	/**************************************************************************
	 *
	 **************************************************************************/
	public JComponent getComponent (PropertyChannel aPropertyChannel) {
		setPropertyChannel (aPropertyChannel);
	registryModel = createRegistryModel();
	antModel = new AntModel (registryModel);
	antModel.setPropertyChannel (aPropertyChannel);
	
	if (pComponent != null) return pComponent;
	pComponent = new JPanel (new GridBagLayout(), true);
	
	// console panel
	console = new CommonConsole();
	console.setAppendMode (true);
	console.setVerboseMode (true);
	antModel.setOutputStream (new ConsoleStream (console, false));
	antModel.setErrorStream (new ConsoleStream (console, true));
	antModel.setMsgOutputLevel (AntModel.MSG_INFO);
	
	// three major parts
	JPanel selection = getServicesSelectionPanel();
	JPanel generators = getGeneratorsPanel();
	JPanel deployment = getDeploymentPanel();
	
	// split it into resizable panels
	JSplitPane split = hSplit (vSplit (hSplit (selection,
						   generators, 0.9),
					   console, 0.2),
				   deployment, 0.9);
	SwingUtils.addComponent (pComponent, split,  0, 0, 1, 1, BOTH, NWEST,  1.0, 1.0);
	return pComponent;
	}
	
	/**************************************************************************
	 * Create a sub-panel for service/authority selection...
	 **************************************************************************/
	protected JPanel getServicesSelectionPanel() {
	JPanel p = new JPanel (new GridBagLayout());
	p.setBorder (createFatBorder
		     ("Select services to work with",
		      GraphColours.getColour ("cadetblue", Color.blue)));
	
	
	// service ontology tree
	ServicesBoard servicesBoard =
	    new ServicesBoard (registryModel,
			       console,
			       propertyChannel,
			       new CustomServicesTree (registryModel,
						       console));
	servicesBoard.updateTree (CommonTree.SORTED_BY_AUTHORITY);
	
	// counters of selecting services/authorities
	JPanel counters = new JPanel (new GridBagLayout());
	JLabel lASelected = new JLabel (" selected authorities");
	aSelectedCount = new JLabel ("0");
	aSelectedCount.setHorizontalAlignment (SwingConstants.RIGHT);
	JLabel lSSelected = new JLabel (" selected services");
	sSelectedCount = new JLabel ("0");
	sSelectedCount.setHorizontalAlignment (SwingConstants.RIGHT);
	
	SwingUtils.addComponent (counters, aSelectedCount, 0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (counters, lASelected,     1, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (counters, sSelectedCount, 0, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (counters, lSSelected,     1, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	
	// put it together
	SwingUtils.addComponent (p, servicesBoard, 0, 0, 1, 1, BOTH, NWEST, 1.0, 1.0);
	SwingUtils.addComponent (p, counters,      0, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	return p;
	}
	
	/**************************************************************************
	 * Create a sub-panel for code generators...
	 **************************************************************************/
	protected JPanel getGeneratorsPanel() {
	JPanel p = new JPanel (new GridBagLayout());
	p.setBorder (createFatBorder
		     ("Code Generators",
		      GraphColours.getColour ("cadetblue", Color.blue)));
	
	// what to do for datatypes
	JPanel genData = createTitledPanel ("Data Types");
	dtGenerate = createActionBox ("Generate code", DP_USE_DT_GEN);
	dtCompile  = createActionBox ("Compile code", DP_USE_DT_COMP);
	dtJavadoc  = createActionBox ("Generate javadoc", DP_USE_DT_DOC);
	dtJar      = createActionBox ("Packed into jar", DP_USE_DT_JAR);
	
	dtgenButton =
	    createButton (" Process datatypes ",
			  "Deal with all datatypes - do what is selected in the boxes above",
			  KeyEvent.VK_D,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onDataTypes();
			      }
			  });
	dtgenButton.setIcon (genDTIcon);
	dtgenButton.setDisabledIcon (genDTIconDis);
	
	dtclsButton =
	    createButton ("",
			  "Remove generated code for all datatypes",
			  -1,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onCleanDataTypes();
			      }
			  });
	dtclsButton.setIcon (trashIcon);
	dtclsButton.setDisabledIcon (trashIconDis);
	SwingUtils.compact (dtclsButton);
	
	SwingUtils.addComponent (genData, dtGenerate,  0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genData, dtclsButton, 1, 0, 1, 1, NONE, NEAST, 0.0, 0.0);
	SwingUtils.addComponent (genData, dtCompile,   0, 1, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genData, dtJavadoc,   0, 2, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genData, dtJar,       0, 3, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genData, dtgenButton, 0, 4, 2, 1, NONE, NWEST, 0.0, 0.0);
	
	// what to do for skeletons
	JPanel genSkel = createTitledPanel ("Services");
	sGenerate = createActionBox ("Generate code", DP_USE_S_GEN);
	sCompile  = createActionBox ("Compile code", DP_USE_S_COMP);
	sJavadoc  = createActionBox ("Generate javadoc", DP_USE_S_DOC);
	sJar      = createActionBox ("Packed into jar", DP_USE_S_JAR);
	sgenButton =
	    createButton (" Process skeletons ",
			  "Deal with all (or for selected, filtered) service skeletons",
			  KeyEvent.VK_S,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  Vector a = (Vector)propertyChannel.get (DP_SEL_AUTHORITIES);
				  Vector s = (Vector)propertyChannel.get (DP_SEL_SERVICES);
				  if ( (a == null || a.size() == 0) &&
				       (s == null || s.size() == 0) ) {
				      String msg = NOSEL_ERROR.replaceAll ("\\\\n", "<br>");
				      error ("<html>" + msg);
				  } else if (isInJMobyDirectory()) {
				      onSkeletons();
				  }
			      }
			  });
	sgenButton.setIcon (genSIcon);
	sgenButton.setDisabledIcon (genSIconDis);
	
	sclsButton =
	    createButton ("",
			  "Remove generated code for all skeletons",
			  -1,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onCleanSkeletons();
			      }
			  });
	sclsButton.setIcon (trashIcon);
	sclsButton.setDisabledIcon (trashIconDis);
	SwingUtils.compact (sclsButton);
	
	SwingUtils.addComponent (genSkel, sGenerate,  0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genSkel, sclsButton, 1, 0, 1, 1, NONE, NEAST, 0.0, 0.0);
	SwingUtils.addComponent (genSkel, sCompile,   0, 1, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genSkel, sJavadoc,   0, 2, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genSkel, sJar,       0, 3, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (genSkel, sgenButton, 0, 4, 2, 1, NONE, NWEST, 0.0, 0.0);
	
	// panel with options
	JPanel options = createTitledPanel ("Options");
	
	boolean usingSimulate = getPrefValue (DP_USE_SIMULATE, false);
	JCheckBox simulate = createCheckBox
	    ("Only simulate generating",
	     usingSimulate, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);
		     onSimulate (enabled);
		     setPrefValue (DP_USE_SIMULATE, enabled);
		     propertyChannel.put (DP_USE_SIMULATE,
					  new Boolean (enabled).toString());
		 }
	     });
	onSimulate (usingSimulate);
	
	boolean usingDot = getPrefValue (DP_USE_DOT, true);
	JCheckBox useDot =
	    createCheckBox ("Add graphics to generated javadoc",
			    usingDot, -1,
			    new ItemListener() {
				public void itemStateChanged (ItemEvent e) {
				    onUseDot (e.getStateChange() == ItemEvent.SELECTED);
				}
			    });
	labelDotLocation = new JLabel ("Path and name of Graphviz 'dot' program");
	dotLocation = createFileSelector ("Select a 'dot' program",
					  "Select",
					  null,
					  DP_DOT_LOC,
					  DP_DOT_LOC);
	onUseDot (usingDot);
	
	SwingUtils.addComponent (options, simulate,         0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (options, useDot,           0, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (options, labelDotLocation, 0, 2, 1, 1, NONE, NWEST, 0.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent (options, dotLocation,      0, 3, 1, 1, HORI, NWEST, 1.0, 0.0);
	
	// all-in-one button
	bothButton =
	    createButton (" All-In-One: Do it all ",
			  "Do everything for all/selected datatypes and service",
			  KeyEvent.VK_A,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onBoth();
			      }
			  });
	bothButton.setIcon (genIcon);
	bothButton.setDisabledIcon (genIconDis);
	
	// TBD: Moses flavours
	//	JPanel flavours = createTitledPanel ("MoSeS service generator's flavours");
	//	ButtonGroup group = new ButtonGroup();
	//	JRadioButton fNone, fBiocase, fSoaplab, fHibernate;
	//	group.add (fNone      = createFlavourBox ("General service - no specific flavour", DP_FL_NONE));
	//	group.add (fSoaplab   = createFlavourBox ("Analysis service based on Soaplab",     DP_FL_SOAPLAB));
	//	group.add (fBiocase   = createFlavourBox ("Database service based on BioCase",     DP_FL_BIOCASE));
	//	group.add (fHibernate = createFlavourBox ("Database service based on Hibernate",   DP_FL_HIBERNATE));
	//	fNone.setSelected (true);  // TBD: remove later
	//	fNone.setEnabled (true);   // TBD: remove later
	
	//	SwingUtils.addComponent (flavours, fNone,      0, 0, 1, 1, HORI, NWEST, 1.0, 0.0);
	//	SwingUtils.addComponent (flavours, fSoaplab,   0, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	//	SwingUtils.addComponent (flavours, fBiocase,   0, 2, 1, 1, NONE, NWEST, 0.0, 0.0);
	//	SwingUtils.addComponent (flavours, fHibernate, 0, 3, 1, 1, NONE, NWEST, 0.0, 0.0);
	
	// put all together
	SwingUtils.addComponent (p, options,    0, 0, 2, 1, HORI, NWEST,  0.0, 0.0);
//	SwingUtils.addComponent (p, flavours,   0, 1, 2, 1, HORI, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (p, genData,    0, 1, 1, 1, HORI, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (p, genSkel,    1, 1, 1, 1, HORI, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (p, bothButton, 0, 2, 2, 1, NONE, CENTER, 0.0, 0.0, BREATH_TOP);
	return p;
	}
	
	/**************************************************************************
	 * Create a sub-panel for deployment...
	 **************************************************************************/
	protected JPanel getDeploymentPanel() {
	JPanel p = new JPanel (new GridBagLayout());
	p.setBorder (createFatBorder
		     ("Services deployment",
		      GraphColours.getColour ("cadetblue", Color.blue)));
	
	// local/remote radio buttons
//	JPanel pDeploy = createTitledPanel ("");
	JPanel pDeploy = new JPanel (new GridBagLayout());
	
	boolean deployLocally = getPrefValue (DP_LOCAL_DEPLOY, true);
	propertyChannel.put (DP_LOCAL_DEPLOY, new Boolean (deployLocally).toString());
	
	locally = new JRadioButton ("On local machine", deployLocally);
	locally.setActionCommand ("true");
	locally.addActionListener (deployListener);
	JRadioButton remotely = new JRadioButton ("On remote machine", ! deployLocally);
	remotely.setActionCommand ("false");
	remotely.addActionListener (deployListener);
	
	ButtonGroup group = new ButtonGroup();
	group.add (locally);
	group.add (remotely);
	
	Component glue = Box.createHorizontalGlue();
	SwingUtils.addComponent (pDeploy, locally,  0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pDeploy, glue,     1, 0, 1, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent (pDeploy, remotely, 2, 0, 1, 1, NONE, NEAST, 0.0, 0.0);
	
	// Tomcat/Axis locations
	JPanel pServer = createTitledPanel ("Server/Axis locations");
	
	String initValue = null;
	String os = System.getProperty ("os.name");
	if (os.startsWith ("Windows"))
	    initValue = null;
	//	    initValue = "c:\\Program Files\\Apache Software Foundation\\Tomcat 5.5";
	else if (os.startsWith ("Mac"))
	    initValue = null;
	else
	    initValue = "/usr/local/";
	
	JLabel lTomcatHome = new JLabel ("Server home directory");
	JFileChooserWithHistory tomcatHome =
	    createFileSelector ("Select server home directory",
				"Select",
				initValue,
				DP_TOMCAT_HOME,
				DP_TOMCAT_HOME);
	tomcatHome.getFileChooser().setFileSelectionMode (JFileChooser.DIRECTORIES_ONLY);
	
	JLabel lDeployInT = new JLabel("Deployment relative path");
	JTextFieldWithHistory deployInT = createText( "webapps", DP_DEPLOY_IN_TOMCAT, DP_DEPLOY_IN_TOMCAT );
	deployInT.setToolTipText( "The relative path to the web application deployment directory - e.g. webapps for tomcat or " +
			"server/default/depoy for jboss");
	
	JLabel lAxisInT = new JLabel ("Axis relative path ");	
	JTextFieldWithHistory axisInT = createText ("axis", DP_AXIS_IN_TOMCAT, DP_AXIS_IN_TOMCAT);
	axisInT.setToolTipText( "The path to Axis - e.g. axis for Tomcat or axis.war for JBoss" );
	
	JLabel lHost = new JLabel ("Hostname");
	JTextFieldWithHistory host = createText ("localhost", DP_HOSTNAME, DP_HOSTNAME);
	JLabel lPort = new JLabel ("Port");
	JTextFieldWithHistory port = createText ("8080", DP_PORT, DP_PORT);
	JLabel lAxisAdm = new JLabel ("URL-path of Axis Admin servlet");
	JTextFieldWithHistory axisAdm = createText ("axis/servlet/AxisServlet", DP_AXIS_ADMIN, DP_AXIS_ADMIN);
	
	SwingUtils.addComponent (pServer, lTomcatHome, 0, 0, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, tomcatHome,  0, 1, 2, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent (pServer, lDeployInT,  0, 2, 1, 1, NONE, NWEST, 0.0, 0.0 );
	SwingUtils.addComponent (pServer, deployInT,   0, 3, 1, 1, HORI, NWEST, 1.0, 0.0 );
	SwingUtils.addComponent (pServer, lAxisInT,    1, 2, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, axisInT,     1, 3, 1, 1, HORI, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, lHost,       0, 4, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, host,        0, 5, 1, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent (pServer, lPort,       1, 4, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, port,        1, 5, 1, 1, HORI, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, lAxisAdm,    0, 6, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pServer, axisAdm,     0, 7, 2, 1, HORI, NWEST, 0.0, 0.0);
	
	// WSDD template (rarely used anyway)
	JPanel pWSDD = createTitledPanel ("WSDD Template file");
	JFileChooserWithHistory wsdd =
	    createFileSelector ("Select file with a WSDD template",
				"Select",
				null,
				DP_WSDD_TEMPL,
				DP_WSDD_TEMPL);
	wsdd.getFileChooser().setFileSelectionMode (JFileChooser.FILES_ONLY);
	SwingUtils.addComponent (pWSDD, wsdd, 0, 0, 1, 1, HORI, NWEST, 1.0, 0.0);
	
	// user implementation
	JPanel pImpl = createTitledPanel ("User implementation classes");
	JLabel lUserJars = new JLabel ("Directory with user's jar files");
	JFileChooserWithHistory userJars =
	    createFileSelector ("Select directory with user's implementation",
				"Select",
				null,
				DP_USER_JARS,
				DP_USER_JARS);
	
	JLabel lPattern = new JLabel ("Pattern for implementation class names");
	pattern = createText (null,
			      DP_PATTERN, DP_PATTERN);
	dTable = new DeploymentTable();
	
	boolean usingCopyBySelect = getPrefValue (DP_CBS_DEPLOY, true);
	copyBySelect =
	    createCheckBox ("Add services here by selecting them in the tree",
			    usingCopyBySelect, -1,
			    new ItemListener() {
				public void itemStateChanged (ItemEvent e) {
				    setPrefValue (DP_CBS_DEPLOY,
						  (e.getStateChange() == ItemEvent.SELECTED));
				}
			    });
	
	
	SwingUtils.addComponent (pImpl, lUserJars,           0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pImpl, userJars,            0, 1, 1, 1, HORI, NWEST, 1.0, 0.0);
	SwingUtils.addComponent (pImpl, lPattern,            0, 2, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent (pImpl, pattern,             0, 3, 1, 1, HORI, NWEST, 1.0, 0.0);
		SwingUtils.addComponent (pImpl, dTable.scrollable(), 0, 4, 1, 1, BOTH, NWEST, 1.0, 1.0, BREATH_TOP);
	SwingUtils.addComponent (pImpl, copyBySelect,        0, 5, 1, 1, HORI, NWEST, 1.0, 0.0);
	
	// buttons
	deployButton =
	    createButton (" Deploy ",
			  "Move all needed files to a server and register services",
			  KeyEvent.VK_Y,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onDeploy();
			      }
			  });
	deployButton.setIcon (deployIcon);
	deployButton.setDisabledIcon (deployIconDis);
	
	undeployButton =
	    createButton (" Undeploy ",
			  "Unregister selected services from a server",
			  KeyEvent.VK_U,
			  new ActionListener() {
			      public void actionPerformed (ActionEvent e) {
				  if (isInJMobyDirectory())
				      onUndeploy();
			      }
			  });
	undeployButton.setIcon (undeployIcon);
	undeployButton.setDisabledIcon (undeployIconDis);
	undeployButton.setEnabled (locally.isSelected());
	undeployButton.setEnabled (false);  // temporarily
	
	// put all together
	Component glue2 = Box.createHorizontalGlue();
	SwingUtils.addComponent (p, pDeploy,             0, 0, 3, 1, HORI, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (p, pServer,             0, 1, 3, 1, HORI, NWEST,  0.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent (p, pWSDD,               0, 2, 3, 1, HORI, NWEST,  0.0, 0.0);
	SwingUtils.addComponent (p, pImpl,               0, 3, 3, 1, BOTH, NWEST,  1.0, 1.0);
	SwingUtils.addComponent (p, deployButton,        0, 4, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent (p, glue2,               1, 4, 1, 1, HORI, NWEST,  1.0, 0.0, BREATH_TOP);
	SwingUtils.addComponent (p, undeployButton,      2, 4, 1, 1, NONE, NWEST,  0.0, 0.0, BREATH_TOP);
	return p;
	}
	
	// this is used both for 'locally' and 'remotelly' radio buttons;
	// 'locally' button has an action command "true" and 'remotely'
	// button has an action command "false"
	private ActionListener deployListener = new ActionListener() {
	    public void actionPerformed (ActionEvent e) {
		String local = e.getActionCommand();
		boolean isLocal = UUtils.is (local);
		setPrefValue (DP_LOCAL_DEPLOY, isLocal);
		propertyChannel.put (DP_LOCAL_DEPLOY, local);
		undeployButton.setEnabled (isLocal);
		undeployButton.setEnabled (false);  // temporarily
	    }
	};
	
	
	/**************************************************************************
	 * Create a specialized check box (use for Moses actions).
	 **************************************************************************/
	private JCheckBox createActionBox (String title, final String preferenceKey) {
	boolean initValue = getPrefValue (preferenceKey, true);
	propertyChannel.put (preferenceKey,
			     new Boolean (initValue).toString());
	return createCheckBox
	    (title, initValue, -1,
	     new ItemListener() {
		 public void itemStateChanged (ItemEvent e) {
		     boolean enabled = (e.getStateChange() == ItemEvent.SELECTED);
		     setPrefValue (preferenceKey, enabled);
		     propertyChannel.put (preferenceKey,
					  new Boolean (enabled).toString());
		 }
	     });
	}
	
	// /**************************************************************************
	//  * Create a specialized check box (use for Moses flavours).
	//  **************************************************************************/
	// private JRadioButton createFlavourBox (String title, String flavour) {
	//	JRadioButton radio = new JRadioButton (title);
	//	radio.setActionCommand (flavour);
	//	radio.addActionListener (flavourListener);
	//	String initFlavour = getPrefValue (DP_MOSES_FLAVOUR, DP_FL_NONE);
	//	if (flavour.equals (initFlavour)) {
	//	    radio.setSelected (true);
	//	    radio.setEnabled (true);
	//	    propertyChannel.put (DP_MOSES_FLAVOUR, flavour);
	//	}
	
	//	radio.setEnabled (false);   // TBD: temporarily
	//	return radio;
	// }
	
	//
	// private ActionListener flavourListener = new ActionListener() {
	//	    public void actionPerformed (ActionEvent e) {
	//		String flavour = e.getActionCommand();
	//		setPrefValue (DP_MOSES_FLAVOUR, flavour);
	//		propertyChannel.put (DP_MOSES_FLAVOUR, flavour);
	//	    }
	//	};
	
	/**************************************************************************
	 * Check if we are running from a jMoby directory. If yes return
	 * true, if not explain it in an error message and return false.
	 **************************************************************************/
	protected boolean isInJMobyDirectory() {
	File f = AntModel.createFileName (new String[] {
	    System.getProperty ("user.dir"),
	    "src", "main", "org", "biomoby", "service", "dashboard",
	    "Dashboard.java" });
	if (f.exists())
	    return true;
	
	String msg = JMOBY_ERROR.replaceAll ("\\\\n", "<br>");
	error ("<html>" + msg);
	return false;
	}
	
	/**************************************************************************
	 * Generate datatypes...
	 **************************************************************************/
	protected void onDataTypes() {
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Processing MoSeS datatypes...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesDatatypes();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "MoSeS datatypes done");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Removing datatypes...
	 **************************************************************************/
	protected void onCleanDataTypes() {
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Removing MoSeS datatypes...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesCleanDatatypes();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "MoSeS datatypes removed");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Generate skeletons...
	 **************************************************************************/
	protected void onSkeletons() {
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Processing MoSeS skeletons...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesSkeletons();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "MoSeS skeletons done");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Removing skeletons...
	 **************************************************************************/
	protected void onCleanSkeletons() {
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Removing MoSeS skeletons...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesCleanSkeletons();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "MoSeS skeletons removed");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Generate both...
	 **************************************************************************/
	protected void onBoth() {
	dtGenerate.setSelected (true);
	dtCompile.setSelected (true);
	dtJavadoc.setSelected (false);
	dtJar.setSelected (true);
	
	sGenerate.setSelected (true);
	sCompile.setSelected (true);
	sJavadoc.setSelected (true);
	sJar.setSelected (true);
	
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Processing MoSeS datatypes...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesDatatypes();
			propertyChannel.fire (DP_STATUS_MSG, "Processing MoSeS skeletons...");
			antModel.mosesSkeletons();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "MoSeS all-in-one done");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Deploy services...
	 **************************************************************************/
	protected void onDeploy() {
	if (! onCheckData()) return;
	Map data = dTable.getData();
	for (Iterator it = data.entrySet().iterator(); it.hasNext(); ) {
	    Map.Entry entry = (Map.Entry)it.next();
	    String className = (String)entry.getValue();
	    if (UUtils.isEmpty (className)) {
		String msg = NOIMPL_ERROR.replaceAll ("\\\\n", "<br>");
		error ("<html>" + msg);
		return;
	    }
	}
	
	// ...and let Ant to deploy it
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Deploying services...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesDeploy();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "Deployment done");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Undeploy services...
	 **************************************************************************/
	protected void onUndeploy() {
	if (! onCheckData()) return;
	final SwingWorker worker = new SwingWorker() {
		MobyException exception = null;
		public Object construct() {
		    try {
			propertyChannel.fire (DP_STATUS_MSG, "Undeploying services...");
			console.setEnabledAppendMode (false);
			antModel.setMsgOutputLevel (console.isVerboseMode() ?
						    AntModel.MSG_INFO : AntModel.MSG_QUIET);
			setEnabledAntButtons (false);
			antModel.mosesUndeploy();
		    } catch (MobyException e) {
			exception = e;
		    }
		    return null;  // not used here
		}
		    
		// runs on the event-dispatching thread
		public void finished() {
		    if (exception != null)
			error (ANT_ERROR, exception);
		    setEnabledAntButtons (true);
		    console.setEnabledAppendMode (true);
		    propertyChannel.fire (DP_STATUS_MSG, "Undeployment done");
		}
	    };
	worker.start(); 
	}
	
	/**************************************************************************
	 * Get data from a deployment table; check if there are some
	 * (report error if not) and put them in the property
	 * channel. Return accordingly.
	 **************************************************************************/
	protected boolean onCheckData() {
	Map data = dTable.getData();
	if (data.size() == 0) {
	    String msg = NODEPL_ERROR.replaceAll ("\\\\n", "<br>");
	    error ("<html>" + msg);
	    return false;
	}
	propertyChannel.put (DP_DEPL_SERVICES, data);
	return true;
	}
	
	/**************************************************************************
	 * Disable/enable Ant-processing buttons...
	 **************************************************************************/
	protected void setEnabledAntButtons (boolean enabled) {
	dtgenButton.setEnabled (enabled);
	sgenButton.setEnabled (enabled);
	bothButton.setEnabled (enabled);
	dtclsButton.setEnabled (enabled);
	sclsButton.setEnabled (enabled);
	deployButton.setEnabled (enabled);
	//	undeployButton.setEnabled (enabled && locally.isSelected());   // temporarily
	}
	
	/**************************************************************************
	 * Select/unselect using simulation...
	 **************************************************************************/
	protected void onSimulate (boolean enabled) {
	setPrefValue (DP_USE_SIMULATE, enabled);
	propertyChannel.put (DP_USE_SIMULATE,
			     new Boolean (enabled).toString());
	}
	
	/**************************************************************************
	 * Select/unselect using graphics...
	 **************************************************************************/
	protected void onUseDot (boolean enabled) {
	setPrefValue (DP_USE_DOT, enabled);
	propertyChannel.put (DP_USE_DOT,
			     new Boolean (enabled).toString());
	labelDotLocation.setEnabled (enabled);
	dotLocation.setEnabled (enabled);
	}
	
	/**************************************************************************
	 *
	 **************************************************************************/
	public String getName() {
	return "MoSeS Generator";
	}
	
	/**************************************************************************
	 *
	 **************************************************************************/
	public String getDescription() {
	return
	    "A panel allowing to generate datatypes and skeletons that can be " +
	    "used by Biomoby service providers to implements their services.";
	}
	
	/**************************************************************************
	 *
	 **************************************************************************/
	public boolean loadOnlyOnDemand() {
	return true;
	}
	
	/**************************************************************************
	 *
	 * Customized tree of services - has different popup menus,
	 * selection model, etc...
	 *
	 **************************************************************************/
	protected class CustomServicesTree
	extends ServicesTree {
	
	/*********************************************************************
	 * Construtor
	 ********************************************************************/
	public CustomServicesTree (RegistryModel model,
				   CommonConsole console) {
	    super (model, console);
	    getSelectionModel().setSelectionMode
		(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
	}
	
	/*********************************************************************
	 *
	 ********************************************************************/
	protected void createPopups (String title) {
	    super.createPopups (title);
	    removeFromPopups (AC_RELOAD);
	    removeSeparatorAfter (AC_COLLAPSE);
	}
	
	/*********************************************************************
	 *
	 ********************************************************************/
	protected void selected (DefaultMutableTreeNode node) {
	
	    // nothing selected
	    if (node == null) {
		console.setText ("Nothing selected\n");
		setPrefValue (DP_SEL_AUTHORITIES, "");
		propertyChannel.remove (DP_SEL_AUTHORITIES);
		setPrefValue (DP_SEL_SERVICES, "");
		propertyChannel.remove (DP_SEL_SERVICES);
		aSelectedCount.setText ("0");
		sSelectedCount.setText ("0");
		return;
	    }
	
	    // find all selected nodes - services and/or authorities
	    // and put them into two vectors (s and a)
	    TreePath[] paths = this.getSelectionModel().getSelectionPaths();
	    if (paths == null) return;
	    Vector<Object> a = new Vector<Object>();   // selected authorities
	    Vector<Object> s = new Vector<Object>();   // selected services
	    for (int i = 0; i < paths.length; i++) {
		DefaultMutableTreeNode aNode =
		    (DefaultMutableTreeNode)paths[i].getLastPathComponent();
		Object uo = aNode.getUserObject();
		if (uo instanceof CommonNode) {
		    int nodeType = ((CommonNode)uo).getType();
		    if (nodeType == CommonNode.NODE_AUTHORITY) {
			// an authority without services is not interesting
			if (aNode.isLeaf()) continue;
			a.addElement (((CommonNode)uo).getValue());
			if (copyBySelect.isSelected()) {
			    // take all leaves-services and propagate them...
			    for (Enumeration en = aNode.children(); en.hasMoreElements(); ) {
				Object uo2 = ((DefaultMutableTreeNode)en.nextElement()).getUserObject();
				if (uo2 instanceof CommonNode)
				    addToTable ( ((CommonNode)uo2).getValue() );
			    }
			}
		    } else if (nodeType == CommonNode.NODE_SERVICE) {
			s.addElement (((CommonNode)uo).getValue());
			if (copyBySelect.isSelected()) {
			    addToTable ((String)s.lastElement());
			}
	
		    }
		}
	    }
	
	    // tell the world what is currently selected
	    StringBuffer conBuf = new StringBuffer (100);
	    StringBuffer prefBufA = new StringBuffer (100);
	    StringBuffer prefBufS = new StringBuffer (100);
	
	    if (a.size() > 0) {
		conBuf.append ("Selected authorities:\n");
		for (Enumeration en = a.elements(); en.hasMoreElements(); ) {
		    String name = en.nextElement().toString();
		    conBuf.append ("\t");
		    conBuf.append (name);
		    conBuf.append ("\n");
		    if (prefBufA.length() > 0)
			prefBufA.append ("|");
		    prefBufA.append (name);
		}
		propertyChannel.put (DP_SEL_AUTHORITIES, a);
	    } else {
		propertyChannel.remove (DP_SEL_AUTHORITIES);
	    }
	    setPrefValue (DP_SEL_AUTHORITIES, new String (prefBufA));
	    aSelectedCount.setText ("" + a.size());
	
	    if (s.size() > 0) {
		conBuf.append ("Selected services:\n");
		for (Enumeration en = s.elements(); en.hasMoreElements(); ) {
		    String name = en.nextElement().toString();
		    conBuf.append ("\t");
		    conBuf.append (name);
		    conBuf.append ("\n");
		    if (prefBufS.length() > 0)
			prefBufS.append ("|");
		    prefBufS.append (name);
		}
		propertyChannel.put (DP_SEL_SERVICES, s);
	    } else {
		propertyChannel.remove (DP_SEL_SERVICES);
	    }
	    setPrefValue (DP_SEL_SERVICES, new String (prefBufS));
	    sSelectedCount.setText ("" + s.size());
	
	    if (conBuf.length() > 0)
		console.setText (new String (conBuf));
	    else
		console.setText ("Nothing selected\n");
	}
	}
	
	/*********************************************************************
	 * 
	 ********************************************************************/
	protected void addToTable (String serviceName) {
	// sorry, bad hack...
	// (originated in CommonNode not having 'value' as Object)
	String[] parts = serviceName.split ("/", 2);
	if (parts.length == 0) return;   // strange...
	dTable.addData (parts[0], applyPattern (parts[0]));
	}
	
	/*********************************************************************
	 * 
	 ********************************************************************/
	protected String applyPattern (String serviceName) {
	String patt = pattern.getText();
	if (UUtils.isEmpty (patt))
	    return serviceName + "Impl";
	if (serviceName.length() < 2)
	    return patt.replaceAll ("\\{SERVICE\\}", serviceName);
	return
	    patt.replaceAll ("\\{SERVICE\\}", serviceName)
	    .replaceAll ("\\{Service\\}",
			 serviceName.substring (0, 1).toUpperCase() +
			 serviceName.substring (1));
	}
	
}
