medsavant.enrichment.app.OntologyAggregatePanel.java Source code

Java tutorial

Introduction

Here is the source code for medsavant.enrichment.app.OntologyAggregatePanel.java

Source

/*
 *    Copyright 2011-2012 University of Toronto
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package medsavant.enrichment.app;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.rmi.RemoteException;
import java.sql.SQLException;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;

import com.jidesoft.grid.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.ut.biolab.medsavant.MedSavantClient;
import medsavant.enrichment.app.AggregatePanel;
import org.ut.biolab.medsavant.client.filter.FilterController;
import org.ut.biolab.medsavant.client.geneset.GeneSetController;
import org.ut.biolab.medsavant.client.login.LoginController;
import org.ut.biolab.medsavant.client.ontology.OntologyFilter;
import org.ut.biolab.medsavant.client.ontology.OntologyFilterView;
import org.ut.biolab.medsavant.client.ontology.OntologyListItem;
import org.ut.biolab.medsavant.shared.model.Gene;
import org.ut.biolab.medsavant.shared.model.OntologyTerm;
import org.ut.biolab.medsavant.shared.model.OntologyType;
import org.ut.biolab.medsavant.shared.model.ProgressStatus;
import org.ut.biolab.medsavant.client.project.ProjectController;
import org.ut.biolab.medsavant.client.reference.ReferenceController;
import org.ut.biolab.medsavant.client.util.MedSavantExceptionHandler;
import org.ut.biolab.medsavant.client.util.MedSavantWorker;
import org.ut.biolab.medsavant.client.util.ThreadController;
import org.ut.biolab.medsavant.client.view.genetics.GeneticsFilterPage;
import org.ut.biolab.medsavant.shared.model.SessionExpiredException;

/**
 *
 * @author mfiume, tarkvara
 */
public class OntologyAggregatePanel extends AggregatePanel {

    private static final Log LOG = LogFactory.getLog(OntologyAggregatePanel.class);

    /** Percentage of total fetch operation which is devoted to retrieving terms. */
    private static final double TERMS_PERCENT = 10.0;

    private JComboBox chooser;
    private JProgressBar progress;
    private TreeTable tree;

    private MedSavantWorker termFetcher;
    private VariantFetcher variantFetcher;

