/* TransferAnnotationTool.java * * created: 2009 * * This file is part of Artemis * * 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 General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package uk.ac.sanger.artemis.components; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Cursor; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Vector; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingConstants; import javax.swing.border.EtchedBorder; import javax.swing.border.TitledBorder; import uk.ac.sanger.artemis.Entry; import uk.ac.sanger.artemis.EntryGroup; import uk.ac.sanger.artemis.Feature; import uk.ac.sanger.artemis.FeaturePredicate; import uk.ac.sanger.artemis.FeatureVector; import uk.ac.sanger.artemis.SimpleEntryGroup; import uk.ac.sanger.artemis.chado.ChadoTransactionManager; import uk.ac.sanger.artemis.components.filetree.LocalAndRemoteFileManager; import uk.ac.sanger.artemis.components.genebuilder.GeneEdit; import uk.ac.sanger.artemis.components.genebuilder.GeneUtils; import uk.ac.sanger.artemis.components.genebuilder.ortholog.MatchPanel; import uk.ac.sanger.artemis.io.ChadoCanonicalGene; import uk.ac.sanger.artemis.io.DatabaseDocumentEntry; import uk.ac.sanger.artemis.io.GFFStreamFeature; import uk.ac.sanger.artemis.io.InvalidRelationException; import uk.ac.sanger.artemis.io.PartialSequence; import uk.ac.sanger.artemis.io.Qualifier; import uk.ac.sanger.artemis.io.QualifierVector; import uk.ac.sanger.artemis.util.DatabaseDocument; import uk.ac.sanger.artemis.util.StringVector; public class TransferAnnotationTool extends JFrame { private static final long serialVersionUID = 1L; private static String[] NON_TRANSFERABLE_QUALIFIERS = { "ID", "feature_id", "Derives_from", "feature_relationship_rank", "Parent", "isObsolete", "Start_range", "End_range", "timelastmodified", "cytoplasm_location", "cytoplasmic_polypeptide_region", "membrane_structure", "non_cytoplasm_location", "non_cytoplasmic_polypeptide_region", "orthologous_to", "paralogous_to", "pepstats_file", "PlasmoAP_score", "polypeptide_domain", "fasta_file", "blastp_file", "blastn_file", "systematic_id", "transmembrane", "transmembrane_polypeptide_region", "previous_systematic_id" }; private MatchPanel matchPanel; private static org.apache.log4j.Logger logger4j = org.apache.log4j.Logger.getLogger(TransferAnnotationTool.class); protected static Color STEEL_BLUE = new Color(25, 25, 112); /** * Tool for transferring annotation from one feature to other feature(s) * @param feature * @param entryGroup * @param geneNames */ public TransferAnnotationTool(final Feature feature, final EntryGroup entryGroup, final MatchPanel matchPanel) { super("Transfer Annotation Tool :: "+ feature.getIDString()); this.matchPanel = matchPanel; List geneNames = null; if(matchPanel != null) geneNames = matchPanel.getGeneNameList(); JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); JPanel pane = new JPanel(new GridBagLayout()); JScrollPane jsp = new JScrollPane(panel); panel.setBackground(Color.white); pane.setBackground(Color.white); panel.add(pane); JPanel framePanel = (JPanel)getContentPane(); framePanel.add(jsp, BorderLayout.CENTER); framePanel.setPreferredSize(new Dimension(600,600)); final Vector geneNameCheckBoxes = new Vector(); final Vector qualifierPanels = new Vector(); addMainPanel(feature, pane, qualifierPanels, geneNameCheckBoxes, geneNames); addBottomButtons(qualifierPanels, geneNameCheckBoxes, framePanel, entryGroup); pack(); setVisible(true); } /** * Construct the panel for setting up the gene list and the * list of qualifiers to transfer. * @param feature * @param pane * @param qualifierCheckBoxes * @param geneNameCheckBoxes * @param geneNames */ private void addMainPanel(final Feature feature, final JPanel pane, final Vector qualifierPanels, final Vector geneNameCheckBoxes, final List geneNames) { GridBagConstraints c = new GridBagConstraints(); int nrows = 0; c.anchor = GridBagConstraints.NORTHWEST; c.gridx = 2; c.gridy = 0; c.ipadx = 50; JLabel geneLabel = new JLabel("Qualifier(s)"); geneLabel.setFont(geneLabel.getFont().deriveFont(Font.BOLD)); pane.add(geneLabel, c); c.gridy = 0; c.gridx = 0; JLabel label = new JLabel("Gene List"); label.setFont(label.getFont().deriveFont(Font.BOLD)); pane.add(label, c); nrows+=3; c.gridx = 2; c.gridy = nrows; c.anchor = GridBagConstraints.WEST; addQualifierPanel(feature, qualifierPanels, c, nrows, pane); nrows+=2; if(feature.getEmblFeature() instanceof GFFStreamFeature) { GFFStreamFeature gffFeature = ((GFFStreamFeature) feature.getEmblFeature()); if (gffFeature.getChadoGene() != null) { String id = GeneUtils.getUniqueName(gffFeature); ChadoCanonicalGene chadoGene = gffFeature.getChadoGene(); Feature gene = (Feature) chadoGene.getGene().getUserData(); if(!id.equals( GeneUtils.getUniqueName(((GFFStreamFeature)chadoGene.getGene())) )) addQualifierPanel(gene, qualifierPanels, c, nrows, pane); nrows+=2; String transcriptName = chadoGene.getTranscriptFromName(GeneUtils.getUniqueName(gffFeature)); if(transcriptName != null) { GFFStreamFeature transcript = (GFFStreamFeature) chadoGene.getFeatureFromId(transcriptName); addQualifierPanel((Feature)transcript.getUserData(), qualifierPanels, c, nrows, pane); nrows+=2; Set children = chadoGene.getChildren(transcript); Iterator it = children.iterator(); while(it.hasNext()) { GFFStreamFeature kid = (GFFStreamFeature)it.next(); if(id.equals( GeneUtils.getUniqueName(((GFFStreamFeature)kid)) )) continue; addQualifierPanel((Feature)kid.getUserData(), qualifierPanels, c, nrows, pane); nrows+=2; } } } } c.gridx = 0; c.gridy = 3; c.gridheight = nrows; c.fill = GridBagConstraints.BOTH; final Box geneNameBox = Box.createVerticalBox(); pane.add(geneNameBox, c); if(geneNames != null) { for(int i = 0; i < geneNames.size(); i++) { JCheckBox cb = new JCheckBox((String) geneNames.get(i),true); geneNameBox.add(cb); geneNameCheckBoxes.add(cb); } } c.gridy = 1; c.gridheight = 1; c.fill = GridBagConstraints.NONE; c.gridx = 2; final JButton toggle = new JButton("Toggle"); toggle.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(int i=0; i enumQualifiers = qP.getQualifierCheckBoxes().keys(); while(enumQualifiers.hasMoreElements()) { JCheckBox cb = enumQualifiers.nextElement(); cb.setSelected(!cb.isSelected()); } } } }); pane.add(toggle, c); Box xBox = Box.createHorizontalBox(); final JButton toggleGeneList = new JButton("Toggle"); toggleGeneList.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(int i = 0; i < geneNameCheckBoxes.size(); i++) { JCheckBox cb = geneNameCheckBoxes.get(i); cb.setSelected(!cb.isSelected()); } geneNameBox.repaint(); } }); xBox.add(toggleGeneList); final JButton addGenes = new JButton("Add"); addGenes.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { JTextArea geneNameTextArea = new JTextArea(); geneNameTextArea.setEditable(true); JScrollPane jsp = new JScrollPane(geneNameTextArea); int res = JOptionPane.showConfirmDialog(TransferAnnotationTool.this, jsp, "Paste Feature Names to Add", JOptionPane.OK_CANCEL_OPTION); if(res == JOptionPane.CANCEL_OPTION) return; String geneNames[] = geneNameTextArea.getText().split("\\s"); for(int i=0;i clusterList = (matchPanel == null ? null : matchPanel.getGeneNameList(true)); if(clusterList != null && !geneNames.contains(clusterList.get(0))) { final JButton importCluster = new JButton("Import Cluster Names"); importCluster.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { for(String n: clusterList) { if(n == null || n.equals("")) continue; JCheckBox cb = new JCheckBox(n,true); geneNameBox.add(cb); geneNameCheckBoxes.add(cb); } importCluster.setEnabled(false); pane.revalidate(); } }); c.gridy = 2; pane.add(importCluster, c); } } /** * Add a panel to display a given features qualifiers. * @param f * @param qualifierPanels * @param c * @param nrows * @param pane */ private void addQualifierPanel(Feature f, Vector qualifierPanels, GridBagConstraints c, int nrows, JPanel pane) { QualifierPanel qPanel = new QualifierPanel(f,f.getKey().getKeyString()); if(qPanel.nrows == 0) return; c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.WEST; c.weightx = 100; qualifierPanels.add(qPanel); c.gridy = ++nrows; JLabel l = new JLabel(f.getIDString()); l.setFont(l.getFont().deriveFont(Font.BOLD)); l.setForeground(STEEL_BLUE); pane.add(l, c); c.gridy = ++nrows; pane.add(qPanel, c); c.weightx = 0.d; } /** * Add panel for the transfer and close button. * @param qualifierCheckBoxes * @param geneNameCheckBoxes * @param framePanel * @param feature * @param entryGroup */ private void addBottomButtons(final Vector qualifierPanels, final Vector geneNameCheckBoxes, final JPanel framePanel, final EntryGroup entryGroup) { final JCheckBox sameKeyCheckBox = new JCheckBox("Add to feature of same key", true); final JCheckBox overwriteCheckBox = new JCheckBox("Overwrite", false); overwriteCheckBox.setToolTipText("overwrite rather than append values"); final JCheckBox cvCheckBox = new JCheckBox("Set evidence as ISO and link to source in WITH/FROM", false); cvCheckBox.setToolTipText("for GO and Product qualifiers set the evidence as ISO (Inferred from\n"+ "Sequence Orthology) and add a link to the source in the WITH/FROM field"); Box buttonBox = Box.createHorizontalBox(); final JButton transfer = new JButton(">>TRANSFER"); transfer.setToolTipText("transfer annotation"); transfer.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if(overwriteCheckBox.isSelected()) { int res = JOptionPane.showConfirmDialog(TransferAnnotationTool.this, "Overwrite selected annotation?", "Overwrite", JOptionPane.OK_CANCEL_OPTION); if(res == JOptionPane.CANCEL_OPTION) return; } final boolean autoHistorySetting = LocalAndRemoteFileManager.isAutomaticHistory(); StringBuffer summary = new StringBuffer(); try { LocalAndRemoteFileManager.setAutomaticHistory(false); setCursor(new Cursor(Cursor.WAIT_CURSOR)); StringBuffer buff = new StringBuffer(); for(int i = 0; i < qualifierPanels.size(); i++) { QualifierPanel qP = qualifierPanels.get(i); int res = transferAnnotation(qP.getQualifierCheckBoxes(), geneNameCheckBoxes, qP.getFeature(), entryGroup, sameKeyCheckBox.isSelected(), overwriteCheckBox.isSelected(), cvCheckBox.isSelected(), buff, summary); if(res == -1) break; } if(buff.length() > 0) logger4j.debug("TRANSFERRED ANNOTATION SUMMARY:\n"+buff.toString()); setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); if(summary.length()>0) { final JTextArea list = new JTextArea(summary.toString()); final JScrollPane jsp = new JScrollPane(list); jsp.setPreferredSize(new Dimension(300,200)); JOptionPane.showMessageDialog( TransferAnnotationTool.this, jsp, "Summary of Genes Changed", JOptionPane.INFORMATION_MESSAGE); } } finally { LocalAndRemoteFileManager.setAutomaticHistory(autoHistorySetting); } } }); Box yBox = Box.createVerticalBox(); yBox.add(transfer); yBox.add(sameKeyCheckBox); yBox.add(overwriteCheckBox); yBox.add(cvCheckBox); buttonBox.add(yBox); final JButton close = new JButton("CLOSE"); close.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); } }); yBox = Box.createVerticalBox(); yBox.add(close); yBox.add(Box.createVerticalGlue()); buttonBox.add(yBox); buttonBox.add(Box.createHorizontalGlue()); framePanel.add(buttonBox, BorderLayout.SOUTH); } /** * Returns true if this qualifier is non-transferable * @param qualifierName * @return */ protected static boolean isNonTransferable(String qualifierName) { for(int i=0; i> qualifierCheckBoxes, final Vector geneNameCheckBoxes, final Feature orginatingFeature, final EntryGroup entryGroup, final boolean sameKey, final boolean overwrite, final boolean setEvidenceAndWithFrom, final StringBuffer buff, final StringBuffer genesUpdated) { // transfer selected annotation to genes final QualifierVector qualifiers = orginatingFeature.getQualifiers(); final QualifierVector qualifiersToTransfer = new QualifierVector(); Enumeration enumQualifiers = qualifierCheckBoxes.keys(); while(enumQualifiers.hasMoreElements()) { JCheckBox cb = enumQualifiers.nextElement(); if (cb.isSelected()) { Vector qualifierValuesCheckBox = qualifierCheckBoxes.get(cb); final StringVector values = qualifiers.getQualifierByName(cb.getText()).getValues(); StringVector valuesToTransfer = new StringVector(values); logger4j.debug("TRANSFER "+cb.getText()); for(int i=0; i genesNotFound = null; if (geneNames != null && orginatingFeature.getEntry().getEMBLEntry() instanceof DatabaseDocumentEntry) { DatabaseDocumentEntry db_entry = (DatabaseDocumentEntry) orginatingFeature.getEntry().getEMBLEntry(); DatabaseDocument doc = (DatabaseDocument) db_entry.getDocument(); for (int i = 0; i < geneNames.length; i++) { DatabaseDocumentEntry newDbEntry = GeneEdit.makeGeneEntry(null, geneNames[i], doc, null); if (newDbEntry == null) { if (genesNotFound == null) genesNotFound = new Vector(); genesNotFound.add(geneNames[i]); continue; } char[] c = new char[1]; PartialSequence ps = new PartialSequence(c, 100, 0, null, null); newDbEntry.setPartialSequence(ps); Entry entry = null; try { entry = new Entry(newDbEntry); } catch (Exception e) { e.printStackTrace(); } SimpleEntryGroup entry_group = new SimpleEntryGroup(); entry_group.addElement(entry); ChadoTransactionManager ctm = new ChadoTransactionManager(); entry_group.addFeatureChangeListener(ctm); entry_group.addEntryChangeListener(ctm); ctm.setEntryGroup(entry_group); transfer(entry.getAllFeatures(), qualifiersToTransfer, key, sameKey, overwrite, true, geneNames, genesUpdated); for(int j=0; j 0) genesUpdated.append(thisFeature.getSystematicName()+ " ("+key+")\n"+qualifierBuffer); } } return geneNames; } /** * Get a StringBuffer representation of the values in a StringVector * @param v * @return */ private static StringBuffer parseStringVector(final StringVector v) { StringBuffer buff = new StringBuffer(); for(int i=0; i= newarray.length) return strArr; newarray[count] = strArr[i]; count++; } if (count < newarray.length) { String[] tmparray = new String[count]; System.arraycopy(newarray, 0, tmparray, 0, count); newarray = tmparray; } return newarray; } /** * Optionally transfer GO fields with evidence code ISO and link back to * the original source in the WITH/FROM column. * @param setEvidenceAndWithFrom * @param feature * @param qName * @param values * @return */ private static StringVector getTransferValues(final boolean setEvidenceAndWithFrom, final Feature feature, final String qName, final StringVector values) { if(!setEvidenceAndWithFrom) return values; if(qName.equals("GO") || qName.equals("product")) { final StringVector tvalues = new StringVector(); final String gene = getGeneName(feature); for (int i = 0; i < values.size(); i++) { String val = changeField("evidence=", "Inferred from Sequence Orthology", null, values.get(i)); if(gene != null) val = changeField("with=", "GeneDB:"+gene, "|", val); tvalues.add(val); } return tvalues; } return values; } private static String getGeneName(Feature feature) { try { return ((GFFStreamFeature)feature.getEmblFeature()).getChadoGene().getGeneUniqueName(); } catch(Exception e){} return null; } /** * Replace or add the value of a field in a qualifier string * @param fieldName * @param newFieldStr * @param separator * @param qualStr * @return */ private static String changeField(final String fieldName, final String newFieldStr, final String separator, String qualStr) { int idx1 = qualStr.toLowerCase().indexOf(fieldName.toLowerCase()); int idx2 = qualStr.indexOf(";", idx1); int len = fieldName.length(); if(idx2 > idx1 && idx1 > -1) { if(separator != null) qualStr = qualStr.substring(0, idx2) + separator + newFieldStr + qualStr.substring(idx2); else qualStr = qualStr.substring(0, idx1+len) + newFieldStr + qualStr.substring(idx2); } else if(idx1 > -1) { if(separator != null) qualStr = qualStr + separator + newFieldStr; else qualStr = qualStr.substring(0, idx1+len) + newFieldStr; } else if(!newFieldStr.equals("")) qualStr = qualStr + ";" + fieldName + newFieldStr; return qualStr; } } class QualifierPanel extends JPanel { private static final long serialVersionUID = 1L; private Hashtable> qualifierCheckBoxes = new Hashtable>(); private Feature feature; protected int nrows = 0; public QualifierPanel(Feature feature, String title) { super(new GridBagLayout()); this.feature = feature; TitledBorder titleBorder = BorderFactory.createTitledBorder( BorderFactory.createEtchedBorder(EtchedBorder.LOWERED), title); titleBorder.setTitleJustification(TitledBorder.LEFT); titleBorder.setTitleColor(TransferAnnotationTool.STEEL_BLUE); setBorder(titleBorder); GridBagConstraints c = new GridBagConstraints(); c.anchor = GridBagConstraints.WEST; c.ipadx = 0; final QualifierVector qualifiers = feature.getQualifiers(); for(int i = 0; i < qualifiers.size(); i++) { nrows = addQualifierComponents(qualifiers.get(i), qualifierCheckBoxes, c, nrows); } setMinimumSize(new Dimension( titleBorder.getMinimumSize(this).width, getMinimumSize().height)); } /** * Add a qualifier to the list of transferable annotation * @param qualifier * @param qualifierCheckBoxes * @param c * @param nrows * @return */ private int addQualifierComponents( final Qualifier qualifier, final Hashtable> qualifierCheckBoxes, final GridBagConstraints c, int nrows) { if(TransferAnnotationTool.isNonTransferable(qualifier.getName())) return nrows; final JCheckBox qualifierNameCheckBox = new JCheckBox(qualifier.getName(), false); c.gridx = 1; c.fill = GridBagConstraints.NONE; c.weightx = 0.d; Box qualifierValueBox = Box.createVerticalBox(); JButton detailsShowHide = new JButton("+"); final Vector qualifierValuesCheckBox = setExpanderButton(detailsShowHide, qualifier, qualifierValueBox, qualifierNameCheckBox); qualifierNameCheckBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { if(qualifierNameCheckBox.isSelected()) { for(int i=0; i setExpanderButton(final JButton butt, final Qualifier qualifier, final Box qualifierValueBox, final JCheckBox qualifierNameCheckBox) { butt.setMargin(new Insets(0, 0, 0, 0)); butt.setHorizontalAlignment(SwingConstants.RIGHT); butt.setHorizontalTextPosition(SwingConstants.RIGHT); butt.setBorderPainted(false); butt.setFont(butt.getFont().deriveFont(Font.BOLD)); butt.setForeground(TransferAnnotationTool.STEEL_BLUE); butt.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (butt.getText().equals("+")) butt.setText("-"); else butt.setText("+"); qualifierValueBox.setVisible(butt.getText().equals("-")); revalidate(); } }); // set-up qualifier values list qualifierValueBox.setVisible(false); final Vector qualifierValuesCheckBox = new Vector(); final StringVector values = qualifier.getValues(); if(values != null) { for (int i = 0; i < values.size(); i++) { final JCheckBox cb = new JCheckBox(values.get(i), qualifierNameCheckBox.isSelected()); cb.setFont(cb.getFont().deriveFont(Font.ITALIC)); qualifierValueBox.add(cb); qualifierValuesCheckBox.add(cb); } } return qualifierValuesCheckBox; } protected Hashtable> getQualifierCheckBoxes() { return qualifierCheckBoxes; } protected Feature getFeature() { return feature; } } /** * Test if the feature is nominated to have annotation transferred * to it. For genes in a chado database it looks at the gene name * and transcript name. */ class TransferFeaturePredicate implements FeaturePredicate { private String geneName; private String key; private boolean sameKey; private boolean isDatabaseEntry; private String[] geneNames; public TransferFeaturePredicate(final String key, final boolean sameKey, final boolean isDatabaseEntry, final String[] geneNames) { this.key = key; this.sameKey = sameKey; this.isDatabaseEntry = isDatabaseEntry; this.geneNames = geneNames; } public boolean testPredicate(Feature targetFeature) { String targetKey = targetFeature.getKey().getKeyString(); if (sameKey && !targetKey.equals(key)) return false; Vector chadoNames = null; if (isDatabaseEntry) { GFFStreamFeature gffFeature = ((GFFStreamFeature) targetFeature.getEmblFeature()); if (gffFeature.getChadoGene() != null) { chadoNames = new Vector(); ChadoCanonicalGene chadoGene = gffFeature.getChadoGene(); chadoNames.add(chadoGene.getGeneUniqueName()); List transcripts = chadoGene.getTranscripts(); for(int i=0;i