/*
* 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.impl;
// stdlib imports
import java.util.Set;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Collections;
// 3rd party imports
import org.apache.log4j.Logger;
// eurocarb imports
import org.eurocarbdb.util.BitSet;
import org.eurocarbdb.util.StringUtils;
import org.eurocarbdb.sugar.Anomer;
import org.eurocarbdb.sugar.Basetype;
import org.eurocarbdb.sugar.Superclass;
import org.eurocarbdb.sugar.StereoConfig;
import org.eurocarbdb.sugar.RingConformation;
import org.eurocarbdb.sugar.CommonBasetype;
import org.eurocarbdb.sugar.PotentiallyIndefinite;
import org.eurocarbdb.sugar.Basetypes;
// import org.eurocarbdb.sugar.Attachable;
import org.eurocarbdb.sugar.Substituent;
import org.eurocarbdb.sugar.BasicMolecule;
import org.eurocarbdb.sugar.SequenceFormat;
import org.eurocarbdb.sugar.Monosaccharide;
import org.eurocarbdb.sugar.Substituent;
import org.eurocarbdb.sugar.PositionOccupiedException;
import org.eurocarbdb.sugar.PositionNotOccupiedException;
import org.eurocarbdb.sugar.SequenceFormatException;
import org.eurocarbdb.sugar.SugarChemistryException;
// static imports
import static java.util.Collections.unmodifiableList;
import static org.eurocarbdb.util.StringUtils.join;
import static org.eurocarbdb.sugar.Basetypes.getBasetype;
import static org.eurocarbdb.sugar.Basetypes.getNormalisedBasetype;
import static org.eurocarbdb.sugar.RingConformation.OpenChain;
import static org.eurocarbdb.sugar.CommonSubstituent.*;
import static org.eurocarbdb.sugar.CarbohydrateChemistry.getCarbohydrateChemistry;
/**
* Straightforward implementation of the {@link Monosaccharide} interface.
*
* @author mjh
*/
public class SimpleMonosaccharide extends BasicMolecule implements Monosaccharide
{
//~~~~~~~~~~~~~~~~~~~~~~ STATIC FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Logging instance. */
static final Logger log = Logger.getLogger( SimpleMonosaccharide.class );
//~~~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private Anomer anomer;
private Basetype basetype;
private RingConformation conformation;
/** Stereochemical configuration, defaults to the {@link StereoConfig}
* of the {@link Basetype} set. */
private StereoConfig stereo;
/** {@link List} of {@link Substituent} substituents, lazily instantiated. */
private List<Substituent> attached = null; //Collections.emptyList();
private List<Substituent> unknowns = null; //Collections.emptyList();
//~~~~~~~~~~~~~~~~~~~~~~ CONSTRUCTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~
SimpleMonosaccharide() {}
/**
* Constructs a new Monosaccharide based on the given {@link Basetype},
* whose anomeric configuration, stereochemistry and ring conformation
* are all set to their respective defaults.
*
* @see Anomer.DefaultAnomer
* @see RingConformation.DefaultRingConformation
*/
public SimpleMonosaccharide( Basetype bt )
{
this(
Anomer.DefaultAnomer,
bt.getStereoConfig(),
bt,
RingConformation.DefaultRingConformation
);
}
/**
* Constructs a new Monosaccharide based on the given {@link Basetype},
* with the given {@link StereoConfig}, with default anomeric configuration
* and ring conformation.
*
* @see Anomer.DefaultAnomer
* @see RingConformation.DefaultRingConformation
*/
public SimpleMonosaccharide( StereoConfig dl, CommonBasetype bt )
{
this(
Anomer.DefaultAnomer,
dl,
Basetypes.getBasetype( dl, bt ),
RingConformation.DefaultRingConformation
);
}
/**
* Constructs a new Monosaccharide based on the given {@link Basetype},
* with the given {@link StereoConfig}, {@link Anomer}, and {@link RingConformation}.
*
* @throws SugarChemistryException if the given chemistry does not conform
* to fundamental rules of {@link CarbohydrateChemistry}.
*/
public SimpleMonosaccharide( Anomer a, StereoConfig dl, Basetype bt, RingConformation rc )
throws SugarChemistryException
{
stereo = dl;
basetype = bt;
setAnomer( a );
setRingConformation( rc );
}
//~~~~~~~~~~~~~~~~~~~~~~ STATIC METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Attempts to parse a {@link SimpleMonosaccharide} from the
* given {@link String}. The expected syntax is given by the
* {@link Basetypes.getBasetype(String)} method.
*
* @throws SequenceFormatException for syntactic errors
* @throws SugarChemistryException for semantic errors
*/
public static final SimpleMonosaccharide forName( String name )
throws SequenceFormatException, SugarChemistryException
{
log.warn("move this code into ResidueFormat subclass");
return new SimpleMonosaccharide( Basetypes.getBasetype( name ) );
}
//~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** {@inheritDoc} @see org.eurocarbdb.sugar.BasicMolecule#getName */
@Override
public String getName()
{
List<String> bits = new ArrayList<String>();
if ( this.unknowns != null )
{
for ( Substituent s : unknowns )
bits.add( "?-" + s.getName() );
}
if ( this.attached != null )
{
List<Substituent> btSubstits = getBasetype().getFunctionalGroups();
assert btSubstits.size() == attached.size();
for ( int i = 0; i < attached.size(); i++ )
if ( attached.get(i) != null && btSubstits.get(i) != attached.get(i) )
bits.add( "" + (i + 1) + "-" + attached.get(i).getName() );
}
bits.add( getBasetype().getName() );
return join( "-", bits );
}
/* implementation of Monosaccharide interface */
public Anomer getAnomer()
{
return anomer;
}
public void setAnomer( Anomer a )
throws IllegalArgumentException, SugarChemistryException
{
getCarbohydrateChemistry().checkAnomer( a, this );
anomer = a;
}
public Basetype getBasetype()
{
return basetype;
}
public RingConformation getRingConformation()
{
return conformation;
}
public void setRingConformation( RingConformation rc )
throws IllegalArgumentException, SugarChemistryException
{
getCarbohydrateChemistry().checkRingConformation( rc, this );
conformation = rc;
}
int getRingStart()
{
if ( ! getRingConformation().isClosedRing() )
return -1;
for ( int i = 0; i < getSuperclass().size(); i++ )
if ( _functional_group(i) == Carbonyl )
return i + 1;
return -1;
}
int getRingEnd()
{
if ( ! getRingConformation().isClosedRing() )
return -1;
int ringStart = getRingStart();
if ( ringStart == -1 )
return -1;
return ringStart + getRingConformation().getRingSize() - 1;
}
public StereoConfig getStereoConfig()
{
return stereo;
}
public Superclass getSuperclass()
{
return basetype.getSuperclass();
}
public boolean isDefinite()
{
if ( anomer != null && ! anomer.isDefinite() )
return false;
if ( basetype != null && ! basetype.isDefinite() )
return false;
if ( stereo != null && ! stereo.isDefinite() )
return false;
return true;
}
/* implementation of Attachable interface */
/**
* {@inheritDoc}
*
* @throws PositionOccupiedException
* If the given {@link Substituent} and position conflicts with
* an existing Substituent at that position
* @throws IllegalArgumentException
* If position is greater than the number of positions available
* for this monosaccharide (see {@link #countPositions}), or
* the number of substituents exceeds {@link #countPositions}
* when substituents with unknown attachment positions are
* considered.
*/
public void attach( Substituent s, int position )
throws PositionOccupiedException, IllegalArgumentException
{
if ( position < 1 )
{
// it's an unknown
log.debug("adding substituent '" + s + "' at unknown position");
if ( unknowns == null )
unknowns = new ArrayList( 2 );
if ( unknowns.size() >= getAttachablePositions().size() )
{
throw new PositionOccupiedException(
"Cannot attach substituent '"
+ s
+ "': there are no free positions remaining"
);
}
unknowns.add( s );
}
else if ( position > countPositions() )
{
throw new IllegalArgumentException(
"Invalid position '"
+ position
+ "' for Substituent '"
+ s
+ "'; valid attachment positions for this monosaccharide are: "
+ StringUtils.join(", ", getAttachablePositions() )
);
}
else
{
// if ( _functional_group( position) == s )
// return;
if ( _position_occupied( position ) )
throw new PositionOccupiedException( this, position );
_attach( s, position );
}
}
public void unattach( int position )
{
if ( ! _position_occupied( position ) )
throw new PositionNotOccupiedException( this, position );
_init_attached();
int i = position - 1;
Substituent s = getBasetype().getFunctionalGroups().get( i );
attached.set( i, s );
// // allow detachment from basetype or not?
// throw new UnsupportedOperationException(
// "Detachment of substituents from Basetype not permitted: "
// + "basetype="
// + getBasetype()
// + ", functional groups="
// + getBasetype().getFunctionalGroups()
// + ", attempted detachment position="
// + position
// );
}
public Set<Integer> getAttachablePositions()
{
int size = getSuperclass().size();
BitSet free_positions = new BitSet( size );
for ( int i = 1; i <= size; i++ )
if ( ! _position_occupied( i ) )
free_positions.set( i );
int ringStart = getRingStart();
int ringEnd = getRingEnd();
if ( ringStart > 0 && ringEnd > 0 )
{
free_positions.clear( ringStart - 1 );
free_positions.clear( ringEnd - 1 );
}
return free_positions;
}
public Substituent getAttached( int position )
{
return _functional_group( position - 1 );
}
public int countPositions()
{
return getSuperclass().size();
}
public String toString()
{
return "["
+ getClass().getSimpleName()
+ "="
// + getBasetype()
// + substitutions
// + unknowns
+ getName()
+ "]"
;
}
//~~~~~~~~~~~~~~~~~~~~~~ PRIVATE METHODS ~~~~~~~~~~~~~~~~~~~~~~~~
/**
* Lazily instantiate {@link #attached} -- a {@link List} of
* {@link Substituent}s obtained from our {@link Basetype}.
*/
private final void _init_attached()
{
if ( attached == null )
{
List<Substituent> fgs = getBasetype().getFunctionalGroups();
attached = new ArrayList<Substituent>( fgs.size() );
for ( Substituent s : fgs )
attached.add( null );
}
assert attached.size() == getSuperclass().size();
}
/**
* Hides whether we have local substituents or we're looking at
* Basetype's substituents.
*/
private final Substituent _functional_group( int index )
{
if ( attached == null || attached.get( index ) == null )
return getBasetype().getFunctionalGroups().get( index );
else
return attached.get( index );
}
/**
* Checks if given position is free in this Monosac or its basetype
* (since our Basetypes contain functional groups too)
*/
private final boolean _position_occupied( int pos )
{
// if ( attached != null )
Substituent s = _functional_group( pos - 1 );
return s != OH && s != Carbonyl;
}
/** Called by {@link #attach} after we know it's safe to attach something. */
private final void _attach( Substituent s, int position )
{
_init_attached();
attached.set( position - 1, s );
_normalise();
}
/**
* Called after attaching or removing a substituent -- need to
* normalise Basetype to see if basetype has changed.
*/
private final void _normalise()
{
assert attached != null;
Basetype b = getNormalisedBasetype( this.basetype, this.attached );
if ( b != this.basetype )
{
log.debug("basetype changed, basetype is now: " + b );
this.basetype = b;
}
}
/** Free all local substituents */
private void unattach()
{
attached = null;
}
/*
protected static class Terminus implements Comparable<Terminus>
{
final byte index;
final Substituent attached;
public Terminus( int index, Substituent s )
{
this.index = (byte) index;
this.attached = s;
}
public int compareTo( Terminus t )
{
return ((Byte) this.index).compareTo( t.index );
}
public int getPosition()
{
return index + 1;
}
public Substituent getAttachedSubstituent()
{
return attached;
}
public String toString()
{
return getPosition() + "-" + getAttachedSubstituent().getName();
}
}
*/
} // end class Monosaccharide