/**
 *    Copyright 2011 Peter Murray-Rust et. al.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.xmlcml.cml.element;

import java.io.File;

import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;

import nu.xom.Attribute;
import nu.xom.Document;
import nu.xom.Element;
import nu.xom.Elements;
import nu.xom.Nodes;
import nu.xom.Text;

import org.apache.log4j.Logger;
import org.xmlcml.cml.base.CMLAttribute;
import org.xmlcml.cml.base.CMLBuilder;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.cml.base.CMLElements;
import org.xmlcml.euclid.Real;
import org.xmlcml.euclid.RealArray;
import org.xmlcml.euclid.Util;
import org.xmlcml.molutil.ChemicalElement;
import org.xmlcml.molutil.ChemicalElement.AS;

/**
 * user-modifiable class supporting formula. * The semantics of formula have
 * been updated (2005-10) and the relationship between concise attribute and
 * atomArray children is explicit. This class supports the parsing of a number
 * of current inline structures but does not guarantee correctness as there are
 * no agreed community syntax/semantics. This is a particular problem for
 * charges which could be "2+", "++", "+2", etc. For robust inline interchange
 * ONLY the concise representation is supported.
 *
 * NOTE: The atomArray child, in array format, together with the formalCharge
 * attribute is the primary means of holding the formula. There is now no lazy
 * evaluation. The concise attribute can be autogenerated from the atomArray and
 * formalCharge. If a formula is input with only concise then the atomArray is
 * automatically generated from it.
 */
public class CMLFormula extends AbstractFormula {

	private static Logger LOG = Logger.getLogger(CMLFormula.class);
	
	public final static String SMILES = "SMILES";
	public final static String SMILES1 = "cml:smiles";
	
	/** type of hydrogen counting
	 * @author pm286
	 */
	public enum HydrogenStrategy {
		/** use hydorgen count attribute*/
		HYDROGEN_COUNT,
		/** use explicit hydrogens*/
		EXPLICIT_HYDROGENS;
	}
	/** namespaced element name.*/
	public final static String NS = C_E+TAG;

    /** dewisott */
	public final static int NPLACES = 10;

    /** dewisott */
	public final static int NDEC = 4;

	/** type */
	public enum Type {
		/**
		 * the simplest representation. an input-only format. parsing is
		 * possible but fragile. The charge semantics are not defined. Not
		 * recommended for output.
		 */
		NOPUNCTUATION("NoPunctuation", "C2H4Cl2"),

		/**
		 * another simple representation. an input-only format. parsing is also
		 * fragile as charge sematics are not defined. Not recommended for
		 * output.
		 */
		ELEMENT_COUNT_WHITESPACE("Element Count Whitespace", "C2 H4 Cl2"),

		/**
		 * Yet another simple representation. an input-only format. Element
		 * counts of 1 should always be given. Fragile as charge field is likely
		 * to be undefined. Not recommended for output.
		 */
		ELEMENT_WHITESPACE_COUNT("Element Whitespace Count", "C 2 H 4 O 1"),

		/**
		 * the format used in concise. recommended as the main output form.
		 * Element counts of 1 should always be given. the charge shoudl always
		 * be included. See concise.xsd and formulaType.xsd for syntax.
		 */
		CONCISE("CML Concise", "C 2 H 3 O 2 -1"),

		/**
		 * multipliers for moieties. an input only format. JUMBO will try to
		 * parse this correctly but no guarantee is given.
		 */
		MULTIPLIED_ELEMENT_COUNT_WHITESPACE(
				"Multiplied Element Whitespace Count",
		"3(C2 H4 Cl2) or (H2 O)3"),
		/**
		 * hierarchical formula. an input-only format. JUMBO will try to parse
		 * this correctly but no guarantee is given.
		 */
		NESTEDBRACKETS("NestedBrackets", "Na2(SO4).10(H2O)"),
		/**
		 * an input only format. JUMBO will not yet parse this correctly.
		 * comments from IUCr
		 *
		 * <pre>
		 *          _chemical_formula_iupac '[V O (H2 O)5] 2(C7 H5 O6 S), 2H2 O'
		 *          _example                    '[Co Re (C12 H22 P)2 (C O)6].0.5C H3 O H'
		 *          _definition
		 *          ;              Formula expressed in conformance with IUPAC rules for inorganic
		 *          and metal-organic compounds where these conflict with the rules
		 *          for any other _chemical_formula_ entries. Typically used for
		 *          formatting a formula in accordance with journal rules. This
		 *          should appear in the data block in addition to the most
		 *          appropriate of the other _chemical_formula_ data names.
		 * </pre>
		 *
		 * I do not know how to parse the second example! I think it means 0.5
		 * MeOH solvate.
		 */
		IUPAC("IUPAC", "[V O (H2 O)5] 2(C7 H5 O6 S), 2H2 O"),

		/**
		 * Moiety, used by IUCr. an input-only format. moieties assumed to be
		 * comma separated then ELEMENT_COUNT_WHITESPACE, with optional brackets
		 * and post or pre-multipliers
		 *
		 * <pre>
		 *          'C46 H50 Cl2 Hg2 N6 O10, 2C2 H6 O, 4H2 O'
		 *          'C46 H34 Co N5 O3'
		 *          'C18 H25 N O3'
		 *          'C12 H17 N4 O S 1+, C6 H2 N3 O7 1-'
		 *          'C12 H16 N2 O6, 5(H2 O1)'
		 *          &quot;(Cd 2+)3, (C6 N6 Cr 3-)2, 2(H2 O)
		 *          'C40 H66 Mo O4 P2'
		 * </pre>
		 */
		MOIETY("Moiety", "(Cd 2+)3, (C6 N6 Cr 3-)2, 2(H2 O)"),

		/**
		 * SubMoiety, used by IUCr. the part of a moiety within the brackets
		 * assumed to b ELEMENT_OPTIONALCOUNT followed by optional FORMULA
		 */
		SUBMOIETY("SubMoiety", "C6 N6 Cr 3-"),

		/**
		 * Structural, used by IUCr. not currently implemented, I think.
		 * probably the same as nested brackets
		 */
		STRUCTURAL("STRUCTURAL", "Sn (C2 O4) K F"),

		/**
		 * any of the above. input-only.
		 */
		ANY("Any", "(Cd 2+)3, (C6 N6 Cr 3-)2, 2(H2 O)");

		String type;

		String example;

		Type(String type, String example) {
			this.type = type;
			this.example = example;
		}
	}

	/** sort type */
	public enum Sort {
		/**
		 * sort alphabetically. output only. Not sure where this is
		 */
		ALPHABETIC_ELEMENTS("Alphabetic Elements", "AgCFHNOS"),

		/**
		 * C H and then alphabetically. (output only)
		 */
		CHFIRST("C and H first", "CHFNOS");

		String type;

		String example;

		Sort(String type, String example) {
			this.type = type;
			this.example = example;
		}
	}

	static Logger logger = Logger.getLogger(CMLFormula.class);

	// only edit insertion module!

	// marks whether concise has been processed before atomArray hass been read
	private boolean processedConcise;
	private boolean allowNegativeCounts;

	/**
	 * normal constructor.
	 *
	 *
	 */
	public CMLFormula() {
		super();
		init();
	}

	/**
	 * Constructor that creates a formula element using the atoms in a molecule.
	 * Adds occupancy (default 1.0) to count for each atom Sums charges for
	 * atoms; if any are non-zero then sets formalCharge as sum. else if
	 * molecule has formalCharge attribute uses this uses hydrogenCount if not
	 * null;
	 *
	 * @param molecule
	 *            to analyze
	 * @throws RuntimeException
	 *             if non-CML elements are present
	 */
	public CMLFormula(CMLMolecule molecule) {
		init();
		int formalCharge = 0;
		// iterate through atoms adding elements, occupancies and charges

//		HydrogenStrategy strategy = null;
		for (CMLAtom atom : molecule.getAtoms()) {
			double occupancy = 1.0;
			if (atom.getOccupancyAttribute() != null) {
				occupancy = atom.getOccupancy();
			}
			if (occupancy <= 0.0) {
				throw new RuntimeException("zero or negative occupancy: " + occupancy);
			}
			String elementType = atom.getElementType();
			if (elementType == null
					|| ChemicalElement.getChemicalElement(elementType) == null) {
				throw new RuntimeException(
						"Missing or invalid elementType for atom : "+atom.getId()+" .. "
						+ elementType);
			}
//			if (AS.H.equals(elementType) && strategy == null) {
//				strategy = HydrogenStrategy.EXPLICIT_HYDROGENS;
//			}
			if (!AS.H.equals(elementType) ) {
//				HydrogenStrategy.EXPLICIT_HYDROGENS == strategy) {
					this.add(elementType, 1.0);
	//			}
				
				if (atom.getFormalChargeAttribute() != null) {
					formalCharge += atom.getFormalCharge();
				}
	
				int hydrogenCount = atom.getHydrogenCount();
				if (hydrogenCount > 0) {
					this.add(AS.H.value, hydrogenCount);
				}
			}
			else {
				// only count a H if it is not bonded to non-hydrogens
				List<CMLAtom> oatoms = atom.getLigandAtoms();
				boolean countit = true;
				for (CMLAtom chk : oatoms) {
					String ctype = chk.getElementType();
					if ( !AS.H.equals(ctype)) {
						countit = false;
					}
				}
				if ( countit ) {
					this.add(AS.H.value, 1.0);
					if (atom.getFormalChargeAttribute() != null) {
						formalCharge += atom.getFormalCharge();
					}
				}
			}
		}
		// has the molecule a net computed charge?
		if (formalCharge != Integer.MIN_VALUE) {
			this.setFormalCharge(formalCharge);
		} else if (molecule.getFormalChargeAttribute() != null) {
			this.setFormalCharge(molecule.getFormalCharge());
		}
		
	}

