/* $RCSfile$
* $Author$
* $Date$
* $Revision$
*
* Copyright (C) 2007 Miguel Rojasch <miguelrojasch@users.sf.net>
*
* Contact: cdk-devel@lists.sourceforge.net
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.openscience.cdk.formula;
import org.openscience.cdk.CDKConstants;
import org.openscience.cdk.DefaultChemObjectBuilder;
import org.openscience.cdk.annotations.TestClass;
import org.openscience.cdk.annotations.TestMethod;
import org.openscience.cdk.interfaces.IChemObjectBuilder;
import org.openscience.cdk.interfaces.IIsotope;
import org.openscience.cdk.interfaces.IMolecularFormula;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
/**
* Class defining a molecular formula object. It maintains
* a list of list {@link IIsotope}.
*
* <p>Examples:
* <ul>
* <li><code>[C<sub>5</sub>H<sub>5</sub>]-</code></li>
* <li><code>C<sub>6</sub>H<sub>6</sub></code></li>
* <li><code><sup>12</sup>C<sub>5</sub><sup>13</sup>CH<sub>6</sub></code></li>
* </ul>
*
* @cdk.module data
* @author miguelrojasch
* @cdk.created 2007-11-20
* @cdk.keyword molecular formula
*/
@TestClass("org.openscience.cdk.formula.MolecularFormulaTest")
public class MolecularFormula implements IMolecularFormula, Cloneable {
/**
* Determines if a de-serialized object is compatible with this class.
*
* This value must only be changed if and only if the new version
* of this class is imcompatible with the old version. See Sun docs
* for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
* /serialization/spec/version.doc.html>details</a>.
*/
private static final long serialVersionUID = -2011407700837295287L;
private Map<IIsotope, Integer> isotopes;
/**
* The partial charge of the molecularFormula. The default value is Double.NaN.
*/
private Integer charge = (Integer) CDKConstants.UNSET;
/**
* A hashtable for the storage of any kind of properties of this IChemObject.
*/
private Map<Object, Object> properties;
/**
* Constructs an empty MolecularFormula.
*/
public MolecularFormula() {
isotopes = new HashMap<IIsotope,Integer>();
}
/**
* Adds an molecularFormula to this MolecularFormula.
*
* @param formula The molecularFormula to be added to this chemObject
* @return The IMolecularFormula
*/
@TestMethod("testAdd_IMolecularFormula")
public IMolecularFormula add(IMolecularFormula formula) {
for (IIsotope newIsotope : formula.isotopes()) {
addIsotope(newIsotope,formula.getIsotopeCount(newIsotope));
}
if(formula.getCharge() != null)charge += formula.getCharge();
return this;
}
/**
* Adds an Isotope to this MolecularFormula one time.
*
* @param isotope The isotope to be added to this MolecularFormula
* @see #addIsotope(IIsotope, int)
*/
@TestMethod("addIsotope_IIsotope")
public IMolecularFormula addIsotope(IIsotope isotope) {
return this.addIsotope(isotope, 1);
}
/**
* Adds an Isotope to this MolecularFormula in a number of occurrences.
*
* @param isotope The isotope to be added to this MolecularFormula
* @param count The number of occurrences to add
* @see #addIsotope(IIsotope)
*/
@TestMethod("testAddIsotope_IIsotope_int")
public IMolecularFormula addIsotope(IIsotope isotope, int count) {
boolean flag = false;
for (IIsotope thisIsotope : isotopes()) {
if(isTheSame(thisIsotope, isotope)){
isotopes.put(thisIsotope, isotopes.get(thisIsotope) + count);
flag = true;
break;
}
}
if(!flag){
isotopes.put(isotope, count);
}
return this;
}
/**
* True, if the MolecularFormula contains the given IIsotope object and not
* the instance. The method looks for other isotopes which has the same
* symbol, natural abundance and exact mass.
*
* @param isotope The IIsotope this MolecularFormula is searched for
* @return True, if the MolecularFormula contains the given isotope object
*/
@TestMethod("testContains_IIsotope")
public boolean contains(IIsotope isotope) {
for (IIsotope thisIsotope : isotopes()) {
if(isTheSame(thisIsotope, isotope)){
return true;
}
}
return false;
}
/**
* Returns the partial charge of this IMolecularFormula. If the charge
* has not been set the return value is Double.NaN.
*
* @return the charge of this IMolecularFormula
*
* @see #setCharge
*/
@TestMethod("testGetCharge")
public Integer getCharge() {
return charge;
}
/**
* Checks a set of Nodes for the occurrence of the isotope in the
* IMolecularFormula from a particular isotope. It returns 0 if the does not exist.
*
* @param isotope The IIsotope to look for
* @return The occurrence of this isotope in this IMolecularFormula
* @see #getIsotopeCount()
*/
@TestMethod("testGetIsotopeCount_IIsotope")
public int getIsotopeCount(IIsotope isotope) {
return !contains(isotope) ? 0 : isotopes.get(getIsotope(isotope));
}
/**
* Checks a set of Nodes for the number of different isotopes in the
* IMolecularFormula.
*
* @return The the number of different isotopes in this IMolecularFormula
* @see #getIsotopeCount(IIsotope)
*/
@TestMethod("testGetIsotopeCount")
public int getIsotopeCount() {
return isotopes.size();
}
/**
* Get the isotope instance given an IIsotope. The instance is those
* that has the isotope with the same symbol, natural abundance and
* exact mass.
*
* @param isotope The IIsotope for looking for
* @return The IIsotope instance
* @see #isotopes
*/
private IIsotope getIsotope(IIsotope isotope){
for (IIsotope thisIsotope : isotopes()) {
if(isTheSame(isotope,thisIsotope))
return thisIsotope;
}
return null;
}
/**
* Returns an Iterator for looping over all isotopes in this IMolecularFormula.
*
* @return An Iterator with the isotopes in this IMolecularFormula
*/
@TestMethod("testIsotopes")
public Iterable<IIsotope> isotopes() {
return isotopes.keySet();
}
/**
* Sets the partial charge of this IMolecularFormula.
*
* @param charge The partial charge
*
* @see #getCharge
*/
@TestMethod("testSetCharge_Integer")
public void setCharge(Integer charge) {
this.charge = charge;
}
/**
* Removes all isotopes of this molecular formula.
*/
@TestMethod("testRemoveAllIsotopes")
public void removeAllIsotopes() {
isotopes.clear();
}
/**
* Removes the given isotope from the MolecularFormula.
*
* @param isotope The IIsotope to be removed
*/
@TestMethod("testRemoveIsotope_IIsotope")
public void removeIsotope(IIsotope isotope) {
isotopes.remove(getIsotope(isotope));
}
/**
* Clones this MolecularFormula object and its content. I should
* integrate into ChemObject.
*
* @return The cloned object
*/
@TestMethod("testClone")
public Object clone() throws CloneNotSupportedException {
// /* it is not a super class of chemObject */
// MolecularFormula clone = (MolecularFormula) super.clone();
// // start from scratch
// clone.removeAllIsotopes();
// // clone all isotopes
// Iterator<IIsotope> iterIso = this.isotopes();
// while(iterIso.hasNext()){
// IIsotope isotope = iterIso.next();
// clone.addIsotope((IIsotope) isotope.clone(),getIsotopeCount(isotope));
// }
MolecularFormula clone = new MolecularFormula();
for (IIsotope isotope : isotopes()) {
clone.addIsotope((IIsotope) isotope.clone(),getIsotopeCount(isotope));
}
clone.setCharge(getCharge());
return clone;
}
/**
* Lazy creation of properties hash. I should
* integrate into ChemObject.
*
* @return Returns in instance of the properties
*/
private Map<Object, Object> lazyProperties(){
if (properties == null)
{
properties = new Hashtable<Object, Object>();
}
return properties;
}
/**
* Sets a property for a IChemObject. I should
* integrate into ChemObject.
*
*@param description An object description of the property (most likely a
* unique string)
*@param property An object with the property itself
*@see #getProperty
*@see #removeProperty
*/
@TestMethod("testSetProperty_Object_Object")
public void setProperty(Object description, Object property){
lazyProperties().put(description, property);
}
/**
* Removes a property for a IChemObject. I should
* integrate into ChemObject.
*
*@param description The object description of the property (most likely a
* unique string)
*@see #setProperty
*@see #getProperty
*/
@TestMethod("testRemoveProperty_Object")
public void removeProperty(Object description){
if (properties == null) {
return;
}
lazyProperties().remove(description);
}
/**
* Returns a property for the IChemObject. I should
* integrate into ChemObject.
*
*@param description An object description of the property (most likely a
* unique string)
*@return The object containing the property. Returns null if
* propert is not set.
*@see #setProperty
*@see #removeProperty
*/
@TestMethod("testGetProperty_Object")
public Object getProperty(Object description){
if (properties != null) {
return lazyProperties().get(description);
}
return null;
}
/**
* Returns a Map with the IChemObject's properties.I should
* integrate into ChemObject.
*
*@return The object's properties as an Hashtable
*@see #setProperties
*/
@TestMethod("testGetProperties")
public Map<Object, Object> getProperties(){
return lazyProperties();
}
/**
* Sets the properties of this object.
*
*@param properties a Hashtable specifying the property values
*@see #getProperties
*/
@TestMethod("testSetProperties_Hashtable")
public void setProperties(Map<Object, Object> properties){
Iterator<Object> keys = properties.keySet().iterator();
while (keys.hasNext()) {
Object key = keys.next();
lazyProperties().put(key, properties.get(key));
}
}
/**
* Compare to IIsotope. The method doesn't compare instance but if they
* have the same symbol, natural abundance and exact mass.
*
* @param isotopeOne The first Isotope to compare
* @param isotopeTwo The second Isotope to compare
* @return True, if both isotope are the same
*/
@TestMethod("testIsTheSame")
protected boolean isTheSame(IIsotope isotopeOne, IIsotope isotopeTwo) {
Double natAbund1 = isotopeOne.getNaturalAbundance();
Double natAbund2 = isotopeTwo.getNaturalAbundance();
Double exactMass1 = isotopeOne.getExactMass();
Double exactMass2 = isotopeTwo.getExactMass();
if (natAbund1 == null) natAbund1 = -1.0;
if (natAbund2 == null) natAbund2 = -1.0;
if (exactMass1 == null) exactMass1 = -1.0;
if (exactMass2 == null) exactMass2 = -1.0;
if(!isotopeOne.getSymbol().equals(isotopeTwo.getSymbol() ))
return false;
if(natAbund1.doubleValue() != natAbund2)
return false;
return exactMass1.doubleValue() == exactMass2;
}
public IChemObjectBuilder getBuilder() {
return DefaultChemObjectBuilder.getInstance();
}
}