org.genedb.jogra.plugins.TermRationaliser.java Source code

Java tutorial

Introduction

Here is the source code for org.genedb.jogra.plugins.TermRationaliser.java

Source

/*
 * Copyright (c) 2009 Genome Research Limited.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by the Free
 * Software Foundation; either version 2 of the License or (at your option) any
 * later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; see the file COPYING.LIB. If not, write to the Free
 * Software Foundation Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307
 * USA
 */

package org.genedb.jogra.plugins;

import org.genedb.db.taxon.TaxonNode;
import org.genedb.db.taxon.TaxonNodeManager;
import org.genedb.jogra.domain.GeneDBMessage;
import org.genedb.jogra.domain.Term;
import org.genedb.jogra.drawing.Jogra;
import org.genedb.jogra.drawing.JograPlugin;
import org.genedb.jogra.drawing.JograProgressBar;
import org.genedb.jogra.drawing.OpenWindowEvent;
import org.genedb.jogra.services.RationaliserJList;
import org.genedb.jogra.services.RationaliserResult;
import org.genedb.jogra.services.TermService;

import org.apache.log4j.Logger;
import org.bushe.swing.event.EventBus;
import org.springframework.util.StringUtils;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.ExecutionException;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.WindowConstants;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/********************************************************************************************************
 * The TermRationalier is a tool that allows curators to correct (rationalise) terms from a chosen 
 * controlled vocabulary. It is most useful for curators wanting to work on a specific organism or a set 
 * of organisms as these can be selected via the Organism tree in Jogra and they will be passed on to the  
 * rationaliser which will then only display the relevant terms. 
 ********************************************************************************************************/
public class TermRationaliser implements JograPlugin {

    private static final Logger logger = Logger.getLogger(TermRationaliser.class);

    /* Constants */
    private static final String WINDOW_TITLE = "Term Rationaliser";
    private static final String FROM_LIST_NAME = "From (selected terms)";
    private static final String TO_LIST_NAME = "To (all terms)";

    /* Variables for rationaliser functionality */
    private TermService termService; //Interface to the SQLTermService
    private TaxonNodeManager taxonNodeManager; //TaxonNodeManager to get the organism phylotree
    private List<TaxonNode> selectedTaxons = new ArrayList<TaxonNode>(); //Taxons corresponding to the selected organism names
    private Jogra jogra; //Instance of Jogra
    private boolean showEVC; //Show Evidence codes?
    private String termType; //One of the CVs set in the Spring application context
    private List<Term> terms = new ArrayList<Term>(); //Terms specific to selected taxons (for JList)
    private List<Term> allTerms = new ArrayList<Term>(); //All the terms in the CV
    private HashMap<String, String> instances = new HashMap<String, String>(); //To hold the types of cvterms
    private String[] cvnames; //Holds the cv names passed in through Spring

    /*Variables related to the user interface */
    private JFrame frame = new JFrame();
    private RationaliserJList fromList = new RationaliserJList();
    private RationaliserJList toList = new RationaliserJList();
    private JTextArea textField;
    private JLabel productCountLabel = new JLabel();
    private JLabel scopeLabel = new JLabel(); //Label showing user's selection. Default: All organisms
    private final JTextArea information = new JTextArea(10, 10);

    /**
     * Supplies the JPanel which is displayed in the main Jogra window.
     */
    public JPanel getMainWindowPlugin() {

        final JPanel ret = new JPanel();
        final JButton loadButton = new JButton("Load Term Rationaliser");
        final JLabel chooseType = new JLabel("Select term: ");
        final JComboBox termTypeBox = new JComboBox(instances.keySet().toArray());
        final JCheckBox showEVCFilter = new JCheckBox("Highlight terms with evidence codes", true);

        loadButton.addActionListener(new ActionListener() {
            public void actionPerformed(final ActionEvent ae) {

                new SwingWorker<JFrame, Void>() {
                    @Override
                    protected JFrame doInBackground() throws Exception {
                        ret.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
                        setTermType(instances.get((String) termTypeBox.getSelectedItem()));
                        setShowEVC(showEVCFilter.isSelected());
                        return makeWindow();
                    }

                    @Override
                    public void done() {
                        try {
                            final GeneDBMessage e = new OpenWindowEvent(TermRationaliser.this, get());
                            EventBus.publish(e);
                        } catch (final InterruptedException exp) {
                            exp.printStackTrace();
                        } catch (final ExecutionException exp) {
                            exp.printStackTrace();
                        }
                        ret.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
                    }
                }.execute();
            }
        });
        Box verticalBox = Box.createVerticalBox();
        Box horizontalBox = Box.createHorizontalBox();
        horizontalBox.add(chooseType);
        horizontalBox.add(termTypeBox);
        verticalBox.add(horizontalBox);
        verticalBox.add(loadButton);
        verticalBox.add(showEVCFilter);
        ret.add(verticalBox);
        return ret;
    }

