001package com.hfg.chem; 002 003import java.util.ArrayList; 004import java.util.Collection; 005import java.util.Collections; 006import java.util.List; 007import java.util.Map; 008 009import com.hfg.util.CompareUtil; 010import com.hfg.util.StringUtil; 011import com.hfg.util.collection.CollectionUtil; 012import com.hfg.util.collection.OrderedMap; 013 014//------------------------------------------------------------------------------ 015/** 016 Container for elemental composition data. 017 <div> 018 @author J. Alex Taylor, hairyfatguy.com 019 </div> 020 */ 021//------------------------------------------------------------------------------ 022// com.hfg XML/HTML Coding Library 023// 024// This library is free software; you can redistribute it and/or 025// modify it under the terms of the GNU Lesser General Public 026// License as published by the Free Software Foundation; either 027// version 2.1 of the License, or (at your option) any later version. 028// 029// This library is distributed in the hope that it will be useful, 030// but WITHOUT ANY WARRANTY; without even the implied warranty of 031// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 032// Lesser General Public License for more details. 033// 034// You should have received a copy of the GNU Lesser General Public 035// License along with this library; if not, write to the Free Software 036// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 037// 038// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 039// jataylor@hairyfatguy.com 040//------------------------------------------------------------------------------ 041 042public class ElementalComposition implements Comparable 043{ 044 private Map<Element, Float> mMap = new OrderedMap<>(10); 045 private String mChemicalFormula; 046 047 //########################################################################## 048 // CONSTRUCTORS 049 //########################################################################## 050 051 //-------------------------------------------------------------------------- 052 public ElementalComposition() 053 { 054 055 } 056 057 //-------------------------------------------------------------------------- 058 public ElementalComposition(Map<Element, Float> inMap) 059 { 060 add(inMap); 061 } 062 063 //-------------------------------------------------------------------------- 064 public ElementalComposition(Map<Element, Float> inMap, int inNum) 065 { 066 add(inMap, inNum); 067 } 068 069 //-------------------------------------------------------------------------- 070 public ElementalComposition(ElementalComposition inElementalComposition) 071 { 072 add(inElementalComposition); 073 } 074 075 //-------------------------------------------------------------------------- 076 public ElementalComposition(ElementalComposition inElementalComposition, int inNum) 077 { 078 add(inElementalComposition, inNum); 079 } 080 081 //########################################################################## 082 // PUBLIC METHODS 083 //########################################################################## 084 085 086 //-------------------------------------------------------------------------- 087 @Override 088 public String toString() 089 { 090 return getChemicalFormula(); 091 } 092 093 //-------------------------------------------------------------------------- 094 @Override 095 public int hashCode() 096 { 097 int hashcode = 31; 098 for (Element element : mMap.keySet()) 099 { 100 hashcode = hashcode + 31 * element.hashCode() * mMap.get(element).intValue(); 101 } 102 103 return hashcode; 104 } 105 106 //-------------------------------------------------------------------------- 107 @Override 108 public boolean equals(Object inObj) 109 { 110 boolean result = false; 111 112 if (inObj != null) 113 { 114 result = (0 == compareTo(inObj)); 115 } 116 117 return result; 118 } 119 120 //-------------------------------------------------------------------------- 121 @Override 122 public int compareTo(Object inObj) 123 { 124 int result = -1; 125 126 if (inObj instanceof ElementalComposition) 127 { 128 result = CompareUtil.compare(getChemicalFormula(), ((ElementalComposition)inObj).getChemicalFormula()); 129 } 130 131 return result; 132 } 133 134 //-------------------------------------------------------------------------- 135 public Collection<Element> keySet() 136 { 137 return mMap != null ? mMap.keySet() : null; 138 } 139 140 //-------------------------------------------------------------------------- 141 public Map<Element, Float> toMap() 142 { 143 return mMap != null ? Collections.unmodifiableMap(mMap) : null; 144 } 145 146 //-------------------------------------------------------------------------- 147 public Float get(Element inElement) 148 { 149 return mMap != null ? mMap.get(inElement) : null; 150 } 151 152 //-------------------------------------------------------------------------- 153 public Float put(Element inElement, int inCount) 154 { 155 return put(inElement, (float) inCount); 156 } 157 158 //-------------------------------------------------------------------------- 159 public Float put(Element inElement, float inCount) 160 { 161 clearCalculatedProperties(); 162 163 if (null == mMap) 164 { 165 mMap = new OrderedMap<>(10); 166 } 167 168 return mMap.put(inElement, inCount); 169 } 170 171 //-------------------------------------------------------------------------- 172 public ElementalComposition add(ElementalComposition inValue) 173 { 174 return add(inValue, 1); 175 } 176 177 //-------------------------------------------------------------------------- 178 public ElementalComposition add(ElementalComposition inValue, int inNum) 179 { 180 if (inValue != null 181 && CollectionUtil.hasValues(inValue.mMap)) 182 { 183 for (Element element : inValue.keySet()) 184 { 185 addAtoms(element, inValue.get(element) * inNum); 186 } 187 } 188 189 return this; 190 } 191 192 //-------------------------------------------------------------------------- 193 public ElementalComposition add(Matter inValue) 194 { 195 if (inValue != null) 196 { 197 Map<Element, Float> elementalComposition = inValue.getElementalComposition(); 198 if (elementalComposition != null) 199 { 200 add(elementalComposition); 201 } 202 } 203 204 return this; 205 } 206 207 //-------------------------------------------------------------------------- 208 public ElementalComposition add(Map<Element, Float> inMap) 209 { 210 return add(inMap, 1); 211 } 212 213 //-------------------------------------------------------------------------- 214 public ElementalComposition add(Map<Element, Float> inMap, int inNum) 215 { 216 if (CollectionUtil.hasValues(inMap)) 217 { 218 for (Element element : inMap.keySet()) 219 { 220 addAtoms(element, inMap.get(element) * inNum); 221 } 222 } 223 224 return this; 225 } 226 227 //-------------------------------------------------------------------------- 228 public ElementalComposition addAtoms(Element inElement, int inNum) 229 { 230 return addAtoms(inElement, new Float(inNum)); 231 } 232 233 //-------------------------------------------------------------------------- 234 public ElementalComposition addAtoms(Element inElement, float inNum) 235 { 236 if (null == mMap) 237 { 238 mMap = new OrderedMap<>(10); 239 } 240 241 Float count = mMap.get(inElement); 242 float newCount = inNum + (count != null ? count : 0); 243 244 mMap.put(inElement, newCount); 245 246 clearCalculatedProperties(); 247 248 return this; 249 } 250 251 //-------------------------------------------------------------------------- 252 public ElementalComposition remove(Matter inValue) 253 { 254 return remove(inValue != null ? inValue.getElementalComposition() : null, 1); 255 } 256 257 //-------------------------------------------------------------------------- 258 public ElementalComposition remove(Map<Element, Float> inMap) 259 { 260 return remove(inMap, 1); 261 } 262 263 //-------------------------------------------------------------------------- 264 public ElementalComposition remove(Map<Element, Float> inMap, int inNum) 265 { 266 if (CollectionUtil.hasValues(inMap)) 267 { 268 for (Element element : inMap.keySet()) 269 { 270 addAtoms(element, inMap.get(element) * -inNum); 271 } 272 } 273 274 return this; 275 } 276 277 //-------------------------------------------------------------------------- 278 public ElementalComposition remove(ElementalComposition inValue) 279 { 280 return remove(inValue, 1); 281 } 282 283 //-------------------------------------------------------------------------- 284 public ElementalComposition remove(ElementalComposition inValue, int inNum) 285 { 286 if (inValue != null) 287 { 288 for (Element element : inValue.keySet()) 289 { 290 addAtoms(element, - inValue.get(element) * inNum); 291 } 292 } 293 294 return this; 295 } 296 297 //-------------------------------------------------------------------------- 298 public void clear() 299 { 300 mMap = null; 301 clearCalculatedProperties(); 302 } 303 304 //-------------------------------------------------------------------------- 305 /** 306 Returns a chemical formula String like 'C5H11NO'. If carbon is present, it 307 is listed first followed by the other elements in ascending mass order. 308 Symbols for isotopes are enclosed in square brackets such as '[2H]2O' 309 for deuterated water. 310 @return the chemical formula string 311 */ 312 public String getChemicalFormula() 313 { 314 if (null == mChemicalFormula) 315 { 316 mChemicalFormula = getChemicalFormula(false); 317 } 318 319 return mChemicalFormula; 320 } 321 322 //-------------------------------------------------------------------------- 323 /** 324 Returns a chemical formula String like 'C₅H₁₁NO'. If carbon is present, it 325 is listed first followed by the other elements in ascending mass order. 326 Symbols for isotopes are enclosed in square brackets such as '[²H]₂O' 327 for deuterated water. 328 @return the chemical formula string 329 */ 330 public String getChemicalFormulaWithSubscripts() 331 { 332 return getChemicalFormula(true); 333 } 334 335 //########################################################################## 336 // PRIVATE METHODS 337 //########################################################################## 338 339 //-------------------------------------------------------------------------- 340 private void clearCalculatedProperties() 341 { 342 mChemicalFormula = null; 343 } 344 345 //-------------------------------------------------------------------------- 346 private String getChemicalFormula(boolean inUseSubscripts) 347 { 348 StringBuilder compositionString = new StringBuilder(); 349 if (CollectionUtil.hasValues(mMap)) 350 { 351 List<Element> elements = new ArrayList<>(mMap.keySet()); 352 Collections.sort(elements); 353 354 if (elements.remove(Element.CARBON)) 355 { 356 Float count = mMap.get(Element.CARBON); 357 if (count != null && count != 0) 358 { 359 String countString = (count == count.intValue() ? count.intValue() + "" : String.format("%3.1f", count.floatValue()) + ""); 360 if (inUseSubscripts) 361 { 362 countString = StringUtil.toSubscript(countString); 363 } 364 compositionString.append("C" + (count == 1 ? "" : (count < 0 ? "(" + countString + ")" : countString))); 365 } 366 } 367 else if (elements.remove(Element.NITROGEN)) 368 { 369 Float count = mMap.get(Element.NITROGEN); 370 if (count != null && count != 0) 371 { 372 String countString = (count == count.intValue() ? count.intValue() + "" : String.format("%3.1f", count.floatValue()) + ""); 373 if (inUseSubscripts) 374 { 375 countString = StringUtil.toSubscript(countString); 376 } 377 compositionString.append("N" + (count == 1 ? "" : (count < 0 ? "(" + countString + ")" : countString))); 378 } 379 } 380 381 for (Element element : elements) 382 { 383 Float count = mMap.get(element); 384 // Encode pure isotopes in square brackets like "[²H]" for deuterium 385 if (count != null && count != 0) 386 { 387 String countString = (count == count.intValue() ? count.intValue() + "" : String.format("%3.1f", count.floatValue()) + ""); 388 String symbol; 389 if (null == element) 390 { 391 symbol = "*"; 392 } 393 else 394 { 395 symbol = element.getSymbol(); 396 if (inUseSubscripts) 397 { 398 countString = StringUtil.toSubscript(countString); 399 if (element instanceof Isotope) 400 { 401 symbol = StringUtil.toSuperscript(((Isotope) element).getMassNumber()) + ((Isotope) element).getElement().getSymbol(); 402 } 403 } 404 } 405 406 compositionString.append((element instanceof Isotope ? "[" + symbol + "]" : symbol) 407 + (count == 1 ? "" : (count < 0 ? "(" + countString + ")" : countString))); 408 } 409 } 410 } 411 412 return compositionString.length() > 0 ? compositionString.toString() : null; 413 } 414 415}