	/**
	 * copy constructor.
	 *
	 * @param old
	 *            to copy
	 */
	public CMLFormula(CMLFormula old) {
		this();
		copyAttributesFrom(old);
		// concise will have triggered the creation of atomArray, so detach if
		// necessary
		CMLAtomArray newAtomArray = this.getAtomArrayElements().get(0);
		if (newAtomArray != null) {
			this.removeChild(newAtomArray);
		}
		copyChildrenFrom(old);
		copyProperties(old);
		
	}

	/**
	 * 
	 * mainly used for software such as Jmol which dislikes
	 * child atomArrays
	 */
	public void detachAllAtomArraysAsTheyAreAMenace() {
		CMLElements<CMLAtomArray> atomArrays = this.getAtomArrayElements();
		for (CMLAtomArray atomArray : atomArrays) {
			atomArray.detach();
		}
	}

	/**
	 * copy node .
	 *
	 * @return Node
	 */
	public Element copy() {
		CMLFormula newForm = new CMLFormula(this);
		return newForm;
	}

	/**
	 * create new instance in context of parent, overridable by subclasses.
	 *
	 * @param parent
	 *            parent of element to be constructed (ignored by default)
	 * @return CMLFormula
	 */
	public CMLElement makeElementInContext(Element parent) {
		return new CMLFormula();
	}

	/**
	 * shouldn't be necessary.
	 *
	 * @param parent
	 *            element
	 */
	public void finishMakingElement(Element parent) {
		super.finishMakingElement(parent);
//		try {
//			// DO NOT NORMALIZE
////			normalize();
//		} catch (RuntimeException e) {
//			LOG.info("skipped normalize() in finishMakingElement");
//		}
	}

	// FIXME move to tools	
	/**
	 * normalizes the formula.
	 * formula can be very polymorphic. Information can be in:
	 * <ul>
	 * <li>concise</li>
	 * <li>inline</li>
	 * <li>text content (not recommended)</li>
	 * <li>atomArray</li>
	 * </ul>
	 * normalize() will test, but NOT resolve inconsistencies
	 * if fields are empty, will fill them, but will not overwrite them.
	 * if content is non-empty and inline is empty, will transfer one to the other
	 * 
	 * convention is honoured. There is a special case for SMILES which will be 
	 * expanded to a molecule contained within the formula. Others may inform
	 * the creation of concise from inline
	 * 
	 * Current precedence:
	 * Concise and atomArray take precedence over inline (which can be difficult
	 * to parse).
	 * 
	 * <ul>
	 * <li>
	 * <li> if two or more atomArrays are present throws CMLRuntime</li>
	 * <li> if no concise or atomArray is present does nothing.</li>
	 * <li> if atomArray is present checks for array format; throws CMLRuntime
	 * if not.</li>
	 * <li> if concise but not atomArray is present, generates atomArray. If
	 * formalCharge is present and in conflict throws CMLRuntime. If non-CML
	 * elements are present or elements are duplicated, throws CMLRuntime. If
	 * format is wrong (should have been detected already) throws CMLRuntime</li>
	 * <li>sorts AtomArray according to CHFIRST</li>
	 * <li> generates concise including trailing charge and overwrites any
	 * previous version</li>
	 * </ul>
	 *
	 * @throws RuntimeException -
	 *             see above
	 */
	public void normalize() {
		// create all possible renderings of formula
		// any or all may be present
		// concise
		CMLAttribute conciseAtt = this.getConciseAttribute();
		// formal charge
		int formalCharge = 0;
		if (this.getFormalChargeAttribute() != null) {
			formalCharge = this.getFormalCharge();
		}
		String conciseS = (conciseAtt == null) ? null : conciseAtt.getValue();
		// convention
    	String convention = this.getConvention();
    	// inline formula (might be SMILES)
    	String inline = this.getInline();
    	if (inline != null) {
    		inline = inline.trim();
    	}
    	// text content (deprecated - use inline)
    	Nodes texts = this.query("text()");
    	String content = null;
    	for (int i = 0; i < texts.size(); i++) {
    		Text text = (Text) texts.get(i);
    		String s = text.getValue();
    		s = s.trim();
    		if (s.length() != 0) {
    			if (content == null) {
    				content = s;
    			} else {
    				throw new RuntimeException("Cannot have 2 non-empty text children");
    			}
    		}
    	}

    	// atomArray
    	String atomArray2Concise = null;
    	CMLAtomArray atomArray = null;
		CMLElements<CMLAtomArray> atomArrays = this.getAtomArrayElements();
		if (atomArrays.size() > 1) {
			throw new RuntimeException(
					"Only one atomArray child allowed for formula; found: "
					+ atomArrays.size());
		} else if (atomArrays.size() == 1) {
			atomArray = atomArrays.get(0);
			atomArray.sort(Sort.CHFIRST);
			atomArray2Concise = atomArray.generateConcise(formalCharge);
		}
		// transfer any content to inline
		if (content != null) {
			if (inline == null || inline.equals(S_EMPTY)) {
				this.setInline(content);
				for (int i = 0; i < texts.size(); i++) {
					texts.get(i).detach();
				}
			} else if (!inline.equals(content)) {
				throw new RuntimeException(
						"inline ("+inline+") differs from content ("+content+")");
			}
		}
		// concise from inline
    	if (inline != null) {
    		if (SMILES.equals(convention) ||
    			SMILES1.equals(convention)) {
//    			throw new RuntimeException("Move to SMILESTool");
//    			inline2Concise = getConciseFromSMILES(inline);
    		}
    	}
    	if (conciseS == null) {
    		if (atomArray2Concise != null) {
    			conciseS = atomArray2Concise;
    		} 
    	}
    	if (conciseS != null) {
    		conciseS = normalizeConciseAndFormalCharge(conciseS, formalCharge);
    	}
		// if no atomArray, create
		if (atomArray == null) {
			// causes problems with Jmol
//			if (conciseS != null) {
//				atomArray = createAndAddAtomArrayAndFormalChargeFromConcise(conciseS);
//			}
		} else {
			checkAtomArrayFormat(atomArray);
		}
		if (atomArray != null) {
			atomArray.sort(Sort.CHFIRST);
			removeZeroAtomCounts();
		}
		// check consistency
		if (atomArray2Concise != null && 
				!atomArray2Concise.equals(conciseS)) {
			throw new RuntimeException("concise ("+conciseS+") and atomArray ("+atomArray2Concise+") differ");
		}
		if (conciseS != null) {
			// by this time we may have generated a non-zero formal charge, so normalize it into concise
    		conciseS = normalizeConciseAndFormalCharge(conciseS, this.getFormalCharge());
			super.setConcise(conciseS);
		}
	}

    private void removeZeroAtomCounts() {
	}

// FIXME move to tools	
//	/** utility to convert SMILES to string.
//	 * @param smiles
//	 * @return string representing concise version
//	 */
//    public static String getConciseFromSMILES(String smiles) {
//    	String concise = null;
//    	if (smiles != null) {
//    		SMILESTool smilesTool = new SMILESTool();
//    		smilesTool.parseSMILES(smiles);
//    		CMLMolecule molecule = smilesTool.getMolecule();
//    		CMLFormula temp = new CMLFormula(molecule);
//    		if (temp != null) {
//    			concise = temp.getConcise();
//    		}
//    	}
//    	return concise;
//    }
//    
	
    /**
     * strips spaces and redundant 1's from concise
     * adds subscripts. Returns a span element without namespace
     * @param concise
     * @return subscripted concise or empty
     */
    public static Element getSubscriptedConcise(String concise) {
    	Element element = new Element("span");
    	if (concise != null) {
	    	String[] split = concise.split(S_SPACE);
	    	int len = split.length;
	    	int n = len/2;
	    	for (int j = 0; j < 2*n; j+=2) {
	    		element.appendChild(split[j]);
	    		if (!split[j+1].equals("1")) {
	    			Element sub = new Element("sub");
	    			sub.appendChild(split[j+1]);
	    			element.appendChild(sub);
	    		}
	    	}
    		if (len % 2 != 0) {
    			element.appendChild(split[len-1]);
    		}
    	}
    	return element;
    }
	

	CMLAtomArray createAndAddAtomArrayAndFormalChargeFromConcise(
			String concise) {
		CMLAtomArray atomArray = new CMLAtomArray();
		if (concise != null) {
			List<String> elements = new ArrayList<String>();
			List<Double> counts = new ArrayList<Double>();
			String[] tokens = concise.split(S_WHITEREGEX);
			int nelement = tokens.length / 2;
			for (int i = 0; i < nelement; i++) {
				String elem = tokens[2 * i];
				ChemicalElement chemicalElement = ChemicalElement
				.getChemicalElement(elem);
				if (chemicalElement == null) {
					throw new RuntimeException("Unknown chemical element: " + elem);
				}
				if (elements.contains(elem)) {
					throw new RuntimeException("Duplicate element in concise: " + elem);
				}
				elements.add(elem);
				String countS = tokens[2 * i + 1];
				try {
					counts.add(Double.valueOf(countS));
				} catch (NumberFormatException nfe) {
					throw new RuntimeException("Bad element count in concise: " + countS);
				}
			}
			if (tokens.length > nelement * 2) {
				String chargeS = tokens[nelement * 2];
				try {
					int formalCharge = Integer.parseInt(chargeS);
					super.setFormalCharge(formalCharge);
				} catch (NumberFormatException nfe) {
					throw new RuntimeException("Bad formal charge in concise: " + chargeS);
				}
			}
			double[] countD = new double[nelement];
			for (int i = 0; i < nelement; i++) {
				countD[i] = counts.get(i).doubleValue();
			}
			atomArray.setElementTypeAndCount((String[]) elements.toArray(new String[0]), countD);
		}
		setAtomArray(atomArray);
		return atomArray;
	}