    public OntologyAggregatePanel(String page) {
        super(page);
        setLayout(new GridBagLayout());

        chooser = new JComboBox(OntologyListItem.DEFAULT_ITEMS);
        chooser.setMaximumSize(new Dimension(400, chooser.getMaximumSize().height));
        progress = new JProgressBar();
        progress.setPreferredSize(new Dimension(600, progress.getMaximumSize().height));
        progress.setStringPainted(true);

        JPanel banner = new JPanel();
        banner.setLayout(new GridBagLayout());
        banner.setBackground(new Color(245, 245, 245));
        banner.setBorder(BorderFactory.createTitledBorder("Ontology"));

        tree = new TreeTable();

        GridBagConstraints gbc = new GridBagConstraints();
        gbc.weightx = 1.0;
        gbc.anchor = GridBagConstraints.WEST;
        banner.add(chooser, gbc);
        gbc.anchor = GridBagConstraints.EAST;
        banner.add(progress, gbc);

        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.weightx = 1.0;
        gbc.weighty = 0.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;
        gbc.anchor = GridBagConstraints.NORTH;
        add(banner, gbc);

        gbc.weighty = 1.0;
        gbc.fill = GridBagConstraints.BOTH;
        add(new JScrollPane(tree), gbc);

        chooser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (termFetcher != null) {
                    termFetcher.cancel(true);
                    termFetcher = null;
                }
                recalculate();
            }
        });

        tree.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                if (SwingUtilities.isRightMouseButton(e)) {
                    createPopup().show(e.getComponent(), e.getX(), e.getY());
                }
            }
        });
    }

    @Override
    public void recalculate() {
        if (chooser != null && chooser.getSelectedItem() != null) {
            // We only want to cancel the VariantFetcher.
            if (variantFetcher != null) {
                variantFetcher.cancel(true);
                variantFetcher = null;
            }

            // It's quite possible that we've successfully fetched all the terms, in which case the dead worker tells us that
            // we don't need to do it again.
            if (termFetcher == null) {
                tree.setModel(new OntologyTreeModel(null));

                termFetcher = new MedSavantWorker<OntologyTerm[]>(pageName) {
                    @Override
                    protected OntologyTerm[] doInBackground() throws Exception {
                        showProgress(0.0);
                        return MedSavantClient.OntologyManager.getAllTerms(
                                LoginController.getInstance().getSessionID(),
                                ((OntologyListItem) chooser.getSelectedItem()).getType());
                    }

                    @Override
                    protected void showProgress(double fract) {
                        if (fract == 1.0) {
                            // Next stage will be indeterminate in duration.
                            progress.setIndeterminate(true);
                        } else {
                            if (fract == 0.0) {
                                progress.setIndeterminate(false);
                                startProgressTimer();
                            }

                            double prog = fract * TERMS_PERCENT;
                            progress.setValue((int) prog);
                            progress.setString(String.format("%.1f%%", prog));
                        }
                    }

                    @Override
                    protected void showSuccess(OntologyTerm[] result) {
                        tree.setModel(new OntologyTreeModel(result));
                        TableColumn col = tree.getColumnModel().getColumn(3);
                        col.setCellRenderer(new NodeProgressRenderer());
                    }

                    @Override
                    protected ProgressStatus checkProgress() throws RemoteException {
                        ProgressStatus stat;
                        try {
                            stat = MedSavantClient.OntologyManager
                                    .checkProgress(LoginController.getInstance().getSessionID(), false);
                        } catch (SessionExpiredException ex) {
                            MedSavantExceptionHandler.handleSessionExpiredException(ex);
                            return null;
                        }
                        LOG.info("OntologyManager returned status " + stat);
                        return stat;
                    }
                };

                termFetcher.execute();
            } else {
                // Already have our terms.  Just reset and start a new variant fetcher.
                if (tree.getModel() != null) {
                    for (int i = 0; i < tree.getRowCount(); i++) {
                        OntologyNode rowNode = (OntologyNode) tree.getRowAt(i);
                        rowNode.resetCount();
                    }
                    OntologyTreeModel actualModel = (OntologyTreeModel) TableModelWrapperUtils
                            .getActualTableModel(tree.getModel());
                    actualModel.geneCounts.clear();
                    actualModel.fireTableDataChanged();
                    variantFetcher = new VariantFetcher(actualModel);
                    variantFetcher.execute();
                }
            }
        }
    }

    private JPopupMenu createPopup() {
        JPopupMenu menu = new JPopupMenu();

        SortableTreeTableModel model = (SortableTreeTableModel) tree.getModel();
        final int[] selRows = tree.getSelectedRows();

        JMenuItem posItem = new JMenuItem(String.format("<html>Filter by %s</html>",
                selRows.length == 1 ? "Ontology Term <i>" + model.getValueAt(selRows[0], 0) + "</i>"
                        : "Selected Ontology Terms"));
        posItem.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                ThreadController.getInstance().cancelWorkers(pageName);

                List<OntologyTerm> terms = new ArrayList<OntologyTerm>();
                SortableTreeTableModel model = (SortableTreeTableModel) tree.getModel();
                for (int r : selRows) {
                    OntologyNode rowNode = (OntologyNode) model.getRowAt(r);
                    terms.add(rowNode.term);
                }

                OntologyType ont = terms.get(0).getOntology();
                GeneticsFilterPage.getSearchBar().loadFilters(
                        OntologyFilterView.wrapState(OntologyFilter.ontologyToTitle(ont), ont, terms, false));
            }

        });
        menu.add(posItem);

        return menu;
    }

    /**
     * Class which provides a tree-like data-structure for all terms within a given ontology.
     */
    private class OntologyTreeModel extends TreeTableModel {

        private final OntologyTerm[] allTerms;
        private final Map<OntologyTerm, OntologyTerm[]> allChildren = new HashMap<OntologyTerm, OntologyTerm[]>();
        private final Map<String, Integer> geneCounts = new HashMap<String, Integer>();

        public OntologyTreeModel(OntologyTerm[] terms) {
            allTerms = terms;

            if (terms != null) {
                for (OntologyTerm t : terms) {
                    if (t.getParentIDs() == null) {
                        OntologyNode node = new OntologyNode(t, this);
                        addRow(node);
                    }
                }
                variantFetcher = new VariantFetcher(this);
                variantFetcher.execute();
            }
        }

        @Override
        public int getColumnCount() {
            return 4;
        }

        @Override
        public String getColumnName(int col) {
            switch (col) {
            case 0:
                return "ID";
            case 1:
                return "Name";
            case 2:
                return "Definition";
            case 3:
                return "Variant Count";
            }
            return null;
        }

        private OntologyTerm[] getChildTerms(OntologyTerm term) {
            if (!allChildren.containsKey(term)) {
                OntologyTerm[] children = term.getChildren(allTerms);
                allChildren.put(term, children);
                return children;
            }
            return allChildren.get(term);
        }

    }

    /**
     * Class which represents a single term within the tree-model.
     */
    private class OntologyNode extends DefaultExpandableRow {

        private static final int COUNT_COLUMN = 3;

        private final OntologyTerm term;
        private final OntologyTreeModel model;
        private Set<String> uncountedGenes = new HashSet<String>();
        private int totalGenes;
        private int count;

        private OntologyNode(OntologyTerm t, OntologyTreeModel m) {
            term = t;
            model = m;
            addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    if (evt.getPropertyName().equals(DefaultExpandableRow.PROPERTY_EXPANDED)) {
                        if (isExpanded()) {
                            // If we add the children in the actual propertyChange method, they get added twice.  A JIDE bug?
                            SwingUtilities.invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    OntologyTerm[] childTerms = model.getChildTerms(term);
                                    if (childTerms.length > 0 && getChildrenCount() == 0) {
                                        synchronized (model) {
                                            for (OntologyTerm t : childTerms) {
                                                model.addRow(OntologyNode.this, new OntologyNode(t, model));
                                            }
                                        }
                                    }
                                    // We use a new thread to push the new nodes onto the VariantFetcher in order to avoid
                                    // blocking the AWT thread.
                                    new Thread() {
                                        @Override
                                        public void run() {
                                            for (Object child : getChildren()) {
                                                variantFetcher.push((OntologyNode) child);
                                            }
                                        }
                                    }.start();
                                }
                            });
                        }
                    }
                }
            });
        }

        @Override
        public Object getValueAt(int col) {
            switch (col) {
            case 0:
                return term.getID();
            case 1:
                return term.getName();
            case 2:
                return term.getDef();
            case COUNT_COLUMN:
                return count;
            }
            return null;
        }

        @Override
        public boolean hasChildren() {
            return model.getChildTerms(term).length > 0;
        }

        private void increment(String gene, Integer n) {
            if (uncountedGenes != null && uncountedGenes.contains(gene)) {
                if (n != null) {
                    count += n;
                }
                uncountedGenes.remove(gene);
                if (uncountedGenes.isEmpty()) {
                    uncountedGenes = null;
                }
                model.fireTableCellUpdated(model.getRowIndex(this), COUNT_COLUMN);
            }
        }

        private void resetCount() {
            LOG.info("Reset count for " + term);
            uncountedGenes = new HashSet<String>();
            count = 0;
        }
    }

    private class NodeProgressRenderer extends JPanel implements TableCellRenderer {

        private JLabel label;
        private JProgressBar bar;

        NodeProgressRenderer() {
            setLayout(new GridBagLayout());
            label = new JLabel() {
                @Override
                public Dimension getMinimumSize() {
                    return new Dimension(90, super.getMinimumSize().height);
                }
            };
            bar = new JProgressBar();
            bar.putClientProperty("JComponent.sizeVariant", "mini");

            GridBagConstraints gbc = new GridBagConstraints();
            gbc.weightx = 0.5;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.insets = new Insets(3, 3, 3, 3);
            add(label, gbc);
            gbc.weightx = 1.0;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            add(bar, gbc);
        }

        @Override
        public Component getTableCellRendererComponent(JTable table, Object val, boolean selected, boolean focus,
                int row, int col) {
            OntologyNode node = (OntologyNode) ((TreeTable) table).getRowAt(row);
            if (node.uncountedGenes == null) {
                // Fully loaded.
                label.setText(val.toString());
                bar.setVisible(false);
            } else {
                if (node.uncountedGenes.isEmpty()) {
                    // Still waiting to get our list of genes.
                    label.setText("Loading...");
                    bar.setIndeterminate(true);
                    bar.setStringPainted(false);
                    bar.setVisible(true);
                } else {
                    double prog = (1.0 - (double) node.uncountedGenes.size() / node.totalGenes) * 100.0;
                    label.setText(" " + val);
                    bar.setIndeterminate(false);
                    bar.setValue((int) prog);
                    bar.setString(String.format("%.1f%%", prog));
                    bar.setStringPainted(true);
                    bar.setVisible(true);
                }
            }

            return this;
        }
    }

    private class VariantFetcher extends MedSavantWorker<Void> {

        private final Stack<OntologyNode> nodeStack = new Stack<OntologyNode>();
        private final Stack<String> geneStack = new Stack<String>();
        private final OntologyTreeModel model;

        private VariantFetcher(OntologyTreeModel m) {
            super(pageName);
            model = m;
            for (int i = 0; i < model.getRowCount(); i++) {
                push((OntologyNode) model.getRowAt(i));
            }
        }

        /**
         * It's hard to get a valid progress percentage for the process as a whole,
         */
        @Override
        protected void showProgress(double fraction) {
        }

        @Override
        protected void showSuccess(Void result) {
        }

        @Override
        protected Void doInBackground() throws Exception {
            while (!isCancelled()) {
                do {
                    progress.setVisible(true);
                    while (!nodeStack.empty() && !isCancelled()) {
                        synchronized (nodeStack) {
                            fetchGenesForNodes();
                        }
                    }
                    if (!geneStack.empty()) {
                        String geneName = geneStack.pop();
                        Integer result = model.geneCounts.get(geneName);
                        if (result == null) {
                            Gene gene = GeneSetController.getInstance().getGene(geneName);
                            if (gene != null) {
                                result = MedSavantClient.VariantManager.getVariantCountInRange(
                                        LoginController.getInstance().getSessionID(),
                                        ProjectController.getInstance().getCurrentProjectID(),
                                        ReferenceController.getInstance().getCurrentReferenceID(),
                                        FilterController.getInstance().getAllFilterConditions(), gene.getChrom(),
                                        gene.getStart(), gene.getEnd());
                            } else {
                                LOG.info(geneName + " referenced in " + model.allTerms[0].getOntology()
                                        + " not found in current gene set.");
                                result = 0;
                            }
                            model.geneCounts.put(geneName, result);
                        }

                        synchronized (model) {
                            for (Object node : model.getRows()) {
                                ((OntologyNode) node).increment(geneName, result);
                            }
                        }
                    }
                } while (!geneStack.empty() && !isCancelled());
                progress.setVisible(false);
                synchronized (nodeStack) {
                    nodeStack.wait();
                }
            }
            throw new InterruptedException();
        }

        private void fetchGenesForNodes() throws InterruptedException, SQLException, RemoteException {
            OntologyTerm[] terms = new OntologyTerm[nodeStack.size()];
            for (int i = 0; i < terms.length; i++) {
                terms[i] = nodeStack.get(i).term;
            }
            Map<OntologyTerm, String[]> genes;
            try {
                genes = MedSavantClient.OntologyManager.getGenesForTerms(
                        LoginController.getInstance().getSessionID(), terms,
                        ReferenceController.getInstance().getCurrentReferenceName());
            } catch (SessionExpiredException ex) {
                MedSavantExceptionHandler.handleSessionExpiredException(ex);
                return;
            }
            for (OntologyNode node : nodeStack) {
                String[] nodeGenes = genes.get(node.term);
                if (nodeGenes != null) {
                    node.uncountedGenes.addAll(Arrays.asList(nodeGenes));
                    node.totalGenes = nodeGenes.length;

                    for (String g : nodeGenes) {
                        Integer geneCount = model.geneCounts.get(g);
                        if (geneCount != null) {
                            // Already counted.
                            node.increment(g, geneCount);
                        } else {
                            push(g);
                        }
                    }
                } else {
                    // No genes for this term.  Mark it null so we know we're done.
                    node.uncountedGenes = null;
                    model.fireTableCellUpdated(model.getRowIndex(node), OntologyNode.COUNT_COLUMN);
                }
            }
            nodeStack.clear();
        }

        private void push(OntologyNode node) {
            synchronized (nodeStack) {
                nodeStack.push(node);
                nodeStack.notify();
            }
        }

        private void push(String g) {
            geneStack.push(g);
        }
    }
}