package org.biomoby.service.dashboard;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Event;
import java.awt.Font;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextPane;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.text.Document;
import javax.swing.text.EditorKit;
import javax.swing.text.StyledEditorKit;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.apache.commons.io.FilenameUtils;
import org.biomoby.shared.MobyException;
import org.biomoby.shared.event.NotificationEvent;
import org.biomoby.shared.event.NotificationListener;
import org.biomoby.shared.event.Notifier;
import org.tulsoft.tools.gui.JTextFieldWithHistory;
import org.tulsoft.tools.gui.SwingUtils;

public class PerlMoSeSPanel extends AbstractPanel {

    // some properties for the channel
    // String: set this to pop up a warning message
    static String PM_WARNING = "perl-moses-warning";

    // String: set this to pop up an error message
    static String PM_ERROR = "perl-moses-error";

    // String: set this for an information message
    static String PM_INFORMATION = "perl-moses-information";

    // String - do we over write files
    static String PM_OVERWRITE = "perl-moses-overwrite";

    // String: do we generate cgi services
    static String PM_CGI = "perl-moses-cgi";
    
    // String: do we generate soap services
    static String PM_SOAP = "perl-moses-soap";
    
    // String: do we generate async services
    static String PM_ASYNC = "perl-moses-async";
    
    // String: do we generate async cgi services
    static String PM_CGI_ASYNC = "perl-moses-cgi-async";

    // String:: are we currently updating the datatype cache
    static String PM_SYNC_DATATYPES = "perl-moses-sync-datatypes";

    // String: are we currently updating the services cache
    static String PM_SYNC_SERVICES = "perl-moses-sync-services";

    // String: are we generating services by name
    static String PM_GENERATING_SPECIFIC_SERVICES = "perl-moses-generating-specific-services";

    // String: are we generating services by authority
    static String PM_GENERATING_BY_AUTHORITY = "perl-moses-generating-by-authority";

    // N/A: set this to make the editor save the current file
    static String PM_FILE_ACTION_SAVE = "perl-moses-file-action-save";

    // N/A: set this to make the editor open a file
    static String PM_FILE_ACTION_OPEN = "perl-moses-file-action-open";

    // N/A: set this to make the editor close a file
    static String PM_FILE_ACTION_CLOSE = "perl-moses-file-action-close";

    // String: the full path of the file that we are editing
    static String PM_FILE_CURRENT = "perl-moses-file-current";
    
    // String: the checksum of the current open file
    static String PM_FILE_CURRENT_CHECKSUM = "perl-moses-file-current-checksum";
    
    // String: the parent directory of the last opened file
    static String PM_FILE_LAST_DIRECTORY = "perl-moses-file-last-directory";
    
    // String: one of the PERL5LIB directories for those that install modules locally
    static String PM_PERL_LIB_1 = "perl-moses-perl-lib-1";

    // String: one of the PERL5LIB directories for those that install modules locally
    static String PM_PERL_LIB_2 = "perl-moses-perl-lib-2";
    
    // String: the exact path to the locally installed moses scripts
    static String PM_SCRIPTS_INSTALL_DIR = "perl-moses-scripts-install-dir";
    
    // String: the exact path to the users perl (in case it is not available on the path)
    static String PM_PERL_INSTALL_DIR = "perl-moses-perl-install-dir";
    
    // String: the font that the editor uses
    static String PM_EDITOR_FONT = "perl-moses-editor-font";

    // generated serialversionUID
    private static final long serialVersionUID = 1379466985182986776L;

    private RegistryModel registryModel;

    private CommonConsole console;

    private JLabel aSelectedCount, sSelectedCount, currentlyEditing, currentPosition;

    private JButton editorSaveButton, editorCloseButton, editorOpenButton, scriptsBtn, generateBtn;

    // are we enabling moses actions
    private boolean enable_moses_actions = true;

    // is the user running windows
    private boolean is_windows_pc = false;

    private Icon openFileIcon, openFileIconDis, closeFileIcon,
    		 closeFileIconDis, saveFileIcon, saveFileIconDis, 
    		 zoomInIcon, zoomInIconDis, zoomOutIcon, zoomOutIconDis;
    
    private static JTextPane editorTextPane;
    
    private JComboBox fonts;
    
    /*
     * The following strings are error messages that perl-moses produces when a 
     * cache needs to be created and updates fail
     */
    private String SERVICE_CACHE_DOESNT_EXIST = "Please create a services cache first!";
    
    private String DATATYPE_CACHE_DOESNT_EXIST = "Please create a datatype cache first!";
    
    /**
     * Default constructor: set the image, and determines 
     * whether or not we are using windows
     */
    public PerlMoSeSPanel() {
	super();
	panelIconFileName = "images/pMoses.gif";
	
	if (System.getProperty("os.name").startsWith("Windows"))
	    is_windows_pc = true;
	
	check_install();
	
	// set up some default values
	if (!enable_moses_actions){
	    String s = "";
	    // this value can be set irregardless of the others
	    s = getPrefValue(PM_PERL_INSTALL_DIR, "");
	    if (s.equals(""))
		setPrefValue(PM_PERL_INSTALL_DIR, is_windows_pc ? "perl.exe" : "/usr/bin/perl");
	    
	}
    }
    
    /*
     * private method that determines if
     * moses is available from the command line. 
     */
    private void check_install() {
	String[] command;	
	ArrayList<String> prefix = new ArrayList<String>();
	if (is_windows_pc) {
	    prefix.add("moses-cache-tester.bat");
	    command = prefix.toArray(new String[]{});
	} else {
	    prefix.add("moses-cache-tester.pl");
	    command = prefix.toArray(new String[]{});
	}
	try {
	    Process process;
	    process = Runtime.getRuntime().exec(command);
	    BufferedReader br = new BufferedReader(
		    new InputStreamReader(process.getInputStream()));
	    
	    while (br.readLine() != null) {
		//System.out.println(line + System.getProperty("line.separator"));
	    }
	    process.waitFor();
	    enable_moses_actions = true;
	} catch (Exception e) {
	    enable_moses_actions = false;
	}
    }
    