	private void setAtomArray(CMLAtomArray atomArray) {
		for (CMLAtomArray aa : this.getAtomArrayElements()) {
			aa.detach();
		}
		super.appendChild(atomArray);
	}

	/**
	 * checks that atomArray is in array format with unduplicated valid
	 * elements. must have elementType and count attributes of equal lengths.
	 *
	 * @param atomArray
	 *            to check (not modified)
	 * @throws RuntimeException
	 *             if invalid
	 */
	public void checkAtomArrayFormat(CMLAtomArray atomArray) {
		if (atomArray.getChildElements().size() > 0) {
			throw new RuntimeException("No children allowed for formula/atomArray");
		}
		String[] elements = atomArray.getElementType();
		double[] counts = atomArray.getCount();
		if (elements == null || counts == null) {
			throw new RuntimeException(
			"formula/atomArray must have elementType and count attributes");
		}
		if (elements.length != counts.length) {
			throw new RuntimeException(
			"formula/atomArray must have equal length elementType and count values");
		}
		Set<String> elementSet = new HashSet<String>();
		for (int i = 0; i < elements.length; i++) {
			if (elements[i] != null && !(elements[i].equals("null"))) {
				if (elementSet.contains(elements[i])) {
					throw new RuntimeException(
							"formula/atomArray@elementType has duplicate element: "
							+ elements[i]);
				}
				elementSet.add(elements[i]);
				if (counts[i] <= 0 && !allowNegativeCounts) {
					throw new RuntimeException(
							"formula/atomArray@count has nonPositive value: "
							+ counts[i] + "  " + elements[i]);
				}
			}
		}
	}

	// trap special combinations
	/**
	 * add child. must not add atomArray
	 *
	 * @param child
	 */
	public void appendChild(Element child) {
		if (child instanceof CMLAtomArray) {
			throw new RuntimeException("atomArray child of formula is deprecated");
		}
		super.appendChild(child);
	}

	/**
	 * removes optional charge field from concise formula. convenience method,
	 * does not affect CMLormula object.
	 *
	 * @param s
	 *            concise string (assumed to be valid)
	 * @return string without trailing charge (does NOT check validity)
	 */
	public static String removeChargeFromConcise(String s) {
		String s0 = s.trim();
		String ss[] = s0.split(S_SPACE);
		if (ss.length % 2 != 0) {
			int idx = s0.lastIndexOf(S_SPACE);
			s0 = s0.substring(0, idx).trim();
		}
		return s0;
	}

	/**
	 * return concise formula without trailing charge.
	 *
	 * @return truncated concise (or null)
	 */
	public String getConciseNoCharge() {
		String concise = this.getConcise();
		return (concise == null) ? null : removeChargeFromConcise(concise);
	}

	/**
	 * strips spaces and redundant 1's from concise
	 * @param concise
	 * @return stripped concise or empty if null
	 */
	public static String getCompactedConcise(String concise) {
		return getCompactedConcise(concise, false);
	}

	/**
	 * strips spaces and redundant 1's from concise
	 * @param concise
	 * @return stripped concise or empty if null
	 */
	public static String getCompactedConcise(String concise, boolean html) {
		String s = CMLConstants.S_EMPTY;
		if (concise != null) {
			String[] split = concise.split(S_SPACE);
			int n = split.length/2;
			for (int j = 0; j < 2*n; j+=2) {
				s += split[j];
				if (!split[j+1].equals("1")) {
					if (html) {
						s += "<sub>";
					}
					s += split[j+1];
					if (html) {
						s += "</sub>";
					}
				}
			}
			if (split.length % 2 != 0) {
				if (html) {
					s += "<sup>";
				}
				s += makeCharge(split[split.length-1]);
				if (html) {
					s += "</sup>";
				}
			}
		}
		return s;
	}
	
	private static String makeCharge(String ss) {
		int i = Integer.parseInt(ss);
		String s = (i < 0) ? CMLConstants.S_MINUS : CMLConstants.S_PLUS;
		i = Math.abs(i);
		if (i > 1) {
			s += i;
		}
		return s;
	}

	/**
	 * set concise attribute. if atomArray is absent will automatically compute
	 * atomArray and formalCharge, so use with care if atomArray is present will
	 * throw CMLRuntime this logic may need to be revised
	 *
	 * @param value
	 *            concise value
	 * @throws RuntimeException
	 *             attribute wrong value/type
	 *
	 */
	public void setConcise(String value) throws RuntimeException {
		if (getAtomArrayElements().size() > 0) {
			throw new RuntimeException("Cannot reset concise if atomArray is present");
		}
		forceConcise(value);
	}
	
	private void forceConcise(String value) {
		super.setConcise(value);
		normalize();
		// if building, then XOM attributes are processed before children
		// this flag will allow subsequent atomArray to override
		processedConcise = true;
	}

// FIXME move to tools	
	private String normalizeConciseAndFormalCharge(String conciseS, int formalCharge) {
		if (conciseS != null) {
			CMLAtomArray atomArray = createAndAddAtomArrayAndFormalChargeFromConcise(conciseS);
			if (atomArray != null) {
				atomArray.sort(Sort.CHFIRST);
				conciseS = atomArray.generateConcise(formalCharge);
			}
		}
		return conciseS;
	}
	
	/**
	 * set formal charge. this will re-compute concise if possible.
	 *
	 * @param ch
	 */
	public void setFormalCharge(int ch) {
		super.setFormalCharge(ch);
		if (this.getConciseAttribute() != null) {
			String concise = this.getConcise();
			concise = CMLFormula.removeChargeFromConcise(concise);
			this.forceConcise(concise);
		}
		normalize();
	}

	void init() {
		processedConcise = false;
	}

	/**
	 * create formula from string. convention defaults to ANY
	 *
	 * @param s
	 *            the String describing the formula
	 * @exception RuntimeException
	 * @return the CMLFormula derived from the String s
	 */
	public static CMLFormula createFormula(String s) {
		return createFormula(s, Type.ANY);
	}

	/**
	 * create formula from molecule.
	 * @author nwe23
	 * @param mol
	 *            the CMLMolecule
	 * @exception RuntimeException
	 * @return the CMLFormula derived from the CMLMolecule
	 */
	public static CMLFormula createFormula(CMLMolecule mol){
		if(mol==null){
			return null;
		}
		return new CMLFormula(mol);
	}
	
	/**
	 * create formula from string. convention defaults to ANY
	 *
	 * @param s the String describing the formula
	 * @param allowNegativeCounts
	 * @exception RuntimeException
	 * @return the CMLFormula derived from the String s
	 */
	public static CMLFormula createFormula(String s, boolean allowNegativeCounts) {
		return createFormula(s, Type.ANY, allowNegativeCounts);
	}

	/**
	 * create formula from string using convention.
	 *
	 * @param s
	 *            the String describing the formula
	 * @param convention
	 *            the convention
	 * @return the CMLFormula derived from the String s
	 */
	// shouldn't this be a constructor instead?
	public static CMLFormula createFormula(String s, Type convention) {
		CMLFormula docFormula = new CMLFormula();
		if (s != null && !s.equals(S_EMPTY)) {
			docFormula.createFromString(s, convention);
		} else {
			docFormula = null;
		}
		return docFormula;
	}

	/**
	 * create formula from string using convention.
	 *
	 * @param s the String describing the formula
	 * @param convention the convention
	 * @param allowNegativeCounts
	 * @return the CMLFormula derived from the String s
	 */
	// shouldn't this be a constructor instead?
	public static CMLFormula createFormula(String s, Type convention, boolean allowNegativeCounts) {
		CMLFormula docFormula = new CMLFormula();
		docFormula.setAllowNegativeCounts(allowNegativeCounts);
		docFormula.createFromString(s, convention);
		return docFormula;
	}

	/**
	 * Sets the multiplier.
	 *
	 * Defaults to 1.0.
	 *
	 * @param count
	 *            multiplier for this moiety;
	 */
	// public void setCount(double count) {this.count = count;}
	/**
	 * Gets the multplier.
	 *
	 * @return double count multiplier for this moiety;
	 */
	// public double getCount() {return count;}
	/**
	 * Interpret string as a formula and replace contents.
	 *
	 * (Normal usage would be to create an empty Formula and fill it. If
	 * formulaConvention is not ANY then a specific convention is used, for ANY
	 * we try a mixture of heuristics. This allows us to throw exceptions if we
	 * deviate from conventions. Does allow non-integer counts Does not do
	 * brackets at present...
	 *
	 * @param formulaString
	 *            the string to interpret
	 * @param formulaConvention
	 *            taken from list above
	 * @throws RuntimeException
	 *             uninterpretable formula.
	 */
	/*
	 * void createFromString(String formulaString, String formulaConvention)
	 * throws CMLRuntime { formulaString = formulaString.trim(); if
	 * (formulaConvention.equals(ELEMENT_WHITESPACE_COUNT) ||
	 * formulaConvention.equals(ELEMENT_COUNT_WHITESPACE)) {
	 * parseElementCountWhitespace(formulaString, formulaConvention); } else if
	 * (formulaConvention.equals(MULTIPLIED_ELEMENT_COUNT_WHITESPACE)) {
	 * parseMultipliedElementCountWhitespace(formulaString); } else if
	 * (formulaConvention.equals(MOIETY)) { parseMoiety(formulaString); } else
	 * if (formulaConvention.equals(IUPAC)) { Util.sysout("IUPAC formula
	 * convention not yet supported"); } else if
	 * (formulaConvention.equals(NESTEDBRACKETS) ||
	 * formulaConvention.equals(STRUCTURAL)) {
	 * Util.sysout("Nested/structural formula convention not yet
	 * supported"); } else if (formulaConvention.equals(ANY)) {
	 * parseAny(formulaString); } else { throw new CMLRuntime("Unknown formula
	 * convention: "); } }
	 */

