1 /* Copyright (C) 2007  Miguel Rojasch <miguelrojasch@users.sf.net>
2  *
3  *  Contact: cdk-devel@lists.sourceforge.net
4  *
5  *  This program is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Lesser General Public License
7  *  as published by the Free Software Foundation; either version 2.1
8  *  of the License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Lesser General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Lesser General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 package org.openscience.cdk.formula;
20 
21 import java.util.*;
22 
23 import org.openscience.cdk.CDKConstants;
24 import org.openscience.cdk.DefaultChemObjectBuilder;
25 import org.openscience.cdk.interfaces.IIsotope;
26 import org.openscience.cdk.interfaces.IMolecularFormula;
27 import org.openscience.cdk.interfaces.IChemObjectBuilder;
28 
29 /**
30  * Class defining a molecular formula object. It maintains
31  * a list of list {@link IIsotope}.
32  *
33  * <p>Examples:
34  * <ul>
35  *   <li><code>[C<sub>5</sub>H<sub>5</sub>]-</code></li>
36  *   <li><code>C<sub>6</sub>H<sub>6</sub></code></li>
37  *   <li><code><sup>12</sup>C<sub>5</sub><sup>13</sup>CH<sub>6</sub></code></li>
38  * </ul>
39  *
40  * @cdk.module  data
41  * @author      miguelrojasch
42  * @cdk.created 2007-11-20
43  * @cdk.keyword molecular formula
44  * @cdk.githash
45  */
46 public class MolecularFormula implements IMolecularFormula {
47 
48     /**
49      * Determines if a de-serialized object is compatible with this class.
50      *
51      * This value must only be changed if and only if the new version
52      * of this class is incompatible with the old version. See Sun docs
53      * for <a href=http://java.sun.com/products/jdk/1.1/docs/guide
54      * /serialization/spec/version.doc.html>details</a>.
55      */
56     private static final long      serialVersionUID = -2011407700837295287L;
57 
58     private Map<IIsotope, Integer> isotopes;
59     /**
60      *  The partial charge of the molecularFormula. The default value is Double.NaN.
61      */
62     private Integer                charge           = null;
63 
64     /**
65      *  A hashtable for the storage of any kind of properties of this IChemObject.
66      */
67     private Map<Object, Object>    properties;
68 
69     /**
70      *  Constructs an empty MolecularFormula.
71      */
MolecularFormula()72     public MolecularFormula() {
73         isotopes = new HashMap<IIsotope, Integer>();
74     }
75 
76     /**
77      * Adds an molecularFormula to this MolecularFormula.
78      *
79      * @param  formula  The molecularFormula to be added to this chemObject
80      * @return          The IMolecularFormula
81      */
82     @Override
add(IMolecularFormula formula)83     public IMolecularFormula add(IMolecularFormula formula) {
84         for (IIsotope newIsotope : formula.isotopes()) {
85             addIsotope(newIsotope, formula.getIsotopeCount(newIsotope));
86         }
87         if (formula.getCharge() != null) {
88             if (charge != null)
89                 charge += formula.getCharge();
90             else
91                 charge = formula.getCharge();
92         }
93         return this;
94     }
95 
96     /**
97      *  Adds an Isotope to this MolecularFormula one time.
98      *
99      * @param  isotope  The isotope to be added to this MolecularFormula
100      * @see             #addIsotope(IIsotope, int)
101      */
102     @Override
addIsotope(IIsotope isotope)103     public IMolecularFormula addIsotope(IIsotope isotope) {
104         return this.addIsotope(isotope, 1);
105     }
106 
107     /**
108      *  Adds an Isotope to this MolecularFormula in a number of occurrences.
109      *
110      * @param  isotope  The isotope to be added to this MolecularFormula
111      * @param  count    The number of occurrences to add
112      * @see             #addIsotope(IIsotope)
113      */
114     @Override
addIsotope(IIsotope isotope, int count)115     public IMolecularFormula addIsotope(IIsotope isotope, int count) {
116         if (count == 0)
117             return this;
118         boolean flag = false;
119         for (IIsotope thisIsotope : isotopes()) {
120             if (isTheSame(thisIsotope, isotope)) {
121                 isotopes.put(thisIsotope, isotopes.get(thisIsotope) + count);
122                 flag = true;
123                 break;
124             }
125         }
126         if (!flag) {
127             isotopes.put(isotope, count);
128         }
129 
130         return this;
131     }
132 
133     /**
134      *  True, if the MolecularFormula contains the given IIsotope object and not
135      *  the instance. The method looks for other isotopes which has the same
136      *  symbol, natural abundance and exact mass.
137      *
138      * @param  isotope  The IIsotope this MolecularFormula is searched for
139      * @return          True, if the MolecularFormula contains the given isotope object
140      */
141     @Override
contains(IIsotope isotope)142     public boolean contains(IIsotope isotope) {
143         for (IIsotope thisIsotope : isotopes()) {
144             if (isTheSame(thisIsotope, isotope)) {
145                 return true;
146             }
147         }
148         return false;
149     }
150 
151     /**
152      * {@inheritDoc}
153      */
154     @Override
getCharge()155     public Integer getCharge() {
156         return charge;
157     }
158 
159     /**
160      *  Checks a set of Nodes for the occurrence of the isotope in the
161      *  IMolecularFormula from a particular isotope. It returns 0 if the does not exist.
162      *
163      * @param   isotope          The IIsotope to look for
164      * @return                   The occurrence of this isotope in this IMolecularFormula
165      * @see                      #getIsotopeCount()
166      */
167     @Override
getIsotopeCount(IIsotope isotope)168     public int getIsotopeCount(IIsotope isotope) {
169         return !contains(isotope) ? 0 : isotopes.get(getIsotope(isotope));
170     }
171 
172     /**
173      *  Checks a set of Nodes for the number of different isotopes in the
174      *  IMolecularFormula.
175      *
176      * @return        The the number of different isotopes in this IMolecularFormula
177      * @see           #getIsotopeCount(IIsotope)
178      */
179     @Override
getIsotopeCount()180     public int getIsotopeCount() {
181         return isotopes.size();
182     }
183 
184     /**
185      *  Get the isotope instance given an IIsotope. The instance is those
186      *  that has the isotope with the same symbol, natural abundance and
187      *  exact mass.
188      *
189      * @param  isotope The IIsotope for looking for
190      * @return         The IIsotope instance
191     * @see            #isotopes
192      */
getIsotope(IIsotope isotope)193     private IIsotope getIsotope(IIsotope isotope) {
194         for (IIsotope thisIsotope : isotopes()) {
195             if (isTheSame(isotope, thisIsotope)) return thisIsotope;
196         }
197         return null;
198     }
199 
200     /**
201      *  Returns an Iterator for looping over all isotopes in this IMolecularFormula.
202      *
203      * @return    An Iterator with the isotopes in this IMolecularFormula
204      */
205     @Override
isotopes()206     public Iterable<IIsotope> isotopes() {
207         return isotopes.keySet();
208     }
209 
210     /**
211      * {@inheritDoc}
212      */
213     @Override
setCharge(Integer charge)214     public void setCharge(Integer charge) {
215         this.charge = charge;
216     }
217 
218     /**
219      * Removes all isotopes of this molecular formula.
220      */
221     @Override
removeAllIsotopes()222     public void removeAllIsotopes() {
223         isotopes.clear();
224     }
225 
226     /**
227      *  Removes the given isotope from the MolecularFormula.
228      *
229      * @param isotope  The IIsotope to be removed
230      */
231     @Override
removeIsotope(IIsotope isotope)232     public void removeIsotope(IIsotope isotope) {
233         isotopes.remove(getIsotope(isotope));
234     }
235 
236     /**
237      * Clones this MolecularFormula object and its content. I should
238      * integrate into ChemObject.
239      *
240      * @return    The cloned object
241      */
242     @Override
clone()243     public Object clone() throws CloneNotSupportedException {
244 
245         //		/* it is not a super class of chemObject */
246         //		MolecularFormula clone = (MolecularFormula) super.clone();
247         //        // start from scratch
248         //		clone.removeAllIsotopes();
249         //        // clone all isotopes
250         //		Iterator<IIsotope> iterIso = this.isotopes();
251         //		while(iterIso.hasNext()){
252         //			IIsotope isotope = iterIso.next();
253         //			clone.addIsotope((IIsotope) isotope.clone(),getIsotopeCount(isotope));
254         //		}
255 
256         MolecularFormula clone = new MolecularFormula();
257         for (IIsotope isotope : isotopes()) {
258             clone.addIsotope((IIsotope) isotope.clone(), getIsotopeCount(isotope));
259         }
260         clone.setCharge(getCharge());
261         return clone;
262     }
263 
264     /**
265      * Lazy creation of properties hash. I should
266      * integrate into ChemObject.
267      *
268      * @return    Returns in instance of the properties
269      */
lazyProperties()270     private Map<Object, Object> lazyProperties() {
271         if (properties == null) {
272             properties = new Hashtable<Object, Object>();
273         }
274         return properties;
275     }
276 
277     /**
278      *  Sets a property for a IChemObject. I should
279      * integrate into ChemObject.
280      *
281      *@param  description  An object description of the property (most likely a
282      *      unique string)
283      *@param  property     An object with the property itself
284      *@see                 #getProperty
285      *@see                 #removeProperty
286      */
287     @Override
setProperty(Object description, Object property)288     public void setProperty(Object description, Object property) {
289         lazyProperties().put(description, property);
290     }
291 
292     /**
293      *  Removes a property for a IChemObject. I should
294      * integrate into ChemObject.
295      *
296      *@param  description  The object description of the property (most likely a
297      *      unique string)
298      *@see                 #setProperty
299      *@see                 #getProperty
300      */
301     @Override
removeProperty(Object description)302     public void removeProperty(Object description) {
303         if (properties == null) {
304             return;
305         }
306         lazyProperties().remove(description);
307     }
308 
309     /**
310      *{@inheritDoc}
311      */
312     @Override
getProperty(Object description)313     public <T> T getProperty(Object description) {
314         if (properties == null) return null;
315         // can't check the type
316         @SuppressWarnings("unchecked")
317         T value = (T) lazyProperties().get(description);
318         return value;
319     }
320 
321     /**
322      *{@inheritDoc}
323      */
324     @Override
getProperty(Object description, Class<T> c)325     public <T> T getProperty(Object description, Class<T> c) {
326         Object value = lazyProperties().get(description);
327 
328         if (c.isInstance(value)) {
329 
330             @SuppressWarnings("unchecked")
331             T typed = (T) value;
332             return typed;
333 
334         } else if (value != null) {
335             throw new IllegalArgumentException("attempted to access a property of incorrect type, expected "
336                     + c.getSimpleName() + " got " + value.getClass().getSimpleName());
337         }
338 
339         return null;
340 
341     }
342 
343     /**
344      *  Returns a Map with the IChemObject's properties.I should
345      * integrate into ChemObject.
346      *
347      *@return    The object's properties as an Hashtable
348      *@see       #setProperties
349      */
350     @Override
getProperties()351     public Map<Object, Object> getProperties() {
352         return lazyProperties();
353     }
354 
355     /**
356      *  Sets the properties of this object.
357      *
358      *@param  properties  a Hashtable specifying the property values
359      *@see                #getProperties
360      */
361     @Override
setProperties(Map<Object, Object> properties)362     public void setProperties(Map<Object, Object> properties) {
363 
364         Iterator<Object> keys = properties.keySet().iterator();
365         while (keys.hasNext()) {
366             Object key = keys.next();
367             lazyProperties().put(key, properties.get(key));
368         }
369     }
370 
371     /**
372      * Compare to IIsotope. The method doesn't compare instance but if they
373      * have the same symbol, natural abundance and exact mass.
374      *
375      * @param isotopeOne   The first Isotope to compare
376      * @param isotopeTwo   The second Isotope to compare
377      * @return             True, if both isotope are the same
378      */
isTheSame(IIsotope isotopeOne, IIsotope isotopeTwo)379     protected boolean isTheSame(IIsotope isotopeOne, IIsotope isotopeTwo) {
380 
381         if (!Objects.equals(isotopeOne.getMassNumber(),
382                             isotopeTwo.getMassNumber()))
383             return false;
384 
385         Double natAbund1 = isotopeOne.getNaturalAbundance();
386         Double natAbund2 = isotopeTwo.getNaturalAbundance();
387 
388         Double exactMass1 = isotopeOne.getExactMass();
389         Double exactMass2 = isotopeTwo.getExactMass();
390 
391         if (natAbund1 == null) natAbund1 = -1.0;
392         if (natAbund2 == null) natAbund2 = -1.0;
393         if (exactMass1 == null) exactMass1 = -1.0;
394         if (exactMass2 == null) exactMass2 = -1.0;
395 
396         if (!isotopeOne.getSymbol().equals(isotopeTwo.getSymbol())) return false;
397         if (natAbund1.doubleValue() != natAbund2) return false;
398         return exactMass1.doubleValue() == exactMass2;
399     }
400 
401     @Override
getBuilder()402     public IChemObjectBuilder getBuilder() {
403         return DefaultChemObjectBuilder.getInstance();
404     }
405 
406 }
407