001package com.hfg.chem; 002 003import java.util.ArrayList; 004import java.util.Collections; 005import java.util.HashMap; 006import java.util.HashSet; 007import java.util.List; 008import java.util.Map; 009import java.util.Set; 010import java.util.Stack; 011 012import com.hfg.util.CharUtil; 013import com.hfg.util.CompareUtil; 014import com.hfg.util.StringUtil; 015import com.hfg.util.collection.CollectionUtil; 016 017//------------------------------------------------------------------------------ 018/** 019 Elemental composition and mass tracking object for any organic or inorganic molecule. 020 <div> 021 @author J. Alex Taylor, hairyfatguy.com 022 </div> 023 */ 024//------------------------------------------------------------------------------ 025// com.hfg XML/HTML Coding Library 026// 027// This library is free software; you can redistribute it and/or 028// modify it under the terms of the GNU Lesser General Public 029// License as published by the Free Software Foundation; either 030// version 2.1 of the License, or (at your option) any later version. 031// 032// This library is distributed in the hope that it will be useful, 033// but WITHOUT ANY WARRANTY; without even the implied warranty of 034// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 035// Lesser General Public License for more details. 036// 037// You should have received a copy of the GNU Lesser General Public 038// License along with this library; if not, write to the Free Software 039// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 040// 041// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 042// jataylor@hairyfatguy.com 043//------------------------------------------------------------------------------ 044 045public class MatterImpl implements Matter, Cloneable, Comparable<MatterImpl> 046{ 047 048 //########################################################################## 049 // PRIVATE FIELDS 050 //########################################################################## 051 052 private Map<Element, Float> mElementalComposition; 053 private Double mMonoisotopicMass; 054 private Double mAverageMass; 055 056 private boolean mMonoisotopicMassIsUserSet; 057 private boolean mAverageMassIsUserSet; 058 059 private Integer mHashCode; 060 061 private static Set<Character> sSaltIndicatorsInChemicalFormulas = new HashSet<>(4); 062 063 static 064 { 065 sSaltIndicatorsInChemicalFormulas.add('.'); 066 sSaltIndicatorsInChemicalFormulas.add('·'); 067 sSaltIndicatorsInChemicalFormulas.add('•'); 068 sSaltIndicatorsInChemicalFormulas.add('*'); 069 } 070 071 //########################################################################## 072 // CONSTRUCTORS 073 //########################################################################## 074 075 //-------------------------------------------------------------------------- 076 public MatterImpl() 077 { 078 } 079 080 //-------------------------------------------------------------------------- 081 public MatterImpl(Map<Element, Float> inMap) 082 { 083 setElementalComposition(inMap); 084 } 085 086 //-------------------------------------------------------------------------- 087 public MatterImpl(Matter inInitialValue) 088 { 089 if (inInitialValue != null) 090 { 091 setMonoisotopicMass(inInitialValue.getMonoisotopicMass()); 092 setAverageMass(inInitialValue.getAverageMass()); 093 setElementalComposition(inInitialValue.getElementalComposition()); 094 } 095 } 096 097 //########################################################################## 098 // PUBLIC METHODS 099 //########################################################################## 100 101 102 //-------------------------------------------------------------------------- 103 // TODO: Add isotope support 104 public static Matter fromChemicalFormula(String inChemicalFormula) 105 { 106 MatterImpl matter = null; 107 108 Stack<Character> enclosingCharStack = new Stack<>(); 109 110 if (StringUtil.isSet(inChemicalFormula)) 111 { 112 matter = new MatterImpl(); 113 114 MatterImpl.FormulaSubBlock currentSubBlock = matter.new FormulaSubBlock(); 115 MatterImpl.FormulaSubBlock topBlock = currentSubBlock; 116 Stack<MatterImpl.FormulaSubBlock> blockStack = new Stack<>(); 117 blockStack.push(topBlock); 118 119 String chemicaFormulaString = inChemicalFormula.trim(); 120 121 for (int i = 0; i < chemicaFormulaString.length(); i++) 122 { 123 char currentChar = chemicaFormulaString.charAt(i); 124 125 if ('(' == currentChar 126 || '[' == currentChar) 127 { 128 enclosingCharStack.push(currentChar); 129 MatterImpl.FormulaSubBlock subBlock = matter.new FormulaSubBlock().setBracketType(currentChar); 130 currentSubBlock.addSubBlock(subBlock); 131 blockStack.push(subBlock); 132 133 currentSubBlock = subBlock; 134 } 135 else if (')' == currentChar 136 || ']' == currentChar) 137 { 138 // Ending enclosure w/o a starting enclosure? 139 if (0 == enclosingCharStack.size()) 140 { 141 throw new ChemicalFormulaParseException("The chemical formula " + StringUtil.singleQuote(chemicaFormulaString) + " has an unbalanced '" + currentChar + "' near position " + (i + 1) + "!"); 142 } 143 144 char openingChar = enclosingCharStack.pop(); 145 // Mismatched enclosure characters? 146 if (('(' == openingChar && ')' != currentChar) 147 || ('[' == openingChar && ']' != currentChar)) 148 { 149 throw new ChemicalFormulaParseException("The chemical formula " + StringUtil.singleQuote(chemicaFormulaString) + " has an unbalanced '" + currentChar + "' near position " + (i + 1) + "!"); 150 } 151 152 currentSubBlock.close(); 153 154 i++; 155 156 String countString = ""; 157 Character convertedSubscriptChar = null; 158 while (i < chemicaFormulaString.length()) 159 { 160 char countChar = chemicaFormulaString.charAt(i); 161 if (Character.isDigit(countChar) 162 || (convertedSubscriptChar = convertSubscriptChar(countChar)) != null) 163 { 164 countString += (convertedSubscriptChar != null ? convertedSubscriptChar : countChar); 165 i++; 166 } 167 else 168 { 169 break; 170 } 171 } 172 173 if (StringUtil.isSet(countString)) 174 { 175 currentSubBlock.setCount(Integer.parseInt(countString)); 176 } 177 178 if (i < chemicaFormulaString.length()) 179 { 180 i--; 181 } 182 183 blockStack.pop(); 184 currentSubBlock = blockStack.peek(); 185 } 186 else 187 { 188 currentSubBlock.append(currentChar); 189 } 190 } 191 192 matter.setElementalComposition(topBlock.getChemicalComposition()); 193 } 194 195 return matter; 196 } 197 198 //-------------------------------------------------------------------------- 199 @Override 200 public int hashCode() 201 { 202 if (null == mHashCode) 203 { 204 int hashcode = 31; 205 if (mElementalComposition != null) 206 { 207 for (Element element : mElementalComposition.keySet()) 208 { 209 hashcode = hashcode + 31 * element.hashCode() * mElementalComposition.get(element).intValue(); 210 } 211 } 212 213 mHashCode = hashcode; 214 } 215 216 return mHashCode; 217 } 218 219 //-------------------------------------------------------------------------- 220 @Override 221 public boolean equals(Object inObj) 222 { 223 boolean result = false; 224 225 if (inObj != null 226 && inObj instanceof MatterImpl) 227 { 228 result = (0 == compareTo((MatterImpl) inObj)); 229 } 230 231 return result; 232 } 233 234 //-------------------------------------------------------------------------- 235 public int compareTo(MatterImpl inObj) 236 { 237 int result = -1; 238 239 if (inObj != null) 240 { 241 result = CompareUtil.compare(hashCode(), inObj.hashCode()); 242 } 243 244 return result; 245 } 246 247 //-------------------------------------------------------------------------- 248 public void setElementalComposition(Map<Element, Float> inMap) 249 { 250 mElementalComposition = null; 251 if (CollectionUtil.hasValues(inMap)) 252 { 253 mElementalComposition = new HashMap<>(inMap); 254 } 255 256 clearCalculatedProperties(); 257 } 258 259 //-------------------------------------------------------------------------- 260 public void clearElementalComposition() 261 { 262 mElementalComposition = null; 263 clearCalculatedProperties(); 264 } 265 266 //-------------------------------------------------------------------------- 267 public MatterImpl addElementalComposition(Map<Element, Float> inMap) 268 { 269 return addElementalComposition(inMap, 1); 270 } 271 272 //-------------------------------------------------------------------------- 273 public MatterImpl addElementalComposition(Map<Element, Float> inMap, int inNum) 274 { 275 if (CollectionUtil.hasValues(inMap)) 276 { 277 for (Element element : inMap.keySet()) 278 { 279 addAtoms(element, inMap.get(element) * inNum); 280 } 281 282 clearCalculatedProperties(); 283 } 284 285 return this; 286 } 287 288 //-------------------------------------------------------------------------- 289 public MatterImpl add(Matter inValue) 290 { 291 return add(inValue, 1); 292 } 293 294 //-------------------------------------------------------------------------- 295 public MatterImpl add(Matter inValue, int inCount) 296 { 297 if (inValue != null) 298 { 299 // This will also clear calculated properties 300 addElementalComposition(inValue.getElementalComposition(), inCount); 301 302 // Now deal with user-set masses... 303 304 if (mMonoisotopicMassIsUserSet 305 || (null == inValue.getElementalComposition() 306 && inValue.getMonoisotopicMass() != null)) 307 { 308 setMonoisotopicMass((mMonoisotopicMassIsUserSet ? mMonoisotopicMass : CollectionUtil.hasValues(getElementalComposition()) ? getMonoisotopicMass() : 0.0) 309 + (inValue.getMonoisotopicMass() != null ? inValue.getMonoisotopicMass() : 0) * inCount); 310 } 311 312 if (mAverageMassIsUserSet 313 || (null == inValue.getElementalComposition() 314 && inValue.getAverageMass() != null)) 315 { 316 setAverageMass((mAverageMassIsUserSet ? mAverageMass : CollectionUtil.hasValues(getElementalComposition()) ? getAverageMass() : 0.0) 317 + (inValue.getAverageMass() != null ? inValue.getAverageMass() : 0) * inCount); 318 } 319 } 320 321 return this; 322 } 323 324 //-------------------------------------------------------------------------- 325 public MatterImpl remove(Matter inValue) 326 { 327 return remove(inValue, 1); 328 } 329 330 //-------------------------------------------------------------------------- 331 public MatterImpl remove(Matter inValue, int inCount) 332 { 333 return add(inValue, - inCount); 334 } 335 336 //-------------------------------------------------------------------------- 337 public MatterImpl addAtoms(Element inElement, int inNum) 338 { 339 return addAtoms(inElement, new Float(inNum)); 340 } 341 342 //-------------------------------------------------------------------------- 343 public MatterImpl addAtoms(Element inElement, float inNum) 344 { 345 if (null == mElementalComposition) 346 { 347 mElementalComposition = new HashMap<Element, Float>(); 348 } 349 350 Float count = mElementalComposition.get(inElement); 351 float newCount = inNum + (count != null ? count : 0); 352 353 mElementalComposition.put(inElement, newCount); 354 355 clearCalculatedProperties(); 356 357 return this; 358 } 359 360 //-------------------------------------------------------------------------- 361 /** 362 If the elemental composition is known, use setElementalComposition() and 363 the masses will be derived automatically; this method is for use in those 364 (hopefully) rare times when the mass is known but not the elemental composition. 365 @param inValue the mass to use as the monoisotopic mass for this object 366 @return this OrganicMatterImpl object to enable method chaining 367 */ 368 public MatterImpl setMonoisotopicMass(Double inValue) 369 { 370 mMonoisotopicMass = inValue; 371 mMonoisotopicMassIsUserSet = (inValue != null); 372 return this; 373 } 374 375 //-------------------------------------------------------------------------- 376 /** 377 If the elemental composition is known, use setElementalComposition() and 378 the masses will be derived automatically; this method is for use in those 379 (hopefully) rare times when the mass is known but not the elemental composition. 380 @param inValue the mass to use as the average mass for this object 381 @return this OrganicMatterImpl object to enable method chaining 382 */ 383 public MatterImpl setAverageMass(Double inValue) 384 { 385 mAverageMass = inValue; 386 mAverageMassIsUserSet = (inValue != null); 387 return this; 388 } 389 390 //-------------------------------------------------------------------------- 391 public Double getMonoisotopicMass() 392 { 393 if (null == mMonoisotopicMass) 394 { 395 calculateMassFromElementalComposition(); 396 } 397 398 return mMonoisotopicMass; 399 } 400 401 //-------------------------------------------------------------------------- 402 public Double getAverageMass() 403 { 404 if (null == mAverageMass) 405 { 406 calculateMassFromElementalComposition(); 407 } 408 409 return mAverageMass; 410 } 411 412 //-------------------------------------------------------------------------- 413 /** 414 Returns the elemental composition as an unmodifiable Map. 415 */ 416 public Map<Element, Float> getElementalComposition() 417 { 418 return (mElementalComposition != null ? Collections.unmodifiableMap(mElementalComposition) : null); 419 } 420 421 //-------------------------------------------------------------------------- 422 /** 423 Returns a chemical formula String like 'C5H11NO'. If carbon is present, it 424 is listed first followed by the other elements in ascending mass order. 425 Symbols for isotopes are enclosed in square brackets such as '[2H]2O' 426 for deuterated water. 427 @return the chemical formula string 428 */ 429 public String getChemicalFormula() 430 { 431 return getChemicalFormula(false); 432 } 433 434 //-------------------------------------------------------------------------- 435 /** 436 Returns a chemical formula String like 'C₅H₁₁NO'. If carbon is present, it 437 is listed first followed by the other elements in ascending mass order. 438 Symbols for isotopes are enclosed in square brackets such as '[²H]₂O' 439 for deuterated water. 440 @return the chemical formula string 441 */ 442 public String getChemicalFormulaWithSubscripts() 443 { 444 return getChemicalFormula(true); 445 } 446 447 //-------------------------------------------------------------------------- 448 private String getChemicalFormula(boolean inUseSubscripts) 449 { 450 StringBuilder compositionString = new StringBuilder(); 451 Map<Element, Float> elemComp = getElementalComposition(); 452 if (CollectionUtil.hasValues(elemComp)) 453 { 454 List<Element> elements = new ArrayList<>(elemComp.keySet()); 455 Collections.sort(elements); 456 457 if (elements.remove(Element.CARBON)) 458 { 459 Float count = elemComp.get(Element.CARBON); 460 if (count != null && count != 0) 461 { 462 String countString = (count == count.intValue() ? count.intValue() + "" : String.format("%3.1f", count.floatValue()) + ""); 463 if (inUseSubscripts) 464 { 465 countString = StringUtil.toSubscript(countString); 466 } 467 compositionString.append("C" + (count == 1 ? "" : (count < 0 ? "(" + countString + ")" : countString))); 468 } 469 } 470 471 for (Element element : elements) 472 { 473 Float count = elemComp.get(element); 474 // Encode pure isotopes in square brackets like "[²H]" for deuterium 475 if (count != null && count != 0) 476 { 477 String countString = (count == count.intValue() ? count.intValue() + "" : String.format("%3.1f", count.floatValue()) + ""); 478 String symbol = element.getSymbol(); 479 if (inUseSubscripts) 480 { 481 countString = StringUtil.toSubscript(countString); 482 if (element instanceof Isotope) 483 { 484 symbol = StringUtil.toSuperscript(((Isotope) element).getMassNumber()) + ((Isotope) element).getElement().getSymbol(); 485 } 486 } 487 488 compositionString.append((element instanceof Isotope ? "[" + symbol + "]" : symbol) 489 + (count == 1 ? "" : (count < 0 ? "(" + countString + ")" : countString))); 490 } 491 } 492 } 493 494 return compositionString.toString(); 495 } 496 497 498 //-------------------------------------------------------------------------- 499 public MatterImpl clone() 500 { 501 MatterImpl copy; 502 try 503 { 504 copy = (MatterImpl) super.clone(); 505 } 506 catch (CloneNotSupportedException e) 507 { 508 throw new RuntimeException("Coding problem! CloneNotSupportedException should not be possible when cloning a " 509 + this.getClass().getSimpleName() + " object!", e); 510 } 511 512 if (mElementalComposition != null) 513 { 514 mElementalComposition = new HashMap<>(mElementalComposition); 515 } 516 517 return copy; 518 } 519 520 521 //-------------------------------------------------------------------------- 522 public void clearCalculatedProperties() 523 { 524 if (! mMonoisotopicMassIsUserSet) mMonoisotopicMass = null; 525 if (! mAverageMassIsUserSet) mAverageMass = null; 526 mHashCode = null; 527 } 528 529 //########################################################################## 530 // PROTECTED METHODS 531 //########################################################################## 532 533 //-------------------------------------------------------------------------- 534 protected void calculateMassFromElementalComposition() 535 { 536 Map<Element, Float> elementalCompositionMap = getElementalComposition(); 537 538 if (elementalCompositionMap != null) 539 { 540 double mono = 0.0; 541 double avg = 0.0; 542 543 for (Element element : elementalCompositionMap.keySet()) 544 { 545 float count = elementalCompositionMap.get(element); 546 mono += count * element.getMonoisotopicMass(); 547 548 Double elementalAvgMass = element.getAverageMass(); 549 avg += count * (elementalAvgMass != null ? elementalAvgMass : element.getMonoisotopicMass()); // If we don't have an avg. mass for the element, use monoisotopic 550 } 551 552 if (! mMonoisotopicMassIsUserSet) mMonoisotopicMass = new Double(mono); 553 if (! mAverageMassIsUserSet) mAverageMass = new Double(avg); 554 } 555 } 556 557 //-------------------------------------------------------------------------- 558 protected boolean massesAreUserSet() 559 { 560 return (mMonoisotopicMassIsUserSet || mAverageMassIsUserSet); 561 } 562 563 564 //--------------------------------------------------------------------------- 565 // Used when parsing chemical formulas 566 private static Character convertSubscriptChar(char inChar) 567 { 568 Character subscriptChar = null; 569 switch (inChar) 570 { 571 case '\u2080': 572 subscriptChar = '0'; 573 break; 574 case '\u2081': 575 subscriptChar = '1'; 576 break; 577 case '\u2082': 578 subscriptChar = '2'; 579 break; 580 case '\u2083': 581 subscriptChar = '3'; 582 break; 583 case '\u2084': 584 subscriptChar = '4'; 585 break; 586 case '\u2085': 587 subscriptChar = '5'; 588 break; 589 case '\u2086': 590 subscriptChar = '6'; 591 break; 592 case '\u2087': 593 subscriptChar = '7'; 594 break; 595 case '\u2088': 596 subscriptChar = '8'; 597 break; 598 case '\u2089': 599 subscriptChar = '9'; 600 break; 601 } 602 603 return subscriptChar; 604 } 605 606 //--------------------------------------------------------------------------- 607 // Used when parsing isotopes in chemical formulas 608 private static Character convertSuperscriptChar(char inChar) 609 { 610 Character superscriptChar = null; 611 switch (inChar) 612 { 613 case '\u207B': 614 superscriptChar = '-'; 615 break; 616 case '\u2070': 617 superscriptChar = '0'; 618 break; 619 case 0xB9: 620 superscriptChar = '1'; 621 break; 622 case 0xB2: 623 superscriptChar = '2'; 624 break; 625 case 0xB3: 626 superscriptChar = '3'; 627 break; 628 case '\u2074': 629 superscriptChar = '4'; 630 break; 631 case '\u2075': 632 superscriptChar = '5'; 633 break; 634 case '\u2076': 635 superscriptChar = '6'; 636 break; 637 case '\u2077': 638 superscriptChar = '7'; 639 break; 640 case '\u2078': 641 superscriptChar = '8'; 642 break; 643 case '\u2079': 644 superscriptChar = '9'; 645 break; 646 } 647 648 return superscriptChar; 649 } 650 651 652 //########################################################################## 653 // PRIVATE CLASS 654 //########################################################################## 655 656 657 private class FormulaSubBlock 658 { 659 private StringBuilder mString = new StringBuilder(); 660 private int mCount = 1; 661 private char mBracketType; 662 private boolean mClosed = false; 663 List<MatterImpl.FormulaSubBlock> mSubBlocks; 664 665 //----------------------------------------------------------------------- 666 public String toString() 667 { 668 return mString.toString(); 669 } 670 671 //----------------------------------------------------------------------- 672 public void append(char inChar) 673 { 674 mString.append(inChar); 675 } 676 677 //----------------------------------------------------------------------- 678 public void setCount(int inValue) 679 { 680 mCount = inValue; 681 } 682 683 //----------------------------------------------------------------------- 684 public FormulaSubBlock setBracketType(char inValue) 685 { 686 mBracketType = inValue; 687 return this; 688 } 689 690 //----------------------------------------------------------------------- 691 public void close() 692 { 693 mClosed = true; 694 } 695 696 //----------------------------------------------------------------------- 697 public boolean isClosed() 698 { 699 return mClosed; 700 } 701 702 //----------------------------------------------------------------------- 703 public void addSubBlock(MatterImpl.FormulaSubBlock inValue) 704 { 705 if (null == mSubBlocks) 706 { 707 mSubBlocks = new ArrayList<>(4); 708 } 709 710 mSubBlocks.add(inValue); 711 } 712 713 //----------------------------------------------------------------------- 714 public Map<Element, Float> getChemicalComposition() 715 { 716 Map<Element, Float> chemicalCompositionMap = getChemicalComposition(mString.toString()); 717 718 if (CollectionUtil.hasValues(mSubBlocks)) 719 { 720 for (MatterImpl.FormulaSubBlock subBlock : mSubBlocks) 721 { 722 Map<Element, Float> subBlockCompositionMap = subBlock.getChemicalComposition(); 723 for (Element element : subBlockCompositionMap.keySet()) 724 { 725 float updatedCount = subBlockCompositionMap.get(element); 726 727 Float existingCount = chemicalCompositionMap.get(element); 728 if (existingCount != null) 729 { 730 updatedCount += existingCount; 731 } 732 733 chemicalCompositionMap.put(element, updatedCount); 734 } 735 } 736 } 737 738 return chemicalCompositionMap; 739 } 740 741 //----------------------------------------------------------------------- 742 private Map<Element, Float> getChemicalComposition(String inString) 743 { 744 Map<Element, Float> chemicalCompositionMap = new HashMap<>(10); 745 746 int i = 0; 747 char prevChar = ' '; 748 while (i < inString.length()) 749 { 750 char currentChar = inString.charAt(i); 751 752 if (currentChar == ' ' // Skip whitespace 753 || currentChar == ':' // Skip bond notation 754 || (currentChar == '-' && ! Character.isDigit(prevChar)) // Skip linear formula single bond notation 755 || currentChar == '−' // Skip linear formula single bond notation 756 || currentChar == '=' // Skip linear formula double bond notation 757 || currentChar == '≡' // Skip linear formula triple bond notation 758 || currentChar == '@') // Skip trapped atom notation 759 { 760 i++; 761 continue; 762 } 763 764 if (mBracketType == '[' 765 && 0 == i 766 && (Character.isDigit(currentChar) 767 || CharUtil.isSuperscript(currentChar))) 768 { 769 // Isotope 770 771 String massNumString = ""; 772 Character convertedSuperscriptChar = null; 773 while (i < inString.length()) 774 { 775 char theChar = inString.charAt(i); 776 if (Character.isDigit(theChar) 777 || (convertedSuperscriptChar = convertSuperscriptChar(theChar)) != null) 778 { 779 massNumString += (convertedSuperscriptChar != null ? convertedSuperscriptChar : theChar); 780 i++; 781 } 782 else 783 { 784 break; 785 } 786 } 787 788 Element element = null; 789 if (i < inString.length() - 1) 790 { 791 element = Element.valueOf(inString.substring(i, i + 2)); 792 } 793 794 if (element != null) 795 { 796 i += 2; 797 } 798 else 799 { 800 element = Element.valueOf(inString.substring(i, i + 1)); 801 if (element != null) 802 { 803 i++; 804 } 805 else 806 { 807 throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + "!"); 808 } 809 } 810 811 Isotope isotope = Isotope.valueOf(element, Integer.parseInt(massNumString)); 812 813 float updatedCount = mCount; 814 815 Float existingCount = chemicalCompositionMap.get(isotope); 816 if (existingCount != null) 817 { 818 updatedCount += existingCount; 819 } 820 821 chemicalCompositionMap.put(isotope, updatedCount); 822 } 823 else if (sSaltIndicatorsInChemicalFormulas.contains(currentChar)) 824 { 825 // molecule of crystallization 826 i++; 827 String countString = ""; 828 while (i < inString.length()) 829 { 830 char countChar = inString.charAt(i); 831 832 if (countChar != ' ') // Skip whitespace 833 { 834 if (Character.isDigit(countChar)) 835 { 836 countString += countChar; 837 } 838 else 839 { 840 break; 841 } 842 } 843 844 i++; 845 } 846 847 float count = StringUtil.isSet(countString) ? Float.parseFloat(countString) : 1; 848 849 Map<Element, Float> crystallizationChemicalCompositionMap = getChemicalComposition(inString.substring(i)); 850 for (Element element : crystallizationChemicalCompositionMap.keySet()) 851 { 852 float updatedCount = crystallizationChemicalCompositionMap.get(element) * count * mCount; 853 854 Float existingCount = chemicalCompositionMap.get(element); 855 if (existingCount != null) 856 { 857 updatedCount += existingCount; 858 } 859 860 chemicalCompositionMap.put(element, updatedCount); 861 } 862 863 // There shouldn't be anything after the crystallization molecule 864 break; 865 } 866 else 867 { 868 Element element = null; 869 if (i < inString.length() - 1) 870 { 871 element = Element.valueOf(inString.substring(i, i + 2)); 872 } 873 874 if (element != null) 875 { 876 i += 2; 877 } 878 else 879 { 880 element = Element.valueOf(inString.substring(i, i + 1)); 881 if (element != null) 882 { 883 i++; 884 } 885 else 886 { 887 throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + "!"); 888 } 889 } 890 891 String countString = ""; 892 Character convertedSubscriptChar = null; 893 while (i < inString.length()) 894 { 895 char countChar = inString.charAt(i); 896 if (Character.isDigit(countChar) 897 || (convertedSubscriptChar = convertSubscriptChar(countChar)) != null) 898 { 899 countString += (convertedSubscriptChar != null ? convertedSubscriptChar : countChar); 900 i++; 901 } 902 else if ('-' == countChar) 903 { 904 throw new ChemicalFormulaParseException("Problem parsing elements from " + StringUtil.singleQuote(inString) + " at char " + (i + 1) + ": Counts can't be ranges!"); 905 } 906 else 907 { 908 break; 909 } 910 } 911 912 float updatedCount = mCount * (StringUtil.isSet(countString) ? Float.parseFloat(countString) : 1); 913 914 Float existingCount = chemicalCompositionMap.get(element); 915 if (existingCount != null) 916 { 917 updatedCount += existingCount; 918 } 919 920 chemicalCompositionMap.put(element, updatedCount); 921 } 922 923 prevChar = currentChar; 924 925 } 926 927 return chemicalCompositionMap; 928 } 929 } 930}