/*
* 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: 1561 $ by $Author: glycoslave $ on $Date:: 2009-07-21 #$
*/
package org.eurocarbdb.sugar;
// stblib imports
import java.util.Iterator;
// 3rd party imports
import org.apache.log4j.Logger;
// eurocarb imports
import org.eurocarbdb.util.graph.Graph;
import org.eurocarbdb.util.graph.Edge;
import org.eurocarbdb.util.graph.Vertex;
import org.eurocarbdb.sugar.SequenceFormat;
// static imports
import static org.eurocarbdb.util.graph.Graphs.unmodifiableGraph;
/* class Sugar *//*************************************************
*<p>
* This class provides numerous methods and other facilities for creating
* and manipulating oligosaccharides. Sugars (oligosaccharides) are
* essentially modelled in this class as a directed {@link Graph graph} of
* {@link Residue}s and {@link Linkage}s.
*</p>
*<p>
* Usage normally involves creating a Sugar object with an initial
* (root) residue, which is then elaborated by the addition of
* further residues with the <code>addResidue</code> method.
*</p>
*
* @see Residue
* @see Graph
* @author mjh
*/
public class Sugar extends BasicMolecule
implements Cloneable, PotentiallyIndefinite, Iterable<Residue>
{
/** Logging instance. */
protected static final Logger log = Logger.getLogger( Sugar.class );
//~~~~~~~~~~~~~~~~~~~~ OBJECT FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~//
/** Internal graph of residues in this sugar. */
protected Graph<Linkage,Residue> graph;
/** Sequence of this sugar. */
protected SugarSequence sequence;
/** The root residue */
protected Residue rootResidue;
/** The molecule attached to the reducing terminus of this sugar, if any. */
private Molecule aglyconResidue = null;
/** The linkage by which {@link #aglyconResidue} is attached to this sugar. */
private Linkage aglyconLinkage = null;
//~~~~~~~~~~~~~~~~~~~~~ CONSTRUCTORS ~~~~~~~~~~~~~~~~~~~~~~~~//
/**
* Constructs a 'null' (empty) Sugar.
*/
public Sugar()
{
// assume most sugars will be 8 residues or less
graph = new Graph<Linkage,Residue>( 8 );
}
/**
* Constructs an empty Sugar pre-allocated for given, expected
* number of residues
*/
public Sugar( int initial_size )
{
graph = new Graph<Linkage,Residue>( initial_size );
}
/* Constructor *//*********************************************
*
* Constructs a Sugar with a single (root) residue.
*/
public Sugar( Residue root )
{
this();
addRootResidue( root );
}
//~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~//
/* addLinkage *//**********************************************
*<p>
* Adds a linkage between two residues that are already present
* in this sugar.
*</p>
*<p>
* As the default names of the residue variables in the argument
* list suggests - the order of residues in the argument list implies
* linkage directionality.
*</p>
* @param child
* @param link
* @param parent
*/
public void addLinkage( Residue parent, Linkage linkage, Residue child )
{
// no args can be null
if ( parent == null )
throw new IllegalArgumentException(
"Argument 'parent' cannot be null");
if ( child == null )
throw new IllegalArgumentException(
"Argument 'child' cannot be null");
if ( linkage == null )
throw new IllegalArgumentException(
"Argument 'linkage' cannot be null");
if ( graph.isEmpty() )
throw new IllegalArgumentException(
"Sugar does not contain any residues -- try adding "
+ "a residue or two before trying to add linkages" );
// check parent is IN sugar, and that child ISN'T.
// these are not expensive checks, since contains calls
// are hashtable lookups.
if ( ! graph.contains( parent ) )
throw new IllegalArgumentException(
"Parent residue does not exist in this sugar");
if ( ! graph.contains( child ) )
throw new IllegalArgumentException(
"Child residue does not exist in this sugar");
graph.addEdge(
graph.getVertex( parent ),
graph.getVertex( child ),
linkage
);
return;
}
/* addResidue *//**********************************************
*
* Adds a new, unlinked residue to the current sugar. If this
* sugar has no residues then the residue added will become the
* root residue.
*
* @see #addLinkage
* @see #addRootResidue
*/
public void addResidue( Residue r )
{
if ( r == null )
throw new IllegalArgumentException(
"residue argument cannot be null");
if ( graph.isEmpty() )
{
addRootResidue( r );
return;
}
if ( log.isDebugEnabled() )
log.debug( "adding new residue " + r );
graph.addVertex( r );
return;
}
/* addResidue *//**********************************************
*
* Core method for elaborating/extending sugars, which adds both a
* new (child) residue and linkage to an existing (parent) residue
* in the sugar. This method is equivalent to calling:
* {@link #addResidue(Residue)} and {@link #addLinkage} in succession.
*
* @throws IllegalArgumentException if any of the arguments are null
*/
public void addResidue( Residue parent, Linkage linkage, Residue child )
{
// make sure none of the arguments are null. nulls are bad.
if ( parent == null )
throw new IllegalArgumentException(
"Invalid argument: parent residue cannot be null");
if ( graph.isEmpty() || ! graph.contains( parent ) )
addRootResidue( parent );
if ( parent != child )
addResidue( child );
addLinkage( parent, linkage, child );
return;
}
/* addRootResidue *//***************************************
*
* Adds the given residue to the root of this sugar. Only callable
* if no other residues have yet been added to this sugar.
*
* @param root
* The residue that will become the root residue of this sugar.
*/
public void addRootResidue( Residue root )
{
if ( graph.size() > 0 )
throw new UnsupportedOperationException(
"Sugar already has "
+ this.countResidues()
+ " residue(s), use method "
+ "addRootResidue( Linkage, Residue ) to "
+ "add a new root residue to an existing structure"
);
if ( log.isDebugEnabled() )
log.debug( "adding initial root residue " + root );
graph.addVertex( root );
rootResidue = root;
return;
}
/* addRootResidue *//******************************************
*
* Adds the given residue to the root of this sugar, displacing the
* this root residue with the passed residue.
*
* @param link
* The desired linkage to create between the old root residue and
* the newly introduced one. Cannot be null.
* @param new_root
* The residue that will become the new root residue of this sugar.
*/
public void addRootResidue( Linkage linkage, Residue new_root )
{
assert( ! this.contains( new_root ) );
if ( linkage == null )
throw new IllegalArgumentException(
"Invalid argument: linkage cannot be null");
log.debug( "adding root residue " + new_root );
graph.addVertex( new_root );
graph.addEdge( graph.getVertex( rootResidue ),
graph.lastVertex(),
linkage
);
return;
}
@Override
@SuppressWarnings("unchecked") // <- I hate java...
public Object clone()
{
Sugar copy = null;
try
{
copy = (Sugar) super.clone();
copy.graph = (Graph<Linkage,Residue>) this.graph.clone();
}
catch ( CloneNotSupportedException e ) { e.printStackTrace(); }
return copy;
}
/* contains *//************************************************
*
* Returns true if the given residue is found in this sugar;
* false otherwise. Note that this method tests for the existance
* of a *specific* residue instance, not for a residue *type*.
*
* This method cannot be used, for instance, to test for the
* presence of mannose, only for a <em>specific</em> mannose residue.
*/
public boolean contains( Residue residue )
{
return graph.contains( residue );
}
/* countResidues *//*******************************************
*
* Returns the number of residues in this Sugar.
*/
public int countResidues()
{
return graph.countVertices();
}
/* getAglycon *//**********************************************
*
* Returns the molecule attached to the reducing terminus of this
* sugar, if any.
*/
public Molecule getAglycon()
{
return this.aglyconResidue;
}
/* getAglyconLinkage *//***************************************
*
* Returns the linkage of the molecule attached to the reducing
* terminus of this sugar, if applicable.
*/
public Linkage getAglyconLinkage()
{
return this.aglyconLinkage;
}
/* getComposition *//******************************************
*
*/
public Composition<Residue> getComposition()
{
return null;
}
/* getGraph *//************************************************
*
* Returns an unmodifiable graph of the linkages and residues
* comprising this sugar.
*
* @see Graphs#unmodifiableGraph
*/
public Graph<Linkage,Residue> getGraph()
{
return unmodifiableGraph( graph );
}
/* getRootResidue *//******************************************
*
* Returns the reducing terminal (root) residue of this sugar.
* Returns null if this Sugar is empty (ie: has no residues).
*/
public Residue getRootResidue()
{
return rootResidue;
}
/* getSequence *//*********************************************
*
* Implicit access to the object encapsulating the
* sequence of this sugar.
*/
public SugarSequence getSequence()
{
return this.sequence;
}
public boolean isDefinite()
{
Residue r;
for ( Vertex<Linkage,Residue> v : graph )
{
r = v.getValue();
if ( r instanceof PotentiallyIndefinite )
if ( ! ((PotentiallyIndefinite) r).isDefinite() )
return false;
}
Linkage l;
for ( Edge<Linkage,Residue> e : graph.getAllEdges() )
{
l = e.getValue();
if ( l instanceof PotentiallyIndefinite )
if ( ! ((PotentiallyIndefinite) l).isDefinite() )
return false;
}
return true;
}
/* iterator *//************************************************
*
* Returns an iterator over all residues in this sugar in
* no particular order.
*
* @see java.lang.Iterable#iterator()
*/
public Iterator<Residue> iterator()
{
return graph.getAllVertexValues().iterator();
}
/* lastResidue *//*********************************************
*
* Returns the most recently added residue.
*/
public Residue lastResidue()
{
return graph.lastVertex().getValue();
}
/* setAglycon *//**********************************************
*
* Sets the molecule/linkage attached to the reducing terminus of
* this sugar.
*/
public void setAglycon( Linkage l, Molecule m )
{
assert l != null;
assert m != null;
this.aglyconLinkage = l;
this.aglyconResidue = m;
}
/* toString *//************************************************
*
* Returns the string representation of this sugar's sequence in
* the default {@link SequenceFormat}.
*/
public String toString()
{
return SugarSequence.DEFAULT_SEQUENCE_FORMAT.getSequence( this );
}
/* toString *//************************************************
*
* Returns the string representation of this sugar's sequence in
* the given {@link SequenceFormat}.
*/
public String toString( SequenceFormat format )
{
assert format != null;
return format.getSequence( this );
}
} // end class