	void createFromString(String formulaString, Type formulaConvention) {
		formulaString = formulaString.trim();
		if (formulaConvention.equals(Type.ELEMENT_WHITESPACE_COUNT)
				|| formulaConvention.equals(Type.ELEMENT_COUNT_WHITESPACE)) {
			parseElementCountWhitespace(formulaString, formulaConvention);
		} else if (formulaConvention.equals(Type.NOPUNCTUATION)) {
//			parseElementCountWhitespace(formulaString, formulaConvention);
			parseAny(formulaString /*, formulaConvention*/);
		} else if (formulaConvention
				.equals(Type.MULTIPLIED_ELEMENT_COUNT_WHITESPACE)) {
			parseMultipliedElementCountWhitespace(formulaString);
		} else if (formulaConvention.equals(Type.MOIETY)) {
			parseMoiety(formulaString);
		} else if (formulaConvention.equals(Type.SUBMOIETY)) {
			parseSubMoiety(formulaString);
		} else if (formulaConvention.equals(Type.IUPAC)) {
			LOG.warn("IUPAC formula convention not yet supported");
		} else if (formulaConvention.equals(Type.NESTEDBRACKETS)
				|| formulaConvention.equals(Type.STRUCTURAL)) {
			LOG.debug("Nested/structural formula convention not yet supported");
		} else if (formulaConvention.equals(Type.CONCISE)) {
			setConcise(formulaString);
		} else if (formulaConvention.equals(Type.ANY)) {
			parseAny(formulaString);
		} else {
			throw new RuntimeException("Unknown formula convention: "
					+ formulaConvention.type);
		}
	}

	private void parseElementCountWhitespace(String formulaString,
			Type formulaConvention) {
		StringTokenizer st = new StringTokenizer(formulaString);
		while (st.hasMoreTokens()) {
			String s = st.nextToken();
			String countS = null;
			String elTypeS = null;
			if (formulaConvention.equals(Type.ELEMENT_WHITESPACE_COUNT)) {
				try {
					countS = st.nextToken();
				} catch (NoSuchElementException e) {
					throw new RuntimeException("Bad formula: {" + formulaString
							+ CMLConstants.S_RCURLY);
				}
				elTypeS = s;
			} else {
				elTypeS = CMLConstants.S_EMPTY;
				int l = s.length();
				if (!Character.isUpperCase(s.charAt(0))) {
					throw new RuntimeException("Bad element 1st char in: " + s);
				}
				if (l == 1) {
					elTypeS = s;
					countS = "1";
				} else {
					if (Character.isLowerCase(s.charAt(1))) {
						elTypeS = s.substring(0, 2);
						countS = s.substring(2);
					} else {
						elTypeS = s.substring(0, 1);
						countS = s.substring(1);
					}
				}
			}
			double c = 0.0;
			ChemicalElement element = ChemicalElement
			.getChemicalElement(elTypeS);
			if (element == null) {
				throw new RuntimeException("Bad element (" + elTypeS + ") in: " + s);
			}
			if (countS.equals(S_EMPTY)) {
				c = 1;
			} else {
				try {
					c = Double.valueOf(countS).doubleValue();
				} catch (NumberFormatException nfe) {
					throw new RuntimeException("Bad element count (" + countS
							+ ") in: " + s);
				}
			}
			this.add(elTypeS, c);
		}
		String concise = getFormattedString(Type.ELEMENT_WHITESPACE_COUNT,
				Sort.CHFIRST, false).trim();
		if (!concise.equals(S_EMPTY)) {
			super.setConcise(concise);
		}
	}

	private void parseMultipliedElementCountWhitespace(String formulaString) {
		formulaString.trim();
		// can start with a number :-(
		int ii = 0;
		double mult = -1.0;
		while (ii < formulaString.length()) {
			if (formulaString.charAt(ii) == '.'
				|| Character.isDigit(formulaString.charAt(ii))) {
				ii++;
			} else {
				break;
			}
		}
		if (ii != 0) {
			try {
				mult = Double.valueOf(formulaString.substring(0, ii)).doubleValue();
			} catch (NumberFormatException nfe) {
				throw new RuntimeException(
						"Cannot interpret start of formula as multiplier: "
						+ formulaString);
			}
			formulaString = formulaString.substring(ii);
		}

		int lb = formulaString.indexOf(S_LBRAK);
		int rb = formulaString.indexOf(S_RBRAK);
		if (lb == -1 && rb == -1) {
			this.createFromString(formulaString, Type.ANY);
		} else if (lb == 0 && rb != -1) {
			if (mult < 0.0) {
				// possible postmultiplier
				String r = formulaString.substring(rb + 1);
				if (r.length() > 0) {
					try {
						mult = Double.valueOf(r).doubleValue();
					} catch (NumberFormatException nfe) {
						throw new RuntimeException("Bad multiplier in: "
								+ formulaString);
					}
				} else {
					mult = 1.0;
				}
			}
			this.createFromString(formulaString.substring(1, rb), Type.ANY);
		} else {
			throw new RuntimeException(
					"Unbalanced () in multiplied formula string: "
					+ formulaString);
		}
		if (mult > 0.0) {
			this.setCount(mult);
		}
	}

	/**
	 * splits at commas. then creates nested formula and parses ANY.
	 *
	 * @param formulaString
	 */
	private void parseMoiety(String formulaString) {
		formulaString = formulaString.trim();
		StringTokenizer st = new StringTokenizer(formulaString, CMLConstants.S_COMMA);
		if (st.countTokens() == 1) {
			parseMoiety0(formulaString);
		} else {
			while (st.hasMoreTokens()) {
				CMLFormula subForm = new CMLFormula();
				String f = st.nextToken().trim();
				subForm.parseMoiety0(f);
				this.appendChild(subForm);
			}
		}
	}

	private void parseMoiety0(String formulaString) {
		formulaString = formulaString.trim();
		// can start with a number :-(
		int ii = 0;
		double mult = -1.0;
		while (ii < formulaString.length()) {
			if (formulaString.charAt(ii) == C_PERIOD
					|| Character.isDigit(formulaString.charAt(ii))) {
				ii++;
			} else {
				break;
			}
		}
		if (ii != 0) {
			try {
				mult = Double.valueOf(formulaString.substring(0, ii)).doubleValue();
			} catch (NumberFormatException nfe) {
				throw new RuntimeException(
						"Cannot interpret start of formula as multiplier: "
						+ formulaString);
			}
			formulaString = formulaString.substring(ii);
		}
		int lb = formulaString.indexOf(S_LBRAK);
		int rb = formulaString.indexOf(S_RBRAK);
		if (lb == -1 && rb == -1) {
			this.parseSubMoiety(formulaString);
		} else if (lb == 0 && rb != -1) {
			if (mult < 0.0) {
				// possible postmultiplier
				String r = formulaString.substring(rb + 1);
				if (r.length() > 0) {
					try {
						mult = Double.valueOf(r).doubleValue();
					} catch (NumberFormatException nfe) {
						throw new RuntimeException("Bad multiplier in: "
								+ formulaString);
					}
				} else {
					mult = 1.0;
				}
			}
			this.parseSubMoiety(formulaString.substring(1, rb));
		} else {
			throw new RuntimeException(
					"Unbalanced () in multiplied formula string: "
					+ formulaString);
		}
		if (mult > 0.0) {
			this.setCount(mult);
		}
	}

	private void parseSubMoiety(String sm) {
		sm = sm.trim();
		// List<String> elementList = new ArrayList<String>();
		int charge = 0;
		StringTokenizer st = new StringTokenizer(sm);
		int l = st.countTokens();
		for (int i = 0; i < l; i++) {
			String ss = st.nextToken();
			String s = ss;
			int mult = 0;
			// charge at the end?
			if (i == l - 1) {
				char c = s.charAt(0);
				if (c == C_PLUS) {
					mult = 1;
					s = s.substring(1);
				} else if (c == C_MINUS) {
					mult = -1;
					s = s.substring(1);
				} else if (s.endsWith(S_PLUS)) {
					mult = 1;
					s = s.substring(0, s.length() - 1);
				} else if (s.endsWith(S_MINUS)) {
					mult = -1;
					s = s.substring(0, s.length() - 1);
				}
				if (mult != 0) {
					if (!s.trim().equals(S_EMPTY)) {
						try {
							charge = Integer.parseInt(s);
						} catch (NumberFormatException nfe) {
							throw new RuntimeException("Bad charge: " + s);
						}
					} else {
						charge = 1;
					}
					charge *= mult;
				}
			}
			// found charge
			if (mult != 0) {
				break;
			}
			s = ss;
			// get Element+count
			String elem = CMLConstants.S_EMPTY;
			double count = 0;
			if (Character.isUpperCase(s.charAt(0))) {
				if (s.length() > 1) {
					if (Character.isLowerCase(s.charAt(1))) {
						elem = s.substring(0, 2);
						s = s.substring(2);
					} else if (!Character.isDigit(s.charAt(1))) {
						throw new RuntimeException("Bad elementCount: " + s);
					} else {
						elem = s.substring(0, 1);
						s = s.substring(1);
					}
					if (s.trim().equals(S_EMPTY)) {
						count = 1.0;
					} else {
						try {
							count = Double.valueOf(s).doubleValue();
						} catch (NumberFormatException nfe) {
							throw new RuntimeException(
									"Moiety cannot parse element count: " + s);
						}
					}
				} else {
					elem = s;
					count = 1.0;
				}
			} else {
				throw new RuntimeException("Moiety cannot parse element at: " + s);
			}
			if (elem.equals(S_EMPTY)) {
				throw new RuntimeException("Moiety cannot parse element: " + s);
			}
			ChemicalElement element = ChemicalElement.getChemicalElement(elem);
			if (element == null) {
				throw new RuntimeException("Bad element (" + elem + ") in: " + elem);
			}
			this.add(elem, count);
		}
		if (charge != 0) {
			this.setFormalCharge(charge);
		}
	}

