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