package com.compomics.util.experiment.biology;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
/**
* A chain of atoms.
*
* @author Marc Vaudel
*/
public class AtomChain implements Serializable {
/**
* Serial number for backward compatibility.
*/
static final long serialVersionUID = 2222259572093523514L;
/**
* The chain of atoms.
*/
private ArrayList<AtomImpl> atomChain;
/**
* The mass of the atom chain.
*/
private Double mass = null;
/**
* Cache for the string value.
*
* @deprecated deprectated since utilities version 4.8.2. Use stringValue1 instead.
*/
private String stringValue = null;
/**
* Cache for the string value.
*/
private String stringValue1 = null;
/**
* Creates an empty atom chain.
*/
public AtomChain() {
atomChain = new ArrayList<AtomImpl>(4);
}
/**
* Returns an atom chain from the input as string. Atoms are
* represented by their canonical short name, e.g. C for Carbon, Na for
* Sodium. The occurrence of a given atom is to be written in parentheses,
* e.g. C(3)PO is parsed as three C's, one P and one O. No negative values
* are allowed. The isotope is to be written prior to the atom, e.g.
* 13C(2)18OP is parsed as two 13C atoms, one 18O, and one P.
*
* @param atomChainAsString the atomic chain as a string
*
* @return the atom chain represented in the given string
*/
public static AtomChain getAtomChain(String atomChainAsString) {
AtomChain atomChain = new AtomChain();
char[] atomChainAsStringCharArray = atomChainAsString.toCharArray();
for (int i = 0; i < atomChainAsStringCharArray.length; i++) {
char character = atomChainAsStringCharArray[i];
if (character != ' ') {
if (character == '-') {
throw new IllegalArgumentException("Negative isotope found in " + atomChainAsString + ". Please use the atom number, e.g. 13 for 13C.");
}
Integer isotopeNumber = 0;
int charAsInt = Character.getNumericValue(character);
// Parse isotope number
while (charAsInt >= 0 && charAsInt <= 9) {
isotopeNumber = 10 * isotopeNumber + charAsInt;
i++;
if (i == atomChainAsStringCharArray.length) {
throw new IllegalArgumentException("Reached the end of the atom chain while parsing isotope number in " + atomChainAsString + ".");
}
character = atomChainAsStringCharArray[i];
charAsInt = Character.getNumericValue(character);
}
// Parse atom name
StringBuilder atomName = new StringBuilder();
atomName.append(character);
Integer occurrence = null;
if (i + 1 < atomChainAsStringCharArray.length) {
char nextCharacter = atomChainAsStringCharArray[i + 1];
while (Character.isLowerCase(nextCharacter)) {
atomName.append(nextCharacter);
i++;
if (i + 1 < atomChainAsStringCharArray.length) {
nextCharacter = atomChainAsStringCharArray[i + 1];
} else {
break;
}
}
if (nextCharacter == '(') {
// Parse occurrence in parentheses
i++;
i++;
if (i == atomChainAsStringCharArray.length) {
throw new IllegalArgumentException("Reached the end of the atom chain while parsing occurrence of " + atomName + " in " + atomChainAsString + ".");
}
character = atomChainAsStringCharArray[i];
if (character == '-') {
throw new IllegalArgumentException("Negative occurrence found for " + atomName + " in " + atomChainAsString + ".");
}
while (character != ')') {
charAsInt = Character.getNumericValue(character);
if (charAsInt < 0 || charAsInt > 9) {
throw new IllegalArgumentException("Encountered unexpected character " + character + " while parsing occurrence of " + atomName + " in " + atomChainAsString + ".");
}
if (occurrence == null) {
occurrence = charAsInt;
} else {
occurrence = 10 * occurrence + charAsInt;
}
i++;
if (i == atomChainAsStringCharArray.length) {
throw new IllegalArgumentException("Reached the end of the atom chain while parsing occurrence of " + atomName + " in " + atomChainAsString + ".");
}
character = atomChainAsStringCharArray[i];
}
}
}
if (occurrence == null) {
occurrence = 1;
}
Atom atom = Atom.getAtom(atomName.toString());
AtomImpl atomImpl = new AtomImpl(atom, isotopeNumber);
if (isotopeNumber != 0) {
isotopeNumber = atomImpl.getIsotopeNumber(isotopeNumber);
if (isotopeNumber == null) {
throw new UnsupportedOperationException("An error occurred while parsing atom chain " + atomChainAsString + "Isotope " + isotopeNumber + " not supported for atom " + atom + ".");
}
atomImpl.setIsotope(isotopeNumber);
}
atomChain.append(atomImpl, occurrence);
}
}
return atomChain;
}
/**
* Appends an atom to the chain of atoms.
*
* @param atom a new atom
*/
public void append(AtomImpl atom) {
atomChain.add(atom);
stringValue1 = null;
}
/**
* Appends an atom to the chain of atoms.
*
* @param atom a new atom
* @param occurrence the number of times this atom should be added
*/
public void append(AtomImpl atom, int occurrence) {
if (occurrence < 0) {
throw new IllegalArgumentException("Negative occurrence");
}
for (int i = 0; i < occurrence; i++) {
atomChain.add(atom);
}
stringValue1 = null;
}
/**
* Returns the atom chain as a list of AtomImpl.
*
* @return the atom chain as a list of AtomImpl
*/
public ArrayList<AtomImpl> getAtomChain() {
return atomChain;
}
/**
* Returns the mass of the atomic chain as sum of the individual atoms.
*
* @return the mass of the atomic chain as sum of the individual atoms
*/
public Double getMass() {
if (mass == null) {
estimateMass();
}
return mass;
}
/**
* Returns the number of atoms in this atom chain.
*
* @return the number of atoms in this atom chain
*/
public int size() {
return atomChain.size();
}
/**
* Estimates the mass of the atom chain.
*/
private synchronized void estimateMass() {
if (mass == null) {
Double tempMass = 0.0;
for (AtomImpl atom : atomChain) {
tempMass += atom.getMass();
}
mass = tempMass;
}
}
/**
* Sets the string value from the stringValue1 attribute, sets it from the
* composition if not set.
*
* @param includeSpaces boolean indicating whether spaces should be included between atoms.
*
* @return the string value
*/
private synchronized String getStringValue(boolean includeSpaces) {
if (stringValue1 == null) {
HashMap<String, Integer> composition = new HashMap<String, Integer>(atomChain.size());
HashMap<String, HashMap<Integer, String>> isotopeMap = new HashMap<String, HashMap<Integer, String>>(atomChain.size());
for (AtomImpl atom : atomChain) {
String atomName = atom.toString();
Integer occurrence = composition.get(atomName);
if (occurrence == null) {
occurrence = 0;
}
composition.put(atomName, occurrence + 1);
String atomLetter = atom.getAtom().getLetter();
HashMap<Integer, String> atomIsotopes = isotopeMap.get(atomLetter);
if (atomIsotopes == null) {
atomIsotopes = new HashMap<Integer, String>(1);
isotopeMap.put(atomLetter, atomIsotopes);
}
Integer isotope = atom.getIsotope();
atomIsotopes.put(isotope, atomName);
}
StringBuilder compositionAsString = new StringBuilder(composition.size());
ArrayList<String> atomNames = new ArrayList<String>(isotopeMap.keySet());
Collections.sort(atomNames);
for (String atomLetter : atomNames) {
HashMap<Integer, String> atomIsotopes = isotopeMap.get(atomLetter);
ArrayList<Integer> isotopes = new ArrayList<Integer>(atomIsotopes.keySet());
Collections.sort(isotopes);
for (Integer isotope : isotopes) {
String atomName = atomIsotopes.get(isotope);
if (includeSpaces && compositionAsString.length() > 0) {
compositionAsString.append(" ");
}
compositionAsString.append(atomName);
Integer occurrence = composition.get(atomName);
if (occurrence > 1) {
compositionAsString.append("(").append(occurrence).append(")");
}
}
}
stringValue1 = compositionAsString.toString();
}
return stringValue1;
}
/**
* Returns the occurrence of a given atom in the chain.
*
* @param atom the atom of interest
* @param isotope the isotope to look for
*
* @return the occurrence of the atom in this atom chain
*/
public int getOccurrence(Atom atom, Integer isotope) {
int occurrence = 0;
AtomImpl atom1 = new AtomImpl(atom, isotope);
for (AtomImpl atom2 : atomChain) {
if (atom1.isSameAs(atom2)) {
occurrence++;
}
}
return occurrence;
}
/**
* Removes all the occurrences of the given atom.
*
* @param atom the atom
* @param isotope the isotope
*/
public void remove(Atom atom, Integer isotope) {
ArrayList<AtomImpl> newAtomChain = new ArrayList<AtomImpl>(atomChain.size());
AtomImpl atom1 = new AtomImpl(atom, isotope);
for (AtomImpl atom2 : atomChain) {
if (!atom1.isSameAs(atom2)) {
newAtomChain.add(atom2);
}
}
atomChain = newAtomChain;
mass = null;
stringValue1 = null;
}
/**
* Sets the occurrence of a given atom.
*
* @param atom the atom
* @param isotope the isotope number
* @param occurrence the occurrence
*/
public void setOccurrence(Atom atom, Integer isotope, Integer occurrence) {
remove(atom, isotope);
append(new AtomImpl(atom, isotope), occurrence);
mass = null;
stringValue1 = null;
}
/**
* Indicates whether two atom chains are of the same composition by
* comparing their string and type. An empty chain is considered to be the
* same composition as a null chain.
*
* @param anotherChain another atom chain
*
* @return a boolean indicating whether two atom chains are of the same
* composition
*/
public boolean isSameCompositionAs(AtomChain anotherChain) {
if (!atomChain.isEmpty() && (anotherChain == null || anotherChain.getAtomChain().isEmpty())) {
return false;
}
return anotherChain.toString().equals(toString());
}
@Override
public String toString() {
if (stringValue1 == null) {
return getStringValue(false);
}
return stringValue1;
}
@Override
public AtomChain clone() {
AtomChain result = new AtomChain();
for (AtomImpl atom : atomChain) {
result.append(new AtomImpl(atom.getAtom(), atom.getIsotope()));
}
return result;
}
}