	private void parseAny(String formulString) {
		// ANY - make whitespace separated string and then recurse
		StringBuilder result = new StringBuilder();
		String ss = formulString.trim();
		int l = ss.length();
		int i = 0;
		int ii = 0;
		int charge = 0;
		while (i < l) {
			// skip white
			i = grabWhite(ss, i, l);
			if (i >= l) {
				break; // end of line
			}
			result.append(S_SPACE);
			ii = grabElement(ss, i, l);
			if (ii == i) {
				ii = grabCharge(ss, i, l);
				if (ii != i) {
					charge = parseCharge(ss.substring(i, ii));
				}
				break;
			}
			result.append(ss.substring(i, ii));
			result.append(S_SPACE);
			i = ii;
			i = grabWhite(ss, i, l);
			ii = grabCount(ss, i, l);
			if (ii == i) {
				result.append(1);
			} else {
				result.append(ss.substring(i, ii));
			}
			i = ii;
			i = grabWhite(ss, i, l);
		}
		String sss = result.toString().trim();
		createFromString(sss, Type.ELEMENT_WHITESPACE_COUNT);
		if (charge != 0) {
			this.setFormalCharge(charge);
		}
	}
	
	private static int parseCharge(String ss) {
		int i = 0;
		ss = ss.trim();
		if (ss.equals(S_PLUS)) {
			i = 1;
		} else if (ss.startsWith(S_PLUS)) {
			i = Integer.parseInt(ss.substring(1));
		} else if (ss.equals(S_MINUS)) {
			i = -1;
		} else if (ss.endsWith(S_MINUS)) {
			i = -Integer.parseInt(ss.substring(0, ss.length()-1));
		} else {
			i = Integer.parseInt(ss);
		}
		return i;
	}
	
	private int grabCharge(String ss, int i, int l) {
		char c = ss.charAt(i);
		if (i < l && (c == C_MINUS || c == C_PLUS)) {
			i++;
		}
		while (i < l) {
			c = ss.charAt(i);
			if (!Character.isDigit(c)) {
				break;
			}
			i++;
		}
		return i;
	}

	private int grabCount(String ss, int i, int l) {
		if (allowNegativeCounts && i < l && ss.charAt(i) == C_MINUS) {
			i++;
		}
		while (i < l) {
			char c = ss.charAt(i);
			if (c != C_PERIOD && !Character.isDigit(c)) {
				break;
			}
			i++;
		}
		return i;
	}

	private int grabElement(String ss, int i, int l) {
		if (i < l && Character.isUpperCase(ss.charAt(i))) {
			i++;
		}
		if (i < l && Character.isLowerCase(ss.charAt(i))) {
			i++;
		}
		return i;
	}

	private int grabWhite(String ss, int i, int l) {
		while (i  < l) {
			if (!Character.isWhitespace(ss.charAt(i))) {
				break;
			}
			i++;
		}
		return i;
	}

//	private void parseAnyOld(String formulaString) {
//		// FIXME
//		// ANY - make whitespace separated string and then recurse
//		String result = CMLConstants.S_EMPTY;
//		formulaString += CMLConstants.S_SPACE;
//		int l = formulaString.length();
//		int i = 0;
//		char c;
//		int charge = 0;
//		String count = CMLConstants.S_EMPTY;
//		while (i < l) {
//			// skip white
//			while (formulaString.charAt(i) == C_SPACE) {
//				if (++i >= l)
//					break;
//			}
//			if (i >= l)
//				break;
//			c = formulaString.charAt(i);
//			// start element or charge
//			String el = CMLConstants.S_EMPTY;
//			// finish with -, -1, 1-, 2-, -2, +1, 1+, +2, 2+ ... etc
//			if (Character.isDigit(c) || c == C_PLUS || c == C_MINUS || c == C_PERIOD) {
//				if (true) throw new RuntimeException("FIX ME");
//				charge = getFinalCharge(formulaString.substring(i).trim());
//				break;
//			}
//			// upper case required
//			if (!Character.isUpperCase(c)) {
//				throw new RuntimeException("Formula: Cannot interpret element ("
//						+ c + ") at char (" + (i + 1) + ") in: "
//						+ formulaString);
//			}
//			el += c;
//			if (++i >= l) {
//				result += el + " 1";
//				break;
//			}
//			c = formulaString.charAt(i);
//			// lower case optional
//			if (Character.isLowerCase(c)) {
//				el += c;
//				i++;
//			}
//			// skip white
//			while (formulaString.charAt(i) == C_SPACE) {
//				if (++i >= l)
//					break;
//			}
//			if (i >= l) {
//				result += el + " 1";
//				break;
//			}
//			// multiplier?
//			c = formulaString.charAt(i);
//			// implied count of 1?
//			count = CMLConstants.S_EMPTY;
//			if (!Character.isDigit(c)) {
//				count = "1";
//			} else {
//				while (true) {
//					c = formulaString.charAt(i);
//					if (!Character.isDigit(c) && c != C_PERIOD) {
//						break;
//					}
//					count += c;
//					if (++i == l)
//						break;
//				}
//			}
//			result += el + CMLConstants.S_SPACE + count + CMLConstants.S_SPACE;
//		}
//		createFromString(result, Type.ELEMENT_WHITESPACE_COUNT);
//		if (charge != 0) {
//			this.setFormalCharge(charge);
//		}
//	}

//	private int getFinalCharge(String f) {
//		if (!allowNegativeCounts && f.indexOf(' ') != -1) {
//			throw new RuntimeException("Charge must be final field: " + f);
//		}
//		int sign = 0;
//		int charge = 0;
//		String ch = CMLConstants.S_EMPTY;
//		int l = f.length();
//		if (f.charAt(0) == C_PLUS) {
//			sign = 1;
//			ch = f.substring(1);
//		} else if (f.charAt(0) == C_MINUS) {
//			sign = -1;
//			ch = f.substring(1);
//		} else if (f.indexOf(C_PLUS) == l - 1) {
//			sign = 1;
//			ch = f.substring(0, l - 1);
//		} else if (f.indexOf(C_MINUS) == l - 1) {
//			sign = -1;
//			ch = f.substring(0, l - 1);
//		} else {
//			throw new RuntimeException("Cannot parse as charge field: " + f);
//		}
//		if (ch.equals(S_EMPTY)) {
//			charge = 1;
//		} else {
//			try {
//				charge = Integer.parseInt(ch);
//			} catch (NumberFormatException nfe) {
//				throw new RuntimeException("Cannot parse as charge field: " + f + "("+ch+")");
//			}
//		}
//		return sign * charge;
//	}

	/**
	 * Adds element and count to formula. If element is already known,
	 * increments the count.
	 *
	 * @param elementType
	 *            the element atomicSymbol
	 * @param count
	 *            the element multiplier
	 */
	public void add(String elementType, double count) {
		CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
				: getAtomArrayElements().get(0);
		if (atomArray == null) {
			// if no atomArray , create from concise
			normalize();
			// if still none, create new one with empty attributes
			if (atomArray == null) {
				atomArray = new CMLAtomArray();
				setAtomArray(atomArray);
				atomArray.setElementTypeAndCount(new String[0], new double[0]);
			}
		}
		String[] elements = getElementTypes();
		if (elements == null) {
			elements = new String[0];
		}
		double[] counts = getCounts();
		if (counts == null) {
			counts = new double[0];
		}
		int nelem = elements.length;
		boolean added = false;
		for (int i = 0; i < nelem; i++) {
			if (elements[i].equals(elementType)) {
				counts[i] += count;
				added = true;
				break;
			}
		}
		if (!added) {
			String[] newElem = new String[nelem + 1];
			System.arraycopy(elements, 0, newElem, 0, nelem);
			newElem[nelem] = elementType;
			double[] newCount = new double[nelem + 1];
			System.arraycopy(counts, 0, newCount, 0, nelem);
			newCount[nelem] = count;
			atomArray.setElementTypeAndCount(newElem, newCount);
		} else {
			atomArray.setElementTypeAndCount(elements, counts);
		}
		int formalCharge = (this.getFormalChargeAttribute() == null) ? 0 : this.getFormalCharge();
		String conciseS = atomArray.generateConcise(formalCharge);
		super.setConcise(conciseS);
	}

