+
+ 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<Integer, Annotation> mapForsq = new HashMap<>();
+ if (sequenceMapping != null)
+ {
+ if (sp2sq != null)
+ {
+ for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
+ {
+ Integer mpos = Integer
+ .valueOf(mapIsTo ? sp2sq.getMappedPosition(ie.getKey())
+ : sp2sq.getPosition(ie.getKey()));
+ 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<Integer, int[]> mapping,
+ int from, int to, int idxoffset)
+ {
+ if (mapping != null)
+ {
+ Map<Integer, Annotation> old = sequenceMapping;
+ Map<Integer, Annotation> 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<String> 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<Annotation[]> annels = new ArrayList<>();
+ Annotation[] els = null;
+
+ int w = 0;
+
+ Iterator<int[]> 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<AlignmentAnnotation> findAnnotations(
+ Iterable<AlignmentAnnotation> list, SequenceI seq, String calcId,
+ String label)
+ {
+
+ ArrayList<AlignmentAnnotation> 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<AlignmentAnnotation> list,
+ String calcId)
+ {
+
+ if (calcId != null && !"".equals(calcId))
+ {
+ for (AlignmentAnnotation a : list)
+ {
+ if (a.getCalcId() == calcId)
+ {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ public static Iterable<AlignmentAnnotation> findAnnotation(
+ List<AlignmentAnnotation> list, String calcId)
+ {
+
+ List<AlignmentAnnotation> 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;
+ }
+