package com.compomics.util.experiment.identification.amino_acid_tags; import com.compomics.util.experiment.biology.AminoAcidPattern; import com.compomics.util.experiment.biology.AminoAcidSequence; import com.compomics.util.experiment.biology.Atom; import com.compomics.util.experiment.biology.PTM; import com.compomics.util.experiment.biology.PTMFactory; import com.compomics.util.experiment.identification.matches.ModificationMatch; import com.compomics.util.experiment.biology.MassGap; import com.compomics.util.experiment.personalization.ExperimentObject; import com.compomics.util.experiment.identification.identification_parameters.PtmSettings; import com.compomics.util.preferences.SequenceMatchingPreferences; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import no.uib.jsparklines.renderers.util.Util; /** * This class represents a sequence mass tag. * * @author Marc Vaudel */ public class Tag extends ExperimentObject { /** * Serial number for backward compatibility. */ static final long serialVersionUID = 1625541843008045218L; /** * The content of the tag. */ private ArrayList<TagComponent> content = new ArrayList<TagComponent>(); /** * Constructor for an empty tag. */ public Tag() { } /** * Creates a new tag instance based on the given one. * * @param tag the reference tag */ public Tag(Tag tag) { for (TagComponent tagComponent : tag.getContent()) { if (tagComponent instanceof MassGap) { MassGap massGap = (MassGap) tagComponent; addMassGap(massGap.getMass()); } else if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = new AminoAcidPattern((AminoAcidPattern) tagComponent); addAminoAcidPattern(aminoAcidPattern); } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = new AminoAcidSequence((AminoAcidSequence) tagComponent); addAminoAcidSequence(aminoAcidSequence); } else { throw new UnsupportedOperationException("Tag constructor not implemeted for tag component " + tagComponent.getClass() + "."); } } } /** * Constructor for a tag consisting of a pattern tag between two mass tags. * * @param nTermGap the N-term mass gap * @param sequenceTag the sequence tag with modifications * @param cTermGap the C-term mass gap */ public Tag(double nTermGap, AminoAcidPattern sequenceTag, double cTermGap) { addMassGap(nTermGap); addAminoAcidPattern(sequenceTag); addMassGap(cTermGap); } /** * Constructor for a tag consisting of a sequence tag between two mass tags. * * @param nTermGap the N-term mass gap * @param sequenceTag the sequence tag with modifications * @param cTermGap the C-term mass gap */ public Tag(double nTermGap, AminoAcidSequence sequenceTag, double cTermGap) { addMassGap(nTermGap); addAminoAcidSequence(sequenceTag); addMassGap(cTermGap); } /** * Returns the content of this tag as a list. * * @return the content of this tag as a list */ public ArrayList<TagComponent> getContent() { return content; } /** * Adds a mass gap to the tag. * * @param massGap the value of the mass gap */ public void addMassGap(double massGap) { if (massGap != 0) { content.add(new MassGap(massGap)); } } /** * Adds a sequence of amino acids to the tag. * * @param aminoAcidPattern the amino acid sequence with modifications */ public void addAminoAcidPattern(AminoAcidPattern aminoAcidPattern) { if (aminoAcidPattern.length() > 0) { if (!content.isEmpty()) { TagComponent lastComponent = content.get(content.size() - 1); if (lastComponent instanceof AminoAcidPattern) { AminoAcidPattern pattern = (AminoAcidPattern) lastComponent; pattern.append(aminoAcidPattern); return; } } content.add(aminoAcidPattern); } } /** * Adds a sequence of amino acids to the tag. * * @param aminoAcidSequence the amino acid sequence with modifications */ public void addAminoAcidSequence(AminoAcidSequence aminoAcidSequence) { if (aminoAcidSequence.length() > 0) { if (!content.isEmpty()) { TagComponent lastComponent = content.get(content.size() - 1); if (lastComponent instanceof AminoAcidSequence) { AminoAcidSequence sequence = (AminoAcidSequence) lastComponent; sequence.appendCTerm(aminoAcidSequence); return; } } content.add(aminoAcidSequence); } } /** * Returns the tag as intelligible sequence of tag components. For example * amino acid tags and mass gaps: <115.2>TAG<110.5>. * * @return The tag as intelligible sequence for display. */ public String asSequence() { StringBuilder result = new StringBuilder(content.size() * 4); for (TagComponent tagComponent : content) { result.append(tagComponent.asSequence()); } return result.toString(); } /** * Returns the longest amino acid sequence contained in this tag. * * @return the longest amino acid sequence contained in this tag */ public String getLongestAminoAcidSequence() { String result = ""; AminoAcidPattern lastAminoAcidPattern = null; AminoAcidSequence lastAminoAcidSequence = null; for (TagComponent tagComponent : content) { if (tagComponent instanceof MassGap) { if (lastAminoAcidPattern != null && lastAminoAcidPattern.length() > result.length()) { result = lastAminoAcidPattern.asSequence(); } lastAminoAcidPattern = null; if (lastAminoAcidSequence != null && lastAminoAcidSequence.length() > result.length()) { result = lastAminoAcidSequence.asSequence(); } lastAminoAcidSequence = null; } else if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern currentPattern = (AminoAcidPattern) tagComponent; if (lastAminoAcidPattern == null) { if (lastAminoAcidSequence == null) { lastAminoAcidPattern = currentPattern; } else { lastAminoAcidPattern = new AminoAcidPattern(lastAminoAcidSequence); lastAminoAcidPattern.append(currentPattern); lastAminoAcidSequence = null; } } else { lastAminoAcidPattern.append(currentPattern); } } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence currentSequence = (AminoAcidSequence) tagComponent; if (lastAminoAcidSequence == null) { if (lastAminoAcidPattern == null) { lastAminoAcidSequence = currentSequence; } else { lastAminoAcidPattern.append(new AminoAcidPattern(currentSequence)); } } else { lastAminoAcidSequence.appendCTerm(currentSequence); } } else { throw new UnsupportedOperationException("Longest amino acid sequence not implemented for tag component " + tagComponent.getClass() + "."); } } if (lastAminoAcidPattern != null && lastAminoAcidPattern.length() > result.length()) { result = lastAminoAcidPattern.asSequence(); } if (lastAminoAcidSequence != null && lastAminoAcidSequence.length() > result.length()) { result = lastAminoAcidSequence.asSequence(); } return result; } /** * Returns the mass of the tag. * * @return the mass of the tag */ public double getMass() { double mass = Atom.H.getMonoisotopicMass(); for (TagComponent tagComponent : content) { mass += tagComponent.getMass(); } mass += Atom.H.getMonoisotopicMass() + Atom.O.getMonoisotopicMass(); return mass; } /** * Returns the theoretic mass of the tag, eventually without terminal gaps. * * @param includeCTermGap if true the C-terminal gap will be added if * present * @param includeNTermGap if true the N-terminal gap will be added if * present * * @return the theoretic mass of the tag */ public double getMass(boolean includeCTermGap, boolean includeNTermGap) { double mass = getMass(); if (!includeCTermGap) { mass -= getCTerminalGap(); } if (!includeNTermGap) { mass -= getNTerminalGap(); } return mass; } /** * Returns the N-terminal gap of the tag. * * @return the N-terminal gap of the tag */ public double getNTerminalGap() { if (content.isEmpty()) { return 0; } TagComponent tagComponent = content.get(0); if (tagComponent instanceof MassGap) { return tagComponent.getMass(); } else { return 0; } } /** * Returns the C-terminal gap of the tag. * * @return the C-terminal gap of the tag */ public double getCTerminalGap() { if (content.isEmpty()) { return 0; } TagComponent tagComponent = content.get(content.size() - 1); if (tagComponent instanceof MassGap) { return tagComponent.getMass(); } else { return 0; } } /** * Returns the modified sequence as an tagged string with potential * modification sites color coded or with PTM tags, e.g, <mox>. /!\ * this method will work only if the PTM found in the peptide are in the * PTMFactory. /!\ This method uses the modifications as set in the * modification matches of this peptide and displays all of them. * * @param modificationProfile the modification profile of the search * @param useHtmlColorCoding if true, color coded HTML is used, otherwise * PTM tags, e.g, <mox>, are used * @param includeHtmlStartEndTags if true, start and end HTML tags are added * @param useShortName if true. the short names are used in the tags * @param includeTerminalGaps if true. the terminal gaps will be displayed on * the sequence * @param excludeAllFixedModifications if true. fixed modifications will not * be displayed on the sequence * * @return the modified sequence as a tagged string */ public String getTaggedModifiedSequence(PtmSettings modificationProfile, boolean useHtmlColorCoding, boolean includeHtmlStartEndTags, boolean useShortName, boolean excludeAllFixedModifications, boolean includeTerminalGaps) { return getTaggedModifiedSequence(modificationProfile, this, useHtmlColorCoding, includeHtmlStartEndTags, useShortName, excludeAllFixedModifications, includeTerminalGaps); } /** * Returns the modified sequence as an tagged string with potential * modification sites color coded or with PTM tags, e.g, <mox>. /!\ * this method will work only if the PTM found in the peptide are in the * PTMFactory. /!\ This method uses the modifications as set in the * modification matches of this peptide and displays all of them. * * @param modificationProfile the modification profile of the search * @param useHtmlColorCoding if true, color coded HTML is used, otherwise * PTM tags, e.g, <mox>, are used * @param includeHtmlStartEndTags if true, start and end HTML tags are added * @param useShortName if true, the short names are used in the tags * @param includeTerminalGaps if true, the terminal gaps will be displayed on * the sequence * * @return the modified sequence as a tagged string */ public String getTaggedModifiedSequence(PtmSettings modificationProfile, boolean useHtmlColorCoding, boolean includeHtmlStartEndTags, boolean useShortName, boolean includeTerminalGaps) { return getTaggedModifiedSequence(modificationProfile, this, useHtmlColorCoding, includeHtmlStartEndTags, useShortName, false, includeTerminalGaps); } /** * Returns the modified sequence as an tagged string with potential * modification sites color coded or with PTM tags, e.g, <mox>. /!\ * This method will work only if the PTM found in the peptide are in the * PTMFactory. /!\ This method uses the modifications as set in the * modification matches of this peptide and displays all of them. * * @param modificationProfile the modification profile of the search * @param tag the tag * @param includeHtmlStartEndTags if true, start and end HTML tags are added * @param useHtmlColorCoding if true, color coded HTML is used, otherwise * PTM tags, e.g, <mox>, are used * @param useShortName if true, the short names are used in the tags * @return the tagged modified sequence as a string * @param excludeAllFixedPtms if true, the fixed PTMs will not be displayed * @param includeTerminalGaps if true, the terminal gaps will be displayed on * the sequence */ public static String getTaggedModifiedSequence(PtmSettings modificationProfile, Tag tag, boolean useHtmlColorCoding, boolean includeHtmlStartEndTags, boolean useShortName, boolean excludeAllFixedPtms, boolean includeTerminalGaps) { String modifiedSequence = ""; if (useHtmlColorCoding && includeHtmlStartEndTags) { modifiedSequence += "<html>"; } modifiedSequence += tag.getNTerminal(includeTerminalGaps); for (int i = 0; i < tag.getContent().size(); i++) { TagComponent tagComponent = tag.getContent().get(i); if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) tagComponent; modifiedSequence += aminoAcidPattern.getTaggedModifiedSequence(modificationProfile, useHtmlColorCoding, useShortName, excludeAllFixedPtms); } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) tagComponent; modifiedSequence += aminoAcidSequence.getTaggedModifiedSequence(modificationProfile, useHtmlColorCoding, useShortName, excludeAllFixedPtms); } else if (tagComponent instanceof MassGap) { if (includeTerminalGaps || i > 0 && i < tag.getContent().size() - 1) { modifiedSequence += tagComponent.asSequence(); } } else { throw new UnsupportedOperationException("Tagged sequence not implemented for tag component " + tagComponent.getClass() + "."); } } modifiedSequence += tag.getCTerminal(includeTerminalGaps); if (useHtmlColorCoding && includeHtmlStartEndTags) { modifiedSequence += "</html>"; } return modifiedSequence; } /** * Returns the N-terminal tag of this tag as a string for sequence display. * * @param includeTerminalGaps indicates whether mass gaps shall be included * * @return the N-terminal tag of this tag */ public String getNTerminal(boolean includeTerminalGaps) { if (content.isEmpty()) { return ""; } TagComponent firstComponent = content.get(0); if (firstComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) firstComponent; String nTerm = "NH2"; PTMFactory ptmFactory = PTMFactory.getInstance(); for (ModificationMatch modificationMatch : aminoAcidPattern.getModificationsAt(1)) { PTM ptm = ptmFactory.getPTM(modificationMatch.getTheoreticPtm()); if (ptm.getType() != PTM.MODAA && ptm.getType() != PTM.MODMAX) { nTerm = ptm.getShortName(); } } nTerm = nTerm.replaceAll("-", " "); return nTerm + "-"; } else if (firstComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) firstComponent; String nTerm = "NH2"; PTMFactory ptmFactory = PTMFactory.getInstance(); for (ModificationMatch modificationMatch : aminoAcidSequence.getModificationsAt(1)) { PTM ptm = ptmFactory.getPTM(modificationMatch.getTheoreticPtm()); if (ptm.getType() != PTM.MODAA && ptm.getType() != PTM.MODMAX) { nTerm = ptm.getShortName(); } } nTerm = nTerm.replaceAll("-", " "); return nTerm + "-"; } else if (firstComponent instanceof MassGap) { if (includeTerminalGaps) { return firstComponent.asSequence(); } else { return Util.roundDouble(firstComponent.getMass(), 2) + "-"; } } else { throw new UnsupportedOperationException("N-terminal tag not implemented for tag component " + firstComponent.getClass() + "."); } } /** * Returns the C-terminal tag of this tag as a string for sequence display. * * @param includeTerminalGaps indicates whether mass gaps shall be included * * @return the C-terminal tag of this tag */ public String getCTerminal(boolean includeTerminalGaps) { if (content.isEmpty()) { return ""; } TagComponent lastComponent = content.get(content.size() - 1); if (lastComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) lastComponent; String cTerm = "COOH"; PTMFactory ptmFactory = PTMFactory.getInstance(); for (ModificationMatch modificationMatch : aminoAcidPattern.getModificationsAt(aminoAcidPattern.length())) { PTM ptm = ptmFactory.getPTM(modificationMatch.getTheoreticPtm()); if (ptm.getType() != PTM.MODAA && ptm.getType() != PTM.MODMAX) { cTerm = ptm.getShortName(); } } cTerm = cTerm.replaceAll("-", " "); return "-" + cTerm; } else if (lastComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) lastComponent; String cTerm = "COOH"; PTMFactory ptmFactory = PTMFactory.getInstance(); for (ModificationMatch modificationMatch : aminoAcidSequence.getModificationsAt(aminoAcidSequence.length())) { PTM ptm = ptmFactory.getPTM(modificationMatch.getTheoreticPtm()); if (ptm.getType() != PTM.MODAA && ptm.getType() != PTM.MODMAX) { cTerm = ptm.getShortName(); } } cTerm = cTerm.replaceAll("-", " "); return "-" + cTerm; } else if (lastComponent instanceof MassGap) { if (includeTerminalGaps) { return lastComponent.asSequence(); } else { return "-" + Util.roundDouble(lastComponent.getMass(), 2); } } else { throw new UnsupportedOperationException("C-terminal tag not implemented for tag component " + lastComponent.getClass() + "."); } } /** * Returns the amino acid length of the tag when mass gaps are considered * like one amino acid * * @return the amino acid length of the tag */ public int getLengthInAminoAcid() { int length = 0; for (TagComponent tagComponent : content) { if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) tagComponent; length += aminoAcidPattern.length(); } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) tagComponent; length += aminoAcidSequence.length(); } else if (tagComponent instanceof MassGap) { length++; } else { throw new UnsupportedOperationException("Tag length in amino acid not implemented for tag component " + tagComponent.getClass() + "."); } } return length; } /** * Returns the potential modification sites as an ordered list of string. 1 * is the first amino acid. An empty list is returned if no possibility was * found. This method does not account for protein terminal modifications. * * @param ptm the PTM considered * @param ptmSequenceMatchingPreferences the sequence matching preferences * for the PTM to amino acid sequence mapping * * @return a list of potential modification sites * * @throws IOException exception thrown whenever an error occurred while * reading a protein sequence * @throws IllegalArgumentException exception thrown whenever an error * occurred while reading a protein sequence * @throws InterruptedException exception thrown whenever an error occurred * while reading a protein sequence * @throws FileNotFoundException if a FileNotFoundException occurs * @throws ClassNotFoundException if a ClassNotFoundException occurs */ public ArrayList<Integer> getPotentialModificationSites(PTM ptm, SequenceMatchingPreferences ptmSequenceMatchingPreferences) throws IOException, IllegalArgumentException, InterruptedException, FileNotFoundException, ClassNotFoundException { ArrayList<Integer> possibleSites = new ArrayList<Integer>(); AminoAcidPattern ptmPattern = ptm.getPattern(); int patternLength = ptmPattern.length(); // @TODO: what if pattern is null..? switch (ptm.getType()) { case PTM.MODAA: int offset = 0; for (TagComponent tagComponent : content) { if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) tagComponent; for (int i : ptmPattern.getIndexes(aminoAcidPattern, ptmSequenceMatchingPreferences)) { possibleSites.add(i + offset); } offset += aminoAcidPattern.length(); } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) tagComponent; for (int i : ptmPattern.getIndexes(aminoAcidSequence.getSequence(), ptmSequenceMatchingPreferences)) { possibleSites.add(i + offset); } offset += aminoAcidSequence.length(); } else { offset++; } } return possibleSites; case PTM.MODC: case PTM.MODCP: possibleSites.add(patternLength); return possibleSites; case PTM.MODN: case PTM.MODNP: possibleSites.add(1); return possibleSites; case PTM.MODCAA: case PTM.MODCPAA: if (content.isEmpty()) { return new ArrayList<Integer>(); } TagComponent component = content.get(content.size() - 1); if (component instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) component; if (ptmPattern.isEnding(aminoAcidPattern, ptmSequenceMatchingPreferences)) { possibleSites.add(patternLength); } } else if (component instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) component; if (ptmPattern.isEnding(aminoAcidSequence.getSequence(), ptmSequenceMatchingPreferences)) { possibleSites.add(patternLength); } } else if (component instanceof MassGap) { possibleSites.add(patternLength); } else { throw new UnsupportedOperationException("Possible modifications not implemnted for tag component " + component.getClass() + "."); } return possibleSites; case PTM.MODNAA: case PTM.MODNPAA: if (content.isEmpty()) { return new ArrayList<Integer>(); } component = content.get(0); if (component instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) component; if (ptmPattern.isStarting(aminoAcidPattern, ptmSequenceMatchingPreferences)) { possibleSites.add(patternLength); } } else if (component instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) component; if (ptmPattern.isStarting(aminoAcidSequence.getSequence(), ptmSequenceMatchingPreferences)) { possibleSites.add(patternLength); } } else if (component instanceof MassGap) { possibleSites.add(patternLength); } else { throw new UnsupportedOperationException("Possible modifications not implemnted for tag component " + component.getClass() + "."); } return possibleSites; default: throw new IllegalArgumentException("PTM type " + ptm.getType() + " not recognized."); } } /** * Indicates whether this tag is the same as another tag. Note: this method * accounts for modification localization. * * @param anotherTag another tag * @param sequenceMatchingPreferences the sequence matching preferences * * @return a boolean indicating whether the tag is the same as another */ public boolean isSameAs(Tag anotherTag, SequenceMatchingPreferences sequenceMatchingPreferences) { if (content.size() != anotherTag.getContent().size()) { return false; } for (int i = 0; i < content.size(); i++) { TagComponent component1 = content.get(i); TagComponent component2 = anotherTag.getContent().get(i); if (!component1.isSameAs(component2, sequenceMatchingPreferences)) { return false; } } return true; } /** * Indicates whether this tag is the same as another tag without accounting * for modification localization. * * @param anotherTag another tag * @param sequenceMatchingPreferences the sequence matching preferences * * @return a boolean indicating whether the tag is the same as another */ public boolean isSameSequenceAndModificationStatusAs(Tag anotherTag, SequenceMatchingPreferences sequenceMatchingPreferences) { if (content.size() != anotherTag.getContent().size()) { return false; } for (int i = 0; i < content.size(); i++) { TagComponent component1 = content.get(i); TagComponent component2 = anotherTag.getContent().get(i); if (!component1.isSameSequenceAndModificationStatusAs(component2, sequenceMatchingPreferences)) { return false; } } return true; } /** * Returns the tag modifications as a string. * * @param tag the tag * @return the peptide modifications as a string */ public static String getTagModificationsAsString(Tag tag) { HashMap<String, ArrayList<Integer>> modMap = new HashMap<String, ArrayList<Integer>>(); int offset = 0; for (TagComponent tagComponent : tag.getContent()) { if (tagComponent instanceof MassGap) { offset++; } else if (tagComponent instanceof AminoAcidPattern) { AminoAcidPattern aminoAcidPattern = (AminoAcidPattern) tagComponent; for (int i = 1; i <= aminoAcidPattern.length(); i++) { for (ModificationMatch modificationMatch : aminoAcidPattern.getModificationsAt(i)) { if (modificationMatch.isVariable()) { if (!modMap.containsKey(modificationMatch.getTheoreticPtm())) { modMap.put(modificationMatch.getTheoreticPtm(), new ArrayList<Integer>()); } modMap.get(modificationMatch.getTheoreticPtm()).add(i + offset); } } } offset += aminoAcidPattern.length(); } else if (tagComponent instanceof AminoAcidSequence) { AminoAcidSequence aminoAcidSequence = (AminoAcidSequence) tagComponent; for (int i = 1; i <= aminoAcidSequence.length(); i++) { for (ModificationMatch modificationMatch : aminoAcidSequence.getModificationsAt(i)) { if (modificationMatch.isVariable()) { if (!modMap.containsKey(modificationMatch.getTheoreticPtm())) { modMap.put(modificationMatch.getTheoreticPtm(), new ArrayList<Integer>()); } modMap.get(modificationMatch.getTheoreticPtm()).add(i + offset); } } } offset += aminoAcidSequence.length(); } else { throw new IllegalArgumentException("Modification summary not implemented for TagComponent " + tagComponent.getClass() + "."); } } StringBuilder result = new StringBuilder(); boolean first = true, first2; ArrayList<String> mods = new ArrayList<String>(modMap.keySet()); Collections.sort(mods); for (String mod : mods) { if (first) { first = false; } else { result.append(", "); } first2 = true; result.append(mod); result.append(" ("); for (int aa : modMap.get(mod)) { if (first2) { first2 = false; } else { result.append(", "); } result.append(aa); } result.append(")"); } return result.toString(); } /** * Returns a new tag instance which is a reversed version of the current * tag. * * @param yIon indicates whether the tag is based on y ions * * @return a new tag instance which is a reversed version of the current tag */ public Tag reverse(boolean yIon) { double water = 2 * Atom.H.getMonoisotopicMass() + Atom.O.getMonoisotopicMass(); Tag newTag = new Tag(); for (int i = content.size() - 1; i >= 0; i--) { TagComponent tagComponent = content.get(i); if (tagComponent instanceof MassGap) { double mass = tagComponent.getMass(); if (i == content.size() - 1) { if (yIon) { mass += water; } else { mass -= water; } } else if (i == 0) { if (yIon) { mass -= water; } else { mass += water; } } newTag.addMassGap(mass); } else if (tagComponent instanceof AminoAcidPattern) { newTag.addAminoAcidPattern(((AminoAcidPattern) tagComponent).reverse()); } else if (tagComponent instanceof AminoAcidSequence) { newTag.addAminoAcidSequence(((AminoAcidSequence) tagComponent).reverse()); } else { throw new UnsupportedOperationException("Reverse method not implemented for tag component " + tagComponent.getClass() + "."); } } return newTag; } /** * Indicates whether the tag can be reversed (ie if termini are mass gaps * with mass ≥ water). * * @return whether the tag can be reversed */ public boolean canReverse() { double water = 2 * Atom.H.getMonoisotopicMass() + Atom.O.getMonoisotopicMass(); TagComponent terminalComponent = content.get(0); if (terminalComponent instanceof MassGap) { MassGap terminalGap = (MassGap) terminalComponent; if (terminalGap.getMass() >= water) { terminalComponent = content.get(content.size() - 1); if (terminalComponent instanceof MassGap) { terminalGap = (MassGap) terminalComponent; if (terminalGap.getMass() >= water) { return true; } } } } return false; } @Override public String toString() { return asSequence(); } }