/**
 *    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.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import nu.xom.Element;
import nu.xom.ParentNode;

import org.apache.log4j.Logger;
import org.xmlcml.cml.base.CMLConstants;
import org.xmlcml.cml.base.CMLElement;
import org.xmlcml.cml.base.CMLElements;
import org.xmlcml.cml.base.DoubleArraySTAttribute;
import org.xmlcml.cml.base.StringArraySTAttribute;
import org.xmlcml.cml.element.CMLFormula.Sort;
import org.xmlcml.euclid.Util;

/**
 * user-modifiable class supporting atomArray. * autogenerated from schema use
 * as a shell which can be edited
 *
 */
public class CMLAtomArray extends AbstractAtomArray {
	private static Logger LOG = Logger.getLogger(CMLAtomArray.class);

    final static Logger logger = Logger.getLogger(CMLAtomArray.class);
	/** namespaced element name.*/
	public final static String NS = C_E+TAG;

    /** map of atom ids to atoms.*/
    Map<String, CMLAtom> atomMap;

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

    void init() {
        atomMap = new HashMap<String, CMLAtom>();
//        new Exception().printStackTrace();
    }

    /** copy constructor.
     * NOTE: this will NOT index the atoms. This is dealt with in
     * the Molecule copy costructor
     *
     * @param old
     */
    public CMLAtomArray(CMLAtomArray old) {
        super(old);
        init();
    }

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

    /**
     * create new instance in context of parent, overridable by subclasses.
     *
     * parent must be non-null molecule with no atomArray Child
     *
     * @param parent
     *            parent of element to be constructed (ignored by default)
     * @return CMLAtom
     */
    public CMLElement makeElementInContext(Element parent) {
        if (parent == null) {
            throw new RuntimeException("atomArray must have parent");
        } else if (parent.getLocalName().equals(CMLMolecule.TAG)) {
            if (parent.getChildElements("atomArray", CMLConstants.CML_NS).size() > 0) {
                throw new RuntimeException(
                    "molecule/atomArray must have no atomArray siblings");
            }
        } else if (parent.getLocalName().equals(CMLFormula.TAG)) {
            CMLFormula formula = (CMLFormula) parent;
            CMLElements<CMLAtomArray> atomArrays = formula.getAtomArrayElements();
            if (atomArrays.size() > 0) {
                if (formula.isProcessedConcise() && atomArrays.size() == 1) {
                    formula.removeChild(atomArrays.get(0));
                } else {
                    throw new RuntimeException(
                        "formula/atomArray must have no atomArray siblings");
                }
            }
        } else {
            throw new RuntimeException("atomArray cannot have parent: "
                    + parent.getLocalName());
        }
        return new CMLAtomArray();
    }


    /** finish making element.
     *
     * @param parent element
     */
    public void finishMakingElement(Element parent) {
        super.finishMakingElement(parent);
        // this is here because the parser doesn't route through the
        // addAtom
        indexAtoms();
    }

    /** An array of atom IDs.
    *
    * Normally an attribute of an array-based element.
    * --type info--
    *
    * An array of atomRefs.
    * The atomRefs
    *  cannot be schema- or schematron-validated. Instances of this type will
    *  be used in array-style representation of bonds and atomParitys.
    *  It can also be used for arrays of atomIDTypes such as in complex stereochemistry,
    *  geometrical definitions, atom groupings, etc.
    *
    *  JUMBO expands this into atoms. If any atoms are already present
    *  throws an error.
    *
    * @param atomIDs
    * @throws RuntimeException duplicate atom, bad id, etc

    */
    public void setAtomID(String[] atomIDs) throws RuntimeException {
        if (atomIDs == null) {
            throw new RuntimeException("null atomIDs");
        }
        if (this.getAtoms().size() > 0) {
            throw new RuntimeException("Cannot use atomID with existing children");
        }
        for (String s : atomIDs) {
            CMLAtom atom = new CMLAtom(s);
            this.addAtom(atom);
        }
    }

