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}