	/**
	 * Count for corresponding element.
	 *
	 * No defaults.
	 *
	 * @return double[] array of element counts; or null for none.
	 */
	public double[] getCounts() {
		CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
				: getAtomArrayElements().get(0);
		return (atomArray == null) ? null : atomArray.getCount();
	}
	
//	CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
//			: getAtomArrayElements().get(0);
//	return (atomArray == null) ? null : atomArray.getElementType();

	/** get atom count
	 * @return count
	 */
	public double getTotalAtomCount() {
		//nwe23 - Fixed a bug here where getCounts() returns null
		// for an empty formula, resulting in this crashing rather than
		// returning 0 as expected for empty formula
		double[] counts = getCounts();
		if(counts==null){
			return 0;
		}
		double total = 0;
		for (double count : counts) {
			total += count;
		}
		return total;
	}

	/**
	 * Count for corresponding element.
	 *
	 * No defaults.
	 *
	 * @return double[] array of element counts; or null for none.
	 */
	public String[] getElementTypes() {
		CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
				: getAtomArrayElements().get(0);
		return (atomArray == null) ? null : atomArray.getElementType();
	}

	/**
	 * multiply all counts by the same constant factor. this is normally used to
	 * multiply formula which represent a subfragment of a symmetrical molecule
	 * it is distinct from the count attribute which represents two of more
	 * independent entities
	 *
	 * @param d
	 *            to multiply by
	 */
	public void multiplyBy(double d) {
		CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
				: getAtomArrayElements().get(0);
		if (atomArray != null) {
			String[] elems = this.getElementTypes();
			double counts[] = getCounts();
			for (int i = 0; i < counts.length; i++) {
				counts[i] *= d;
			}
//			atomArray.setCount(counts);
			atomArray.setElementTypeAndCount(elems, counts);
		} else {
			// convoluted! create new formula , multiply it and suck out the
			// values
			String concise = this.getConcise();
			CMLFormula fNew = new CMLFormula();
			fNew.createFromString(concise, Type.ELEMENT_WHITESPACE_COUNT);
			fNew.multiplyBy(d);
			String concise1 = fNew.getConcise();
			this.setConcise(concise1);
			CMLAtomArray fAtomArray = fNew.getAtomArrayElements().get(0);
			fAtomArray.detach();
			this.addAtomArray(fAtomArray);
		}
		String concise = this.getAtomArrayElements()
		.get(0).generateConcise((int) (this.getFormalCharge() * d));
		super.setConcise(concise);
	}

	/**
	 * Adds a child formula to the content. if this is empty formula, no action.
	 * if this has no children, copy this and add as a child, detaching
	 * atomArray and removing concise from this after that f is copied and
	 * appended as child
	 *
	 * @param f
	 *            to be copied and added. null is ignored
	 */
	public void addFormula(CMLFormula f) {
		CMLAtomArray atomArray = this.getAtomArrayElements().get(0);
		CMLElements<CMLFormula> formulas = this.getFormulaElements();
		if (formulas.size() == 0 && atomArray != null) {
			CMLFormula thisCopy = new CMLFormula(this);
			super.appendChild(thisCopy);
			atomArray.detach();
			// remove current concise attribute
			CMLAttribute att = this.getConciseAttribute();
			if (att != null) {
				this.removeAttribute(att);
			}
			att = this.getCountAttribute();
			if (att != null) {
				this.removeAttribute(att);
			}
		}
		if (atomArray != null) {
			CMLFormula ff = new CMLFormula(f);
			super.appendChild(ff);
		}
	}

	/**
	 * gets single formula representing all contents.
	 *
	 * all formulae are added, using count when present. the current formula is
	 * not modified can be used for adding separate formula
	 *
	 * @return composite formula (no formulaVector child)
	 */
	public CMLFormula getAggregateFormula() {
		CMLFormula newFormula = null;
		CMLElements<CMLFormula> formulas = this.getFormulaElements();
		if (formulas.size() == 0) {
			newFormula = this;
		} else {
			newFormula = new CMLFormula();
			for (CMLFormula formula : formulas) {
				newFormula = newFormula.createAggregatedFormula(formula);
			}
		}
		return newFormula;
	}

	/**
	 * aggregates two formulae including charge.
	 *
	 * if either formula is composite, aggregates them first current formula is
	 * updated to contain aggregate, destroying any structure if no contents,
	 * uses concise attribute
	 *
	 * @param form
	 *            to aggregate
	 * @return formula
	 */
	public CMLFormula createAggregatedFormula(CMLFormula form) {
		if (form == null) {
			throw new RuntimeException("Null formula in createAggregatedFormula");
		}
		CMLFormula newFormula = new CMLFormula();
		newFormula.setAllowNegativeCounts(this.allowNegativeCounts | form.allowNegativeCounts);
		newFormula.aggregate(this);
		form = form.getAggregateFormula();
		newFormula.aggregate(form);
		return newFormula;
	}

	void aggregate(CMLFormula form) {
		String[] fElements = form.getElementTypes();
		double[] fCounts = form.getCounts();
		double thisCount = (this.getCountAttribute() == null) ? 1.0 : this
				.getCount();
		double fCount = (form.getCountAttribute() == null) ? 1.0 : form
				.getCount();
		int thisCharge = (this.getFormalChargeAttribute() == null) ? 0 : this
				.getFormalCharge();
		int fCharge = (form.getFormalChargeAttribute() == null) ? 0 : form
				.getFormalCharge();
		if (fElements != null) {
			for (int i = 0; i < fElements.length; i++) {
				this.add(fElements[i], fCounts[i] * fCount);
			}
		} else {
			// build from concise
			String concise = CMLConstants.S_EMPTY;
			concise = form.getFormattedString(Type.ELEMENT_WHITESPACE_COUNT,
					Sort.CHFIRST, false).trim();
			if (getConciseAttribute() != null) {
				CMLFormula newForm = new CMLFormula();
				newForm.createFromString(concise, Type.ANY);
				aggregate(newForm);
			}
		}
		// fix charges
		int newCharge = (int) (thisCharge * thisCount + fCharge * fCount);
		if (newCharge != 0) {
			this.setFormalCharge(newCharge);
		} else if (this.getFormalChargeAttribute() != null) {
			this.removeAttribute(this.getFormalChargeAttribute());
		}
		// set count to unity/remove
		Attribute countAttribute = this.getCountAttribute();
		if (countAttribute != null) {
			this.removeAttribute(this.getCountAttribute());
		}
		normalize();
	}

	/**
	 * gets count of formula. defaults to 1.0
	 *
	 * @return formula count
	 */
	public double getCount() {
		double fCount = 1.0;
		if (getCountAttribute() != null) {
			fCount = super.getCount();
			if (Math.round((float) fCount) == 0) {
				fCount = 1.0;
				this.setCount(fCount);
			}
		}
		return fCount;
	}
	
	public Double getElementCount(String elementType) {
		String[] elementTypes = getElementTypes();
		double[] counts = getCounts();
		if (elementTypes != null) {
			for (int i = 0; i < elementTypes.length; i++) {
				String el = elementTypes[i];
				if (el.equals(elementType)) {
					return counts[i];
				}
			}
		}
		return null;
	}

	/**
	 * divide one formula by another.
	 *
	 * requires exactly the same elements are present in each and in the same
	 * order. 'this' should be the largest formula at present expects the
	 * results to be integer.
	 *
	 * @param f
	 *            formula to divide into 'this'
	 * @param eps
	 *            allowed tolerance between element ratios
	 * @return the multiplier (Double.NaN if operation fails)
	 */
	public double divideBy(CMLFormula f, double eps) {
		double divide = Double.NaN;
		String[] elementTypes = this.getElementTypes();
		double[] counts = this.getCounts();
		if (f != null) {
			String[] fElem = f.getElementTypes();
			double[] fCounts = f.getCounts();
			if (fElem != null && fElem.length == elementTypes.length) {
				for (int i = 0; i < fElem.length; i++) {
					// sorted; check for error
					if (!elementTypes[i].equals(fElem[i]) || fCounts[i] == 0.0) {
						divide = Double.NaN;
						break;
					}
					double dd = counts[i] / fCounts[i];
					if (Double.isNaN(divide)) {
						divide = dd;
					} else if (Math.abs(divide - dd) > eps) {
						divide = Double.NaN;
						break;
					}
				}
			}
		}
		return divide;
	}

	/**
	 * get calculated molecular mass.
	 *
	 * @return calculated molecular mass.
	 * @throws RuntimeException unknown/unsupported element type (Dummy counts as zero mass)
	 */
	public double getCalculatedMolecularMass() throws RuntimeException {
		double mwt = 0.0;
		Elements formulas = this.getChildElements("formula", CMLConstants.CML_NS);
		if (formulas.size() > 0) {
			for (int i = 0; i < formulas.size(); i++) {
				CMLFormula formula = (CMLFormula) formulas.get(i);
				mwt += formula.getCalculatedMolecularMass();
			}
		} else {
			String[] elementTypes = this.getElementTypes();
			double[] counts = this.getCounts();
			if (counts != null) {
				for (int i = 0; i < counts.length; i++) {
					mwt += getAtomicMass(elementTypes[i]) * counts[i];
				}
			}
		}
		return getCount() * mwt;
	}

	/**
	 * get mass for elementType.
	 *
	 * @param elementType
	 * @return 0.0 if unknown or null
	 */
	private static double getAtomicMass(String elementType) {
		double mass = 0.0;
		if (elementType != null) {
			ChemicalElement el = ChemicalElement
			.getChemicalElement(elementType);
			mass = (el == null) ? 0.0 : el.getAtomicWeight();
		}
		return mass;
	}