    /** adds a atom.
     * reroutes to addAtom(atom)
     * @param atom to add
     * @return added atom or null
     * @throws RuntimeException if already child or duplicate hash
     */
    public CMLAtom appendChild(CMLAtom atom) {
        CMLAtom atom0 = this.addAtom(atom);
        return atom0;
    }

    /** get number of child atoms.
     *
     * @return count
     */
    public int size() {
        return this.getAtomElements().size();
    }
    
    /** sorts atomArray.
     * currently only works for array type syntax.
     * 
     * @param sort
     */
	public void sort(Sort sort) {
		String[] elems = this.getElementType();
		double[] counts = this.getCount();
		int nelem = elems.length;
		String[] sortS = new String[elems.length];
		for (int i = 0; i < nelem; i++) {
			sortS[i] = elems[i] + CMLConstants.S_SPACE + counts[i];
		}
		Arrays.sort(sortS);
		if (sort.equals(Sort.ALPHABETIC_ELEMENTS)) {
			; // already done
		} else if (sort.equals(Sort.CHFIRST)) {
			String[] temp = new String[nelem];
			int c = 0;
			for (int i = 0; i < nelem; i++) {
				if (sortS[i].startsWith("C ")) {
					temp[c++] = sortS[i];
				}
			}
			for (int i = 0; i < nelem; i++) {
				if (sortS[i].startsWith("H ")) {
					temp[c++] = sortS[i];
				}
			}
			for (int i = 0; i < nelem; i++) {
				if (sortS[i].startsWith("C ") || sortS[i].startsWith("H ")) {
					; //
				} else {
					temp[c++] = sortS[i];
				}
			}
			System.arraycopy(temp, 0, sortS, 0, nelem);
		}
		for (int i = 0; i < nelem; i++) {
			String[] ss = sortS[i].split(S_SPACE);
			elems[i] = ss[0];
			counts[i] = Double.valueOf(ss[1]).doubleValue();
		}
		this.setElementTypeAndCount(elems, counts);
	}