    /**
     * Populates the JLists with data from the database
     * This happens right at the start to load the rationaliser
     * and then any time the user decides to refresh the 
     * models from the database. We use two parallel threads 
     * here to fetch the terms, but will eventually implement
     * some sort of caching mechanism.
     */
    private void initModels() {

        JograProgressBar jpb = new JograProgressBar("Loading terms from database..."); //Progress bar added for better user information    
        this.setSelectedTaxonsAndScopeLabel(jogra.getSelectedOrganismNames());

        logger.info("Are we in EDT when loading data? " + SwingUtilities.isEventDispatchThread());

        /* Loading the terms from the database */
        SwingWorker<Void, Void> worker_1 = new SwingWorker<Void, Void>() {
            @Override
            public Void doInBackground() {
                try {
                    long startTime = System.nanoTime();
                    long endTime;
                    terms = termService.getTerms(getSelectedTaxons(), getTermType());
                    /* Java Collections sort is a modified mergesort guaranteeing 
                     * n log(n) performance. The question is, is it slower or faster
                     * than doing a sql order by in postgres? It appears postgres
                     * uses qsort so the 'average' performance should also be n log(n).
                     * After some experimentation, we use Java's sort here since it appeared
                     * marginally faster. */
                    Collections.sort(terms);
                    endTime = System.nanoTime();
                    logger.info("Doing sorting in SQL (specific) took : " + (endTime - startTime) + " ns.");
                    for (Term term : terms) {
                        if (isShowEVC()) { //Fetch the evidence codes for the terms if the user wants them
                            term.setEvidenceCodes(termService.getEvidenceCodes(term));
                        }
                    }

                } catch (SQLException se) {
                    se.printStackTrace(); //How do we process this exception?
                }
                return null;
            }

            @Override
            public void done() {
                logger.info("Finished worker 1");

            }
        };

        SwingWorker<Void, Void> worker_2 = new SwingWorker<Void, Void>() {
            @Override
            public Void doInBackground() {
                try {
                    long startTime = System.nanoTime();
                    long endTime;
                    allTerms = termService.getAllTerms(getTermType());
                    Collections.sort(allTerms);
                    endTime = System.nanoTime();
                    logger.info("Doing sorting in SQL (general) took : " + (endTime - startTime) + " ns.");

                    for (Term term : allTerms) {
                        if (isShowEVC()) { //Fetch the evidence codes for the terms if the user wants them
                            term.setEvidenceCodes(termService.getEvidenceCodes(term));
                        }
                    }

                } catch (SQLException se) {
                    se.printStackTrace();
                }

                return null;
            }

            @Override
            public void done() {
                logger.info("Finished worker 2");

            }
        };

        logger.info("Inside swing worker 1 to fetch and sort the specific terms");
        writeMessage("Fetching organism-specific terms from the database...\n");
        worker_1.run();
        logger.info("Inside swing worker 2 to fetch and sort all the cv terms");
        writeMessage("Fetching all the terms in the cv from the database...\n");
        worker_2.run();

        //After the data is available...
        fromList.addAll(terms);
        toList.addAll(allTerms);
        productCountLabel.setText(String.format("Number of terms for selected organisms: %d terms found (%s)",
                terms.size(), getTermType()));
        textField.setText(""); //Re-set the editable text box     
        fromList.clearSelection(); //Clear any previous selections
        toList.clearSelection();
        fromList.repaint();
        toList.repaint();

        jpb.stop();

    }