	/** convenience to get concise from a string.
	 * uses type ANY
	 * typically used for inline
	 * @param s
	 * @return concise of null
	 */
	public static String createConcise(String s) {
		CMLFormula temp = CMLFormula.createFormula(s, Type.ANY);
		return (temp == null) ? null : temp.getConcise();
	}

	/**
	 * Get formatted formula.
	 *
	 * @param convention
	 *
	 * <pre>
	 *      NOPUNCTUATION      &quot;C2H4Cl2&quot; (default)
	 *      ELEMENT_COUNT_WHITESPACE &quot;C2 H4 Cl2&quot;
	 *      ELEMENT_WHITESPACE_COUNT &quot;C 2 H 4 Cl 2&quot;
	 *      NESTEDBRACKETS     &quot;(Na2)(SO4).10(H2O)&quot;
	 * </pre>
	 *
	 * @param sort
	 *
	 * <pre>
	 *      ALPHABETIC_ELEMENTS    &quot;Br C Cl H&quot;
	 *      CHFIRST      = &quot;C H Br Cl&quot;; (default)
	 * </pre>
	 *
	 * @param omitCount1
	 *            omit elements count if 1 (default false)
	 * @return String the formatted formula
	 * @throws RuntimeException
	 */
	public String getFormattedString(Type convention, Sort sort,
			boolean omitCount1) throws RuntimeException {
		if (sort == null) {
			sort = Sort.CHFIRST;
		}
		if (!sort.equals(Sort.CHFIRST)
				&& !sort.equals(Sort.ALPHABETIC_ELEMENTS)) {
			sort = Sort.CHFIRST;
		}
		if (convention == null || convention.equals(S_EMPTY)) {
			convention = Type.NOPUNCTUATION;
		}
		if (!convention.equals(Type.NOPUNCTUATION)
				&& !convention.equals(Type.NESTEDBRACKETS)
				&& !convention.equals(Type.ELEMENT_WHITESPACE_COUNT)
				&& !convention.equals(Type.ELEMENT_COUNT_WHITESPACE)) {
			convention = Type.NOPUNCTUATION;
		}
		normalize();
		CMLAtomArray atomArray = (getAtomArrayElements().size() == 0) ? null
				: getAtomArrayElements().get(0);
		if (atomArray != null) {
			atomArray.sort(sort);
		}
		StringBuffer sb = new StringBuffer();
		Elements formulas = this.getChildElements("formula", CMLConstants.CML_NS);
		if (convention.equals(Type.NESTEDBRACKETS)) {
			for (int i = 0; i < formulas.size(); i++) {
				CMLFormula formula = (CMLFormula) formulas.get(i);
				String c = Util.outputNumber(NPLACES, NDEC, formula.getCount());
				if (!omitCount1 || !c.equals("1")) {
					sb.append(c);
				}
				sb.append(S_LBRAK);
				sb.append(this.getFormattedString(convention, sort,
						omitCount1));
				sb.append(S_RBRAK);
			}
			return sb.toString();
		} else {
			combineSubFormulaElementVectors();
			if (atomArray != null) {
				atomArray.sort(sort);
			}
		}
		String[] elementTypes = this.getElementTypes();
		double[] counts = this.getCounts();
		if (elementTypes != null && counts != null) {
			for (int i = 0; i < elementTypes.length; i++) {
				sb.append(elementTypes[i]);
				String s = Util.outputNumber(NPLACES, NDEC, counts[i]).trim();
				if (!omitCount1 || !s.equals("1")) {
					if (convention.equals(Type.ELEMENT_WHITESPACE_COUNT)) {
						sb.append(S_SPACE);
					}
					sb.append(s);
				}
				if (convention.equals(Type.ELEMENT_WHITESPACE_COUNT)
						|| convention.equals(Type.ELEMENT_COUNT_WHITESPACE)) {
					sb.append(S_SPACE);
				}
			}
		}
		// trim any trailing blanks
		int l = sb.length();
		while (l > 0 && sb.charAt(--l) == ' ') {
			sb.deleteCharAt(l);
		}
		String s = getFormalChargeString().trim();
		if (!s.equals(S_EMPTY)) {
			if (!convention.equals(Type.NOPUNCTUATION)) {
				sb.append(S_SPACE);
			}
			sb.append(s);
		}
		return sb.toString();
	}

	/**
	 * gets the charge on the formula.
	 *
	 * if there are child Formulas, iterate through, summing.
	 *
	 * @return the charge
	 */
	public int getFormalCharge() {
		int charge = 0;
		Elements childFormulas = this.getChildElements("formula", CMLConstants.CML_NS);
		if (childFormulas.size() > 0) {
			for (int i = 0; i < childFormulas.size(); i++) {
				CMLFormula childFormula = (CMLFormula) childFormulas.get(i);
				charge += childFormula.getFormalCharge()
				* childFormula.getCount();
			}
		} else {
			if (this.getFormalChargeAttribute() != null) {
				charge = super.getFormalCharge();
			}
		}
		return charge;
	}

	/**
	 * translates charge to an integer and then to String.
	 *
	 * @return charge as a String
	 */
	public String getFormalChargeString() {
		String s = CMLConstants.S_EMPTY;
		int iCharge = this.getFormalCharge();
		if (iCharge == 0) {
			;
		} else if (iCharge < 0) {
			for (int i = iCharge; i < 0; i++) {
				s += CMLConstants.S_MINUS;
			}
		} else {
			for (int i = 0; i < iCharge; i++) {
				s += CMLConstants.S_PLUS;
			}
		}
		return s;
	}

	/**
	 * default formula.
	 *
	 * @return default formula
	 */
	public String getFormattedString() {
		return getFormattedString(null, null, true);
	}

	private void combineSubFormulaElementVectors() {
		Elements formulas = this.getChildElements("formula", CMLConstants.CML_NS);
		if (formulas.size() > 0) {
			for (int i = 0; i < formulas.size(); i++) {
				CMLFormula subFormula = (CMLFormula) formulas.get(i);
				String[] subElementTypes = subFormula.getElementTypes();
				double[] subCounts = subFormula.getCounts();
				for (int j = 0; j < subElementTypes.length; j++) {
					this.add((String) subElementTypes[j], subCounts[j]
					                                                * subFormula.getCount());
				}
			}
		}
	}

	/**
	 * Calculate molecular mass from vector of element symbols and their counts.
	 *
	 * @param countArray
	 * @param elementTypeVector
	 * @return mass
	 */
	public static double getCalculatedMass(List<String> elementTypeVector,
			RealArray countArray)  {
		if (elementTypeVector == null || countArray == null
				|| elementTypeVector.size() != countArray.size()) {
			throw new RuntimeException("Bad arguments");
		}
		double mwt = 0.0;
		for (int i = 0; i < elementTypeVector.size(); i++) {
			String elType = elementTypeVector.get(i);
			ChemicalElement el = ChemicalElement.getChemicalElement(elType);
			if (el == null) {
				throw new RuntimeException("Unsupported element: " + elType);
			}
			mwt += el.getAtomicWeight() * countArray.elementAt(i);
		}
		return mwt;
	}

	/**
	 * Output debug info to Writer.
	 *
	 * @param w
	 *            output
	 * @throws IOException
	 */
	public void debug(Writer w) throws IOException {
		w.write("\n------------formula-------------\n");
		w.write(this.toString());
	}

	/**
	 * tests equality of formulae. order of elements should be unimportant
	 *
	 * @param form
	 *            to compare with this
	 * @param eps
	 *            tolerance for element counts
	 * @return true if formulae are equals
	 */
	public boolean equals(CMLFormula form, double eps) {
		boolean equals = false;
		String[] thisElem = this.getElementTypes();
		String[] formElem = form.getElementTypes();
		boolean ee = Arrays.equals(thisElem, formElem);
		double[] thisCount = this.getCounts();
		double[] formCount = form.getCounts();
		if (thisCount == null) {
			thisCount = new double[0];
		}
		if (formCount == null) {
			formCount = new double[0];
		}
		boolean cc = Real.isEqual(thisCount, formCount, eps);
		if (ee && cc) {
			equals = true;
		}
		return equals;
	}

	/**
	 * compares current concise strings.
	 * could fail if strings have decimal points
	 *
	 * @param f
	 * @return true if equal
	 */
	public boolean equalsConcise(CMLFormula f) {
		boolean equals = false;
		equals = this.getConcise().equals(f.getConcise());
		return equals;
	}

	/**
	 * difference between formulas.
	 *
	 * formula is difference between this and f on an element by element basis.
	 * Negative values are used where f has a greater element count that this.
	 * Zero element counts are ommitted
	 *
	 * @param form formula to compare
	 * @return difference  (but may contain negative values)
	 */
	public CMLFormula getDifference(CMLFormula form) {
		Map<String, Double> countTable = new HashMap<String, Double>();
		String[] elementTypes = this.getElementTypes();
		double[] counts = this.getCounts();
		double thisCount = (this.getCountAttribute() == null) ? 1.0 : this.getCount();
		if (elementTypes != null && counts != null) {
			for (int i = 0; i < elementTypes.length; i++) {
				countTable.put(elementTypes[i], Double.valueOf(counts[i]));
			}
		}
		CMLFormula newFormula = new CMLFormula();
		newFormula.setAllowNegativeCounts(true);
		String[] fElementTypes = form.getElementTypes();
		double[] fCounts = form.getCounts();
		double fCount = (form.getCountAttribute() == null) ? 1.0 : form.getCount();
		if (fElementTypes != null && fCounts != null) {
			for (int i = 0; i < fElementTypes.length; i++) {
				String element = fElementTypes[i];
				Double count = countTable.get(element);
				// not in this
				double delta = 0.0;
				if (count == null) {
					delta = 0.0 - fCounts[i];
				} else {
					delta = count.doubleValue() * thisCount - fCounts[i]
					                                                  * fCount;
				}
				if (Math.abs(delta) > 0.000001) {
					newFormula.add(element, delta);
				}
				countTable.remove(element);
			}
		}
		// not in f
		for (String element : countTable.keySet()) {
			Double count = countTable.get(element);
			if (count != null) {
				newFormula.add(element, count.doubleValue() * thisCount);
			}
		}
		int charge = (int) Math.round(this.getFormalCharge() * thisCount
				- form.getFormalCharge() * fCount);
		if (charge != 0) {
			newFormula.setFormalCharge(charge);
		}
		return newFormula;
	}

