com.hfg.chem.Molecule Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of com_hfg Show documentation
Show all versions of com_hfg Show documentation
com.hfg xml, html, svg, and bioinformatics utility library
package com.hfg.chem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import com.hfg.bio.HfgBioXML;
import com.hfg.bio.PhysicalProperty;
import com.hfg.exception.UnmodifyableObjectException;
import com.hfg.util.AttributeMgr;
import com.hfg.util.CharUtil;
import com.hfg.util.CompareUtil;
import com.hfg.util.collection.CollectionUtil;
import com.hfg.util.StringUtil;
import com.hfg.xml.HfgXML;
import com.hfg.xml.HfgXMLSerializable;
import com.hfg.xml.XMLAttribute;
import com.hfg.xml.XMLNode;
import com.hfg.xml.XMLTag;
//------------------------------------------------------------------------------
/**
Generic chemical entity.
@author J. Alex Taylor, hairyfatguy.com
*/
//------------------------------------------------------------------------------
// com.hfg XML/HTML Coding Library
//
// This library 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 library 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 library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com
// [email protected]
//------------------------------------------------------------------------------
public class Molecule implements Matter, HfgXMLSerializable, Cloneable, Comparable
{
//##########################################################################
// PRIVATE FIELDS
//##########################################################################
private ElementalComposition mElementalComposition;
private Map mMonoisotopicMass;
private Map mAverageMass;
private Double mOrganicAverageMass;
private String mChemicalFormula;
private boolean mMonoisotopicMassIsUserSet;
private boolean mAverageMassIsUserSet;
private boolean mOrganicAverageMassIsUserSet;
private int mOrdinal;
private Integer mHashCode;
private List mKas;
private Map mPropertyMap;
private AttributeMgr mAttributeMgr;
// Optionally, individual atoms can be specified instead of
// general numbers of elements.
private List mAtoms;
protected String mName;
protected boolean mLocked;
// protected OrganicMatterImpl mMatter = new OrganicMatterImpl();
private static final Set sSaltIndicatorsInChemicalFormulas = new HashSet<>(4);
static
{
// These are the various symbols that can precede hydration notation
sSaltIndicatorsInChemicalFormulas.add('.');
sSaltIndicatorsInChemicalFormulas.add('·');
sSaltIndicatorsInChemicalFormulas.add('•');
sSaltIndicatorsInChemicalFormulas.add('*');
}
//##########################################################################
// PUBLIC FIELDS
//##########################################################################
public static final Molecule H2O = new Molecule().setName("water").setChemicalFormula("H₂O").lock();
public static final Molecule NH3 = new Molecule().setName("ammonia").setChemicalFormula("NH₃").lock();
public static final Molecule NaCl = new Molecule().setName("sodium chloride").setChemicalFormula("NaCl").lock();
//##########################################################################
// CONSTRUCTORS
//##########################################################################
//--------------------------------------------------------------------------
public Molecule()
{
}
//--------------------------------------------------------------------------
public Molecule(String inName)
{
this(inName, null);
}
//--------------------------------------------------------------------------
public Molecule(Map inElementalComposition)
{
this(null, inElementalComposition);
}
//--------------------------------------------------------------------------
public Molecule(String inName, Map inElementalComposition)
{
mName = inName;
setElementalComposition(inElementalComposition);
}
//--------------------------------------------------------------------------
public Molecule(Matter inInitialValue)
{
add(inInitialValue);
}
//--------------------------------------------------------------------------
public Molecule(XMLNode inXML)
{
mName = inXML.getAttributeValue(HfgBioXML.NAME_ATT);
XMLNode compTag = inXML.getOptionalSubtagByName(HfgBioXML.ELEMENTAL_COMP_TAG);
if (compTag != null)
{
for (XMLAttribute attr : compTag.getAttributes())
{
addAtoms(Element.valueOf(attr.getName()), Float.parseFloat(attr.getValue()));
}
}
XMLNode attributesTag = inXML.getOptionalSubtagByName(HfgXML.ATTRIBUTES);
if (attributesTag != null)
{
mAttributeMgr = new AttributeMgr(attributesTag);
}
}
//##########################################################################
// PUBLIC METHODS
//##########################################################################
//--------------------------------------------------------------------------
public Molecule setName(String inValue)
{
mName = inValue;
return this;
}
//--------------------------------------------------------------------------
public String name()
{
return mName;
}
//--------------------------------------------------------------------------
@Override
public String toString()
{
return name();
}
//--------------------------------------------------------------------------
@Override
public int hashCode()
{
if (null == mHashCode)
{
int hashcode = 31;
if (mElementalComposition != null)
{
hashcode += mElementalComposition.hashCode();
}
if (mName != null)
{
hashcode = hashcode + 31 * mName.hashCode();
}
mHashCode = hashcode;
}
return mHashCode;
}
//--------------------------------------------------------------------------
@Override
public boolean equals(Object inObj)
{
boolean result = false;
if (inObj != null)
{
result = (0 == compareTo(inObj));
}
return result;
}
//--------------------------------------------------------------------------
@Override
public int compareTo(Object inObj)
{
int result = -1;
if (inObj instanceof Molecule)
{
Molecule mol2 = ((Molecule) inObj);
// First compare by the ordinal value if one was specified
result = CompareUtil.compare(getOrdinal(), mol2.getOrdinal());
if (0 == result)
{
// Compare the elemental compositions
result = CompareUtil.compare(mElementalComposition, mol2.mElementalComposition);
}
}
return result;
}
//--------------------------------------------------------------------------
/**
Returns an unlocked copy of the Molecule.
@return an unlocked copy of the Molecule
*/
public Molecule clone()
{
Molecule copy;
try
{
copy = (Molecule) super.clone();
}
catch (CloneNotSupportedException e)
{
throw new RuntimeException("Coding problem! CloneNotSupportedException should not be possible when cloning a "
+ this.getClass().getSimpleName() + " object!", e);
}
if (mElementalComposition != null)
{
copy.mElementalComposition = new ElementalComposition(mElementalComposition);
}
if (mPropertyMap != null)
{
copy.mPropertyMap = new HashMap<>(mPropertyMap);
}
if (mAttributeMgr != null)
{
// For right now this is a shallow clone of the attributes
copy.mAttributeMgr = mAttributeMgr.clone();
}
// Clones should be unlocked.
copy.mLocked = false;
return copy;
}
//--------------------------------------------------------------------------
public boolean isLocked()
{
return mLocked;
}
//--------------------------------------------------------------------------
public Molecule lock()
{
mLocked = true;
return this;
}
//--------------------------------------------------------------------------
public Molecule setOrdinal(int inValue)
{
mOrdinal = inValue;
return this;
}
//--------------------------------------------------------------------------
public int getOrdinal()
{
return mOrdinal;
}
//--------------------------------------------------------------------------
public void setElementalComposition(Map inMap)
{
mElementalComposition = null;
if (CollectionUtil.hasValues(inMap))
{
mElementalComposition = new ElementalComposition(inMap);
}
clearCalculatedProperties();
}
//--------------------------------------------------------------------------
public void clearElementalComposition()
{
if (mElementalComposition != null)
{
mElementalComposition.clear();
}
clearCalculatedProperties();
}
//--------------------------------------------------------------------------
public Molecule addElementalComposition(Map inMap)
{
return addElementalComposition(inMap, 1);
}
//--------------------------------------------------------------------------
public Molecule addElementalComposition(Map inMap, int inNum)
{
if (CollectionUtil.hasValues(inMap))
{
if (mElementalComposition != null)
{
mElementalComposition.add(inMap, inNum);
}
else
{
mElementalComposition = new ElementalComposition(inMap, inNum);
}
clearCalculatedProperties();
}
return this;
}
//--------------------------------------------------------------------------
public Molecule addElementalComposition(ElementalComposition inElementalComposition, int inNum)
{
if (inElementalComposition != null)
{
if (mElementalComposition != null)
{
mElementalComposition.add(inElementalComposition, inNum);
}
clearCalculatedProperties();
}
return this;
}
//--------------------------------------------------------------------------
public Molecule add(Matter inValue)
{
return add(inValue, 1);
}
//--------------------------------------------------------------------------
public Molecule add(Matter inValue, int inCount)
{
if (inValue != null)
{
// This will also clear calculated properties
addElementalComposition(inValue.getElementalComposition(), inCount);
// Now deal with user-set masses...
if (mMonoisotopicMassIsUserSet
|| (null == inValue.getElementalComposition()
&& inValue.getMonoisotopicMass() != null))
{
setMonoisotopicMass((getMonoisotopicMass() != null ? getMonoisotopicMass(): 0.0)
+ ((inValue.getMonoisotopicMass() != null ? inValue.getMonoisotopicMass() : 0.0) * inCount));
}
if (mAverageMassIsUserSet
|| (null == inValue.getElementalComposition()
&& inValue.getAverageMass() != null))
{
setMonoisotopicMass((getAverageMass() != null ? getAverageMass(): 0.0)
+ ((inValue.getAverageMass() != null ? inValue.getAverageMass() : 0.0) * inCount));
}
if (mOrganicAverageMassIsUserSet
|| (null == inValue.getElementalComposition()
&& inValue.getOrganicAverageMass() != null))
{
setOrganicAverageMass((mOrganicAverageMassIsUserSet ? mOrganicAverageMass : CollectionUtil.hasValues(getElementalComposition()) ? getOrganicAverageMass() : 0.0)
+ (inValue.getOrganicAverageMass() != null ? inValue.getOrganicAverageMass() : 0) * inCount);
}
}
return this;
}
//--------------------------------------------------------------------------
public Molecule remove(Matter inValue)
{
return remove(inValue, 1);
}
//--------------------------------------------------------------------------
public Molecule remove(Matter inValue, int inCount)
{
return add(inValue, - inCount);
}
//--------------------------------------------------------------------------
public Molecule addAtoms(Element inElement, int inNum)
{
return addAtoms(inElement, Float.valueOf(inNum));
}
//--------------------------------------------------------------------------
public Molecule addAtoms(Element inElement, float inNum)
{
if (null == mElementalComposition)
{
mElementalComposition = new ElementalComposition();
}
Float count = mElementalComposition.get(inElement);
float newCount = inNum + (count != null ? count : 0);
mElementalComposition.put(inElement, newCount);
clearCalculatedProperties();
return this;
}
//--------------------------------------------------------------------------
public Molecule addAtom(Atom inAtom)
{
if (null == mAtoms)
{
mAtoms = new ArrayList<>(20);
}
mAtoms.add(inAtom);
// Update the elemental composition
return addAtoms(inAtom.getElement(), 1);
}
//--------------------------------------------------------------------------
public Molecule addAtoms(List inAtoms)
{
if (CollectionUtil.hasValues(inAtoms))
{
if (null == mAtoms)
{
mAtoms = new ArrayList<>(20);
}
mAtoms.addAll(inAtoms);
// Update the elemental composition
for (Atom atom : inAtoms)
{
addAtoms(atom.getElement(), 1);
}
}
return this;
}
//--------------------------------------------------------------------------
public List getAtoms()
{
return mAtoms != null ? Collections.unmodifiableList(mAtoms) : null;
}
//--------------------------------------------------------------------------
public Atom getLastAtom()
{
Atom atom = null;
if (CollectionUtil.hasValues(mAtoms))
{
atom = mAtoms.get(mAtoms.size() - 1);
}
return atom;
}
//--------------------------------------------------------------------------
/**
If the elemental composition is known, use setElementalComposition() and
the masses will be derived automatically; this method is for use in those
(hopefully) rare times when the mass is known but not the elemental composition.
@param inValue the mass to use as the monoisotopic mass for this object
@return this Molecule object to enable method chaining
*/
public Molecule setMonoisotopicMass(Double inValue)
{
if (null == mMonoisotopicMass)
{
mMonoisotopicMass = new HashMap<>(5);
}
mMonoisotopicMass.put(null, inValue);
mMonoisotopicMassIsUserSet = (inValue != null);
return this;
}
//--------------------------------------------------------------------------
public Double getMonoisotopicMass()
{
Double mass;
if (mMonoisotopicMassIsUserSet)
{
mass = mMonoisotopicMass.get(null);
}
else
{
// Use the default atomic weights version
mass = getMonoisotopicMass(Element.getDefaultAtomicWeightsVersion());
}
return mass;
}
//--------------------------------------------------------------------------
public Double getMonoisotopicMass(Element.AtomicWeightsVersion inVersion)
{
Double mass = null;
if (mMonoisotopicMassIsUserSet)
{
mass = mMonoisotopicMass.get(null);
}
else
{
if (null == mMonoisotopicMass
|| null == mMonoisotopicMass.get(inVersion))
{
calculateMassFromElementalComposition(inVersion);
}
mass = mMonoisotopicMass.get(inVersion);
}
return mass;
}
//--------------------------------------------------------------------------
/**
If the elemental composition is known, use setElementalComposition() and
the masses will be derived automatically; this method is for use in those
(hopefully) rare times when the mass is known but not the elemental composition.
@param inValue the mass to use as the average mass for this object
@return this Molecule object to enable method chaining
*/
public Molecule setAverageMass(Double inValue)
{
if (null == mAverageMass)
{
mAverageMass = new HashMap<>(5);
}
mAverageMass.put(null, inValue);
mAverageMassIsUserSet = (inValue != null);
return this;
}
//--------------------------------------------------------------------------
@Override
public Double getAverageMass()
{
Double mass;
if (mAverageMassIsUserSet)
{
mass = mAverageMass.get(null);
}
else
{
// Use the default atomic weights version
mass = getAverageMass(Element.getDefaultAtomicWeightsVersion());
}
return mass;
}
//--------------------------------------------------------------------------
public Double getAverageMass(Element.AtomicWeightsVersion inVersion)
{
Double mass = null;
if (mAverageMassIsUserSet)
{
mass = mAverageMass.get(null);
}
else
{
if (null == mAverageMass
|| null == mAverageMass.get(inVersion))
{
calculateMassFromElementalComposition(inVersion);
}
mass = mAverageMass.get(inVersion);
}
return mass;
}
//--------------------------------------------------------------------------
/**
If the elemental composition is known, use setElementalComposition() and
the masses will be derived automatically; this method is for use in those
(hopefully) rare times when the mass is known but not the elemental composition.
@param inValue the mass to use as the organic average mass for this object
@return this Molecule object to enable method chaining
*/
public Molecule setOrganicAverageMass(Double inValue)
{
mOrganicAverageMass = inValue;
mOrganicAverageMassIsUserSet = (inValue != null);
return this;
}
//--------------------------------------------------------------------------
@Override
public Double getOrganicAverageMass()
{
if (null == mOrganicAverageMass)
{
calculateMassFromElementalComposition(Element.getDefaultAtomicWeightsVersion());
}
return mOrganicAverageMass;
}
//--------------------------------------------------------------------------
/**
Returns the elemental composition as an unmodifiable Map.
*/
@Override
public Map getElementalComposition()
{
return (mElementalComposition != null ? mElementalComposition.toMap() : null);
}
//--------------------------------------------------------------------------
public Molecule setChemicalFormula(String inValue)
{
mChemicalFormula = inValue;
if (StringUtil.isSet(inValue))
{
Stack enclosingCharStack = new Stack<>();
FormulaSubBlock currentSubBlock = new FormulaSubBlock();
FormulaSubBlock topBlock = currentSubBlock;
Stack blockStack = new Stack<>();
blockStack.push(topBlock);
String chemicaFormulaString = inValue.trim();
for (int i = 0; i < chemicaFormulaString.length(); i++)
{
char currentChar = chemicaFormulaString.charAt(i);
if ('(' == currentChar
|| '[' == currentChar)
{
enclosingCharStack.push(currentChar);
FormulaSubBlock subBlock = new FormulaSubBlock().setBracketType(currentChar);
currentSubBlock.addSubBlock(subBlock);
blockStack.push(subBlock);
currentSubBlock = subBlock;
}
else if (')' == currentChar
|| ']' == currentChar)
{
// Ending enclosure w/o a starting enclosure?
if (0 == enclosingCharStack.size())
{
throw new ChemicalFormulaParseException("The chemical formula " + StringUtil.singleQuote(chemicaFormulaString) + " has an unbalanced '" + currentChar + "' near position " + (i + 1) + "!");
}
char openingChar = enclosingCharStack.pop();
// Mismatched enclosure characters?
if (('(' == openingChar && ')' != currentChar)
|| ('[' == openingChar && ']' != currentChar))
{
throw new ChemicalFormulaParseException("The chemical formula " + StringUtil.singleQuote(chemicaFormulaString) + " has an unbalanced '" + currentChar + "' near position " + (i + 1) + "!");
}
currentSubBlock.close();
i++;
String countString = "";
Character convertedSubscriptChar = null;
while (i < chemicaFormulaString.length())
{
char countChar = chemicaFormulaString.charAt(i);
if (Character.isDigit(countChar)
|| (convertedSubscriptChar = convertSubscriptChar(countChar)) != null)
{
countString += (convertedSubscriptChar != null ? convertedSubscriptChar : countChar);
i++;
}
else
{
break;
}
}
if (StringUtil.isSet(countString))
{
currentSubBlock.setCount(Integer.parseInt(countString));
}
if (i < chemicaFormulaString.length())
{
i--;
}
blockStack.pop();
currentSubBlock = blockStack.peek();
}
else
{
currentSubBlock.append(currentChar);
}
}
setElementalComposition(topBlock.getChemicalComposition());
}
return this;
}
//--------------------------------------------------------------------------
/**
Returns a chemical formula String like 'C5H11NO'. If carbon is present, it
is listed first followed by the other elements in ascending mass order.
Symbols for isotopes are enclosed in square brackets such as '[2H]2O'
for deuterated water.
@return the chemical formula string
*/
public String getChemicalFormula()
{
if (null == mChemicalFormula)
{
mChemicalFormula = (mElementalComposition != null ? mElementalComposition.getChemicalFormula() : null);
}
return mChemicalFormula;
}
//--------------------------------------------------------------------------
/**
Returns a chemical formula String like 'C₅H₁₁NO'. If carbon is present, it
is listed first followed by the other elements in ascending mass order.
Symbols for isotopes are enclosed in square brackets such as '[²H]₂O'
for deuterated water.
@return the chemical formula string
*/
public String getChemicalFormulaWithSubscripts()
{
return mElementalComposition != null ? mElementalComposition.getChemicalFormulaWithSubscripts() : null;
}
//--------------------------------------------------------------------------
public void clearCalculatedProperties()
{
if (mMonoisotopicMass != null)
{
for (Element.AtomicWeightsVersion version : Element.AtomicWeightsVersion.values())
{
mMonoisotopicMass.put(version, null);
}
}
if (mAverageMass != null)
{
for (Element.AtomicWeightsVersion version : Element.AtomicWeightsVersion.values())
{
mAverageMass.put(version, null);
}
}
if (! mOrganicAverageMassIsUserSet)
{
mOrganicAverageMass = null;
}
mHashCode = null;
mChemicalFormula = null;
}
//--------------------------------------------------------------------------
public Molecule addKa(IonizableGroup inValue)
{
return addKa(inValue, 1);
}
//--------------------------------------------------------------------------
public Molecule addKa(IonizableGroup inValue, int inCount)
{
if (isLocked()) throw new UnmodifyableObjectException(name() + " is locked and cannot be modified!");
if (null == mKas)
{
mKas = new ArrayList<>(3);
}
for (int i = 0; i < inCount; i++)
{
mKas.add(inValue);
}
return this;
}
//--------------------------------------------------------------------------
/**
Returns a List of IonizableGroup objects.
@return List of IonizableGroups
*/
public List getKas()
{
return mKas;
}
//--------------------------------------------------------------------------
public Double getPhysicalProperty(PhysicalProperty inProperty)
{
Double value = null;
if (mPropertyMap != null)
{
value = mPropertyMap.get(inProperty);
}
return value;
}
//--------------------------------------------------------------------------
public Molecule setPhysicalProperty(PhysicalProperty inProperty, Double inValue)
{
if (null == mPropertyMap)
{
mPropertyMap = new HashMap<>(5);
}
mPropertyMap.put(inProperty, inValue);
return this;
}
//--------------------------------------------------------------------------
public void setAttribute(String inName, Object inValue)
{
getOrInitAttributeMgr().setAttribute(inName, inValue);
}
//--------------------------------------------------------------------------
public boolean hasAttributes()
{
return mAttributeMgr != null && mAttributeMgr.hasAttributes();
}
//--------------------------------------------------------------------------
public boolean hasAttribute(String inName)
{
return mAttributeMgr != null && getOrInitAttributeMgr().hasAttribute(inName);
}
//--------------------------------------------------------------------------
public Object getAttribute(String inName)
{
return getOrInitAttributeMgr().getAttribute(inName);
}
//--------------------------------------------------------------------------
public Collection getAttributeNames()
{
return getOrInitAttributeMgr().getAttributeNames();
}
//--------------------------------------------------------------------------
public void clearAttributes()
{
if (mAttributeMgr != null)
{
mAttributeMgr.clearAttributes();
}
}
//--------------------------------------------------------------------------
public Object removeAttribute(String inName)
{
Object attr = null;
if (mAttributeMgr != null)
{
attr = getOrInitAttributeMgr().removeAttribute(inName);
}
return attr;
}
//##########################################################################
// PROTECTED METHODS
//##########################################################################
//--------------------------------------------------------------------------
public XMLNode toXMLNode()
{
XMLNode node = new XMLTag(HfgBioXML.MOL_TAG);
if (StringUtil.isSet(name())) node.setAttribute(HfgBioXML.NAME_ATT, name());
if (getMonoisotopicMass() != null) node.setAttribute(HfgBioXML.MONO_MASS_ATT,
formatMassString(getMonoisotopicMass()));
if (getAverageMass() != null) node.setAttribute(HfgBioXML.AVG_MASS_ATT,
formatMassString(getAverageMass()));
// TODO: Add an organic mass tag?
Map elementalCompositionMap = getElementalComposition();
if (CollectionUtil.hasValues(elementalCompositionMap))
{
XMLNode elementalCompTag = new XMLTag(HfgBioXML.ELEMENTAL_COMP_TAG);
node.addSubtag(elementalCompTag);
for (Map.Entry entry : elementalCompositionMap.entrySet())
{
elementalCompTag.setAttribute(entry.getKey().getName(), entry.getValue());
}
}
if (CollectionUtil.hasValues(getKas()))
{
XMLNode kasTag = new XMLTag(HfgBioXML.KAS_TAG);
node.addSubtag(kasTag);
for (IonizableGroup grp : getKas())
{
kasTag.addSubtag(grp.toXMLNode());
}
}
if (mAttributeMgr != null)
{
node.addSubtag(mAttributeMgr.toXMLNode());
}
return node;
}
//--------------------------------------------------------------------------
protected void calculateMassFromElementalComposition(Element.AtomicWeightsVersion inVersion)
{
Map elementalCompositionMap = getElementalComposition();
if (elementalCompositionMap != null)
{
double mono = 0.0;
double avg = 0.0;
double organicAvg = 0.0;
for (Element element : elementalCompositionMap.keySet())
{
float count = elementalCompositionMap.get(element);
mono += count * element.getMonoisotopicMass(inVersion);
Double elementalAvgMass = element.getAverageMass(inVersion);
avg += count * (elementalAvgMass != null ? elementalAvgMass : element.getMonoisotopicMass()); // If we don't have an avg. mass for the element, use monoisotopic
Double elementalOrgAvgMass = element.getOrganicAverageMass();
organicAvg += count * (elementalOrgAvgMass != null ? elementalOrgAvgMass : elementalAvgMass != null ? elementalAvgMass : element.getMonoisotopicMass());
}
if (null == mMonoisotopicMass)
{
mMonoisotopicMass = new HashMap<>(3);
}
mMonoisotopicMass.put(inVersion, mono);
if (null == mAverageMass)
{
mAverageMass = new HashMap<>(3);
}
mAverageMass.put(inVersion, avg);
if (! mOrganicAverageMassIsUserSet) mOrganicAverageMass = organicAvg;
}
}
//--------------------------------------------------------------------------
protected boolean massesAreUserSet()
{
return (mMonoisotopicMassIsUserSet || mAverageMassIsUserSet);
}
//--------------------------------------------------------------------------
protected static String formatMassString(double inValue)
{
String massString = inValue + "";
int index = massString.indexOf(".");
// More than 6 decimal places? Round to 6.
if (index > 0
&& massString.length() - index > 6)
{
massString = String.format("%.6f", inValue);
}
return massString;
}
//##########################################################################
// PRIVATE METHODS
//##########################################################################
//--------------------------------------------------------------------------
private AttributeMgr getOrInitAttributeMgr()
{
if (null == mAttributeMgr)
{
mAttributeMgr = new AttributeMgr();
}
return mAttributeMgr;
}
//---------------------------------------------------------------------------
// Used when parsing chemical formulas
private static Character convertSubscriptChar(char inChar)
{
Character subscriptChar = null;
switch (inChar)
{
case '\u2080':
subscriptChar = '0';
break;
case '\u2081':
subscriptChar = '1';
break;
case '\u2082':
subscriptChar = '2';
break;
case '\u2083':
subscriptChar = '3';
break;
case '\u2084':
subscriptChar = '4';
break;
case '\u2085':
subscriptChar = '5';
break;
case '\u2086':
subscriptChar = '6';
break;
case '\u2087':
subscriptChar = '7';
break;
case '\u2088':
subscriptChar = '8';
break;
case '\u2089':
subscriptChar = '9';
break;
}
return subscriptChar;
}
//---------------------------------------------------------------------------
// Used when parsing isotopes in chemical formulas
private static Character convertSuperscriptChar(char inChar)
{
Character superscriptChar = null;
switch (inChar)
{
case '\u207B':
superscriptChar = '-';
break;
case '\u2070':
superscriptChar = '0';
break;
case 0xB9:
superscriptChar = '1';
break;
case 0xB2:
superscriptChar = '2';
break;
case 0xB3:
superscriptChar = '3';
break;
case '\u2074':
superscriptChar = '4';
break;
case '\u2075':
superscriptChar = '5';
break;
case '\u2076':
superscriptChar = '6';
break;
case '\u2077':
superscriptChar = '7';
break;
case '\u2078':
superscriptChar = '8';
break;
case '\u2079':
superscriptChar = '9';
break;
}
return superscriptChar;
}
//##########################################################################
// PRIVATE CLASS
//##########################################################################
private class FormulaSubBlock
{
private StringBuilder mString = new StringBuilder();
private int mCount = 1;
private char mBracketType;
private boolean mClosed = false;
List mSubBlocks;
//-----------------------------------------------------------------------
public String toString()
{
return mString.toString();
}
//-----------------------------------------------------------------------
public void append(char inChar)
{
mString.append(inChar);
}
//-----------------------------------------------------------------------
public void setCount(int inValue)
{
mCount = inValue;
}
//-----------------------------------------------------------------------
public FormulaSubBlock setBracketType(char inValue)
{
mBracketType = inValue;
return this;
}
//-----------------------------------------------------------------------
public void close()
{
mClosed = true;
}
//-----------------------------------------------------------------------
public boolean isClosed()
{
return mClosed;
}
//-----------------------------------------------------------------------
public void addSubBlock(FormulaSubBlock inValue)
{
if (null == mSubBlocks)
{
mSubBlocks = new ArrayList<>(4);
}
mSubBlocks.add(inValue);
}
//-----------------------------------------------------------------------
public Map getChemicalComposition()
{
Map chemicalCompositionMap = getChemicalComposition(mString.toString());
if (CollectionUtil.hasValues(mSubBlocks))
{
for (FormulaSubBlock subBlock : mSubBlocks)
{
Map subBlockCompositionMap = subBlock.getChemicalComposition();
for (Element element : subBlockCompositionMap.keySet())
{
float updatedCount = subBlockCompositionMap.get(element);
Float existingCount = chemicalCompositionMap.get(element);
if (existingCount != null)
{
updatedCount += existingCount;
}
chemicalCompositionMap.put(element, updatedCount);
}
}
}
return chemicalCompositionMap;
}
//-----------------------------------------------------------------------
private Map getChemicalComposition(String inString)
{
Map chemicalCompositionMap = new HashMap<>(10);
int i = 0;
char prevChar = ' ';
while (i < inString.length())
{
char currentChar = inString.charAt(i);
if (currentChar == ' ' // Skip whitespace
|| currentChar == ':' // Skip bond notation
|| (currentChar == '-' && ! Character.isDigit(prevChar)) // Skip linear formula single bond notation
|| currentChar == '−' // Skip linear formula single bond notation
|| currentChar == '=' // Skip linear formula double bond notation
|| currentChar == '≡' // Skip linear formula triple bond notation
|| currentChar == '@') // Skip trapped atom notation
{
i++;
continue;
}
if (mBracketType == '['
&& 0 == i
&& (Character.isDigit(currentChar)
|| CharUtil.isSuperscript(currentChar)))
{
// Isotope
String massNumString = "";
Character convertedSuperscriptChar = null;
while (i < inString.length())
{
char theChar = inString.charAt(i);
if (Character.isDigit(theChar)
|| (convertedSuperscriptChar = convertSuperscriptChar(theChar)) != null)
{
massNumString += (convertedSuperscriptChar != null ? convertedSuperscriptChar : theChar);
i++;
}
else
{
break;
}
}
Element element = null;
if (i < inString.length() - 1)
{
element = Element.valueOf(inString.substring(i, i + 2));
}
if (element != null)
{
i += 2;
}
else
{
element = Element.valueOf(inString.substring(i, i + 1));
if (element != null)
{
i++;
}
else
{
throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + "!");
}
}
Isotope isotope = Isotope.valueOf(element, Integer.parseInt(massNumString));
float updatedCount = mCount;
Float existingCount = chemicalCompositionMap.get(isotope);
if (existingCount != null)
{
updatedCount += existingCount;
}
chemicalCompositionMap.put(isotope, updatedCount);
}
else if (sSaltIndicatorsInChemicalFormulas.contains(currentChar))
{
// molecule of crystallization
i++;
String countString = "";
while (i < inString.length())
{
char countChar = inString.charAt(i);
if (countChar != ' ') // Skip whitespace
{
if (Character.isDigit(countChar))
{
countString += countChar;
}
else
{
break;
}
}
i++;
}
float count = StringUtil.isSet(countString) ? Float.parseFloat(countString) : 1;
Map crystallizationChemicalCompositionMap = getChemicalComposition(inString.substring(i));
for (Element element : crystallizationChemicalCompositionMap.keySet())
{
float updatedCount = crystallizationChemicalCompositionMap.get(element) * count * mCount;
Float existingCount = chemicalCompositionMap.get(element);
if (existingCount != null)
{
updatedCount += existingCount;
}
chemicalCompositionMap.put(element, updatedCount);
}
// There shouldn't be anything after the crystallization molecule
break;
}
else
{
Element element = null;
if (i < inString.length() - 1)
{
element = Element.valueOf(inString.substring(i, i + 2));
}
if (element != null)
{
i += 2;
}
else
{
element = Element.valueOf(inString.substring(i, i + 1));
if (element != null)
{
i++;
}
else
{
throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + "!");
}
}
String countString = "";
Character convertedSubscriptChar = null;
while (i < inString.length())
{
char countChar = inString.charAt(i);
if (Character.isDigit(countChar)
|| (convertedSubscriptChar = convertSubscriptChar(countChar)) != null)
{
countString += (convertedSubscriptChar != null ? convertedSubscriptChar : countChar);
i++;
}
else if ('-' == countChar)
{
throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + ": Counts can't be ranges!");
}
else
{
break;
}
}
float updatedCount = mCount * (StringUtil.isSet(countString) ? Float.parseFloat(countString) : 1);
Float existingCount = chemicalCompositionMap.get(element);
if (existingCount != null)
{
updatedCount += existingCount;
}
chemicalCompositionMap.put(element, updatedCount);
}
prevChar = currentChar;
}
return chemicalCompositionMap;
}
}
}