/* * EuroCarbDB, a framework for carbohydrate bioinformatics * * Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as * indicated by the @author tags or express copyright attribution * statements applied by the authors. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * A copy of this license accompanies this distribution in the file LICENSE.txt. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * Last commit: $Rev: 1930 $ by $Author: david@nixbioinf.org $ on $Date:: 2010-07-29 #$ */ package org.eurocarbdb.application.glycanbuilder; import java.util.TreeMap; import java.util.Map; import java.util.Vector; import java.util.regex.Pattern; import java.util.regex.Matcher; /** * Manages a collection of charges that will be associated to a glycan * structure. The identity of the possible ions is defined in * {@link MassOptions} * * @author Alessio Ceroni (a.ceroni@imperial.ac.uk) */ public class IonCloud { protected TreeMap<String, Integer> ions; protected int ionsNum; protected int ionsRelCount; protected double ionsTotalMass; /** * Empty constructor */ public IonCloud() { ions = new TreeMap<String, Integer>(); ionsNum = 0; ionsRelCount = 0; ionsTotalMass = 0.; } /** * Create a new object from an initialization string * * @see #initFromString */ public IonCloud(String init) { this(); try { initFromString(init); } catch (Exception e) { LogUtils.report(e); } } // methods public boolean equals(Object other) { if (!(other instanceof IonCloud)) return false; return this.toString().equals(other.toString()); } /** * Compute the mass/charge value of a structure with a certain mass given * the charges in this object * * @param mass * the mass of the glycan molecule */ public double computeMZ(double mass) { if (mass <= 0.) return mass; if (ionsNum == 0) return (mass + ionsTotalMass); return (mass + ionsTotalMass) / ionsNum; } /** * Compute the original mass of a structure with a certain mass/charge value * given the charges in this object * * @param mz * the mass/charge value of the glycan molecule with the * associated charges */ public double computeMass(double mz) { if (mz <= 0.) return mz; if (ionsNum == 0) return (mz - ionsTotalMass); return (mz * ionsNum - ionsTotalMass); } /** * Return <code>true</code> if the object represent a set of charges that * could be found associated to a glycan structure */ public boolean isRealistic() { for (Map.Entry<String, Integer> c : ions.entrySet()) if (!c.getKey().equals(MassOptions.ION_H) && c.getValue().intValue() < 0) return false; return true; } /** * Return <code>true</code> if the total charge is negative */ public boolean isNegative() { return (ionsRelCount < 0); } /** * Return the total number of charges */ public int getNoCharges() { return Math.abs(ionsRelCount); } /** * Return <code>true</code> if the number of charges is undetermined. */ public boolean isUndetermined() { for (Map.Entry<String, Integer> c : this.ions.entrySet()) if (c.getValue() == 999) return true; return false; } // data access /** * Create a copy of the object. */ public IonCloud clone() { IonCloud ret = new IonCloud(); for (Map.Entry<String, Integer> c : this.ions.entrySet()) ret.ions.put(c.getKey(), c.getValue()); ret.ionsNum = this.ionsNum; ret.ionsRelCount = this.ionsRelCount; ret.ionsTotalMass = this.ionsTotalMass; return ret; } /** * Reset the object to contain no charges */ public void clear() { ions.clear(); ionsNum = 0; ionsRelCount = 0; ionsTotalMass = 0.; } /** * Return a copy of this object to which the content of a second object is * added */ public IonCloud and(IonCloud other) { IonCloud ret = this.clone(); ret.add(other); return ret; } /** * Add the content of a second object */ public void add(IonCloud other) { if (other == null) return; for (Map.Entry<String, Integer> c : other.ions.entrySet()) add(c.getKey(), c.getValue()); } /** * Return a copy of this object to which a new charge has been added */ public IonCloud and(String charge) { return and(charge, 1); } /** * Return a copy of this object to which a certain quantity of a new charge * has been added */ public IonCloud and(String charge, int quantity) { if (charge.equals(MassOptions.ION_H)) return this.and(charge, MassUtils.h_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_LI)) return this.and(charge, MassUtils.li_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_NA)) return this.and(charge, MassUtils.na_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_K)) return this.and(charge, MassUtils.k_ion.getMass(), quantity); return this.clone(); } /** * Add a certain quantity of a new charge to this object */ public void add(String charge, int quantity) { if (charge.equals(MassOptions.ION_H)) add(charge, MassUtils.h_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_LI)) add(charge, MassUtils.li_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_NA)) add(charge, MassUtils.na_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_K)) add(charge, MassUtils.k_ion.getMass(), quantity); } /** * Set the quantity of a specific charge */ public void set(String charge, int quantity) { if (charge.equals(MassOptions.ION_H)) set(charge, MassUtils.h_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_LI)) set(charge, MassUtils.li_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_NA)) set(charge, MassUtils.na_ion.getMass(), quantity); else if (charge.equals(MassOptions.ION_K)) set(charge, MassUtils.k_ion.getMass(), quantity); } /** * Return a copy of this object to which a certain quantity of a new charge * has been added */ public IonCloud and(String charge_name, double charge_mass, int quantity) { IonCloud ret = this.clone(); ret.add(charge_name, charge_mass, quantity); return ret; } /** * Add a certain quantity of a new charge to this object */ public void add(String charge_name, double charge_mass, int quantity) { if (quantity == 0) return; // add to list if (ions.containsKey(charge_name)) ions.put(charge_name, ions.get(charge_name) + quantity); else ions.put(charge_name, quantity); if (ions.get(charge_name) == 0) ions.remove(charge_name); // update count ionsRelCount += quantity; ionsNum = Math.abs(ionsRelCount); // update mass ionsTotalMass += quantity * charge_mass; } /** * Set the quantity of a specific charge */ public void set(String charge_name, double charge_mass, int quantity) { add(charge_name, charge_mass, -get(charge_name)); add(charge_name, charge_mass, quantity); } /** * Copy the content of a second object on this one * * @param skip_undetermined * <code>true</code> if charges with undetermined quantity should * be ignored */ public boolean set(IonCloud other, boolean skip_undetermined) { boolean changed = false; if (!skip_undetermined || other.get(MassOptions.ION_H) != 999) { this.set(MassOptions.ION_H, other.get(MassOptions.ION_H)); changed = true; } if (!skip_undetermined || other.get(MassOptions.ION_NA) != 999) { this.set(MassOptions.ION_NA, other.get(MassOptions.ION_NA)); changed = true; } if (!skip_undetermined || other.get(MassOptions.ION_LI) != 999) { this.set(MassOptions.ION_LI, other.get(MassOptions.ION_LI)); changed = true; } if (!skip_undetermined || other.get(MassOptions.ION_K) != 999) { this.set(MassOptions.ION_K, other.get(MassOptions.ION_K)); changed = true; } return changed; } /** * Return the identities of the charges contained in this object */ public Vector<String> getIons() { Vector<String> ret = new Vector<String>(); for (Map.Entry<String, Integer> e : this.ions.entrySet()) ret.add(e.getKey()); return ret; } /** * Return the quantity of a specific charge */ public int get(String charge_name) { Integer ret = ions.get(charge_name); return (ret == null) ? 0 : ret; } /** * Merge the content of a second object to this one */ public void merge(IonCloud other) { this.merge(MassOptions.ION_H, other); this.merge(MassOptions.ION_NA, other); this.merge(MassOptions.ION_LI, other); this.merge(MassOptions.ION_K, other); } /** * Merge the information about a specific charge in a second object to this * one */ public void merge(String charge_name, IonCloud other) { if (other.get(charge_name) == 999) this.set(charge_name, 999); else if (this.get(charge_name) != other.get(charge_name)) this.set(charge_name, 999); } // member access /** * Return the total number of ions */ public int size() { return ionsNum; } /** * Return the total number of ions */ public int getIonsNum() { return ionsNum; } /** * Return the total mass */ public double getIonsMass() { return ionsTotalMass; } /** * Return a map containing the identities and quantities of the charges in * this object */ public Map<String, Integer> getIonsMap() { return ions; } /** * Convert this object to a {@link Molecule} object containing the same * information */ public Molecule getMolecule() throws Exception { Molecule ret = new Molecule(); for (Map.Entry<String, Integer> e : this.ions.entrySet()) { String charge = e.getKey(); int num = e.getValue(); if (charge.equals(MassOptions.ION_H)) ret.add(MassUtils.h_ion, num); else if (charge.equals(MassOptions.ION_LI)) ret.add(MassUtils.li_ion, num); else if (charge.equals(MassOptions.ION_NA)) ret.add(MassUtils.na_ion, num); else if (charge.equals(MassOptions.ION_K)) ret.add(MassUtils.k_ion, num); } return ret; } // serialization /** * Create a new object from its string representation. * * @throws Exception * if the string is in the wrong format * @see #initFromString */ static public IonCloud fromString(String str) throws Exception { IonCloud ret = new IonCloud(); ret.initFromString(str); return ret; } /** * Create a new object from its string representation. The string must * contain either 0 or a mathematical formula specifying the identity and * quantity of the charges (Example: 2Na-2H) * * @throws Exception * if the string is in the wrong format * @see #initFromString */ public void initFromString(String str) throws Exception { clear(); if (str == null || str.length() == 0 || str.equals("0")) return; char[] str_buffer = str.toCharArray(); for (int i = 0; i < str_buffer.length;) { StringBuilder count = new StringBuilder(); StringBuilder ion = new StringBuilder(); // read sign if (str_buffer[i] == '+' || str_buffer[i] == '-') { if (str_buffer[i] == '-') count.append(str_buffer[i]); i++; } if (i == str_buffer.length) throw new Exception("Invalid string format: <" + str + ">"); // read count if (Character.isDigit(str_buffer[i])) { for (; i < str_buffer.length && Character.isDigit(str_buffer[i]); i++) count.append(str_buffer[i]); } else count.append('1'); if (i == str_buffer.length) throw new Exception("Invalid string format: <" + str + ">"); if (Character.isLetter(str_buffer[i])) { for (; i < str_buffer.length && Character.isLetter(str_buffer[i]); i++) ion.append(str_buffer[i]); } else throw new Exception("Invalid string format: <" + str + ">"); this.add(ion.toString(), Integer.valueOf(count.toString())); } } /** * Return a string representation of this object * * @see #initFromString */ public String toString() { StringBuilder sb = new StringBuilder(); // first positives then negatives for (Map.Entry<String, Integer> entry : ions.entrySet()) { if (entry.getValue() > 0) { if (sb.length() > 0) sb.append('+'); if (entry.getValue() > 1) sb.append(entry.getValue()); sb.append(entry.getKey()); } } for (Map.Entry<String, Integer> entry : ions.entrySet()) { if (entry.getValue() < 0) { if (entry.getValue() == -1) sb.append('-'); else sb.append(entry.getValue()); sb.append(entry.getKey()); } } if (sb.length() == 0) sb.append('0'); return sb.toString(); } }