/*
* 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;
// stdlib imports
import java.util.List;
import java.util.Arrays;
import java.util.ArrayList;
// 3rd party imports
import org.apache.log4j.Logger;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.ArrayListMultimap;
// eurocarb imports
import org.eurocarbdb.util.BitSet;
import org.eurocarbdb.sugar.Basetypes;
import org.eurocarbdb.sugar.Monosaccharide;
// static imports
import static org.eurocarbdb.sugar.Superclass.*;
import static org.eurocarbdb.sugar.StereoConfig.*;
import static org.eurocarbdb.sugar.RingConformation.*;
import static org.eurocarbdb.sugar.CommonSubstituent.*;
import static org.eurocarbdb.sugar.Basetypes.UnknownBasetype;
import static org.eurocarbdb.util.StringUtils.join;
/**
* Implementation of the {@link Basetype} interface for building
* mutable, user-defineable basetypes, usually based on the immutable
* basetypes in {@link CommonBasetype}.
*
* @see Basetypes
* @see CommonBasetype
* @see Substituent
*
* @author mjh
*/
public class CustomBasetype implements Basetype
{
//~~~~~~~~~~~~~~~~~~~~~~ STATIC FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~
/** logging handle */
static Logger log = Logger.getLogger( CustomBasetype.class );
static final boolean debugging = log.isDebugEnabled();
//~~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private String name = null;
/** Array of the basetypes whose stereochemistry defines this
* object's stereochemistry; basetype at index 0 is furthest from C1. */
private CommonBasetype[] basetypes;
/** Array of the stereoconfigs of each of {@link #basetypes}.
* Since stereoconfig of basetype is derived from stereoconfig of
* the chiral position furthest from the reducing carbonyl, the
* stereoconfig at index=0 is also always the stereoconfig for
* the whole basetype. */
private StereoConfig[] stereoConfigs;
/** Our superclass */
private Superclass superclass;
/** Stereochemistry of this basetype -- true in the BitSet means pointed
* right in a Fischer projection, index=0 means position=1 */
private BitSet stereochemistry;
/** BitSet specifying which basetype positions are chiral, true means chiral,
* index=0 means position=1. */
private BitSet chiralPositions;
/** List of the functional groups of this basetype, index=0 means position 1 */
private List<Substituent> functionalGroups;
//~~~~~~~~~~~~~~~~~~~~~~ CONSTRUCTORS ~~~~~~~~~~~~~~~~~~~~~~~~~~~
CustomBasetype() {}
public CustomBasetype(
StereoConfig[] stereoConfigs,
CommonBasetype[] basetypes,
Superclass superclass,
BitSet stereochem,
BitSet chiralPos,
List<Substituent> functionalGroups
)
{
// assert dl == D || dl == L;
this.stereoConfigs = stereoConfigs;
this.basetypes = basetypes;
this.superclass = superclass;
this.stereochemistry = stereochem.clone();
this.chiralPositions = chiralPos.clone();
this.functionalGroups = new ArrayList<Substituent>( functionalGroups );
}
/*
int size = 0;
List<String> names = new ArrayList<String>( basetypes.length );
for ( Basetype b : basetypes )
{
assert b.isDefinite();
assert b.getStereoConfig() == D;
size += b.getSuperclass().size();
names.add( b.getName() );
}
BitSet stereochemistry = new BitSet();
BitSet chiralPositions = new BitSet();
this.functionalGroups = new ArrayList<Substituent>( size );
for ( Basetype b : basetypes )
{
stereochemistry.append( b.getStereochemistry() );
chiralPositions.append( b.getChiralPositions() );
for ( Substituent s : b.getFunctionalGroups() )
this.functionalGroups.add( s );
}
this.superclass = Superclass.forSize( size );
this.stereochemistry = stereochemistry;
this.chiralPositions = chiralPositions;
this.name = join("-", names );
}
*/
//~~~~~~~~~~~~~~~~~~~~~ STATIC METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~
static CustomBasetype clone( Basetype b )
{
if ( b instanceof CommonBasetype )
{
return new CustomBasetype(
new StereoConfig[] { b.getStereoConfig() },
new CommonBasetype[] { (CommonBasetype) b },
b.getSuperclass(),
b.getStereochemistry(),
b.getChiralPositions(),
b.getFunctionalGroups()
);
}
else if ( b instanceof CustomBasetype )
{
CustomBasetype cb = (CustomBasetype) b;
return new CustomBasetype(
// Arrays.copyOf( cb.stereoConfigs, cb.stereoConfigs.length ),
// Arrays.copyOf( cb.basetypes, cb.basetypes.length ),
arrayCopyOf( cb.stereoConfigs ),
arrayCopyOf( cb.basetypes ),
cb.getSuperclass(),
cb.getStereochemistry(),
cb.getChiralPositions(),
cb.getFunctionalGroups()
);
}
else throw new UnsupportedOperationException();
}
/**
* Returns a (typed) copy of the passed Array.
* Needed cause we are targeting JDK1.5, and Arrays.copyOf() is JDK1.6.
*/
private static final <T> T[] arrayCopyOf( T[] array )
{
T[] copy = (T[]) java.lang.reflect.Array.newInstance(
array.getClass().getComponentType(), array.length );
System.arraycopy( array, 0, copy, 0, array.length );
return copy;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public CommonBasetype[] getComponentBasetypes()
{
return basetypes;
}
void addChiralPosition( int position, boolean preserveSuperclass )
{
int index = position - 1;
if ( preserveSuperclass )
{
assert chiralPositions.get( index ) == false;
chiralPositions.set( index, true );
return;
}
else
{
insertNewPosition( index );
chiralPositions.set( index, true );
stereochemistry.set( index, true );
}
name = null;
}
static void normalise( CustomBasetype b )
{
BitSet stereochem = b.getStereochemistry();
BitSet chiral_pos = b.getChiralPositions();
int nmb_basetypes = (int) (chiral_pos.size() / 4);
CommonBasetype[] basetypes = new CommonBasetype[ nmb_basetypes ];
/*
for ( int i = 0; i < nmb_basetypes; i++ )
{
int id =
CommonBasetype cb =
}
*/
// TODO
}
/**
* Assuming given position is a chiral position:
* If preserving superclass, just flip a bit;
* If not preserving superclass, then in order to preserve
* basetype, have to insert a new non-chiral position.
*/
void removeChiralPosition( int position, boolean preserveSuperclass )
{
int index = position - 1;
if ( preserveSuperclass )
{
assert chiralPositions.get( index ) == true;
chiralPositions.set( index, false );
return;
}
else
{
insertNewPosition( index );
chiralPositions.set( index, false );
stereochemistry.set( index, false );
}
name = null;
}
/**
* Insert a whole new position at given index, shifting
* everything up a position, effectively bumping up the
* {@link Superclass} by 1, and setting functional group
* at new position to null.
*/
private final void insertNewPosition( int index )
{
BitSet b = new BitSet( 1 ); // defaults to false
chiralPositions.bitShiftInsert( index, b );
stereochemistry.bitShiftInsert( index, b );
functionalGroups.add( index, null );
superclass = Basetypes.determineSuperclass( this );
name = null;
}
/**
* Equivalent to calling <code>{@link #setFunctionalGroup}(s,position,true)</code>;
* that is, the given {@link Substituent} will be set at the given
* position by preserving the superclass and changing the stereochemistry
* for substituents that induce stereochemical changes.
*
* @param s
* the {@link Substituent} to substitute
* @param position
* the position to substitute
* @return
* true if the stereochemistry of this basetype changed
* @see #setFunctionalGroup(Substituent,int,boolean)
*/
public boolean setFunctionalGroup( Substituent s, int position )
throws IllegalArgumentException, NullPointerException
{
return setFunctionalGroup( s, position, true );
}
/**
*<p>
* Sets the given {@link Substituent} as the functional group at
* the given position in this basetype. If the given Substituent
* causes a loss or gain of stereochemistry as a result of its
* introduction then the choice of whether to alter the
* {@link Superclass} or the {@link Basetype} is determined by
* the given boolean <code>preserveSuperclass</code> argument.
*</p>
*<p>
* If preserveSuperclass is false, then the {@link Superclass}
* of this basetype will be increased to accomodate a substituent
* that causes a loss of stereochemistry, and decreased for a
* substituent that introduces a new stereo-genic centre.
*</p>
*<p>
* If preserveSuperclass is true, then {@link Superclass} will
* be preserved, which means that substituents that induce a
* change of stereochemistry will change the {@link Basetype}
* identity of this basetype.
*</p>
*<p>
* If the given {@link Substituent} causes a new stereogenic
* centre to be created where there wasn't one before, then
* the stereochemistry of that position defaults to TRUE
* (ie: oriented RIGHT in a Fischer projection). Note that the
* method returns true if there was a change in stereochemistry.
*</p>
*
* @param s
* the {@link Substituent} to substitute
* @param position
* the position to substitute
* @param preserveSuperclass
* if false, {@link Superclass} will be changed to accomodate
* stereochemistry-changing substituents, thus preserving the
* underlying basetype stereochemistry. if true, the Basetype
* will be changed and Superclass preserved when introducing
* a stereochemistry-changing substituent.
* @return
* true if the stereochemistry of this basetype changed
* @throws IllegalArgumentException
* if position is out of bounds for the basetype
* @throws NullPointerException
* if s is null
*/
public boolean setFunctionalGroup( Substituent s, int position, boolean preserveSuperclass )
throws IllegalArgumentException, NullPointerException
{
int index = position - 1;
boolean old_sub_is_chiral = chiralPositions.get( index );
boolean new_sub_is_chiral = ! s.causesStereoloss();
Substituent current = functionalGroups.get( index );
assert current != null;
assert old_sub_is_chiral == ! current.causesStereoloss();
boolean stereochem_changes = old_sub_is_chiral ^ new_sub_is_chiral;
name = null;
if ( stereochem_changes )
{
if ( new_sub_is_chiral /* then old sub isn't */)
{
// old sub position isn't chiral, new sub is
if ( debugging )
{
log.debug(
"setting "
+ s
+ " at position="
+ position
+ ", replacing "
+ current
+ ", resulting in the gain of a stereogenic centre"
);
}
addChiralPosition( position, preserveSuperclass );
functionalGroups.set( index, s );
// default == fischer right
stereochemistry.set( index, true );
return true;
}
else
{
// old sub position is chiral, new sub isn't
if ( debugging )
{
log.debug(
"setting "
+ s
+ " at position="
+ position
+ ", replacing "
+ current
+ ", resulting in a loss of a stereogenic centre"
);
}
removeChiralPosition( position, preserveSuperclass );
functionalGroups.set( index, s );
return true;
}
}
else
{
// no change in stereochem
if ( debugging )
{
log.debug(
"setting "
+ s
+ " at position="
+ position
+ ", replacing "
+ current
+ ", resulting in no change of stereochemistry"
);
}
functionalGroups.set( index, s );
return false;
}
}
/* implementation of Basetype interface */
public BitSet getChiralPositions()
{
return chiralPositions;
}
public List<Substituent> getFunctionalGroups()
{
return functionalGroups;
}
public StereoConfig getStereoConfig()
{
return stereoConfigs[0];
}
public BitSet getStereochemistry()
{
return stereochemistry;
}
public Superclass getSuperclass()
{
return superclass;
}
/* implementation of Molecule interface */
public String getName()
{
if ( this.name != null )
return this.name;
StringBuilder stem_name = new StringBuilder();
StringBuilder substits = new StringBuilder();
for ( int i = 0; i < basetypes.length; i++ )
{
if ( i != 0 )
stem_name.append( '-' );
stem_name.append( stereoConfigs[i] );
stem_name.append( '-' );
stem_name.append( basetypes[i].name() );
}
// substituents
Substituent s;
ListMultimap<Substituent,Integer> map = ArrayListMultimap.create();
CommonBasetype basetype_with_carbonyl = basetypes[(basetypes.length - 1)];
for ( int i = 0; i < functionalGroups.size(); i++ )
{
s = functionalGroups.get( i );
if ( s == OH )
continue;
if ( s == basetype_with_carbonyl.getFunctionalGroups().get(i) )
continue;
map.put( s, i+1 );
}
List<Integer> positions;
for ( int i = 0; i < functionalGroups.size(); i++ )
{
s = functionalGroups.get( i );
positions = map.get( s );
if ( positions == null || positions.size() == 0 )
continue;
if ( substits.length() > 0 )
substits.append( '-' );
substits.append( join(",", positions) );
substits.append( '-' );
substits.append( s.getName() );
map.removeAll( s );
}
// superclass, if needed
if ( substits.length() > 0 || basetypes.length > 1 )
{
stem_name.append('-');
stem_name.append( getSuperclass().getFullName() );
}
String name = ( substits.length() > 0
? substits.toString() + "-"
: "" )
+ stem_name.toString()
;
this.name = name;
return name;
}
public String getFullName()
{
return getName();
}
public double getMass()
{
return 0;
}
public double getAvgMass()
{
return 0;
}
/* implementation of PotentiallyIndefinite interface */
public boolean isDefinite()
{
return true;
}
public String toString()
{
return getName();
}
//~~~~~~~~~~~~~~~~~~~~~ PRIVATE METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~
}