	/**
	 * generates concise representation. correponds to concise attribute in
	 * schema. only works if atomArray has elementType and count in array format
	 *
	 * @param formalCharge (maybe omit this)
	 * @throws RuntimeException if atomArray of wrong sort
	 * @return concise string
	 */
	public String generateConcise(int formalCharge) {
		String concise = null;
		String[] elems = this.getElementType();
		double[] counts = this.getCount();
		if (elems == null && counts == null) {
			elems = new String[0];
			counts = new double[0];
		} else if (elems == null || counts == null) {
			throw new RuntimeException(
					"atomArray has elements/counts "+elems+S_SLASH+counts);
		} else if (counts.length != elems.length) {
			throw new RuntimeException(
					"atomArray has inconsistent counts/elems "+counts.length+S_SLASH+elems.length);
		}
		int nelem = elems.length;
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < nelem; i++) {
			sb.append(S_SPACE);
			sb.append(elems[i]);
			sb.append(S_SPACE);
			sb.append(Util.trim(Util.format(counts[i], 4)));
		}
		if (formalCharge != 0) {
			sb.append(S_SPACE);
			sb.append(formalCharge);
		}
		concise = (sb.length() == 0) ? CMLConstants.S_EMPTY : sb.substring(1);
		return concise;
	}


    /**
     * adds a atom.
     *
     * @param atom to add
     * @return added atom or null
     * @throws RuntimeException if already child or duplicate hash
     */
    public CMLAtom addAtom(CMLAtom atom) {
        int count = this.getChildCount();
        return this.insertAtom(atom, count);
    }
    /**
     * adds a atom.
     *
     * @param atom to add
     * @param pos position (see insertChild)
     * @return added atom or null
     * @throws RuntimeException if already child or duplicate hash
     */
    public CMLAtom insertAtom(CMLAtom atom, int pos) {
        String id = atom.getId();
        if (id == null) {
            throw new RuntimeException("Atom must have id");
        }
        ParentNode parent = atom.getParent();
        if (parent != null && parent.equals(this)) {
            throw new RuntimeException("atom already added");
        }
        if (atomMap != null && atomMap.containsKey(id)) {
            throw new RuntimeException("atom already in array: "+id);
        }
        indexAtom(atom);
        this.insertChild(atom, pos);
        return atom;
    }

    void indexAtom(CMLAtom atom) {
        if (atomMap == null) {
            atomMap = new HashMap<String, CMLAtom>();
        }
        atomMap.put(atom.getId(), atom);
    }


    /** removes a atom.
     * reroutes to removeAtom(atom)
     * @param atom to remove
     * @return removed atom or null
     * @throws RuntimeException
     */
    public CMLAtom removeChild(CMLAtom atom) {
        return this.removeAtom(atom);
    }

    /**
     * removes a atom.
     *
     * @param atom
     * @return deleted atom or null
     */
    public CMLAtom removeAtom(CMLAtom atom) {
        CMLAtom deletedAtom = null;
        if (this.equals(atom.getParent())) {
            super.removeChild(atom);
            if (atomMap != null) {
                atomMap.remove(atom.getId());
            }
            deleteLigandBonds(atom);
            deletedAtom = atom;
        }
        return deletedAtom;
    }

    /** delete all bonds to this atom.
     *
     * @param atom
     * @return list of deleted bonds
     */
    List<CMLBond> deleteLigandBonds(CMLAtom atom) {
        CMLBondArray bondArray = this.getBondArray();
        List<CMLBond> ligandBonds = atom.getLigandBonds();
        // make copy or we get conncurrentModification
        List<CMLBond> ligandBonds1 = new ArrayList<CMLBond>();
        for (CMLBond bond : ligandBonds) {
            ligandBonds1.add(bond);
        }
        for (CMLBond bond : ligandBonds1) {
            bondArray.removeBond(bond);
        }
        return ligandBonds;
    }

    void deleteLigandBonds() {
        List<CMLAtom> atomList = this.getAtoms();
        for (CMLAtom atom : atomList) {
            deleteLigandBonds(atom);
        }
    }

    void clearLigandInfo() {
        List<CMLAtom> atomList = this.getAtoms();
        for (CMLAtom atom : atomList) {
            atom.clearLigandInfo();
        }
    }

    void debugLigands() {
        List<CMLAtom> atomList = this.getAtoms();
        for (CMLAtom atom : atomList) {
            Util.println("ATOM: "+atom.getString());
            for (CMLAtom ligand : atom.getLigandAtoms()) {
                Util.println("  LIG: "+ligand.getString());
            }
        }
    }


    /** get sibling bondArray.
     *
     * @return bondArray or null
     */
    CMLBondArray getBondArray() {
        CMLBondArray bondArray = null;
        CMLMolecule molecule = this.getMolecule();
        if (molecule != null) {
            CMLElements<CMLBondArray> bondArrays = molecule.getBondArrayElements();
            bondArray = (bondArrays.size() == 0) ? null : bondArrays.get(0);
        }
        return bondArray;
    }

    /** gets parent molecule.
     *
     * @return parent molecule or null
     */
    public CMLMolecule getMolecule() {
        CMLMolecule molecule = null;
        ParentNode node = this.getParent();
        if (node != null && node instanceof CMLMolecule) {
            molecule = (CMLMolecule) node;
        }
        return molecule;
    }

    /** reroute to molecule.removeAtomArray().
     * also implicitly removes bondArray
     */
    public void detach() {
        ParentNode parent = this.getParent();
        if (parent != null) {
            if (parent instanceof CMLMolecule) {
                CMLMolecule molecule = (CMLMolecule) parent;
                molecule.removeAtomArray();
            } else {
                super.detach();
            }
        }
    }

    void indexAtoms() {
        List<CMLAtom> atoms = this.getAtoms();
        this.getAtomMap();
        atomMap.clear();
        for (CMLAtom atom : atoms) {
//        	atom.debug("ATOM IN MOLECULE");
            String id = atom.getId();
//            LOG.debug("ATOMID: "+id);
            if (atomMap.containsKey(id)) {
//            	this.debug("DUPLICATE INDEX ATOMS");
                throw new RuntimeException("Index atom: duplicate atom: "+id);
            }
            atomMap.put(id, atom);
        }
    }

    /** get map of atom hash to atoms.
     *
     * @return map
     */
    public Map<String, CMLAtom> getAtomMap() {
        return atomMap;
    }

    /** get list of atoms in order.
     *
     * @return atoms
     */
    public List<CMLAtom> getAtoms() {
        List<CMLAtom> atomList = new ArrayList<CMLAtom>();
        CMLElements<CMLAtom> atoms = this.getAtomElements();
        for (CMLAtom atom : atoms) {
            atomList.add(atom);
        }
        return atomList;
    }

    /** get atom by id.
     *
     * @param id
     * @return atom or null
     */
    public CMLAtom getAtomById(String id) {
        return (atomMap == null) ? null : atomMap.get(id);
    }

    private List<CMLAtom> getAtoms(int length, String typeName) {
        ParentNode parent = this.getParent();
        if (parent == null) {
            throw new RuntimeException("null parent for atomArray");
        }
        if (parent instanceof CMLFormula) {
            throw new RuntimeException("don't use this for formula");
        }
        // if parent is formula, artificially create atoms
        List<CMLAtom> atomList = this.getAtoms();
        // check atoms match arrays
        if (atomList.size() != length) {
            throw new RuntimeException("inconsistent atom count ("+
                atomList.size()+") and "+typeName+" ("+length+S_RBRAK);
        }
        return atomList;
    }

    /** formula-specific.
     *
     * @param elem
     * @param count
     * @throws RuntimeException
     */

    public void setElementTypeAndCount(String[] elem, double[] count)
        throws RuntimeException {
        if (elem.length > 0 && count.length > 0) {
            StringArraySTAttribute att = new StringArraySTAttribute("elementType");
            att.setCMLValue(elem);
            this.addAttribute(att);

            DoubleArraySTAttribute datt = new DoubleArraySTAttribute("count");
            datt.setCMLValue(count);
            this.addAttribute(datt);
        }
    }
    /** The identity of a chemical element.
    *
    * Normally mandatory on _atom_, _isotope_, etc.
    * --type info--
    *
    * An array of elementTypes.
    * Instances of this type will be used in array-style representation of atoms.

    * @param value elementType value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setElementType(String[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null elementType");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "elementType");
        LOG.debug("ATOM "+this.getAtoms().size());
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setElementType(value[i++]);
        }
    }
    /** Array of object counts.
    *
    * No fixed semantics or default, normally integral. It is presumed that the element can be multiplied by the count value.
    * --type info--
    *
    * Array of counts.
    * Normally, but not always, integers. can be used with a number of elements

    * @param value count value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setCount(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null count");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "count");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setCount(value[i++]);
        }
    }
    /** An array of formalCharges.
    *
    * Used in CML2 Array mode. NOT the calculated charge or oxidation state. No formal defaults, but assumed to be zero if omitted. It may become good practice to include it.
    * --type info--
    *
    * Array of formalCharges.
    * Used for electron-bookeeping. This has no relation to its calculated (fractional) charge or oxidation state.

    * @param value formalCharge value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setFormalCharge(String[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null formalCharge");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "formalCharge");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setFormalCharge(Integer.parseInt(value[i++]));
        }
    }
    /** Array of hydrogenCounts.
    *
    * Normally used in CML2 array mode. The total number of hydrogens bonded to the atom or molecule. It is preferable to include hydrogens explicitly, and where this is done their count represents the minimum (and may thus override this attribute). It is dangerous to use this attribute for electron-deficient molecules (e.g. diborane) or hydrogen bonds. There is NO DEFAULT and the absence of this attribute must not be given any meaning.
    * --type info--
    *
    * Array of hydrogenCounts.
    * The total number of hydrogen atoms bonded to an atom or contained in a molecule, whether explicitly included as atoms or not. It is an error to have hydrogen count less than the explicit hydrogen count. There is no default value and no assumptions about hydrogen Count can be made if it is not given. If hydrogenCount is given on every atom, then the values can be summed to give the total hydrogenCount for the (sub)molecule. Because of this hydrogenCount should not be used where hydrogen atoms bridge 2 or more atoms.

    * @param value hydrogenCount value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setHydrogenCount(String[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null hydrogenCount");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "hydrogenCount");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setHydrogenCount(Integer.parseInt(value[i++]));
        }
    }
    /** Array of occupancies.
    *
    * Normally only found in crystallography. Defaults to 1.0. The occupancy is required to calculate the molecular formula from the atoms.
    * --type info--
    *
    * Array of atomic occupancies.
    * Primarily for crystallography. Values outside 0-1 are not allowed.

    * @param value occupancy value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setOccupancy(String[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null occupancy");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "occupancy");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setOccupancy(Double.valueOf(value[i++]).doubleValue());
        }
    }
    /** array of x2 coordinate.
    *
    * Normally used in CML2 array mode. Used for displaying the object in 2 dimensions. Unrelated to the 3-D coordinates for the object. The orientation of the axes matters as it can affect the chirality of object.
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value x2 value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setX2(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null x2: "+this.getId());
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "x2");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setX2(value[i++]);
        }
    }
    /** array of y2 coordinate.
    *
    * Normally used in CML2 array mode. Used for displaying the object in 2 dimensions. Unrelated to the 3-D coordinates for the object. The orientation of the axes matters as it can affect the chirality of object.
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value y2 value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setY2(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null y2");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "y2");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setY2(value[i++]);
        }
    }
    /** Normally used in CML2 array mode.
    *
    *
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).
    * must have already processed setAtomID

    * @param value must match atom count
    * @throws RuntimeException null x3, inconsistent atom count, etc.
     */
    public void setX3(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null x3");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "x3");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setX3(value[i++]);
        }
    }
    /** Normally used in CML2 array mode.
    *
    *
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value y3 value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setY3(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null y3");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "y3");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setY3(value[i++]);
        }
    }
    /** Normally used in CML2 array mode.
    *
    *
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value z3 value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setZ3(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null z3");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "z3");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setZ3(value[i++]);
        }
    }
    /** Array of fractional x coordinate.
    *
    * normally xFract, yFract and zFract should all be present or absent. If present a _crystal_ element should also occur.
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value xFract value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setXFract(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null xFract");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "xFract");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setXFract(value[i++]);
        }
    }
    /** Array of fractional y coordinate.
    *
    * normally xFract, yFract and zFract should all be present or absent. If present a _crystal_ element should also occur.
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value yFract value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setYFract(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null yFract");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "yFract");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setYFract(value[i++]);
        }
    }
    /** Array of fractional z coordinate.
    *
    * normally xFract, yFract and zFract should all be present or absent. If present a _crystal_ element should also occur.
    * --type info--
    *
    * An array of coordinateComponents for a single coordinate.
    * An array of coordinateComponents for a single coordinate
    * where these all refer to an X-coordinate (NOT x,y,z).Instances of this type will be
    * used in array-style representation of 2-D or 3-D coordinates. Currently no machine
    * validation. Currently not used in STMML, but re-used by CML (see example).

    * @param value zFract value
    * @throws RuntimeException attribute wrong value/type

    */
    public void setZFract(double[] value) throws RuntimeException {
        if (value == null) {
            throw new RuntimeException("null zFract");
        }
        List<CMLAtom> atomList = this.getAtoms(value.length, "zFract");
        int i = 0;
        for (CMLAtom atom : atomList) {
            atom.setZFract(value[i++]);
        }
    }
}