    // private method that checks if moses is installed given user values
    private void user_check_install() {
	String[] command;	
	ArrayList<String> prefix = generatePerlPrefix();
	if (is_windows_pc) {
	    prefix.add("moses-cache-tester.bat");
	    command = prefix.toArray(new String[]{});
	} else {
	    prefix.add((prefix.size() > 0 ? prefix.remove(prefix.size()-1) : "") + "moses-cache-tester.pl");
	    command = prefix.toArray(new String[]{});
	}
	try {
	    Process process;
	    process = Runtime.getRuntime().exec(command);
	    BufferedReader br = new BufferedReader(
		    new InputStreamReader(process.getInputStream()));
	    
	    while (br.readLine() != null) {
		//System.out.println(line + System.getProperty("line.separator"));
	    }
	    process.waitFor();
	    enable_moses_actions = true;
	} catch (Exception e) {
	    enable_moses_actions = false;
	}
    }
    
    /*
     * private method that generates the
     * 
     *  /path/to/perl -I/some/dir /path/to/scripts
     *  
     *  part of the perl call
     * 
     */
    private ArrayList<String> generatePerlPrefix() {
	// usually windows installs everything as root
	if (is_windows_pc)
	    return new ArrayList<String>();
	
	ArrayList<String> list = new ArrayList<String>();
	String script_path = propertyChannel.getString(PM_SCRIPTS_INSTALL_DIR);
	String lib1 = propertyChannel.getString(PM_PERL_LIB_1);
	String lib2 = propertyChannel.getString(PM_PERL_LIB_2);
	String perl = propertyChannel.getString(PM_PERL_INSTALL_DIR);
	
	if (perl != null && !perl.trim().equals("") && script_path != null && !script_path.trim().equals("")) {
	    list.add(perl);
	    if (lib1 != null && !lib1.trim().equals("")) {
		list.add("-I" + lib1.trim());
	    }
	    if (lib2 != null && !lib2.trim().equals("")) {
		list.add("-I" + lib2.trim());
	    }
	    if (!script_path.endsWith("/"))
		script_path += "/";
	    list.add(script_path);
	}
	return list;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.biomoby.service.dashboard.AbstractPanel#getComponent(org.biomoby.service.dashboard.PropertyChannel)
     */
    @Override
    public JComponent getComponent(PropertyChannel propertyChannel) {
	
	setPropertyChannel(propertyChannel);	
	determineMoSeSDirectory();
	// put some properties in the channel
	propertyChannel.put(PM_GENERATING_BY_AUTHORITY, new Boolean(false));
	propertyChannel.put(PM_GENERATING_SPECIFIC_SERVICES,new Boolean(false));
	propertyChannel.put(PM_SYNC_SERVICES, new Boolean(false));
	propertyChannel.put(PM_SYNC_DATATYPES, new Boolean(false));
	propertyChannel.put(PM_OVERWRITE, new Boolean(false));
	propertyChannel.put(PM_CGI, new Boolean(false));
	propertyChannel.put(PM_CGI_ASYNC, new Boolean(false));
	propertyChannel.put(PM_ASYNC, new Boolean(false));

	propertyChannel.put(PM_PERL_INSTALL_DIR, getPrefValue(PM_PERL_INSTALL_DIR, ""));
	propertyChannel.put(PM_PERL_LIB_1, getPrefValue(PM_PERL_LIB_1, ""));
	propertyChannel.put(PM_PERL_LIB_2, getPrefValue(PM_PERL_LIB_2, ""));
	propertyChannel.put(PM_SCRIPTS_INSTALL_DIR, getPrefValue(PM_SCRIPTS_INSTALL_DIR, ""));
	
	propertyChannel.addPropertyChangeListener(new PropertyChangeListener() {
	    public void propertyChange(PropertyChangeEvent e) {
		if (e.getPropertyName().equals(PM_ERROR)) {
		    error(e.getNewValue());
		    console.setText("Error:" + e.getNewValue().toString()
			    + "\n");
		    return;
		}
		if (e.getPropertyName().equals(PM_WARNING)) {
		    JOptionPane.showMessageDialog(null, e.getNewValue(),
			    "Warning", JOptionPane.WARNING_MESSAGE);
		    console.setText("Warning:" + e.getNewValue().toString()
			    + "\n");
		    return;
		}
		if (e.getPropertyName().equals(PM_INFORMATION)) {
		   /* JOptionPane.showMessageDialog(null, e.getNewValue(),
			    "Information", JOptionPane.INFORMATION_MESSAGE);*/
		    console.setText("Information:" + e.getNewValue().toString()
			    + "\n");
		    return;
		}
		if (e.getPropertyName().equals(PM_FILE_ACTION_SAVE)) {
		    doSaveFile();
		    return;
		}
		if (e.getPropertyName().equals(PM_FILE_ACTION_OPEN)) {
		    doOpenFile(e.getNewValue().toString());
		    return;
		}

		if (e.getPropertyName().equals(PM_FILE_ACTION_CLOSE)) {
		    doCloseFile();
		    return;
		}
	    }
	});
	
	registryModel = createRegistryModel();
	registryModel.addNotificationListener(new NotificationListener() {
	    public void notified(NotificationEvent e) {
		if (e.getType() == Notifier.AUTHORITIES_UPDATED) {
		    if (enable_moses_actions && !getPropertyChannel().getBoolean(PM_SYNC_SERVICES, false)) {
			getPropertyChannel().put(PM_SYNC_SERVICES, true);
			onUpdateServiceCache();
			return;
		    }
		}
		
		if (enable_moses_actions && e.getType() == Notifier.DATA_TYPES_UPDATED) {
		    if (!getPropertyChannel().getBoolean(PM_SYNC_DATATYPES, false)) {
			getPropertyChannel().put(PM_SYNC_DATATYPES, true);
		    	onUpdateDatatypeCache();
		    	return;
		    }
		}
	    }});
	if (pComponent != null)
	    return pComponent;
	pComponent = new JPanel(new GridBagLayout(), true);

	// console panel
	console = new CommonConsole();
	console.setAppendMode(true);
	console.setVerboseMode(true);

	// set up the splitpane
	JSplitPane vsplit = vSplit(getServicesSelectionPanel(), getMoSeSPanel(), 0.8);
	JSplitPane vsplit2 = vSplit(getEditorPanel(), console, 0.8);
	JSplitPane splitPane = hSplit(vsplit, vsplit2, 0.1);
	// add the components
	SwingUtils.addComponent(pComponent, splitPane, 0, 0, 1, 1, BOTH, NWEST, 1.0, 1.0);
	return pComponent;
    }

    /*
     * method for setting what we think is the Perl-MoSeS home
     * directory
     */
    private void determineMoSeSDirectory() {
	try {
	    // set PM_FILE_LAST_DIRECTORY
	    File f = new File(System.getProperty("user.home"),"Perl-MoSeS");
	    if (f.exists() && f.isDirectory() && f.canRead() && f.canWrite())
		propertyChannel.put(PM_FILE_LAST_DIRECTORY, f.getPath());
	} catch (Exception e) {
	    
	}
	
    }

    private final PropertyChannel getPropertyChannel() {
	return this.propertyChannel;
    }
    private JPanel getMoSeSPanel() {
	
	JPanel p = new JPanel (new GridBagLayout());
	p.setBorder (createFatBorder
		     ("Perl-MoSeS",
		      GraphColours.getColour ("cadetblue", Color.blue)));

	JPanel sPanel = createTitledPanel("Local User Config");
	if (!enable_moses_actions) {
	       scriptsBtn = createButton(
		    "Confirm values are correct",
		    "Check whether or not your local user config is correct",
		    KeyEvent.VK_R, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			    if (!enable_moses_actions) {
				    if (propertyChannel.getString(PM_SCRIPTS_INSTALL_DIR) != null 
					    && !propertyChannel.getString(PM_SCRIPTS_INSTALL_DIR).endsWith("/"))
					propertyChannel.put(
						PM_SCRIPTS_INSTALL_DIR, 
						propertyChannel.getString(PM_SCRIPTS_INSTALL_DIR) + "/");
				    // check for the scripts
				    user_check_install();
				    if (enable_moses_actions) {
					// we are good to go! enable buttons
					propertyChannel.fire(PM_INFORMATION, "Your local configuration was successful!");
					if (enable_moses_actions && !getPropertyChannel().getBoolean(PM_SYNC_SERVICES, false)) {
					    propertyChannel.fire(PM_INFORMATION, "Setting up Perl-MoSeS services!");
					    getPropertyChannel().put(PM_SYNC_SERVICES, true);
					    onUpdateServiceCache();
					}
					if (!getPropertyChannel().getBoolean(PM_SYNC_DATATYPES, false)) {
					    propertyChannel.fire(PM_INFORMATION, "Setting up Perl-MoSeS datatypes!");
					    getPropertyChannel().put(PM_SYNC_DATATYPES, true);
					    onUpdateDatatypeCache();
					}
					scriptsBtn.setEnabled(!enable_moses_actions);
				    } else {
					// alert them that this directory didnt
					// contain anything useful
					
					propertyChannel.fire(PM_ERROR, 
						"Sorry, your configuration resulted in error.\n" +
						"Please review your values and try again!");
				    }
				}
			}
		    });

	    JTextFieldWithHistory lib1 = createText (null, PM_PERL_LIB_1, PM_PERL_LIB_1);
	    lib1.setText(propertyChannel.getString(PM_PERL_LIB_1));
	    JTextFieldWithHistory lib2 = createText (null, PM_PERL_LIB_2, PM_PERL_LIB_2);
	    lib2.setText(propertyChannel.getString(PM_PERL_LIB_2));
	    JTextFieldWithHistory install_path = createText (null, PM_SCRIPTS_INSTALL_DIR, PM_SCRIPTS_INSTALL_DIR);
	    install_path.setText(propertyChannel.getString(PM_SCRIPTS_INSTALL_DIR));
	    JTextFieldWithHistory perl_install_path = createText(null, PM_PERL_INSTALL_DIR, PM_PERL_INSTALL_DIR);
	    perl_install_path.setText(propertyChannel.getString(PM_PERL_INSTALL_DIR));
	    
	    SwingUtils.addComponent(sPanel, new JLabel("Perl Path: "),        0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	    SwingUtils.addComponent(sPanel, perl_install_path,                1, 0, 3, 1, HORI, NWEST, 1.0, 0.0);
	    SwingUtils.addComponent(sPanel, new JLabel("PERL5LIB dir: "),     0, 1, 1, 1, NONE, NWEST, 0.0, 0.0);
	    SwingUtils.addComponent(sPanel, lib1,                             1, 1, 3, 1, HORI, NWEST, 1.0, 0.0);
	    SwingUtils.addComponent(sPanel, new JLabel("PERL5LIB dir (2): "), 0, 2, 1, 1, NONE, NWEST, 0.0, 0.0);
	    SwingUtils.addComponent(sPanel, lib2,                             1, 2, 3, 1, HORI, NWEST, 1.0, 0.0);
	    SwingUtils.addComponent(sPanel, new JLabel("MoSeS Scripts dir: "),0, 3, 1, 1, NONE, NWEST, 0.0, 0.0);
	    SwingUtils.addComponent(sPanel, install_path,                     1, 3, 3, 1, HORI, NWEST, 1.0, 0.0);
	    SwingUtils.addComponent(sPanel, scriptsBtn,                       1, 4, 3, 1, REMAINDER, NWEST, 1.0, 0.0);
	} else {
	    // we dont show the config sub panel
	    // remove the PERL5LIB, ScriptsInstallDir and PerlInstallDir so that they dont cause problems
	    propertyChannel.put(PM_PERL_INSTALL_DIR, "");
	    propertyChannel.put(PM_PERL_LIB_1, "");
	    propertyChannel.put(PM_PERL_LIB_2, "");
	    propertyChannel.put(PM_SCRIPTS_INSTALL_DIR, "");
	}
	
	JPanel bPanel = createTitledPanel("Generate");
	JCheckBox genOverwrite = createActionBox("Overwrite Existing Code",
		PM_OVERWRITE);
	
	generateBtn = createButton("Generate Skeletons",
		"Generate skeletons from selected services", KeyEvent.VK_G,
		new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			if (propertyChannel
				.get(DashboardProperties.DP_SEL_AUTHORITIES) == null
				&& propertyChannel
					.get(DashboardProperties.DP_SEL_SERVICES) == null) {
			    propertyChannel
				    .put(PerlMoSeSPanel.PM_ERROR,
					    "There were no services selected. Generating of skeletons failed.");
			    return;
			}
			generateBtn.setEnabled(false);
			propertyChannel.fire(DP_STATUS_MSG,
				"Processing MoSeS skeletons...");
			if (propertyChannel
				.get(DashboardProperties.DP_SEL_SERVICES) != null) {
			    propertyChannel.put(
				    PM_GENERATING_SPECIFIC_SERVICES,
				    new Boolean(true));
			    onGenerateSpecificService();

			}

			if (propertyChannel
				.get(DashboardProperties.DP_SEL_AUTHORITIES) != null) {
			    propertyChannel.put(PM_GENERATING_BY_AUTHORITY,
				    new Boolean(true));
			    onGenerateFromAuthority();
			}
			propertyChannel.fire(DP_STATUS_MSG,
				"Processing MoSeS skeletons completed");
		    }
		});
	generateBtn.setEnabled(enable_moses_actions);
	ButtonGroup group = new ButtonGroup();
	JRadioButton genCgi, genCgiAsync, genSoap, genAsync;
	group.add (genCgi      = createHowToButton ("Generate CGI Service", PM_CGI));
	group.add (genCgiAsync = createHowToButton ("Generate ASYNC CGI Service", PM_CGI_ASYNC));
	group.add (genSoap     = createHowToButton ("Generate SOAP Service", PM_SOAP));
	group.add (genAsync    = createHowToButton ("Generate ASYNC SOAP Service", PM_ASYNC));
	
	SwingUtils.addComponent(bPanel, genOverwrite, 0, 0, 1, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(bPanel, genCgi,       0, 1, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(bPanel, genCgiAsync,  0, 2, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(bPanel, genSoap,      0, 3, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(bPanel, genAsync,     0, 4, 2, 1, NONE, NWEST, 0.0, 0.0);
	SwingUtils.addComponent(bPanel, generateBtn,  0, 5, 2, 1, HORI, NWEST, 0.0, 0.0);

	// add to the main panel
	int count = 0;
	if (!enable_moses_actions)
	    SwingUtils.addComponent(p, sPanel, 0, count++, 2, 1, HORI, NWEST, 0.0, 0.0);    
	SwingUtils.addComponent(p, bPanel, 0, count++,     2, 1, HORI, NWEST, 0.0, 0.0);
		
	return p;

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.biomoby.service.dashboard.AbstractPanel#getName()
     */
    @Override
    public String getName() {
	return "Perl-MoSeS Generator";
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.biomoby.service.dashboard.AbstractPanel#getDescription()
     */
    public String getDescription() {
	return "A Panel for those wishing to create Perl based Biomoby Web Services.";
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.biomoby.service.dashboard.AbstractPanel#loadOnlyOnDemand()
     */
    public boolean loadOnlyOnDemand() {
	return true;
    }

    protected void loadIcons() {
	if (openFileIconDis == null)
	    openFileIconDis = loadIcon("images/smallEdit_dis.gif");
	if (openFileIcon == null)
	    openFileIcon = loadIcon("images/smallEdit.gif");
	if (saveFileIcon == null)
	    saveFileIcon = loadIcon("images/smallSave.gif");
	if (saveFileIconDis == null)
	    saveFileIconDis = loadIcon("images/smallSave_dis.gif");
	if (closeFileIcon == null)
	    closeFileIcon = loadIcon("images/smallClear.gif");
	if (closeFileIconDis == null)
	    closeFileIconDis = loadIcon("images/smallClear_dis.gif");
	if (zoomInIcon == null)
	    zoomInIcon = loadIcon("images/smallZoomIn.gif");
	if (zoomInIconDis == null)
	    zoomInIconDis = loadIcon("images/smallZoomIn_dis.gif");

	if (zoomOutIcon == null)
	    zoomOutIcon = loadIcon("images/smallZoomOut.gif");
	if (zoomOutIconDis == null)
	    zoomOutIconDis = loadIcon("images/smallZoomOut_dis.gif");

    }

    private JPanel getEditorPanel() {	
	JPanel p = new JPanel();
	BorderLayout thisLayout = new BorderLayout();
	p.setLayout(thisLayout);
	Border blackline = BorderFactory.createLineBorder(Color.black);
	CompoundBorder compoundBorder = BorderFactory.createCompoundBorder(
		BorderFactory.createTitledBorder(blackline,
			"Perl-MoSeS: Editor"), BorderFactory
			.createEmptyBorder(5, 5, 5, 5));
	p.setBorder(compoundBorder);
	
	JPanel footer = new JPanel();
	footer.setLayout(new BorderLayout());
	currentlyEditing = new JLabel("");
	currentPosition = new JLabel("");
	footer.add(currentlyEditing, BorderLayout.WEST);
	footer.add(currentPosition, BorderLayout.EAST);
	
	editorTextPane = new JTextPane();
	editorTextPane.addCaretListener(new CaretListener(){
	    public void caretUpdate(CaretEvent e) {
		int dot = e.getDot();
		//get current line
		int line = ((JTextPane)e.getSource()).getDocument().getDefaultRootElement().getElementIndex(dot);
		// get the position on the line
		int pos = dot - ((JTextPane)e.getSource()).getDocument().getDefaultRootElement().getElement(line).getStartOffset();
		//System.out.println("line: "+line + " column: "+ pos);
		line++; pos++;
		currentPosition.setText(line + ":"+ pos);
		
	    }});
	
	EditorKit editorKit = new StyledEditorKit() {
	    private static final long serialVersionUID = 1L;
	    public Document createDefaultDocument() {
		return new PerlMoSeSSyntaxDocument();
	    }
	};
	
	editorTextPane.setEditorKitForContentType("text/perl", editorKit);
	editorTextPane.setContentType("text/perl");
	editorTextPane.setEditable(false);

	KeyStroke key = KeyStroke.getKeyStroke(KeyEvent.VK_S, Event.CTRL_MASK);
	editorTextPane.registerKeyboardAction(
		new ActionListener() {
		    public void actionPerformed(ActionEvent e) {
			propertyChannel.fire(PM_FILE_ACTION_SAVE, "");
		    }
		},
		key, 
		JComponent.WHEN_IN_FOCUSED_WINDOW);
		
	JScrollPane scrollPane = new JScrollPane(editorTextPane);
	
	p.add(getFontChooser(), BorderLayout.PAGE_START);
	p.add(footer, BorderLayout.PAGE_END);
	p.add(scrollPane, BorderLayout.CENTER);
	// set the default font
	Font f = new Font(fonts.getSelectedItem().toString(), editorTextPane.getFont().getStyle(), editorTextPane.getFont().getSize());
	editorTextPane.setFont(f);
	return p;
    }

    protected static void moveCursor(int pos) {
	if (editorTextPane != null && editorTextPane.isEnabled() && pos >= 0 && pos < editorTextPane.getText().length())
	    editorTextPane.setCaretPosition(pos);
    }
    private JComponent getFontChooser() {
	JLabel label = new JLabel("Font:");
	JToolBar toolbar = new JToolBar("Editor");
	toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS));
	
	// get all the fonts available
	GraphicsEnvironment env =
	    GraphicsEnvironment.getLocalGraphicsEnvironment();
	String[] fontFamilies = env.getAvailableFontFamilyNames();
	int index = 0;
	String saved_font = getPrefValue(PM_EDITOR_FONT, "");
	if (saved_font.equals("")) {
	    saved_font = new Font(null).getName();
	    setPrefValue(PM_EDITOR_FONT, saved_font);
	}
	if (saved_font != null && !saved_font.trim().equals("")) {
	    index = 0;
	    for (String f:fontFamilies) {
		if (f.equals(saved_font))
		    break;
		else
		    index++;
	    }
	    Font oldFont = editorTextPane.getFont();
	    Font font = new Font(saved_font, oldFont.getStyle(), oldFont.getSize());
	    editorTextPane.setFont(font);
	}
	
	// set up the combobox
	fonts = new JComboBox(fontFamilies);
	fonts.setSelectedIndex(index >= fontFamilies.length ? 0 : index);
	// add a listener
	fonts.addItemListener(new ItemListener(){
	    public void itemStateChanged(ItemEvent e) {
		String newFont = (String)e.getItem();
		Font oldFont = editorTextPane.getFont();
		setPrefValue(PM_EDITOR_FONT, newFont);
		Font font = new Font(newFont, oldFont.getStyle(), oldFont.getSize());
		editorTextPane.setFont(font);
	    }
	    
	});
	fonts.setPreferredSize(fonts.getMinimumSize());
	// create a zoom in button
	JButton zoomInButton = AbstractPanel.createButton
	    ("",
	     "Increase font in the editor",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     Font font = editorTextPane.getFont();
		     editorTextPane.setFont (font.deriveFont (font.getSize2D() + 1));
		 }
	     });
	zoomInButton.setIcon (zoomInIcon);
	zoomInButton.setDisabledIcon (zoomInIconDis);
	SwingUtils.compact (zoomInButton);

	// create a zoom out button
	JButton zoomOutButton = AbstractPanel.createButton
	    ("",
	     "Decrease font in the editor",
	     -1,
	     new ActionListener() {
		 public void actionPerformed (ActionEvent e) {
		     Font font = editorTextPane.getFont();
		     editorTextPane.setFont (font.deriveFont (Math.max (1, font.getSize2D() - 1)));
		 }
	     });
	zoomOutButton.setIcon (zoomOutIcon);
	zoomOutButton.setDisabledIcon (zoomOutIconDis);
	SwingUtils.compact (zoomOutButton);
	
	//JPanel ePanel = createTitledPanel("File");
	
	editorOpenButton = new JButton("Open", openFileIcon);
	editorOpenButton.setIcon(openFileIcon);
	editorOpenButton.setDisabledIcon(openFileIconDis);
	editorOpenButton.setVerticalTextPosition(AbstractButton.BOTTOM);
	editorOpenButton.setHorizontalTextPosition(AbstractButton.CENTER);
	editorOpenButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		String path = propertyChannel.getString(PM_FILE_LAST_DIRECTORY);
		JFileChooser openFile;
		// provide a history
		if (path == null)
		    openFile = new JFileChooser();
		else 
		    openFile = new JFileChooser(path);
		
		openFile.setDialogTitle("Open a Perl Script:");
		openFile.setApproveButtonText("Open script");
		openFile.setFileSelectionMode(JFileChooser.FILES_ONLY);
		openFile.addChoosableFileFilter(new FileFilter() {
		    @Override
		    public boolean accept(File f) {
			if (f.isDirectory())
			    return true;
			String extension = FilenameUtils.getExtension(f.getName());
			return ("pm".equalsIgnoreCase(extension)
				|| "pl".equalsIgnoreCase(extension) 
				|| "cgi".equalsIgnoreCase(extension));
		    }
		    @Override
		    public String getDescription() {
			return "Perl scripts";
		    }
		});
		int doOpen = openFile.showOpenDialog(null);
		if (doOpen == JFileChooser.APPROVE_OPTION) {
		    propertyChannel.fire(PM_FILE_ACTION_OPEN, openFile.getSelectedFile().getPath());
		}
	    }
	});
	
	editorSaveButton = new JButton("Save", saveFileIcon);
	editorSaveButton.setIcon(saveFileIcon);
	editorSaveButton.setDisabledIcon(saveFileIconDis);
	editorSaveButton.setVerticalTextPosition(AbstractButton.BOTTOM);
	editorSaveButton.setHorizontalTextPosition(AbstractButton.CENTER);
	editorSaveButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		propertyChannel.fire(PM_FILE_ACTION_SAVE, "");
	    }
	});

	editorCloseButton = new JButton("Close", closeFileIcon);
	editorCloseButton.setIcon(closeFileIcon);
	editorCloseButton.setDisabledIcon(closeFileIconDis);
	editorCloseButton.setVerticalTextPosition(AbstractButton.BOTTOM);
	editorCloseButton.setHorizontalTextPosition(AbstractButton.CENTER);
	editorCloseButton.addActionListener(new ActionListener() {
	    public void actionPerformed(ActionEvent e) {
		propertyChannel.fire(PM_FILE_ACTION_CLOSE, "");
	    }
	});
	// initially open is enabled, close and save are not
	editorOpenButton.setEnabled(true);
	editorCloseButton.setEnabled(false);
	editorSaveButton.setEnabled(false);
			
	// add the font chooser, file and zoom buttons to the panel
	toolbar.add(editorOpenButton);
	toolbar.add(editorSaveButton);
	toolbar.add(editorCloseButton);
	toolbar.addSeparator();
	toolbar.add(label);
	toolbar.addSeparator();
	toolbar.add(fonts);
	toolbar.addSeparator();
	toolbar.add(zoomInButton);
	toolbar.add(zoomOutButton);
	toolbar.add(Box.createGlue());
	Box b = Box.createHorizontalBox();
	SwingUtils.addComponent(b, toolbar,          0, 0, 1, 1, NONE, WEST, 0.0, 0.0);
	return b;
    }

    private JRadioButton createHowToButton (String title, String howTo) {
	JRadioButton radio = new JRadioButton (title);
	radio.setActionCommand (howTo);
	radio.addActionListener (howToListener);
	if (!howTo.equals(PM_CGI) && !howTo.equals(PM_ASYNC)) {
	    radio.setSelected (true);
	    radio.setEnabled (true);
	    propertyChannel.put (PM_CGI, false);
	    propertyChannel.put (PM_CGI_ASYNC, false);
	    propertyChannel.put (PM_ASYNC, false);
	}
	return radio;
    }
    private ActionListener howToListener = new ActionListener() {
	    public void actionPerformed (ActionEvent e) {
		String howTo = e.getActionCommand();
		propertyChannel.put (PM_CGI, howTo.equals(PM_CGI));
		propertyChannel.put (PM_CGI_ASYNC, howTo.equals(PM_CGI_ASYNC));
		propertyChannel.put (PM_ASYNC, howTo.equals(PM_ASYNC));
		propertyChannel.put (PM_SOAP, howTo.equals(PM_SOAP));
	    }
	};
    private 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;
    }

    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());
	    }
	});
    }

    private void onUpdateDatatypeCache() {
	final SwingWorker worker = new SwingWorker() {
	    MobyException exception = null;
	    boolean updateFailed = false;
	    
	    public Object construct() {
		if (generateBtn != null)
		    generateBtn.setEnabled(false);
		propertyChannel.fire(DP_STATUS_MSG,
			"Synchronizing Perl-MoSeS Datatype Cache...");
		console.setEnabledAppendMode(false);
		ArrayList<String> command = new ArrayList<String>();
		// update the moses cache first ...
		Process p;
		StringBuffer inputstream = new StringBuffer();
		StringBuffer errorstream = new StringBuffer();
		try {
		    for (int x = 0; x < 2; x++) {
        		    inputstream = new StringBuffer();
        		    errorstream = new StringBuffer();
        		    // empty the command list
        		    command = new ArrayList<String>();
        		    command.addAll(generatePerlPrefix());
        		    String s = (command.size() > 0 ? command.remove(command.size()-1) : "") ;
        		    
        		    // construct the command
        		    command.add(is_windows_pc ?  "moses-generate-datatypes.bat"
        			    : s + "moses-generate-datatypes.pl");
        		    command.add("-R");
        		    command
        			    .add(propertyChannel
        				    .getString(DashboardProperties.DP_REGISTRY_ENDPOINT));
        		    command.add(updateFailed ? "-f" : "-u");
        		    // place command into com
        		    String[] com = command.toArray(new String[] {});
        
        		    // execute the command
        		    p = Runtime.getRuntime().exec(com);
        		    BufferedReader br = new BufferedReader(
        			    new InputStreamReader(p.getInputStream()));
        		    String line = null;
        		    while ((line = br.readLine()) != null) {
        			inputstream.append(line
        				+ System.getProperty("line.separator"));
        		    }
        		    BufferedReader errorBr = new BufferedReader(
        			    new InputStreamReader(p.getErrorStream()));
        		    line = null;
        		    while ((line = errorBr.readLine()) != null) {
        			errorstream.append(line
        				+ System.getProperty("line.separator"));
        		    }
        		    p.waitFor();
        		    if (
        			    inputstream.toString().indexOf(DATATYPE_CACHE_DOESNT_EXIST) > 0 || 
        			    errorstream.toString().indexOf(DATATYPE_CACHE_DOESNT_EXIST) > 0) {
        			updateFailed = true;
        		    } else {
        			break;
        		    }
		    }
		} catch (IOException ioe) {
		    exception = new MobyException(
			    "There was a problem synchronizing the datatype cache(x001):\n" + ioe.getMessage(),
			    ioe);
		} catch (InterruptedException ie) {
		    exception = new MobyException(
			    "There was a problem synchronizing the datatype cache(x002)\n: + ie.getMessage()",
			    ie);
		}
		console.setText(inputstream.toString());
		console.setText(errorstream.toString());
		return null; // not used here
	    }

	    // runs on the event-dispatching thread
	    public void finished() {
		if (exception != null)
		    error("Problem executing 'moses-generate-datatypes.pl'",
			    exception);
		console.setEnabledAppendMode(true);
		propertyChannel
			.fire(DP_STATUS_MSG,
				"Synchronization of Perl-MoSeS Datatype Cache Complete");
		propertyChannel.put(PM_SYNC_DATATYPES, new Boolean(false));
				
		if (!(propertyChannel.getBoolean(PM_SYNC_DATATYPES, false) || propertyChannel
			.getBoolean(PM_SYNC_SERVICES, false))) {
		    generateBtn.setEnabled(true);
		}
		
	    }
	};
	worker.start();
    }

    private void onUpdateServiceCache() {
	final SwingWorker worker = new SwingWorker() {
	    MobyException exception = null;
	    boolean updateFailed = false;
	    
	    public Object construct() {
		propertyChannel.fire(DP_STATUS_MSG,
			"Synchronizing Perl-MoSeS Service Cache...");
		generateBtn.setEnabled(false);
		console.setEnabledAppendMode(false);
		ArrayList<String> command = new ArrayList<String>();
		// update the moses cache first ...
		Process p;
		StringBuffer inputstream = new StringBuffer();
		StringBuffer errorstream = new StringBuffer();
		try {
		    for (int x = 0; x < 2; x++) {
        		    inputstream = new StringBuffer();
        		    errorstream = new StringBuffer();
        		    // empty the command list
        		    command = new ArrayList<String>();
        		    command.addAll(generatePerlPrefix());
        		    String s = (command.size() > 0 ? command.remove(command.size()-1) : "") ;
        		    // construct the command
        		    command.add(is_windows_pc ?  "moses-generate-services.bat"
        			    : s +  "moses-generate-services.pl");
        		    command.add("-R");
        		    command
        			    .add(propertyChannel
        				    .getString(DashboardProperties.DP_REGISTRY_ENDPOINT));
        		    command.add(updateFailed ? "-f" : "-u");
        		    // place command into com
        		    String[] com = command.toArray(new String[] {});
        
        		    // execute the command
        		    p = Runtime.getRuntime().exec(com);
        		    BufferedReader br = new BufferedReader(
        			    new InputStreamReader(p.getInputStream()));
        		    String line = null;
        		    while ((line = br.readLine()) != null) {
        			inputstream.append(line
        				+ System.getProperty("line.separator"));
        		    }
        		    BufferedReader errorBr = new BufferedReader(
        			    new InputStreamReader(p.getErrorStream()));
        		    line = null;
        		    while ((line = errorBr.readLine()) != null) {
        			errorstream.append(line
        				+ System.getProperty("line.separator"));
        		    }
        		    p.waitFor();
        		    if (
        			    inputstream.toString().indexOf(SERVICE_CACHE_DOESNT_EXIST) > 0 || 
        			    errorstream.toString().indexOf(SERVICE_CACHE_DOESNT_EXIST) > 0) {
        			updateFailed = true;
        		    } else {
        			break;
        		    }
		    }
		} catch (IOException ioe) {
		    exception = new MobyException(
			    "There was a problem synchronizing the service cache(x001):\n" + ioe.getMessage(),
			    ioe);
		} catch (InterruptedException ie) {
		    exception = new MobyException(
			    "There was a problem synchronizing the service cache(x002):\n" + ie.getMessage(),
			    ie);
		}
		console.setText(inputstream.toString());
		console.setText(errorstream.toString());
		return null; // not used here
	    }

	    // runs on the event-dispatching thread
	    public void finished() {
		if (exception != null)
		    error("Problem executing 'moses-generate-services.pl'",
			    exception);
		console.setEnabledAppendMode(true);
		propertyChannel.put(PM_SYNC_SERVICES, new Boolean(false));
		propertyChannel.fire(DP_STATUS_MSG,
			"Synchronization of Perl-MoSeS Service Cache Complete");
		if (!(
			propertyChannel.getBoolean(PM_SYNC_DATATYPES, false) 
			|| 
			propertyChannel.getBoolean(PM_SYNC_SERVICES, false))) {
		    generateBtn.setEnabled(true);
		}
	    }
	};
	worker.start();
    }

    private void onGenerateFromAuthority() {
	final SwingWorker worker = new SwingWorker() {
	    MobyException exception = null;
	    @SuppressWarnings("unchecked")
	    public Object construct() {
		Vector<String> services = (Vector<String>) propertyChannel
			.get(DashboardProperties.DP_SEL_AUTHORITIES);
		for (String authority : services) {
		    propertyChannel.fire(DP_STATUS_MSG,
			    "Processing service provider '" + authority
				    + "' ...");
		    console.setEnabledAppendMode(false);
		    ArrayList<String> command = new ArrayList<String>();
		    // update the moses cache first ...
		    command = new ArrayList<String>();
		    command.addAll(generatePerlPrefix());
		    String s = (command.size() > 0 ? command.remove(command.size()-1) : "") ;
		    Process p;
		    try {
			// construct the command		
			command.add(is_windows_pc ?  "moses-generate-services.bat"
				:  s + "moses-generate-services.pl");
			if (Boolean.parseBoolean(propertyChannel
				.getString(PM_OVERWRITE)))
			    command.add("-F");
			command.add("-R");
			command
				.add(propertyChannel
					.getString(DashboardProperties.DP_REGISTRY_ENDPOINT));
			if (Boolean.parseBoolean(propertyChannel
				.getString(PM_CGI))){
			    command.add("-c");
			} else if (Boolean.parseBoolean(propertyChannel
				.getString(PM_ASYNC))){
			    command.add("-A");
			} else if (Boolean.parseBoolean(propertyChannel
				.getString(PM_CGI_ASYNC))){
			    command.add("-C");
			} 
			command.add(authority);
			// place command into com
			String[] com = command.toArray(new String[] {});
			p = Runtime.getRuntime().exec(com);
			BufferedReader br = new BufferedReader(
				new InputStreamReader(p.getInputStream()));
			String line = null;
			while ((line = br.readLine()) != null) {
			    console.setText(line
				    + System.getProperty("line.separator"));
			}
			BufferedReader errorBr = new BufferedReader(
				new InputStreamReader(p.getErrorStream()));
			line = null;
			while ((line = errorBr.readLine()) != null) {
			    console.setText(line
				    + System.getProperty("line.separator"));
			}
			p.waitFor();
		    } catch (IOException ioe) {
			exception = new MobyException(
				"There was a problem generating MoSeS skeletons for '"
					+ authority + "'(x001):\n" + ioe.getMessage(), ioe);
		    } catch (InterruptedException ie) {
			exception = new MobyException(
				"There was a problem generating MoSeS skeletons for '"
					+ authority + "'(x002):\n" + ie.getMessage(), ie);
		    }
		}
		return null; // not used here
	    }

	    // runs on the event-dispatching thread
	    public void finished() {
		if (exception != null)
		    error("Problem executing 'moses-generate-services.pl'",
			    exception);
		console.setEnabledAppendMode(true);
		propertyChannel.put(PM_GENERATING_BY_AUTHORITY, new Boolean(
			false));
		
		if (!(propertyChannel.getBoolean(PM_GENERATING_BY_AUTHORITY, false)|| propertyChannel.getBoolean(PM_GENERATING_SPECIFIC_SERVICES, false))) {
		    generateBtn.setEnabled(true);
		    propertyChannel
			    .fire(DP_STATUS_MSG,
				    "Generating Perl-MoSeS service skeletons complete!");
		    propertyChannel.fire(PM_INFORMATION,
			        "Processing of selected service completed! " +
		    		"Any errors encountered should be reported above.");
		}
	    }
	};
	worker.start();
    }

    private void onGenerateSpecificService() {
	final SwingWorker worker = new SwingWorker() {
	    MobyException exception = null;

	    @SuppressWarnings("unchecked")
	    public Object construct() {
		Vector<String> services = (Vector<String>) propertyChannel
			.get(DashboardProperties.DP_SEL_SERVICES);
		for (String str : services) {
		    String[] parts = str.split("/", 2);
		    if (parts.length != 2) {
			continue;
		    }
		    String auth = parts[1];
		    String name = parts[0];
		    propertyChannel.fire(DP_STATUS_MSG, "Processing service '"
			    + name + "' provided by '" + auth + "' ...");
		    console.setEnabledAppendMode(false);
		    ArrayList<String> command = new ArrayList<String>();
		    command.addAll(generatePerlPrefix());
		    String s = (command.size() > 0 ? command.remove(command.size()-1) : "") ;
		    Process p;
		    try {
			// construct the command
			command.add(is_windows_pc ? "moses-generate-services.bat"
				: s + "moses-generate-services.pl");
			if (Boolean.parseBoolean(propertyChannel
				.getString(PM_OVERWRITE)))
			    command.add("-F");
			command.add("-R");
			command
				.add(propertyChannel
					.getString(DashboardProperties.DP_REGISTRY_ENDPOINT));
			if (Boolean.parseBoolean(propertyChannel
				.getString(PM_CGI))){
			    command.add("-c");
			} else if (Boolean.parseBoolean(propertyChannel
				.getString(PM_ASYNC))){
			    command.add("-A");
			} else if (Boolean.parseBoolean(propertyChannel
				.getString(PM_CGI_ASYNC))){
			    command.add("-C");
			}
			command.add(auth);
			command.add(name);
			// place command into com
			String[] com = command.toArray(new String[] {});
			p = Runtime.getRuntime().exec(com);
			BufferedReader br = new BufferedReader(
				new InputStreamReader(p.getInputStream()));
			String line = null;
			while ((line = br.readLine()) != null) {
			    console.setText(line
				    + System.getProperty("line.separator"));
			}
			BufferedReader errorBr = new BufferedReader(
				new InputStreamReader(p.getErrorStream()));
			line = null;
			while ((line = errorBr.readLine()) != null) {
			    console.setText(line
				    + System.getProperty("line.separator"));
			}
			p.waitFor();
		    } catch (IOException ioe) {
			exception = new MobyException(
				"There was a problem generating service skeleton for service '"
					+ name + "' provided by '" + auth
					+ "' (x001):\n" + ioe.getMessage(), ioe);
		    } catch (InterruptedException ie) {
			exception = new MobyException(
				"There was a problem generating service skeleton for service '"
					+ name + "' provided by '" + auth
					+ "' (x002):\n" + ie.getMessage(), ie);
		    }
		}
		return null; // not used here
	    }

	    // runs on the event-dispatching thread
	    public void finished() {
		if (exception != null)
		    error("Problem executing 'moses-generate-services.pl'",
			    exception);
		console.setEnabledAppendMode(true);
		propertyChannel.put(PM_GENERATING_SPECIFIC_SERVICES,
			new Boolean(false));

		if (!(propertyChannel.getBoolean(PM_GENERATING_BY_AUTHORITY, false)|| propertyChannel.getBoolean(PM_GENERATING_SPECIFIC_SERVICES, false))) {
		    generateBtn.setEnabled(true);
		    propertyChannel
			    .fire(DP_STATUS_MSG,
				    "Generating Perl-MoSeS service skeletons complete!");
		    propertyChannel.fire(PM_INFORMATION,
			        "Processing of selected service completed! " +
		    		"Any errors encountered should be reported above.");
		}
	    }
	};
	worker.start();
    }

    private void doOpenFile(String name) {
	File f = new File(name);
	if (f.isDirectory()) {
	    error("The file '" + f.getAbsolutePath() + "' is not a file, but a directory.\nPlease choose a file!");
	    return;
	}
	if (!f.canRead()) {
	    error("The file '" + f.getAbsolutePath() + "' is not readable ...");
	    return;
	}
	if (!f.canWrite()) {
	    error("The file '" + f.getAbsolutePath() + "' is not writable ...");
	    return;
	}
	// can read & write the file
	try {
	    StringBuilder sb = new StringBuilder();
	    BufferedReader br = new BufferedReader(new FileReader(f));
	    String newline = System.getProperty("line.separator");
	    String line = null;
	    while ((line = br.readLine()) != null) {
		sb.append(line + newline);
	    }
	    propertyChannel.put(PM_FILE_CURRENT_CHECKSUM, getMD5Checksum(sb.toString()));
	    editorTextPane.setText(sb.toString());
	    currentlyEditing.setText("Editing: " + f.getAbsolutePath());
	    currentPosition.setText("");
	    editorTextPane.setCaretPosition(0);
	    editorTextPane.setEditable(true);
	    editorCloseButton.setEnabled(true);
	    editorSaveButton.setEnabled(true);
	    editorOpenButton.setEnabled(false);
	} catch (Exception e) {
	    error("There were problems opening the file!",e);
	}
	fonts.setSelectedItem(editorTextPane.getFont().getFamily());
	propertyChannel.put(PM_FILE_CURRENT, name);
	propertyChannel.put(PM_FILE_LAST_DIRECTORY, f.getParent());
	
    }
    private void doCloseFile() {
	String text = editorTextPane.getText();
	String checksum = getMD5Checksum(text);
	if (!checksum.equals(propertyChannel.getString(PM_FILE_CURRENT_CHECKSUM))) {
	    // prompt to save
	    if (JOptionPane.showConfirmDialog(null, "File has changed. Shall we save it?", "Save the file?", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
		doSaveFile();
	    }
	}
	editorTextPane.setText("");
	currentlyEditing.setText("");
	currentPosition.setText("");
	editorTextPane.setEditable(false);
	editorCloseButton.setEnabled(false);
	editorSaveButton.setEnabled(false);
	editorOpenButton.setEnabled(true);
	
    }
    private void doSaveFile() {
	try {
	    BufferedWriter out = new BufferedWriter(new FileWriter(propertyChannel.getString(PM_FILE_CURRENT)));
	    out.write(editorTextPane.getText());
	    out.close();
	    propertyChannel.put(PM_FILE_CURRENT_CHECKSUM, getMD5Checksum(editorTextPane.getText()));
	} catch (IOException e) {
	    error("There was a problem saving to the file!", e);
	}
    }
     
    private String getMD5Checksum(String text) {
	MessageDigest complete;
	try {
	    complete = MessageDigest.getInstance("MD5");
	} catch (NoSuchAlgorithmException e) {
	    return "";
	}
	byte[] b = complete.digest(text.getBytes());
	String result = "";
	for (int i=0; i < b.length; i++) {
	    result +=
		Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
	}
	return result;
    }
    
    @SuppressWarnings("unchecked")
    private class CustomServicesTree extends ServicesTree {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/***********************************************************************
	 * 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());

			// take all leaves-services and propagate them...
			for (Enumeration en = aNode.children(); en
				.hasMoreElements();) {
			    Object uo2 = ((DefaultMutableTreeNode) en
				    .nextElement()).getUserObject();
			    if (uo2 instanceof CommonNode)
				System.out.println(((CommonNode) uo2).getValue());
			}

		    } else if (nodeType == CommonNode.NODE_SERVICE) {
			s.addElement(((CommonNode) uo).getValue());
			System.out.println((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");
	}
    }
}
