/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * 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 Lesser General Public License * for more details. * * Last commit: $Rev: 1930 $ by $Author: david@nixbioinf.org $ on $Date:: 2010-07-29 #$ */ package org.eurocarbdb.application.glycanbuilder; import java.awt.*; import java.io.*; import java.util.*; import javax.swing.*; import javax.swing.table.*; import javax.swing.event.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import javax.xml.transform.sax.TransformerHandler; import org.xml.sax.helpers.AttributesImpl; /** An instance of the BaseDocument class that stores glycan structure with the list of computed fragments. @author Alessio Ceroni (a.ceroni@imperial.ac.uk) */ public class FragmentDocument extends BaseDocument implements SAXUtils.SAXWriter { protected Vector<Glycan> theStructures; protected Vector<FragmentCollection> theFragments; protected Vector<Double> theFragmentMZs; protected Vector<FragmentGroup> theFragmentGroups; protected Vector<StructuresChangeListener> sc_listeners = new Vector<StructuresChangeListener>(); /** Listener for changes in the number and identity of the structures contained in the document. For changes in the list of fragments see {@link BaseDocument.DocumentChangeListener} */ public interface StructuresChangeListener { public void structuresChanged(StructuresChangeEvent e); } /** Basic event object used to notify the listeners of a change in the number or identity of the structures contained in the document. */ public static class StructuresChangeEvent { protected FragmentDocument source; /** Default constructor @param _source the document that originated the event */ public StructuresChangeEvent(FragmentDocument _source) { source = _source; } /** Return the document that originated the event */ public FragmentDocument getSource() { return source; } } //---------------- /** Empty constructor */ public FragmentDocument() { super(); } //---------------- DATA ACCESS ----------------- public int size() { return theFragments.size(); } /** Return the number of glycan structures in the document */ public int getNoStructures() { return theStructures.size(); } /** Return the number of different mass/charge values corresponding to the fragments in the document. */ public int getNoPeaks() { return theFragmentMZs.size(); } public String getName() { return "Fragments"; } public javax.swing.ImageIcon getIcon() { return FileUtils.themeManager.getImageIcon("fragmentdoc"); } public Collection<javax.swing.filechooser.FileFilter> getFileFormats() { Vector<javax.swing.filechooser.FileFilter> filters = new Vector<javax.swing.filechooser.FileFilter>(); filters.add(new ExtensionFileFilter("gwf", "GlycoWorkbench fragments file")); return filters; } public javax.swing.filechooser.FileFilter getAllFileFormats() { return new ExtensionFileFilter("gwf", "Fragments files"); } //----------- // data /** Copy the content of another document */ public void copy(FragmentDocument src) { setData(src,true); } private void setData(FragmentDocument src, boolean fire) { if( src!=null ) { // clear initData(); // copy for( int i=0; i<src.getNoStructures(); i++ ) addFragments(src.getStructure(i),src.getFragments(i)); // fire events if( fire ) { fireStructuresChanged(); fireDocumentChanged(); } } } /** Return the list of structures contained in the document */ public Collection<Glycan> getStructures() { return theStructures; } /** Return a specific structure contained in the document @param s_ind the index of the structure, from 0 to {@link #getNoStructures} */ public Glycan getStructure(int s_ind) { return theStructures.elementAt(s_ind); } /** Return the list of fragments for a specific structure contained in the document @param s_ind the index of the structure, from 0 to {@link #getNoStructures} */ public FragmentCollection getFragments(int s_ind) { return theFragments.elementAt(s_ind); } /** Return a specific mass/charge value from the ones contained in the document @param p_ind the index of the mass/charge value, from 0 to {@link #getNoPeaks} */ public Double getFragmentMZ(int p_ind) { return theFragmentMZs.elementAt(p_ind); } /** Return the fragments associated with a specific mass/charge value from the ones contained in the document @param p_ind the index of the mass/charge value, from 0 to {@link #getNoPeaks} */ public FragmentGroup getFragmentGroup(int p_ind) { return theFragmentGroups.elementAt(p_ind); } /** Return the fragments associated with a specific mass/charge value and structure from the ones contained in the document @param p_ind the index of the mass/charge value, from 0 to {@link #getNoPeaks} @param s_ind the index of the structure, from 0 to {@link #getNoStructures} */ public Vector<Glycan> getFragments(int p_ind, int s_ind) { return theFragmentGroups.elementAt(p_ind).getFragments(s_ind); } /** Add a glycan structure and the corresponding list of fragments to the document. Send a {@link FragmentDocument.StructuresChangeEvent} and a {@link BaseDocument.DocumentChangeEvent} to the listeners @return <code>true</code> if the operation was successful */ public boolean addFragments(Glycan _structure, FragmentCollection _fragments) { return addFragments(_structure,_fragments,true); } /** Add a glycan structure and the corresponding list of fragments to the document. @param fire if <code>true</code> send a {@link FragmentDocument.StructuresChangeEvent} and a {@link BaseDocument.DocumentChangeEvent} to the listeners @return <code>true</code> if the operation was successful */ public boolean addFragments(Glycan _structure, FragmentCollection _fragments, boolean fire) { if( _structure==null ) return false; if( _fragments==null ) _fragments = new FragmentCollection(); Glycan toadds = _structure.clone(); FragmentCollection toaddf = _fragments.clone(); theStructures.add(toadds); theFragments.add(toaddf); for( FragmentEntry fe : toaddf.getFragments() ) addFragment(theStructures.size()-1,fe); if( fire ) { fireStructuresChanged(); fireDocumentChanged(); } return true; } /** Add a collection of fragments to a specific glycan structure. Send a {@link BaseDocument.DocumentChangeEvent} to the listeners @return <code>true</code> if the operation was successful */ public boolean addFragments(int s_ind, Collection<FragmentEntry> toadd) { if( s_ind<0 || s_ind>=size() ) return false; boolean added = false; for( FragmentEntry fe : toadd ) { added |= theFragments.elementAt(s_ind).addFragment(fe); if( added ) addFragment(s_ind,fe); } if( added ) fireDocumentChanged(); return added; } private void addFragment(int s_ind, FragmentEntry fe) { // search position int p_ind = 0; for( ; p_ind<theFragmentMZs.size() && theFragmentMZs.elementAt(p_ind)<fe.mz_ratio; p_ind++); // if empty or non existing mz valu add if( p_ind==theFragmentMZs.size() || theFragmentMZs.elementAt(p_ind)>(fe.mz_ratio+0.000001) ) { theFragmentMZs.insertElementAt(fe.mz_ratio,p_ind); theFragmentGroups.insertElementAt(new FragmentGroup(),p_ind); } // make sure every fragment group is the same size for( FragmentGroup fg : theFragmentGroups ) fg.assertSize(s_ind); // add entry to fragment group theFragmentGroups.elementAt(p_ind).addFragment(s_ind,fe); } /** Remove a structure and all its corresponding fragments. Send a {@link FragmentDocument.StructuresChangeEvent} and a {@link BaseDocument.DocumentChangeEvent} to the listeners @param s_ind the index of the structure, from 0 to {@link #getNoStructures} @return <code>true</code> if the operation was successful */ public boolean removeFragments(int s_ind) { if( s_ind<0 || s_ind>=size() ) return false; theStructures.removeElementAt(s_ind); theFragments.removeElementAt(s_ind); for( int p_ind=0; p_ind<theFragmentMZs.size(); p_ind++ ) { theFragmentGroups.elementAt(p_ind).removeFragments(s_ind); if( theFragmentGroups.elementAt(p_ind).isEmpty() ) { theFragmentGroups.removeElementAt(p_ind); theFragmentMZs.removeElementAt(p_ind); --p_ind; } } fireStructuresChanged(); fireDocumentChanged(); return true; } /** Remove all the fragments at the specified mass/charge values from all the structures. Send a {@link BaseDocument.DocumentChangeEvent} to the listeners @param p_inds the list of indexes of the mass/charge values, each one from 0 to {@link #getNoPeaks} @return <code>true</code> if the operation was successful */ public boolean clearFragmentsFor(int[] p_inds) { Vector<Double> toremove_mzs = new Vector<Double>(); for( int i=0; i<p_inds.length; i++ ) toremove_mzs.add(theFragmentMZs.elementAt(p_inds[i])); Vector<FragmentGroup> toremove_groups = new Vector<FragmentGroup>(); for( int i=0; i<p_inds.length; i++ ) { FragmentGroup fg = theFragmentGroups.elementAt(p_inds[i]); for( int s_ind=0; s_ind<theStructures.size(); s_ind++ ) theFragments.elementAt(s_ind).removeFragments(fg.getFragmentEntries(s_ind)); toremove_groups.add(fg); } theFragmentMZs.removeAll(toremove_mzs); theFragmentGroups.removeAll(toremove_groups); fireDocumentChanged(); return true; } /** Remove a collection of fragments from the list corresponding to a particular a structure. Send a {@link FragmentDocument.StructuresChangeEvent} and a {@link BaseDocument.DocumentChangeEvent} to the listeners @param s_ind the index of the structure, from 0 to {@link #getNoStructures} @param toremove the list of fragment entries to be removed @return <code>true</code> if the operation was successful */ public boolean removeFragments(int s_ind, Collection<FragmentEntry> toremove) { if( s_ind<0 || s_ind>=size() ) return false; boolean removed = false; for( FragmentEntry fe : toremove ) { removed |= theFragments.elementAt(s_ind).removeFragment(fe); if( removed ) { int p_ind = removeFragment(s_ind,fe); if( p_ind!=-1 ) { theFragmentGroups.removeElementAt(p_ind); theFragmentMZs.removeElementAt(p_ind); } } } if( removed ) fireDocumentChanged(); return removed; } private int removeFragment(int s_ind, FragmentEntry fe) { // search position int p_ind = 0; for( ; p_ind<theFragmentMZs.size() && theFragmentMZs.elementAt(p_ind)<fe.mz_ratio; p_ind++); // if empty or non existing mz value return -1 if( p_ind==theFragmentMZs.size() || theFragmentMZs.elementAt(p_ind)>(fe.mz_ratio+0.000001) ) return -1; // remove entry from fragment group theFragmentGroups.elementAt(p_ind).removeFragment(s_ind,fe); return p_ind; } //--------------- // initialization public void initData() { theStructures = new Vector<Glycan>(); theFragments = new Vector<FragmentCollection>(); theFragmentMZs = new Vector<Double>(); theFragmentGroups = new Vector<FragmentGroup>(); } //--------------- // events /** Register a new listener for changes in the structure list */ public void addStructuresChangeListener(StructuresChangeListener l) { if( l!=null ) sc_listeners.add(l); } /** Deregister an existing listener for changes in the structure list */ public void removeStructuresChangeListener(StructuresChangeListener l) { if( l!=null ) sc_listeners.remove(l); } /** Send a structure list change event to the listeners */ public void fireStructuresChanged() { if( sc_listeners!=null ) { for( StructuresChangeListener scl : sc_listeners ) scl.structuresChanged(new StructuresChangeEvent(this)); } } /** Send a document init event to all listeners and reset the <code>changed</code> flag. Also send a structure list change event to the corresponding listeners */ public void fireDocumentInit() { super.fireDocumentInit(); fireStructuresChanged(); } /** Send a document init event to all listeners with a different source and reset the <code>changed</code> flag. Also send a structure list change event to the corresponding listeners if the <code>source</code> is this document */ public void fireDocumentInit(BaseDocument source) { super.fireDocumentInit(source); if( source==this ) fireStructuresChanged(); } //--------------- // serialization protected void read(InputStream is, boolean merge) throws Exception { /*Document document = XMLUtils.read(bis); if( document==null ) throw new Exception("Cannot read from string"); Node root_node = XMLUtils.assertChild(document,"Fragments"); fromXML(root_node,merge);*/ SAXUtils.read(is,new SAXHandler(this,merge)); } public void fromString(String str, boolean merge) throws Exception { read(new ByteArrayInputStream(str.getBytes()),merge); } public String toString() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); write(bos); return bos.toString(); } catch(Exception e) { LogUtils.report(e); return ""; } } public void write(OutputStream os) throws Exception { /* Document document = XMLUtils.newDocument(); if( document==null ) return ""; Element root_node = toXML(document); if( root_node == null ) return ""; document.appendChild(root_node); XMLUtils.write(os,document);*/ SAXUtils.write(os,this); } /** Create a new document from its XML representation as part of a DOM tree. */ public void fromXML(Node root_node, boolean merge) throws Exception { // clear if( !merge ) { resetStatus(); initData(); } else setChanged(true); // read data Vector<Glycan> structures = new Vector<Glycan>(); Vector<FragmentCollection> fragments = new Vector<FragmentCollection>(); // read structures Vector<Node> s_nodes = XMLUtils.findAllChildren(root_node, "Glycan"); for( Node s_node : s_nodes) structures.add(Glycan.fromXML(s_node,new MassOptions())); // read fragments Vector<Node> fc_nodes = XMLUtils.findAllChildren(root_node, "FragmentCollection"); for( Node fc_node : fc_nodes) fragments.add(FragmentCollection.fromXML(fc_node)); if( structures.size()!=fragments.size() ) throw new Exception("Invalid number of fragments"); // add all for( int i=0; i<structures.size(); i++ ) addFragments(structures.elementAt(i),fragments.elementAt(i),false); } /** Create an XML representation of this object to be part of a DOM tree. */ public Element toXML(Document document) { if( document==null ) return null; // create root node Element root_node = document.createElement("Fragments"); if( root_node==null ) return null; // add structures for(Glycan s : theStructures ) root_node.appendChild(s.toXML(document)); // add fragments for(FragmentCollection fc : theFragments ) root_node.appendChild(fc.toXML(document)); return root_node; } /** Default SAX handler to read a representation of this object from an XML stream. */ public static class SAXHandler extends SAXUtils.ObjectTreeHandler { private FragmentDocument theDocument; private boolean merge; /** Construct a new handler. @param _doc recipient for the data parsed from the XML @param _merge if <code>true</code> append the new data to the existing document. */ public SAXHandler(FragmentDocument _doc, boolean _merge) { theDocument = _doc; merge = _merge; } public boolean isElement(String namespaceURI, String localName, String qName) { return qName.equals(getNodeElementName()); } /** Return the element tag recognized by this handler */ public static String getNodeElementName() { return "Fragments"; } protected SAXUtils.ObjectTreeHandler getHandler(String namespaceURI, String localName, String qName) { if( qName.equals(Glycan.SAXHandler.getNodeElementName()) ) return new Glycan.SAXHandler(new MassOptions()); if( qName.equals(FragmentCollection.SAXHandler.getNodeElementName()) ) return new FragmentCollection.SAXHandler(); return null; } protected Object finalizeContent(String namespaceURI, String localName, String qName) throws SAXException{ // clear if( !merge ) { theDocument.resetStatus(); theDocument.initData(); } else theDocument.setChanged(true); // read data Vector<Object> structures = getSubObjects(Glycan.SAXHandler.getNodeElementName()); Vector<Object> fragments = getSubObjects(FragmentCollection.SAXHandler.getNodeElementName()); if( structures.size()!=fragments.size() ) throw new SAXException(createMessage("Invalid number of fragments")); // add all for( int i=0; i<structures.size(); i++ ) theDocument.addFragments((Glycan)structures.elementAt(i),(FragmentCollection)fragments.elementAt(i),false); return (object = theDocument); } } public void write(TransformerHandler th) throws SAXException { th.startElement("","","Fragments",new AttributesImpl()); for(Glycan s : theStructures ) s.write(th); for(FragmentCollection fc : theFragments ) fc.write(th); th.endElement("","","Fragments"); } }