    /**
     * Return a new JFrame which is the main interface to the Rationaliser.
     */
    public JFrame getMainPanel() {

        /* JFRAME */
        frame.setTitle(WINDOW_TITLE);
        frame.setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        /* MENU */
        JMenuBar menuBar = new JMenuBar();

        JMenu actions_menu = new JMenu("Actions");
        JMenuItem actions_mitem_1 = new JMenuItem("Refresh lists");
        actions_mitem_1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                initModels();
            }
        });
        actions_menu.add(actions_mitem_1);

        JMenu about_menu = new JMenu("About");
        JMenuItem about_mitem_1 = new JMenuItem("About");
        about_mitem_1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                JOptionPane.showMessageDialog(null,
                        "Term Rationaliser \n" + "Wellcome Trust Sanger Institute, UK \n" + "2009",
                        "Term Rationaliser", JOptionPane.PLAIN_MESSAGE);
            }
        });
        about_menu.add(about_mitem_1);

        menuBar.add(about_menu);
        menuBar.add(actions_menu);
        frame.add(menuBar, BorderLayout.NORTH);

        /* MAIN BOX */
        Box center = Box.createHorizontalBox(); //A box that displays contents from left to right
        center.add(Box.createHorizontalStrut(5)); //Invisible fixed-width component

        /* FROM LIST AND PANEL */
        fromList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); //Allow multiple products to be selected 
        fromList.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent arg0) {
                if (arg0.getKeyCode() == KeyEvent.VK_RIGHT) {
                    synchroniseLists(fromList, toList); //synchronise from left to right
                }
            }

            @Override
            public void keyReleased(KeyEvent arg0) {
            }

            @Override
            public void keyTyped(KeyEvent arg0) {
            }
        });

        Box fromPanel = this.createRationaliserPanel(FROM_LIST_NAME, fromList); //Box on left hand side
        fromPanel.add(Box.createVerticalStrut(55)); //Add some space
        center.add(fromPanel); //Add to main box
        center.add(Box.createHorizontalStrut(3)); //Add some space

        /* MIDDLE PANE */
        Box middlePane = Box.createVerticalBox();

        ClassLoader classLoader = this.getClass().getClassLoader(); //Needed to access the images later on
        ImageIcon leftButtonIcon = new ImageIcon(classLoader.getResource("left_arrow.gif"));
        ImageIcon rightButtonIcon = new ImageIcon(classLoader.getResource("right_arrow.gif"));

        leftButtonIcon = new ImageIcon(leftButtonIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon!
        rightButtonIcon = new ImageIcon(rightButtonIcon.getImage().getScaledInstance(20, 20, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon!

        JButton rightSynch = new JButton(rightButtonIcon);
        rightSynch.setToolTipText("Synchronise TO list. \n Shortcut: Right-arrow key");

        rightSynch.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                synchroniseLists(fromList, toList);
            }
        });

        JButton leftSynch = new JButton(leftButtonIcon);
        leftSynch.setToolTipText("Synchronise FROM list. \n Shortcut: Left-arrow key");

        leftSynch.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                synchroniseLists(toList, fromList);
            }
        });

        middlePane.add(rightSynch);
        middlePane.add(leftSynch);

        center.add(middlePane); //Add middle pane to main box
        center.add(Box.createHorizontalStrut(3));

        /* TO LIST AND PANEL */
        toList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); //Single product selection in TO list
        toList.addKeyListener(new KeyListener() {
            @Override
            public void keyPressed(KeyEvent arg0) {
                if (arg0.getKeyCode() == KeyEvent.VK_LEFT) {
                    synchroniseLists(toList, fromList); //synchronise from right to left
                }
            }

            @Override
            public void keyReleased(KeyEvent arg0) {
            }

            @Override
            public void keyTyped(KeyEvent arg0) {
            }
        });

        Box toPanel = this.createRationaliserPanel(TO_LIST_NAME, toList);

        Box newTerm = Box.createVerticalBox();

        textField = new JTextArea(1, 1); //textfield to let the user edit the name of an existing term
        textField.setMaximumSize(new Dimension(Toolkit.getDefaultToolkit().getScreenSize().height, 10));

        textField.setForeground(Color.BLUE);
        JScrollPane jsp = new JScrollPane(textField); //scroll pane so that there is a horizontal scrollbar
        jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        newTerm.add(jsp);
        TitledBorder editBorder = BorderFactory.createTitledBorder("Edit term name");
        editBorder.setTitleColor(Color.DARK_GRAY);
        newTerm.setBorder(editBorder);
        toPanel.add(newTerm); //add textfield to panel

        center.add(toPanel); //add panel to main box
        center.add(Box.createHorizontalStrut(5));

        frame.add(center); //add the main panel to the frame

        initModels(); //load the lists with data

        /* BOTTOM HALF OF FRAME */
        Box main = Box.createVerticalBox();
        TitledBorder border = BorderFactory.createTitledBorder("Information");
        border.setTitleColor(Color.DARK_GRAY);

        /* INFORMATION BOX */
        Box info = Box.createVerticalBox();

        Box scope = Box.createHorizontalBox();
        scope.add(Box.createHorizontalStrut(5));
        scope.add(scopeLabel); //label showing the scope of the terms
        scope.add(Box.createHorizontalGlue());

        Box productCount = Box.createHorizontalBox();
        productCount.add(Box.createHorizontalStrut(5));
        productCount.add(productCountLabel); //display the label showing the number of terms
        productCount.add(Box.createHorizontalGlue());

        info.add(scope);
        info.add(productCount);
        info.setBorder(border);

        /* ACTION BUTTONS */
        Box actionButtons = Box.createHorizontalBox();
        actionButtons.add(Box.createHorizontalGlue());
        actionButtons.add(Box.createHorizontalStrut(10));

        JButton findFix = new JButton(new FindClosestMatchAction());
        actionButtons.add(findFix);
        actionButtons.add(Box.createHorizontalStrut(10));

        RationaliserAction ra = new RationaliserAction();
        // RationaliserAction2 ra2 = new RationaliserAction2();
        JButton go = new JButton(ra);
        actionButtons.add(go);
        actionButtons.add(Box.createHorizontalGlue());

        /* MORE INFORMATION TOGGLE */
        Box buttonBox = Box.createHorizontalBox();
        final JButton toggle = new JButton("Hide information <<");

        buttonBox.add(Box.createHorizontalStrut(5));
        buttonBox.add(toggle);
        buttonBox.add(Box.createHorizontalGlue());

        Box textBox = Box.createHorizontalBox();

        final JScrollPane scrollPane = new JScrollPane(information);
        scrollPane.setPreferredSize(new Dimension(frame.getWidth(), 100));
        scrollPane.setVisible(true);
        textBox.add(Box.createHorizontalStrut(5));
        textBox.add(scrollPane);

        ActionListener actionListener = new ActionListener() {
            public void actionPerformed(ActionEvent actionEvent) {
                if (toggle.getText().equals("Show information >>")) {
                    scrollPane.setVisible(true);
                    toggle.setText("Hide information <<");
                    frame.setPreferredSize(new Dimension(frame.getWidth(), frame.getHeight() + 100));
                    frame.pack();
                } else if (toggle.getText().equals("Hide information <<")) {
                    scrollPane.setVisible(false);
                    toggle.setText("Show information >>");
                    frame.setPreferredSize(new Dimension(frame.getWidth(), frame.getHeight() - 100));
                    frame.pack();
                }
            }
        };
        toggle.addActionListener(actionListener);

        main.add(Box.createVerticalStrut(5));
        main.add(info);
        main.add(Box.createVerticalStrut(5));
        main.add(Box.createVerticalStrut(5));
        main.add(actionButtons);
        main.add(Box.createVerticalStrut(10));
        main.add(buttonBox);
        main.add(textBox);

        frame.add(main, BorderLayout.SOUTH);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
        //initModels();

        return frame;
    }

    /* */

    public JFrame makeWindow() {
        System.err.println("Am I on EDT '" + EventQueue.isDispatchThread() + "'  x");
        JFrame lookup = Jogra.findNamedWindow(WINDOW_TITLE);
        if (lookup == null) {
            lookup = getMainPanel();
        } else {
            initModels();
            lookup.pack();
            lookup.repaint();
        }
        //lookup = getMainPanel(); //Always getting a new frame since it has to pick up variable organism (improve efficiency later: NDS)
        return lookup;
    }

    /**
     * PRIVATE HELPER METHODS
     */

    /* 
     * Synchronise to the item selected in sourcelist 
     */
    private void synchroniseLists(JList sourceList, JList targetList) {
        Term term = (Term) sourceList.getSelectedValue();
        targetList.setSelectedValue(term, true);
        targetList.ensureIndexIsVisible(targetList.getSelectedIndex());
    }

    /* 
     * Write a message in the information box
     * and set the position to the last line
     * (automatically scrolls down to that line) 
     */
    private void writeMessage(final String m) {
        information.append(m);
        //Bit of a fiddle to get the pane to autoscroll to end
        information.selectAll();
        int x = information.getSelectionEnd();
        information.select(x, x);

    }

    /*
     * Set the scope and taxon label
     */
    private void setSelectedTaxonsAndScopeLabel(List<String> organismNames) {
        if (this.selectedTaxons != null && this.selectedTaxons.size() > 0) {
            this.selectedTaxons.clear(); //clear anything that already is in the list
        }

        if (organismNames != null && organismNames.size() != 0 && !organismNames.contains("root")) { // 'root' with a simple r causes problems 
            scopeLabel.setText("Organism(s): " + StringUtils.collectionToCommaDelimitedString(organismNames));
            for (String s : organismNames) {
                this.selectedTaxons.add(taxonNodeManager.getTaxonNodeForLabel(s));
            }
        } else { //If there are no selections, get all terms
            scopeLabel.setText("Organism(s): All organisms");
            this.selectedTaxons.add(taxonNodeManager.getTaxonNodeForLabel("Root"));
        }
    }

    /* 
     * Since both the TO and FROM panels are so similar, we put all the common
     * drawing tasks inside the following method.  
     */

    private Box createRationaliserPanel(final String name, final RationaliserJList rjlist) {

        int preferredHeight = 500; //change accordingly
        int preferredWidth = 500;

        Toolkit tk = Toolkit.getDefaultToolkit();
        Dimension size = tk.getScreenSize();
        int textboxHeight = 10; //change accordingly
        int textboxWidth = size.width;

        Box box = Box.createVerticalBox();
        box.add(new JLabel(name));

        JTextField searchField = new JTextField(20); //Search field on top
        /* We don't want this textfield's height to expand when
         * the Rationaliser is dragged to exapnd. So we set it's
         * height to what we want and the width to the width of
         * the screen  
         */
        searchField.setMaximumSize(new Dimension(textboxWidth, textboxHeight));
        rjlist.installJTextField(searchField);
        box.add(searchField);

        JScrollPane scrollPane = new JScrollPane(); //scroll pane
        scrollPane.setViewportView(rjlist);
        scrollPane.setPreferredSize(new Dimension(preferredWidth, preferredHeight));
        box.add(scrollPane);

        TitledBorder sysidBorder = BorderFactory.createTitledBorder("Systematic IDs"); //systematic ID box
        sysidBorder.setTitleColor(Color.DARK_GRAY);

        final JTextArea idField = new JTextArea(1, 1);
        idField.setMaximumSize(new Dimension(textboxWidth, textboxHeight));
        idField.setEditable(false);
        idField.setBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY));
        idField.setForeground(Color.DARK_GRAY);
        JScrollPane scroll = new JScrollPane(idField);
        scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);

        Box sysidBox = Box.createVerticalBox();
        sysidBox.add(scroll /*idField*/);
        sysidBox.setBorder(sysidBorder);
        box.add(sysidBox);

        rjlist.addListSelectionListener(new ListSelectionListener() {
            @Override
            public void valueChanged(ListSelectionEvent e) {
                Term highlightedTerm = (Term) rjlist.getSelectedValue();
                if (highlightedTerm != null) {
                    /* For each list, call the relevant methods
                     * to get the systematic IDs. Then for the
                     * right list, add the term name in the
                     * text box below
                     */
                    if (name.equals(FROM_LIST_NAME)) {
                        idField.setText(StringUtils.collectionToCommaDelimitedString(
                                termService.getSystematicIDs(highlightedTerm, selectedTaxons)));
                    } else if (name.equals(TO_LIST_NAME)) {
                        idField.setText(StringUtils.collectionToCommaDelimitedString(
                                termService.getSystematicIDs(highlightedTerm, null)));
                        /* We allow the user to edit the term name */
                        textField.setText(highlightedTerm.getName());
                    }

                }
            }
        });

        return box;

    }

    /**
     *  SETTER/GETTER METHODS
     */

    public JFrame getFrame() {
        return frame;
    }

    public List<TaxonNode> getSelectedTaxons() {
        return this.selectedTaxons;
    }

    public void setTaxonNodeManager(TaxonNodeManager taxonNodeManager) {
        this.taxonNodeManager = taxonNodeManager;
    }

    public void setTermType(String type) {
        this.termType = type;
    }

    public String getTermType() {
        return termType;
    }

    public String getName() {
        return WINDOW_TITLE;
    }

    public void setShowEVC(boolean value) {
        showEVC = value;
    }

    public boolean isShowEVC() {
        return showEVC;
    }

    public boolean isSingletonByDefault() {
        return true;
    }

    public boolean isUnsaved() {
        // TODO
        return false;
    }

    public void setTermService(TermService termService) {
        this.termService = termService;
    }

    /*************************************************************************************
     * An action wrapping code which identifies the closest match in the right hand column
     * to the selected value in the left hand column. Closest is defined by the smallest
     * Levenshtein value.
     *************************************************************************************/
    class FindClosestMatchAction extends AbstractAction implements ListSelectionListener {

        public FindClosestMatchAction() {
            putValue(Action.NAME, "Find possible fix");

            ClassLoader classLoader = this.getClass().getClassLoader();
            ImageIcon hammerIcon = new ImageIcon(classLoader.getResource("hammer_and_spanner.png"));
            hammerIcon = new ImageIcon(hammerIcon.getImage().getScaledInstance(35, 35, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon!

            putValue(Action.SMALL_ICON, hammerIcon);
            fromList.addListSelectionListener(this);
            enableBasedOnSelection();
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            Term from = (Term) fromList.getSelectedValue();

            int match = findClosestMatch(from.getName(), fromList.getSelectedIndex(), toList.getModel());
            if (match != -1) {
                toList.setSelectedIndex(match);
                toList.ensureIndexIsVisible(match);
            }

        }

        int findClosestMatch(String in, int fromIndex, ListModel list) {

            int current = -1;
            int distance = Integer.MAX_VALUE;
            for (int i = 0; i < list.getSize(); i++) {
                if (i == fromIndex) {
                    continue;
                }
                String element = ((Term) list.getElementAt(i)).getName();
                if (in.equalsIgnoreCase(element)) {
                    return i;
                }
                int d = org.apache.commons.lang.StringUtils.getLevenshteinDistance(in, element);

                if (d == 1) {
                    return i;
                }
                if (d < distance) {
                    distance = d;
                    current = i;
                }
            }
            return current;
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            enableBasedOnSelection();
        }

        private void enableBasedOnSelection() {
            boolean selection = (fromList.getMinSelectionIndex() != -1);
            if (this.isEnabled() != selection) {
                this.setEnabled(selection);
            }
        }

    }

    /********************************************************************************************
     * Action which wraps the rationalise action in the TermService.
     ********************************************************************************************/
    class RationaliserAction extends AbstractAction implements ListSelectionListener {

        public RationaliserAction() {
            putValue(Action.NAME, "Rationalise Terms");
            ClassLoader classLoader = this.getClass().getClassLoader();
            ImageIcon greenTickIcon = new ImageIcon(classLoader.getResource("green_tick.png"));
            greenTickIcon = new ImageIcon(greenTickIcon.getImage().getScaledInstance(35, 35, Image.SCALE_SMOOTH)); //TODO: Investigate simpler way to resize an icon!

            putValue(Action.SMALL_ICON, greenTickIcon);
            fromList.addListSelectionListener(this);
            toList.addListSelectionListener(this);
            enableBasedOnSelection();
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            List<Term> from = new ArrayList<Term>();
            Object[] temp = fromList.getSelectedValues(); //terms selected on the left
            for (Object o : temp) { //TODO:is there an easier way to convert an array of objects to a list of **typed** objects?
                from.add((Term) o);
            }
            String text = textField.getText(); //Whatever is in the testfield at the bottom

            /* 
             * Doing a bit of input validation and
             * getting user confirmation before sending 
             * the values to be rationalised. If the user
             * chooses to cancel the operation, 
             * we get out of here.
             */
            if (toList.contains(text) == 1) { //User chose to rationalise to a term already in the cv

                int userDecision = this
                        .show_OK_Cancel_Dialogue("You are about to rationalise the following terms:\n"
                                + StringUtils.collectionToDelimitedString(from, "\n") + "\n\n" + "to: \n" + text
                                + "\n\n" + "Do you want to continue? ", "Confirm");
                if (userDecision == JOptionPane.CANCEL_OPTION) {
                    writeMessage("Request to rationalise cancelled.\n");
                    return;
                }

            } else if (toList.contains(text) == 0) { //So, user has decided to rationalise to a new term that is currently not in the cv

                int userDecision = this
                        .show_OK_Cancel_Dialogue(
                                "You are about to rationalise the following terms:\n"
                                        + StringUtils.collectionToDelimitedString(from, "\n") + "\n\n"
                                        + "to a new term: \n" + text + "\n\n" + "Do you want to continue? ",
                                "Confirm");
                if (userDecision == JOptionPane.CANCEL_OPTION) {
                    writeMessage("Request to rationalise cancelled.\n");
                    return;
                }

            } else if (toList.contains(text) == 2) { //So, the user is just changing the case of the terms
                /* The cvterm table does not allow multiple terms with the same name 
                 * even if they differ in case. So, in a case when the user wants to
                 * change case, we have to do it across all organisms in the
                 * database.
                 */
                int userDecision = this.show_OK_Cancel_Dialogue(
                        "You are about to change the case of this term:\n" + text + "\n\n"
                                + " and this will be done across ALL the organisms. " + "Do you want to continue? ",
                        "Confirm");
                if (userDecision == JOptionPane.CANCEL_OPTION) {
                    writeMessage("Request to rationalise cancelled.\n");
                    return;
                }

            }

            /* 
             * Having validated the input, the rationaliseTerm method can be called. The result of this process is then used
             * to update the JLists. The changes in the terms are made in the jlists rather than fetching *all* the
             * terms again from the database as this was too slow.The user can opt to fetch the terms from the database by 
             * selecting 'refresh models' which will call the init method
             */

            try {

                getFrame().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));

                writeMessage("Rationalising...\n");

                /* The rationalising process is pretty fast so we 
                 * let it happen in the EDT */
                final RationaliserResult result = termService.rationaliseTerm(from, text, selectedTaxons);
                logger.info(result.toString());
                //All the rest we stick in a Swingworker so that the UI
                //does not freeze
                SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {

                    @Override
                    public Void doInBackground() {
                        //Removing the terms that ought to be removed
                        //from both the lists
                        terms.removeAll(result.getTermsDeletedSpecific());
                        allTerms.removeAll(result.getTermsDeletedGeneral());

                        //We try to add the new terms in the right position
                        //using the Collections binarysearch feature. When
                        //an items does not exist, it can tell us where it 
                        //should be inserted. This saves doing a sort again
                        //after this insert which can be expensive.
                        for (Term t : result.getTermsAdded()) {
                            int index = Collections.binarySearch(terms, t);
                            if (index < 0) { //If index > 0 the term already exists
                                terms.add(-index - 1, t);
                            }
                            index = Collections.binarySearch(allTerms, t);
                            if (index < 0) { //If index > 0 the term already exists
                                allTerms.add(-index - 1, t);
                            }

                        }

                        writeMessage(result.getMessage());

                        fromList.addAll(terms);
                        toList.addAll(allTerms);

                        return null;
                    }

                    public void done() {
                        toList.setSelectedValue(result.getTermsAdded().iterator().next(), true);
                        fromList.setSelectedValue(result.getTermsAdded().iterator().next(), true);
                        toList.repaint();
                        fromList.repaint();
                    }
                };
                worker.run();

            } catch (Exception se) { //All other unexpected errors
                writeMessage("There was an error while trying to rationalise. "
                        + "Please contact the WTSI Pathogens Informatics team with details of what you tried to do. \n");
                writeMessage("Error:" + se.toString());
                se.printStackTrace();
            }

            getFrame().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        }

        @Override
        public void valueChanged(ListSelectionEvent e) {
            enableBasedOnSelection();
        }

        private void enableBasedOnSelection() {
            boolean selection = (fromList.getMinSelectionIndex() != -1) && (toList.getMinSelectionIndex() != -1);
            if (this.isEnabled() != selection) {
                this.setEnabled(selection);
            }
        }

        private int show_OK_Cancel_Dialogue(String message, String title) {
            return JOptionPane.showConfirmDialog(null, message, title, JOptionPane.OK_CANCEL_OPTION,
                    JOptionPane.WARNING_MESSAGE);

        }

    }

    public void process(List<String> newArgs) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setJogra(Jogra jogra) {
        this.jogra = jogra;
    }

    public String[] getCvnames() {
        return cvnames;
    }

    public void setCvnames(String[] cvnames) {
        this.cvnames = cvnames;
        for (String c : cvnames) {
            String[] splitparts = StringUtils.split(c, ",");
            instances.put(splitparts[1], splitparts[0]);
        }
    }

}