	/**
	 * are two formulas equal.
	 *
	 * use aggregated formulae as basis
	 *
	 * @param form formula to compare
	 * @return true if equal
	 */
	public boolean equalsAggregate(CMLFormula form) {
		CMLFormula differenceFormula = this.getDifference(form);
		return differenceFormula.isEmpty();
	}
	
	/** formula contains no element or elemnts with (near) zero counts
	 * 
	 * @return true if empty
	 */
	public boolean isEmpty() {
		boolean zero = true;
		String[] elementTypes = getElementTypes();
		double[] counts = getCounts();
		if (elementTypes != null && counts != null) {
			for (int i = 0; i < elementTypes.length; i++) {
				if (!Real.isZero(counts[i], Real.getEpsilon())) {
					zero = false;
					break;
				}
			}
		}
		return zero;
	}

	/**
	 * create formula string.
	 *
	 * @return the string
	 */
	public String toFormulaString() {
		String s = "count: " + this.getCount() + "; charge: "
		+ this.getFormalCharge() + ": ";
		CMLElements<CMLFormula> formulas = this.getFormulaElements();
		for (CMLFormula formula : formulas) {
			s += "\nForm: " + formula.toFormulaString();
		}
		String[] elementTypes = this.getElementTypes();
		if (elementTypes != null) {
			double[] counts = this.getCounts();
			for (int i = 0; i < elementTypes.length; i++) {
				double d = counts[i];
				s += elementTypes[i]
				                  + ((d > 1.000000001) ? CMLConstants.S_LBRAK + counts[i] + CMLConstants.S_RBRAK : CMLConstants.S_EMPTY);
			}
		}
		return s;
	}

	/**
	 * output HTML.
	 *
	 * @param w
	 * @throws IOException
	 */
	public void writeHTML(Writer w) throws IOException {
		w.write("<span class='formula'>");
		CMLElements<CMLFormula> formulas = this.getFormulaElements();
		if (formulas.size() > 0) {
			for (CMLFormula formula : formulas) {
				double count = formula.getCount();
				w.write(S_LBRAK);
				formula.writeHTML(w);
				w.write(S_RBRAK);
				String cStr = String.valueOf(count);
				if (cStr.endsWith(".0")) {
					cStr = cStr.substring(0, cStr.length()-2);
				}
				if (!cStr.equals("1")) {
					w.write("<sub>"+cStr+"</sub>");
				}
			}
		} else {
			String[] elementTypes = this.getElementTypes();
			if (elementTypes != null) {
				double[] counts = this.getCounts();
				for (int i = 0; i < elementTypes.length; i++) {
					w.write(elementTypes[i]);
					double d = counts[i];
					int c = (int) Math.round(d);
					String countS = CMLConstants.S_EMPTY;
					if (Math.abs(d - c) < EPS) {
						countS = CMLConstants.S_EMPTY+c;
					} else {
						countS = CMLConstants.S_EMPTY+d;
					}
					if (!countS.equals("1")) {
						w.write("<sub>"+countS+"</sub>");
					}
				}
				if (this.getFormalChargeAttribute() != null) {
					int fc = this.getFormalCharge();
					int signum = Integer.signum(fc);
					String sign = CMLConstants.S_EMPTY;

					//no need to assign CMLConstants.S_MINUS to sign if signum is negative as the CMLConstants.S_MINUS will be in the formalChargeAttribute
					if (signum == 1) {
						sign = CMLConstants.S_PLUS;
					}
					if (fc != 0) {
						w.write("<sup>"+fc+sign+"</sup>");
					}
				}
				w.write("</span>");
			}
		}
	}

	/**
	 * test. maybe obsolete
	 *
	 * @param args
	 */
	public static void mainTest(String[] args) {
		if (args.length == 0) {
			Util.println("Usage: FormulaImpl -IN xmlFile -OUT formFile -FORMAT format -MWT");
			// format -MWT -HCOUNT|EXPLICIT");
			Util.println("      Format NOSPACE EL_SPACE_COUNT EL_COUNT_SPACE");
			System.exit(0);
		}
		int i = 0;
		String infile = CMLConstants.S_EMPTY;
		String outfile = CMLConstants.S_EMPTY;
		String formatS = CMLConstants.S_EMPTY;
		Type format = null;
		// String hCount = MoleculeTool.USE_HYDROGEN_COUNT;
		while (i < args.length) {
			if (args[i].equalsIgnoreCase("-IN")) {
				infile = args[++i];
				i++;
			} else if (args[i].equalsIgnoreCase("-OUT")) {
				outfile = args[++i];
				i++;
			} else if (args[i].equalsIgnoreCase("-FORMAT")) {
				formatS = args[++i];
				i++;
				// } else if (args[i].equalsIgnoreCase("-HCOUNT")) {
				// hCount = MoleculeTool.USE_HYDROGEN_COUNT;
				// i++;
				// } else if (args[i].equalsIgnoreCase("-EXPLICIT")) {
				// hCount = MoleculeTool.USE_EXPLICIT_HYDROGENS;
				// i++;
			} else {
				System.err.println("Bad argument: " + args[i]);
				i++;
			}
		}
		if (formatS.equals(S_EMPTY)) {
			formatS = "EL_SPACE_COUNT";
		}
		if (formatS.equals("EL_SPACE_COUNT")) {
			format = Type.ELEMENT_WHITESPACE_COUNT;
		} else if (formatS.equals("EL_COUNT_SPACE")) {
			format = Type.ELEMENT_COUNT_WHITESPACE;
		} else if (formatS.equals("NOSPACE")) {
			format = Type.NOPUNCTUATION;
		} else {
			System.err.println("Unsupported format: " + formatS);
		}
		try {
			if (!infile.equals(S_EMPTY)) {
				CMLBuilder builder = new CMLBuilder();
				Document document = builder.build(new File(infile));
				Elements moleculeVector = document.getRootElement()
				.getChildElements(CMLMolecule.TAG, CMLConstants.CML_NS);
				List<String> formulaVector = new ArrayList<String>();
				for (i = 0; i < moleculeVector.size(); i++) {
					CMLMolecule molecule = (CMLMolecule) moleculeVector.get(i);
					if (molecule == null) {
						System.err.println("No molecule");
					} else {
						CMLFormula formula = (CMLFormula) molecule
						.getFirstChildElement("formula", CMLConstants.CML_NS);
						if (formula != null) {
							String s = "<formula>"
								+ formula.getFormattedString(format,
										Sort.CHFIRST, false) + "</formula>";
							formulaVector.add(s);
						}
					}
				}
				if (!outfile.equals(S_EMPTY)) {
					FileWriter fw = new FileWriter(outfile);
					fw.write("<cml>\n");
					for (i = 0; i < formulaVector.size(); i++) {
						fw.write(S_EMPTY + formulaVector.get(i) + CMLConstants.S_NL);
					}
					fw.write("</cml>\n");
					fw.close();
				}
			}
			/*--
             "Formula: "+this.toString()+S_NL+
             "MWt:"+this.getCalculatedMolecularMass()+S_NL+
             this.getFormattedString(CMLFormula.NOPUNCTUATION, CMLFormula.CHFIRST, false)+S_NL+
             this.getFormattedString(CMLFormula.ELEMENT_WHITESPACE_COUNT, CMLFormula.CHFIRST, false)+S_NL+
             this.getFormattedString(CMLFormula.ELEMENT_COUNT_WHITESPACE, CMLConstants.S_EMPTY, false)+S_NL+
             this.getFormattedString(CMLFormula.NESTEDBRACKETS, CMLFormula.CHFIRST, false)+S_NL+
             this.getFormattedString(CMLFormula.NOPUNCTUATION, CMLFormula.ALPHABETIC_ELEMENTS, true)+S_NL+
             this.getFormattedString(CMLFormula.ELEMENT_WHITESPACE_COUNT, CMLFormula.CHFIRST, true)+S_NL+
             this.getFormattedString(CMLFormula.ELEMENT_COUNT_WHITESPACE, CMLConstants.S_EMPTY, true)+S_NL+
             this.getFormattedString(CMLFormula.NESTEDBRACKETS, CMLFormula.ALPHABETIC_ELEMENTS, true);
             --*/
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	/**
	 * @return the allowNegativeCounts
	 */
	public boolean isAllowNegativeCounts() {
		return allowNegativeCounts;
	}

	/**
	 * @param allowNegativeCounts the allowNegativeCounts to set
	 */
	public void setAllowNegativeCounts(boolean allowNegativeCounts) {
		this.allowNegativeCounts = allowNegativeCounts;
	}

	/**
	 * @return the processedConcise
	 */
	public boolean isProcessedConcise() {
		return processedConcise;
	}


}
