/*
* Copyright (C) Lennart Martens
*
* Contact: lennart.martens AT UGent.be (' AT ' to be replaced with '@')
*/
/**
* Created by IntelliJ IDEA.
* User: Lennart
* Date: 1-jul-2004
* Time: 15:03:58
*/
package com.compomics.util.protein;
import org.apache.log4j.Logger;
import java.util.*;
/*
* CVS information:
*
* $Revision: 1.3 $
* $Date: 2007/07/06 09:41:53 $
*/
/**
* This class holds a template for a Modification, meaning that it contains all shared characteristics of
* a Modification, but not the instance-specific one. As such, instances can be treated as singletons and are
* obviously immutable by nature.
*
* @author Lennart Martens
*/
public class ModificationTemplate {
// Class specific log4j logger for ModificationTemplate instances.
Logger logger = Logger.getLogger(ModificationTemplate.class);
/**
* This HashMap will contain the following 'key-value' mappings: (key > value) <br>
* (residue > double[]{MONOISOTOPIC_DELTA, AVERAGE_DELTA})
*/
protected HashMap iMassDeltas = null;
/**
* The code for this modification (eg., Mox).
*/
protected String iCode = null;
/**
* The title for this modification (eg., Oxidation Met).
*/
protected String iTitle = null;
/**
* Boolean to indicate whether this modification is an artifact.
*/
protected boolean iArtifact = false;
/**
* This constructor allows initialization of all the properties for the modification template.
*
* @param aTitle String with the title for the modification (eg., Oxidation Met).
* @param aCode String with the code for the modification (eg., Mox). The code can be used when
* annotating a sequence String (eg., NH2-MGTEFSM<Mox>R-COOH).
* @param aMassDeltas HashMap with the following 'key-value' mappings: (key > value) <br>
* (residue > double[]{MONOISOTOPIC_DELTA, AVERAGE_DELTA} <br>
* Note that the residues for the N-terminus and C-terminus are represented
* by the NTERMINUS and CTERMINUS constants, respectively.
*/
public ModificationTemplate(String aTitle, String aCode, HashMap aMassDeltas) {
this(aTitle, aCode, aMassDeltas, false);
}
/**
* This constructor allows initialization of all the properties for the modification template.
*
* @param aTitle String with the title for the modification (eg., Oxidation Met).
* @param aCode String with the code for the modification (eg., Mox). The code can be used when
* annotating a sequence String (eg., NH2-MGTEFSM<Mox>R-COOH).
* @param aMassDeltas HashMap with the following 'key-value' mappings: (key > value) <br>
* (residue > double[]{MONOISOTOPIC_DELTA, AVERAGE_DELTA} <br>
* Note that the residues for the N-terminus and C-terminus are represented
* by the NTERMINUS and CTERMINUS constants, respectively.
* @param aArtifact boolean to indicate whether this class is an artifact.
*/
public ModificationTemplate(String aTitle, String aCode, HashMap aMassDeltas, boolean aArtifact) {
this.iTitle = aTitle;
this.iCode = aCode;
// Check the structure of the HashMap.
// If it doesn't conform to 'String, double[] (length 2; more is permitted however)' key/value pairs,
// throw an appropriate IllegalArumentException.
Iterator it = aMassDeltas.keySet().iterator();
while(it.hasNext()) {
// By default, assume it doesn't pass; let it prove itself.
boolean pass = false;
Object key = it.next();
// Check the key to see whether it is a String.
if(key instanceof String) {
// Okay, key is correct, now the value.
Object value = aMassDeltas.get(key);
if(value instanceof double[]) {
if(((double[])value).length >= 2) {
// This element in the HashMap passes the test.
pass = true;
}
}
}
// If the element didn't pass the test, we reject the entire HashMap.
if(!pass) {
throw new IllegalArgumentException("Your HashMap did not conform to the required (String, double[] (length 2)) conformation!");
}
}
this.iMassDeltas = aMassDeltas;
this.iArtifact = aArtifact;
}
/**
* This method returns a double with the average mass difference
* conferred on the sequence by this modification for the specified residue.
* This mass delta can be negative! When a residue was specified that is not affected by this
* modification, '0.0' is returned.
*
* @param aResidue String with the residue for which the mass delta needs to be calculated.
* @return double with the average mass difference.
*/
public double getAverageMassDelta(String aResidue) {
return this.getMassDelta(aResidue, com.compomics.util.interfaces.Modification.AVERAGE);
}
/**
* This method returns the short code for the modification, eg. 'Mox'.
*
* @return String with the short code for the modification.
* Can be used to annotate a sequence.
*/
public String getCode() {
return this.iCode;
}
/**
* This method returns the title of the modification,
* eg. 'Oxidation Met'.
*
* @return String with the title for the modification.
*/
public String getTitle() {
return this.iTitle;
}
/**
* This method reports on all the residues that can be modified by this Modification. <br>
* The Collection is a keySet of a HashMap.
*
* @return Collection with the residues that can be modified by this modification.
*/
public Collection getResidues() {
return this.iMassDeltas.keySet();
}
/**
* This method returns a double with the monoisotopic mass difference
* conferred on the sequence by this modification for the specified residue.
* This mass delta can be negative! When a residue was specified that is not affected by this
* modification, '0.0' is returned.
*
* @param aResidue String with the residue for which the mass delta needs to be calculated.
* @return double with the monoisotopic mass difference.
*/
public double getMonoisotopicMassDelta(String aResidue) {
return this.getMassDelta(aResidue, com.compomics.util.interfaces.Modification.MONOISOTOPIC);
}
/**
* This method indicates whether this modification is considered an artifact.
*
* @return boolean that indicates whether this modification is an artifact.
*/
public boolean isArtifact() {
return this.iArtifact;
}
/**
* Indicates whether some other object is "equal to" this one.
* For this class comparison is based on:
* <ul>
* <li> class identity </li>
* <li> title equality </li>
* <li> location equality </li>
* </ul>
*
* @param obj the reference object with which to compare.
* @return <code>true</code> if this object is the same as the obj
* argument; <code>false</code> otherwise.
* @see #hashCode()
* @see java.util.Hashtable
*/
public boolean equals(Object obj) {
boolean result = true;
// Class equality (instanceof is too lenient as it will vouch for subclasses as well!)
if(obj == null || !obj.getClass().equals(this.getClass())) {
result = false;
} else if(!((ModificationTemplate)obj).iTitle.equals(this.iTitle)) {
// Equality is further defined by title.
result = false;
}
return result;
}
/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hashtables such as those provided by
* <code>java.util.Hashtable</code>.
* <p>
* The general contract of <code>hashCode</code> is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the <tt>hashCode</tt> method
* must consistently return the same integer, provided no information
* used in <tt>equals</tt> comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the <tt>equals(Object)</tt>
* method, then calling the <code>hashCode</code> method on each of
* the two objects must produce the same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link Object#equals(Object)}
* method, then calling the <tt>hashCode</tt> method on each of the
* two objects must produce distinct integer results. However, the
* programmer should be aware that producing distinct integer results
* for unequal objects may improve the performance of hashtables.
* </ul>
* <p>
* As much as is reasonably practical, the hashCode method defined by
* class <tt>Object</tt> does return distinct integers for distinct
* objects. (This is typically implemented by converting the internal
* address of the object into an integer, but this implementation
* technique is not required by the Java programming language.)
*
* @return a hash code value for this object.
* @see Object#equals(Object)
* @see java.util.Hashtable
*/
public int hashCode() {
return iTitle.hashCode();
}
/**
* This method returns a String representation of the Modification.
*
* @return String with a String Representation of the Modification.
*/
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("Modification: '" + this.iTitle + "':");
if(this.iArtifact) {
sb.append("\n\tHidden");
}
sb.append("\n\tCode: " + this.iCode);
Set keySet = iMassDeltas.keySet();
String[] residues = new String[keySet.size()];
keySet.toArray(residues);
Arrays.sort(residues);
for(int i = 0; i < residues.length; i++) {
String lResidue = residues[i];
double[] deltas = (double[])iMassDeltas.get(lResidue);
if(lResidue.equals(com.compomics.util.interfaces.Modification.NTERMINUS)) {
lResidue = "Nterm";
} else if(lResidue.equals(com.compomics.util.interfaces.Modification.CTERMINUS)) {
lResidue = "Cterm";
}
sb.append("\n\tResidue: " + lResidue + " " + deltas[com.compomics.util.interfaces.Modification.MONOISOTOPIC]
+ " Da [MONOISOTOPIC] " + deltas[com.compomics.util.interfaces.Modification.AVERAGE] + " Da [AVERAGE]");
}
return sb.toString();
}
/**
* Override of the clone method. It doesn't do anything except making the method public
* and catching the 'CloneNotSupportedException'. The method now returns a 'null' when cloning was not
* possible.
*
* @return Object with a clone of this class, or 'null' when the CloneNotSupportedException was thrown.
*/
public Object clone () {
Object clone = null;
try {
clone = super.clone();
} catch(CloneNotSupportedException cnse) {
logger.error(cnse.getMessage(), cnse);
}
return clone;
}
/**
* Compares this object with the specified object for order. Returns a
* negative integer, zero, or a positive integer as this object is less
* than, equal to, or greater than the specified object.<p>
*
* In this implementation, ordering is first done on location, and only if this is
* identical, the title is compared. Therefore, since equals uses title and location as well,
* two equals instances will compare to '0'.
*
* @param o the Object to be compared.
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this Object.
*/
public int compareTo(Object o) {
// Cast first.
ModificationTemplate mi = (ModificationTemplate)o;
// Compare the locations.
int result = this.iTitle.compareTo(mi.iTitle);
return result;
}
/**
* This method returns the mass delta for the specified residue, measured either
* monoisotopically or averaged.
*
* @param aResidue String with the residue for which the modification applies (value for the N-terminus is
* the NTERMINUS constant and for the C-terminus the CTERMINUS constant).
* @param aMonoOrAvg int which should be either MONOISOTOPIC or AVERAGE
* @return double with the mass delta (can be negative, of course!) or '0.0' if this modification
* does not apply to the specified residue.
*/
protected double getMassDelta(String aResidue, int aMonoOrAvg) {
// Check whether the int index is 0 or 1 (MONOISTOPIC or AVERAGE).
if(aMonoOrAvg < com.compomics.util.interfaces.Modification.MONOISOTOPIC || aMonoOrAvg > com.compomics.util.interfaces.Modification.AVERAGE) {
throw new IllegalArgumentException("The index in the double[] you specified (" + aMonoOrAvg + ") is outside the range of MONOISOTOPIC-AVERAGE (0-1)!!");
}
double delta = 0.0;
Object temp = iMassDeltas.get(aResidue);
// Only fill out the delta if we find something.
if(temp != null && temp instanceof double[]) {
double[] deltas = (double[])temp;
delta = deltas[aMonoOrAvg];
}
return delta;
}
}