/* * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$) * Copyright (C) $$Year-Rel$$ The Jalview Authors * * This file is part of Jalview. * * Jalview is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * Jalview 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Jalview. If not, see . * The Jalview Authors are detailed in the 'AUTHORS' file. */ package jalview.datamodel; import jalview.analysis.Rna; import jalview.analysis.SecStrConsensus.SimpleBP; import jalview.analysis.WUSSParseException; import jalview.util.MapList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; /** * DOCUMENT ME! * * @author $author$ * @version $Revision$ */ public class AlignmentAnnotation { private static final String ANNOTATION_ID_PREFIX = "ann"; /* * Identifers for different types of profile data */ public static final int SEQUENCE_PROFILE = 0; public static final int STRUCTURE_PROFILE = 1; public static final int CDNA_PROFILE = 2; private static long counter = 0; /** * If true, this annotations is calculated every edit, eg consensus, quality * or conservation graphs */ public boolean autoCalculated = false; /** * unique ID for this annotation, used to match up the same annotation row * shown in multiple views and alignments */ public String annotationId; /** * the sequence this annotation is associated with (or null) */ public SequenceI sequenceRef; /** label shown in dropdown menus and in the annotation label area */ public String label; /** longer description text shown as a tooltip */ public String description; /** Array of annotations placed in the current coordinate system */ public Annotation[] annotations; public List bps = null; /** * RNA secondary structure contact positions */ public SequenceFeature[] _rnasecstr = null; /** * position of annotation resulting in invalid WUSS parsing or -1. -2 means * there was no RNA structure in this annotation */ private long invalidrnastruc = -2; /** * Updates the _rnasecstr field Determines the positions that base pair and * the positions of helices based on secondary structure from a Stockholm file * * @param rnaAnnotation */ private void _updateRnaSecStr(CharSequence rnaAnnotation) { try { _rnasecstr = Rna.getHelixMap(rnaAnnotation); invalidrnastruc = -1; } catch (WUSSParseException px) { // DEBUG System.out.println(px); invalidrnastruc = px.getProblemPos(); } if (invalidrnastruc > -1) { return; } if (_rnasecstr != null && _rnasecstr.length > 0) { // show all the RNA secondary structure annotation symbols. isrna = true; showAllColLabels = true; scaleColLabel = true; _markRnaHelices(); } // System.out.println("featuregroup " + _rnasecstr[0].getFeatureGroup()); } private void _markRnaHelices() { int mxval = 0; // Figure out number of helices // Length of rnasecstr is the number of pairs of positions that base pair // with each other in the secondary structure for (int x = 0; x < _rnasecstr.length; x++) { /* * System.out.println(this.annotation._rnasecstr[x] + " Begin" + * this.annotation._rnasecstr[x].getBegin()); */ // System.out.println(this.annotation._rnasecstr[x].getFeatureGroup()); int val = 0; try { val = Integer.valueOf(_rnasecstr[x].getFeatureGroup()); if (mxval < val) { mxval = val; } } catch (NumberFormatException q) { } ; annotations[_rnasecstr[x].getBegin()].value = val; annotations[_rnasecstr[x].getEnd()].value = val; // annotations[_rnasecstr[x].getBegin()].displayCharacter = "" + val; // annotations[_rnasecstr[x].getEnd()].displayCharacter = "" + val; } setScore(mxval); } /** * Get the RNA Secondary Structure SequenceFeature Array if present */ public SequenceFeature[] getRnaSecondaryStructure() { return this._rnasecstr; } /** * Check the RNA Secondary Structure is equivalent to one in given * AlignmentAnnotation param */ public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that) { return rnaSecondaryStructureEquivalent(that, true); } public boolean rnaSecondaryStructureEquivalent(AlignmentAnnotation that, boolean compareType) { SequenceFeature[] thisSfArray = this.getRnaSecondaryStructure(); SequenceFeature[] thatSfArray = that.getRnaSecondaryStructure(); if (thisSfArray == null || thatSfArray == null) { return thisSfArray == null && thatSfArray == null; } if (thisSfArray.length != thatSfArray.length) { return false; } Arrays.sort(thisSfArray, new SFSortByEnd()); // probably already sorted // like this Arrays.sort(thatSfArray, new SFSortByEnd()); // probably already sorted // like this for (int i=0; i < thisSfArray.length; i++) { SequenceFeature thisSf = thisSfArray[i]; SequenceFeature thatSf = thatSfArray[i]; if (compareType) { if (thisSf.getType() == null || thatSf.getType() == null) { if (thisSf.getType() == null && thatSf.getType() == null) { continue; } else { return false; } } if (! thisSf.getType().equals(thatSf.getType())) { return false; } } if (!(thisSf.getBegin() == thatSf.getBegin() && thisSf.getEnd() == thatSf.getEnd())) { return false; } } return true; } /** * map of positions in the associated annotation */ private Map sequenceMapping; /** * lower range for quantitative data */ public float graphMin; /** * Upper range for quantitative data */ public float graphMax; /** * Score associated with label and description. */ public double score = Double.NaN; /** * flag indicating if annotation has a score. */ public boolean hasScore = false; public GraphLine threshold; // Graphical hints and tips /** Can this row be edited by the user ? */ public boolean editable = false; /** Indicates if annotation has a graphical symbol track */ public boolean hasIcons; // /** Indicates if annotation has a text character label */ public boolean hasText; /** is the row visible */ public boolean visible = true; public int graphGroup = -1; /** Displayed height of row in pixels */ public int height = 0; public int graph = 0; public int graphHeight = 40; public boolean padGaps = false; public static final int NO_GRAPH = 0; public static final int BAR_GRAPH = 1; public static final int LINE_GRAPH = 2; public boolean belowAlignment = true; public SequenceGroup groupRef = null; /** * display every column label, even if there is a row of identical labels */ public boolean showAllColLabels = false; /** * scale the column label to fit within the alignment column. */ public boolean scaleColLabel = false; /** * centre the column labels relative to the alignment column */ public boolean centreColLabels = false; private boolean isrna; public static int getGraphValueFromString(String string) { if (string.equalsIgnoreCase("BAR_GRAPH")) { return BAR_GRAPH; } else if (string.equalsIgnoreCase("LINE_GRAPH")) { return LINE_GRAPH; } else { return NO_GRAPH; } } /** * Creates a new AlignmentAnnotation object. * * @param label * short label shown under sequence labels * @param description * text displayed on mouseover * @param annotations * set of positional annotation elements */ public AlignmentAnnotation(String label, String description, Annotation[] annotations) { setAnnotationId(); // always editable? editable = true; this.label = label; this.description = description; this.annotations = annotations; validateRangeAndDisplay(); } /** * Checks if annotation labels represent secondary structures * */ void areLabelsSecondaryStructure() { boolean nonSSLabel = false; isrna = false; StringBuffer rnastring = new StringBuffer(); char firstChar = 0; for (int i = 0; i < annotations.length; i++) { // DEBUG System.out.println(i + ": " + annotations[i]); if (annotations[i] == null) { continue; } if (annotations[i].secondaryStructure == 'H' || annotations[i].secondaryStructure == 'E') { // DEBUG System.out.println( "/H|E/ '" + // annotations[i].secondaryStructure + "'"); hasIcons |= true; } else // Check for RNA secondary structure { // DEBUG System.out.println( "/else/ '" + // annotations[i].secondaryStructure + "'"); // TODO: 2.8.2 should this ss symbol validation check be a function in // RNA/ResidueProperties ? if (annotations[i].secondaryStructure == '(' || annotations[i].secondaryStructure == '[' || annotations[i].secondaryStructure == '<' || annotations[i].secondaryStructure == '{' || annotations[i].secondaryStructure == 'A' || annotations[i].secondaryStructure == 'B' || annotations[i].secondaryStructure == 'C' || annotations[i].secondaryStructure == 'D' // || annotations[i].secondaryStructure == 'E' // ambiguous on // its own -- already checked above || annotations[i].secondaryStructure == 'F' || annotations[i].secondaryStructure == 'G' // || annotations[i].secondaryStructure == 'H' // ambiguous on // its own -- already checked above || annotations[i].secondaryStructure == 'I' || annotations[i].secondaryStructure == 'J' || annotations[i].secondaryStructure == 'K' || annotations[i].secondaryStructure == 'L' || annotations[i].secondaryStructure == 'M' || annotations[i].secondaryStructure == 'N' || annotations[i].secondaryStructure == 'O' || annotations[i].secondaryStructure == 'P' || annotations[i].secondaryStructure == 'Q' || annotations[i].secondaryStructure == 'R' || annotations[i].secondaryStructure == 'S' || annotations[i].secondaryStructure == 'T' || annotations[i].secondaryStructure == 'U' || annotations[i].secondaryStructure == 'V' || annotations[i].secondaryStructure == 'W' || annotations[i].secondaryStructure == 'X' || annotations[i].secondaryStructure == 'Y' || annotations[i].secondaryStructure == 'Z') { hasIcons |= true; isrna |= true; } } // System.out.println("displaychar " + annotations[i].displayCharacter); if (annotations[i].displayCharacter == null || annotations[i].displayCharacter.length() == 0) { rnastring.append('.'); continue; } if (annotations[i].displayCharacter.length() == 1) { firstChar = annotations[i].displayCharacter.charAt(0); // check to see if it looks like a sequence or is secondary structure // labelling. if (annotations[i].secondaryStructure != ' ' && !hasIcons && // Uncomment to only catch case where // displayCharacter==secondary // Structure // to correctly redisplay SS annotation imported from Stockholm, // exported to JalviewXML and read back in again. // && // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure firstChar != ' ' && firstChar != '$' && firstChar != 0xCE && firstChar != '(' && firstChar != '[' && firstChar != '<' && firstChar != '{' && firstChar != 'A' && firstChar != 'B' && firstChar != 'C' && firstChar != 'D' && firstChar != 'E' && firstChar != 'F' && firstChar != 'G' && firstChar != 'H' && firstChar != 'I' && firstChar != 'J' && firstChar != 'K' && firstChar != 'L' && firstChar != 'M' && firstChar != 'N' && firstChar != 'O' && firstChar != 'P' && firstChar != 'Q' && firstChar != 'R' && firstChar != 'S' && firstChar != 'T' && firstChar != 'U' && firstChar != 'V' && firstChar != 'W' && firstChar != 'X' && firstChar != 'Y' && firstChar != 'Z' && firstChar != '-' && firstChar < jalview.schemes.ResidueProperties.aaIndex.length) { if (jalview.schemes.ResidueProperties.aaIndex[firstChar] < 23) // TODO: // parameterise // to // gap // symbol // number { nonSSLabel = true; } } } else { rnastring.append(annotations[i].displayCharacter.charAt(1)); } if (annotations[i].displayCharacter.length() > 0) { hasText = true; } } if (nonSSLabel) { hasIcons = false; for (int j = 0; j < annotations.length; j++) { if (annotations[j] != null && annotations[j].secondaryStructure != ' ') { annotations[j].displayCharacter = String .valueOf(annotations[j].secondaryStructure); annotations[j].secondaryStructure = ' '; } } } else { if (isrna) { _updateRnaSecStr(new AnnotCharSequence()); } } } /** * flyweight access to positions in the alignment annotation row for RNA * processing * * @author jimp * */ private class AnnotCharSequence implements CharSequence { int offset = 0; int max = 0; public AnnotCharSequence() { this(0, annotations.length); } AnnotCharSequence(int start, int end) { offset = start; max = end; } @Override public CharSequence subSequence(int start, int end) { return new AnnotCharSequence(offset + start, offset + end); } @Override public int length() { return max - offset; } @Override public char charAt(int index) { return ((index + offset < 0) || (index + offset) >= max || annotations[index + offset] == null || (annotations[index + offset].secondaryStructure <= ' ') ? ' ' : annotations[index + offset].displayCharacter == null || annotations[index + offset].displayCharacter .length() == 0 ? annotations[index + offset].secondaryStructure : annotations[index + offset].displayCharacter .charAt(0)); } @Override public String toString() { char[] string = new char[max - offset]; int mx = annotations.length; for (int i = offset; i < mx; i++) { string[i] = (annotations[i] == null || (annotations[i].secondaryStructure <= 32)) ? ' ' : (annotations[i].displayCharacter == null || annotations[i].displayCharacter .length() == 0 ? annotations[i].secondaryStructure : annotations[i].displayCharacter .charAt(0)); } return new String(string); } }; private long _lastrnaannot = -1; public String getRNAStruc() { if (isrna) { String rnastruc = new AnnotCharSequence().toString(); if (_lastrnaannot != rnastruc.hashCode()) { // ensure rna structure contacts are up to date _lastrnaannot = rnastruc.hashCode(); _updateRnaSecStr(rnastruc); } return rnastruc; } return null; } /** * Creates a new AlignmentAnnotation object. * * @param label * DOCUMENT ME! * @param description * DOCUMENT ME! * @param annotations * DOCUMENT ME! * @param min * DOCUMENT ME! * @param max * DOCUMENT ME! * @param winLength * DOCUMENT ME! */ public AlignmentAnnotation(String label, String description, Annotation[] annotations, float min, float max, int graphType) { setAnnotationId(); // graphs are not editable editable = graphType == 0; this.label = label; this.description = description; this.annotations = annotations; graph = graphType; graphMin = min; graphMax = max; validateRangeAndDisplay(); } /** * checks graphMin and graphMax, secondary structure symbols, sets graphType * appropriately, sets null labels to the empty string if appropriate. */ public void validateRangeAndDisplay() { if (annotations == null) { visible = false; // try to prevent renderer from displaying. invalidrnastruc = -1; return; // this is a non-annotation row annotation - ie a sequence score. } int graphType = graph; float min = graphMin; float max = graphMax; boolean drawValues = true; _linecolour = null; if (min == max) { min = 999999999; for (int i = 0; i < annotations.length; i++) { if (annotations[i] == null) { continue; } if (drawValues && annotations[i].displayCharacter != null && annotations[i].displayCharacter.length() > 1) { drawValues = false; } if (annotations[i].value > max) { max = annotations[i].value; } if (annotations[i].value < min) { min = annotations[i].value; } if (_linecolour == null && annotations[i].colour != null) { _linecolour = annotations[i].colour; } } // ensure zero is origin for min/max ranges on only one side of zero if (min > 0) { min = 0; } else { if (max < 0) { max = 0; } } } graphMin = min; graphMax = max; areLabelsSecondaryStructure(); if (!drawValues && graphType != NO_GRAPH) { for (int i = 0; i < annotations.length; i++) { if (annotations[i] != null) { annotations[i].displayCharacter = ""; } } } } /** * Copy constructor creates a new independent annotation row with the same * associated sequenceRef * * @param annotation */ public AlignmentAnnotation(AlignmentAnnotation annotation) { setAnnotationId(); this.label = new String(annotation.label); if (annotation.description != null) { this.description = new String(annotation.description); } this.graphMin = annotation.graphMin; this.graphMax = annotation.graphMax; this.graph = annotation.graph; this.graphHeight = annotation.graphHeight; this.graphGroup = annotation.graphGroup; this.groupRef = annotation.groupRef; this.editable = annotation.editable; this.autoCalculated = annotation.autoCalculated; this.hasIcons = annotation.hasIcons; this.hasText = annotation.hasText; this.height = annotation.height; this.label = annotation.label; this.padGaps = annotation.padGaps; this.visible = annotation.visible; this.centreColLabels = annotation.centreColLabels; this.scaleColLabel = annotation.scaleColLabel; this.showAllColLabels = annotation.showAllColLabels; this.calcId = annotation.calcId; if (annotation.properties != null) { properties = new HashMap<>(); for (Map.Entry val : annotation.properties.entrySet()) { properties.put(val.getKey(), val.getValue()); } } if (this.hasScore = annotation.hasScore) { this.score = annotation.score; } if (annotation.threshold != null) { threshold = new GraphLine(annotation.threshold); } Annotation[] ann = annotation.annotations; if (annotation.annotations != null) { this.annotations = new Annotation[ann.length]; for (int i = 0; i < ann.length; i++) { if (ann[i] != null) { annotations[i] = new Annotation(ann[i]); if (_linecolour != null) { _linecolour = annotations[i].colour; } } } } if (annotation.sequenceRef != null) { this.sequenceRef = annotation.sequenceRef; if (annotation.sequenceMapping != null) { Integer p = null; sequenceMapping = new HashMap<>(); Iterator pos = annotation.sequenceMapping.keySet() .iterator(); while (pos.hasNext()) { // could optimise this! p = pos.next(); Annotation a = annotation.sequenceMapping.get(p); if (a == null) { continue; } if (ann != null) { for (int i = 0; i < ann.length; i++) { if (ann[i] == a) { sequenceMapping.put(p, annotations[i]); } } } } } else { this.sequenceMapping = null; } } // TODO: check if we need to do this: JAL-952 // if (this.isrna=annotation.isrna) { // _rnasecstr=new SequenceFeature[annotation._rnasecstr]; } validateRangeAndDisplay(); // construct hashcodes, etc. } /** * clip the annotation to the columns given by startRes and endRes (inclusive) * and prune any existing sequenceMapping to just those columns. * * @param startRes * @param endRes */ public void restrict(int startRes, int endRes) { if (annotations == null) { // non-positional return; } if (startRes < 0) { startRes = 0; } if (startRes >= annotations.length) { startRes = annotations.length - 1; } if (endRes >= annotations.length) { endRes = annotations.length - 1; } if (annotations == null) { return; } Annotation[] temp = new Annotation[endRes - startRes + 1]; if (startRes < annotations.length) { System.arraycopy(annotations, startRes, temp, 0, endRes - startRes + 1); } if (sequenceRef != null) { // Clip the mapping, if it exists. int spos = sequenceRef.findPosition(startRes); int epos = sequenceRef.findPosition(endRes); if (sequenceMapping != null) { Map newmapping = new HashMap<>(); Iterator e = sequenceMapping.keySet().iterator(); while (e.hasNext()) { Integer pos = e.next(); if (pos.intValue() >= spos && pos.intValue() <= epos) { newmapping.put(pos, sequenceMapping.get(pos)); } } sequenceMapping.clear(); sequenceMapping = newmapping; } } annotations = temp; } /** * set the annotation row to be at least length Annotations * * @param length * minimum number of columns required in the annotation row * @return false if the annotation row is greater than length */ public boolean padAnnotation(int length) { if (annotations == null) { return true; // annotation row is correct - null == not visible and // undefined length } if (annotations.length < length) { Annotation[] na = new Annotation[length]; System.arraycopy(annotations, 0, na, 0, annotations.length); annotations = na; return true; } return annotations.length > length; } /** * DOCUMENT ME! * * @return DOCUMENT ME! */ @Override public String toString() { if (annotations == null) { return ""; } StringBuilder buffer = new StringBuilder(256); for (int i = 0; i < annotations.length; i++) { if (annotations[i] != null) { if (graph != 0) { buffer.append(annotations[i].value); } else if (hasIcons) { buffer.append(annotations[i].secondaryStructure); } else { buffer.append(annotations[i].displayCharacter); } } buffer.append(", "); } // TODO: remove disgusting hack for 'special' treatment of consensus line. if (label.indexOf("Consensus") == 0) { buffer.append("\n"); for (int i = 0; i < annotations.length; i++) { if (annotations[i] != null) { buffer.append(annotations[i].description); } buffer.append(", "); } } return buffer.toString(); } public void setThreshold(GraphLine line) { threshold = line; } public GraphLine getThreshold() { return threshold; } /** * Attach the annotation to seqRef, starting from startRes position. If * alreadyMapped is true then the indices of the annotation[] array are * sequence positions rather than alignment column positions. * * @param seqRef * @param startRes * @param alreadyMapped */ public void createSequenceMapping(SequenceI seqRef, int startRes, boolean alreadyMapped) { if (seqRef == null) { return; } sequenceRef = seqRef; if (annotations == null) { return; } sequenceMapping = new HashMap<>(); int seqPos; for (int i = 0; i < annotations.length; i++) { if (annotations[i] != null) { if (alreadyMapped) { seqPos = seqRef.findPosition(i); } else { seqPos = i + startRes; } sequenceMapping.put(new Integer(seqPos), annotations[i]); } } } /** * When positional annotation and a sequence reference is present, clears and * resizes the annotations array to the current alignment width, and adds * annotation according to aligned positions of the sequenceRef given by * sequenceMapping. */ public void adjustForAlignment() { if (sequenceRef == null) { return; } if (annotations == null) { return; } int a = 0, aSize = sequenceRef.getLength(); if (aSize == 0) { // Its been deleted return; } int position; Annotation[] temp = new Annotation[aSize]; Integer index; if (sequenceMapping != null) { for (a = sequenceRef.getStart(); a <= sequenceRef.getEnd(); a++) { index = new Integer(a); Annotation annot = sequenceMapping.get(index); if (annot != null) { position = sequenceRef.findIndex(a) - 1; temp[position] = annot; } } } annotations = temp; } /** * remove any null entries in annotation row and return the number of non-null * annotation elements. * * @return */ public int compactAnnotationArray() { int i = 0, iSize = annotations.length; while (i < iSize) { if (annotations[i] == null) { if (i + 1 < iSize) { System.arraycopy(annotations, i + 1, annotations, i, iSize - i - 1); } iSize--; } else { i++; } } Annotation[] ann = annotations; annotations = new Annotation[i]; System.arraycopy(ann, 0, annotations, 0, i); ann = null; return iSize; } /** * Associate this annotation with the aligned residues of a particular * sequence. sequenceMapping will be updated in the following way: null * sequenceI - existing mapping will be discarded but annotations left in * mapped positions. valid sequenceI not equal to current sequenceRef: mapping * is discarded and rebuilt assuming 1:1 correspondence TODO: overload with * parameter to specify correspondence between current and new sequenceRef * * @param sequenceI */ public void setSequenceRef(SequenceI sequenceI) { if (sequenceI != null) { if (sequenceRef != null) { boolean rIsDs = sequenceRef.getDatasetSequence() == null, tIsDs = sequenceI.getDatasetSequence() == null; if (sequenceRef != sequenceI && (rIsDs && !tIsDs && sequenceRef != sequenceI.getDatasetSequence()) && (!rIsDs && tIsDs && sequenceRef.getDatasetSequence() != sequenceI) && (!rIsDs && !tIsDs && sequenceRef.getDatasetSequence() != sequenceI .getDatasetSequence()) && !sequenceRef.equals(sequenceI)) { // if sequenceRef isn't intersecting with sequenceI // throw away old mapping and reconstruct. sequenceRef = null; if (sequenceMapping != null) { sequenceMapping = null; // compactAnnotationArray(); } createSequenceMapping(sequenceI, 1, true); adjustForAlignment(); } else { // Mapping carried over sequenceRef = sequenceI; } } else { // No mapping exists createSequenceMapping(sequenceI, 1, true); adjustForAlignment(); } } else { // throw away the mapping without compacting. sequenceMapping = null; sequenceRef = null; } } /** * @return the score */ public double getScore() { return score; } /** * @param score * the score to set */ public void setScore(double score) { hasScore = true; this.score = score; } /** * * @return true if annotation has an associated score */ public boolean hasScore() { return hasScore || !Double.isNaN(score); } /** * Score only annotation * * @param label * @param description * @param score */ public AlignmentAnnotation(String label, String description, double score) { this(label, description, null); setScore(score); } /** * copy constructor with edit based on the hidden columns marked in colSel * * @param alignmentAnnotation * @param colSel */ public AlignmentAnnotation(AlignmentAnnotation alignmentAnnotation, HiddenColumns hidden) { this(alignmentAnnotation); if (annotations == null) { return; } makeVisibleAnnotation(hidden); } public void setPadGaps(boolean padgaps, char gapchar) { this.padGaps = padgaps; if (padgaps) { hasText = true; for (int i = 0; i < annotations.length; i++) { if (annotations[i] == null) { annotations[i] = new Annotation(String.valueOf(gapchar), null, ' ', 0f, null); } else if (annotations[i].displayCharacter == null || annotations[i].displayCharacter.equals(" ")) { annotations[i].displayCharacter = String.valueOf(gapchar); } } } } /** * format description string for display * * @param seqname * @return Get the annotation description string optionally prefixed by * associated sequence name (if any) */ public String getDescription(boolean seqname) { if (seqname && this.sequenceRef != null) { int i = description.toLowerCase().indexOf(""); if (i > -1) { // move the html tag to before the sequence reference. return "" + sequenceRef.getName() + " : " + description.substring(i + 6); } return sequenceRef.getName() + " : " + description; } return description; } public boolean isValidStruc() { return invalidrnastruc == -1; } public long getInvalidStrucPos() { return invalidrnastruc; } /** * machine readable ID string indicating what generated this annotation */ protected String calcId = ""; /** * properties associated with the calcId */ protected Map properties = new HashMap<>(); /** * base colour for line graphs. If null, will be set automatically by * searching the alignment annotation */ public java.awt.Color _linecolour; public String getCalcId() { return calcId; } public void setCalcId(String calcId) { this.calcId = calcId; } public boolean isRNA() { return isrna; } /** * transfer annotation to the given sequence using the given mapping from the * current positions or an existing sequence mapping * * @param sq * @param sp2sq * map involving sq as To or From */ public void liftOver(SequenceI sq, Mapping sp2sq) { if (sp2sq.getMappedWidth() != sp2sq.getWidth()) { // TODO: employ getWord/MappedWord to transfer annotation between cDNA and // Protein reference frames throw new Error( "liftOver currently not implemented for transfer of annotation between different types of seqeunce"); } boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == sq || sp2sq.getTo() == sq.getDatasetSequence()) : false; // TODO build a better annotation element map and get rid of annotations[] Map mapForsq = new HashMap<>(); if (sequenceMapping != null) { if (sp2sq != null) { int[] reg = new int[MapList.LEN]; for (Entry ie : sequenceMapping.entrySet()) { reg[MapList.POS] = ie.getKey(); int mpos = sp2sq.getPosition(reg, mapIsTo); if (mpos >= sq.getStart() && mpos <= sq.getEnd()) { mapForsq.put(mpos, ie.getValue()); } } sequenceMapping = mapForsq; sequenceRef = sq; adjustForAlignment(); } else { // trim positions } } } /** * like liftOver but more general. * * Takes an array of int pairs that will be used to update the internal * sequenceMapping and so shuffle the annotated positions * * @param newref * - new sequence reference for the annotation row - if null, * sequenceRef is left unchanged * @param mapping * array of ints containing corresponding positions * @param from * - column for current coordinate system (-1 for index+1) * @param to * - column for destination coordinate system (-1 for index+1) * @param idxoffset * - offset added to index when referencing either coordinate system * @note no checks are made as to whether from and/or to are sensible * @note caller should add the remapped annotation to newref if they have not * already */ public void remap(SequenceI newref, HashMap mapping, int from, int to, int idxoffset) { if (mapping != null) { Map old = sequenceMapping; Map remap = new HashMap<>(); int index = -1; for (int mp[] : mapping.values()) { if (index++ < 0) { continue; } Annotation ann = null; if (from == -1) { ann = sequenceMapping.get(Integer.valueOf(idxoffset + index)); } else { if (mp != null && mp.length > from) { ann = sequenceMapping.get(Integer.valueOf(mp[from])); } } if (ann != null) { if (to == -1) { remap.put(Integer.valueOf(idxoffset + index), ann); } else { if (to > -1 && to < mp.length) { remap.put(Integer.valueOf(mp[to]), ann); } } } } sequenceMapping = remap; old.clear(); if (newref != null) { sequenceRef = newref; } adjustForAlignment(); } } public String getProperty(String property) { if (properties == null) { return null; } return properties.get(property); } public void setProperty(String property, String value) { if (properties == null) { properties = new HashMap<>(); } properties.put(property, value); } public boolean hasProperties() { return properties != null && properties.size() > 0; } public Collection getProperties() { if (properties == null) { return Collections.emptyList(); } return properties.keySet(); } /** * Returns the Annotation for the given sequence position (base 1) if any, * else null * * @param position * @return */ public Annotation getAnnotationForPosition(int position) { return sequenceMapping == null ? null : sequenceMapping.get(position); } /** * Set the id to "ann" followed by a counter that increments so as to be * unique for the lifetime of the JVM */ protected final void setAnnotationId() { this.annotationId = ANNOTATION_ID_PREFIX + Long.toString(nextId()); } /** * Returns the match for the last unmatched opening RNA helix pair symbol * preceding the given column, or '(' if nothing found to match. * * @param column * @return */ public String getDefaultRnaHelixSymbol(int column) { String result = "("; if (annotations == null) { return result; } /* * for each preceding column, if it contains an open bracket, * count whether it is still unmatched at column, if so return its pair * (likely faster than the fancy alternative using stacks) */ for (int col = column - 1; col >= 0; col--) { Annotation annotation = annotations[col]; if (annotation == null) { continue; } String displayed = annotation.displayCharacter; if (displayed == null || displayed.length() != 1) { continue; } char symbol = displayed.charAt(0); if (!Rna.isOpeningParenthesis(symbol)) { continue; } /* * found an opening bracket symbol * count (closing-opening) symbols of this type that follow it, * up to and excluding the target column; if the count is less * than 1, the opening bracket is unmatched, so return its match */ String closer = String .valueOf(Rna.getMatchingClosingParenthesis(symbol)); String opener = String.valueOf(symbol); int count = 0; for (int j = col + 1; j < column; j++) { if (annotations[j] != null) { String s = annotations[j].displayCharacter; if (closer.equals(s)) { count++; } else if (opener.equals(s)) { count--; } } } if (count < 1) { return closer; } } return result; } protected static synchronized long nextId() { return counter++; } /** * * @return true for rows that have a range of values in their annotation set */ public boolean isQuantitative() { return graphMin < graphMax; } /** * delete any columns in alignmentAnnotation that are hidden (including * sequence associated annotation). * * @param hiddenColumns * the set of hidden columns */ public void makeVisibleAnnotation(HiddenColumns hiddenColumns) { if (annotations != null) { makeVisibleAnnotation(0, annotations.length, hiddenColumns); } } /** * delete any columns in alignmentAnnotation that are hidden (including * sequence associated annotation). * * @param start * remove any annotation to the right of this column * @param end * remove any annotation to the left of this column * @param hiddenColumns * the set of hidden columns */ public void makeVisibleAnnotation(int start, int end, HiddenColumns hiddenColumns) { if (annotations != null) { if (hiddenColumns.hasHiddenColumns()) { removeHiddenAnnotation(start, end, hiddenColumns); } else { restrict(start, end); } } } /** * The actual implementation of deleting hidden annotation columns * * @param start * remove any annotation to the right of this column * @param end * remove any annotation to the left of this column * @param hiddenColumns * the set of hidden columns */ private void removeHiddenAnnotation(int start, int end, HiddenColumns hiddenColumns) { // mangle the alignmentAnnotation annotation array ArrayList annels = new ArrayList<>(); Annotation[] els = null; int w = 0; Iterator blocks = hiddenColumns.getVisContigsIterator(start, end + 1, false); int copylength; int annotationLength; while (blocks.hasNext()) { int[] block = blocks.next(); annotationLength = block[1] - block[0] + 1; if (blocks.hasNext()) { // copy just the visible segment of the annotation row copylength = annotationLength; } else { if (annotationLength + block[0] <= annotations.length) { // copy just the visible segment of the annotation row copylength = annotationLength; } else { // copy to the end of the annotation row copylength = annotations.length - block[0]; } } els = new Annotation[annotationLength]; annels.add(els); System.arraycopy(annotations, block[0], els, 0, copylength); w += annotationLength; } if (w != 0) { annotations = new Annotation[w]; w = 0; for (Annotation[] chnk : annels) { System.arraycopy(chnk, 0, annotations, w, chnk.length); w += chnk.length; } } } public static Iterable findAnnotations( Iterable list, SequenceI seq, String calcId, String label) { ArrayList aa = new ArrayList<>(); for (AlignmentAnnotation ann : list) { if ((calcId == null || (ann.getCalcId() != null && ann.getCalcId().equals(calcId))) && (seq == null || (ann.sequenceRef != null && ann.sequenceRef == seq)) && (label == null || (ann.label != null && ann.label.equals(label)))) { aa.add(ann); } } return aa; } /** * Answer true if any annotation matches the calcId passed in (if not null). * * @param list * annotation to search * @param calcId * @return */ public static boolean hasAnnotation(List list, String calcId) { if (calcId != null && !"".equals(calcId)) { for (AlignmentAnnotation a : list) { if (a.getCalcId() == calcId) { return true; } } } return false; } public static Iterable findAnnotation( List list, String calcId) { List aa = new ArrayList<>(); if (calcId == null) { return aa; } for (AlignmentAnnotation a : list) { if (a.getCalcId() == calcId || (a.getCalcId() != null && calcId != null && a.getCalcId().equals(calcId))) { aa.add(a); } } return aa; } }