/*
* Copyright (c) 2003-2012 Fred Hutchinson Cancer Research Center
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fhcrc.cpl.toolbox.chem;
import org.openscience.cdk.interfaces.IMolecule;
import org.openscience.cdk.Molecule;
import org.openscience.cdk.tools.manipulator.MolecularFormulaManipulator;
import org.openscience.cdk.tools.manipulator.AtomContainerManipulator;
import java.util.*;
/**
* Represents a chemical compound with optional ChemicalModifications.
* User: dhmay
* Date: Apr 6, 2010
* Time: 4:06:07 PM
* To change this template use File | Settings | File Templates.
*/
public class Adduct
{
protected ChemicalFormula formula;
//None of these variables should ever be set without using the setters. setting any of these with the
//setters will update formula
protected ChemicalCompound compound;
//Molecule structure that's correct for this adduct. Hopefully we can always populate this
protected IMolecule molecule;
//These are guaranteed never to be null unless explicitly set that way by pathological code
protected List<ChemicalModification> modifications = new ArrayList<ChemicalModification>();
/**
* Create a fully-defined Adduct based on the supplied compound, with modifications supplied to be applied in order
* @param compound
* @param modifications
*
* @throws IllegalArgumentException if the modifications can't be performed
*/
public Adduct(ChemicalCompound compound,
List<ChemicalModification> modifications)
throws IllegalArgumentException
{
setCompound(compound);
if (compound.getCdkMolecule() != null)
{
try
{
setMolecule((IMolecule) compound.getCdkMolecule().clone());
}
catch (CloneNotSupportedException e)
{
//cloning /is/ supported
}
}
if (modifications != null)
for (ChemicalModification mod : modifications)
{
if (!mod.canPerform(this))
throw new IllegalArgumentException("Can't perform modification " + mod.getSymbol());
mod.perform(this);
}
}
/**
* Takes a slightly cheaper route of simply copying the compound, formula and modifications from the other
* adduct, rather than applying all the modifications one by one
* @param adduct
*/
public Adduct(Adduct adduct)
{
setCompound(adduct.getCompound());
this.formula = adduct.getFormula();
this.modifications = new ArrayList<ChemicalModification>(modifications);
try
{
setMolecule((IMolecule) compound.getCdkMolecule().clone());
}
catch (Exception e)
{
//cloning /is/ supported
}
}
/**
* Create an adduct equivalent to the compound with no additions or subtractions
* @param compound
*/
public Adduct(ChemicalCompound compound)
{
this(compound, null);
}
public ChemicalCompound getCompound()
{
return compound;
}
public void setCompound(ChemicalCompound compound)
{
this.compound = compound;
formula = new ChemicalFormula(compound.getFormula());
}
//convenience methods for getting at a few things in ChemicalFormula
public double getCommonestIsotopeMass()
{
return formula.getCommonestIsotopeMass();
}
public double[] getPeakFrequencies()
{
return formula.getPeakFrequencies();
}
public double[] getPeakMasses()
{
return formula.getPeakMasses();
}
public ChemicalFormula getFormula() {
return formula;
}
/**
* Use with EXTREME CAUTION, because it can make formula disagree with molecule.
* This is used by, e.g., ChemicalModification
* @param formula
*/
public void setFormula(ChemicalFormula formula) {
this.formula = formula;
}
public String getCompoundNameAndIonTypeString()
{
return compound.getName() + ":" + getIonTypeString();
}
/**
* Returns a string that describes the modifications done to the base compound to produce this adduct
* @return
*/
public String getIonTypeString()
{
if (modifications.isEmpty())
return "[M]";
StringBuffer resultBuf = new StringBuffer("[");
boolean first = true;
for (ChemicalModification mod : modifications)
{
if (!first)
resultBuf.append(" ");
first=false;
resultBuf.append(mod.getSymbol());
}
resultBuf.append("]");
return resultBuf.toString();
}
public String toString()
{
StringBuffer resultBuf = new StringBuffer(compound.getName() + "\t" + formula.toString() + "\t" +
getIonTypeString());
return resultBuf.toString();
}
public static class ComparatorMassAsc implements Comparator<Adduct>
{
public int compare(Adduct o1, Adduct o2)
{
if (o1.getCommonestIsotopeMass() == o2.getCommonestIsotopeMass())
return 0;
return o1.getCommonestIsotopeMass() == o2.getCommonestIsotopeMass() ? 0 :
o1.getCommonestIsotopeMass() < o2.getCommonestIsotopeMass() ? -1 : 1;
}
}
public static class ComparatorNameAndIonTypeAsc implements Comparator<Adduct>
{
public int compare(Adduct o1, Adduct o2)
{
return o1.getCompoundNameAndIonTypeString().compareTo(o2.getCompoundNameAndIonTypeString());
}
}
public List<ChemicalModification> getModifications() {
return modifications;
}
public IMolecule getMolecule() {
return molecule;
}
public void setMolecule(IMolecule molecule) {
this.molecule = molecule;
updateFormula();
}
/**
* Update formula, mass, etc., because molecule has changed
*/
public void updateFormula()
{
AtomContainerManipulator.convertImplicitToExplicitHydrogens(molecule);
formula = ChemCalcs.CDKMolForm2ChemForm(MolecularFormulaManipulator.getMolecularFormula(molecule));
//This doesn't actually remove hydrogens in the argument, just returns a new IMolecule
this.molecule = (IMolecule) AtomContainerManipulator.removeHydrogens(molecule);
}
}