Clover icon

Coverage Report

  1. Project Clover database Tue Mar 10 2026 14:58:44 GMT
  2. Package jalview.datamodel

File Sequence.java

 

Coverage histogram

../../img/srcFileCovDistChart0.png
0% of files have more coverage

Code metrics

414
684
99
2
2,306
1,611
360
0.53
6.91
49.5
3.64

Classes

Class Line # Actions
Sequence 51 683 359
0.00%
Sequence.DBModList 63 1 1
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.datamodel;
22    import jalview.workers.InformationThread;
23   
24    import java.util.ArrayList;
25    import java.util.Arrays;
26    import java.util.BitSet;
27    import java.util.Collection;
28    import java.util.Collections;
29    import java.util.Enumeration;
30    import java.util.Iterator;
31    import java.util.List;
32    import java.util.ListIterator;
33    import java.util.Vector;
34   
35    import fr.orsay.lri.varna.models.rna.RNA;
36    import jalview.analysis.AlignSeq;
37    import jalview.analysis.AlignmentUtils;
38    import jalview.analysis.SeqsetUtils;
39    import jalview.datamodel.features.SequenceFeatures;
40    import jalview.datamodel.features.SequenceFeaturesI;
41    import jalview.util.Comparison;
42    import jalview.util.DBRefUtils;
43    import jalview.util.MapList;
44    import jalview.util.StringUtils;
45    import jalview.ws.datamodel.alphafold.MappableContactMatrix;
46   
47    /**
48    *
49    * Implements the SequenceI interface for a char[] based sequence object
50    */
 
51    public class Sequence extends ASequence implements SequenceI
52    {
53   
54    /**
55    * A subclass that gives us access to modCount, which tracks whether there
56    * have been any changes. We use this to update
57    *
58    * @author hansonr
59    *
60    * @param <T>
61    */
62    @SuppressWarnings("serial")
 
63    public class DBModList<T> extends ArrayList<DBRefEntry>
64    {
65   
 
66  0 toggle protected int getModCount()
67    {
68  0 return modCount;
69    }
70   
71    }
72   
73    SequenceI datasetSequence;
74   
75    private String name;
76   
77    private char[] sequence;
78   
79    private String description;
80   
81    private int start;
82   
83    private int end;
84   
85    private Vector<PDBEntry> pdbIds;
86   
87    private String vamsasId;
88   
89    HiddenMarkovModel hmm;
90   
91    boolean isHMMConsensusSequence = false;
92    private DBModList<DBRefEntry> dbrefs; // controlled access
93   
94    /**
95    * a flag to let us know that elements have changed in dbrefs
96    *
97    * @author Bob Hanson
98    */
99    private int refModCount = 0;
100   
101    private RNA rna;
102   
103    /**
104    * This annotation is displayed below the alignment but the positions are tied
105    * to the residues of this sequence
106    *
107    * TODO: change to List<>
108    */
109    private Vector<AlignmentAnnotation> annotation;
110   
111    private SequenceFeaturesI sequenceFeatureStore;
112   
113    /*
114    * A cursor holding the approximate current view position to the sequence,
115    * as determined by findIndex or findPosition or findPositions.
116    * Using a cursor as a hint allows these methods to be more performant for
117    * large sequences.
118    */
119    private SequenceCursor cursor;
120   
121    /*
122    * A number that should be incremented whenever the sequence is edited.
123    * If the value matches the cursor token, then we can trust the cursor,
124    * if not then it should be recomputed.
125    */
126    private int changeCount;
127   
128    /**
129    * Creates a new Sequence object.
130    *
131    * @param name
132    * display name string
133    * @param sequence
134    * string to form a possibly gapped sequence out of
135    * @param start
136    * first position of non-gap residue in the sequence
137    * @param end
138    * last position of ungapped residues (nearly always only used for
139    * display purposes)
140    */
 
141  0 toggle public Sequence(String name, String sequence, int start, int end)
142    {
143  0 this();
144  0 initSeqAndName(name, sequence.toCharArray(), start, end);
145    }
146   
 
147  0 toggle public Sequence(String name, char[] sequence, int start, int end)
148    {
149  0 this();
150  0 initSeqAndName(name, sequence, start, end);
151    }
152   
153    /**
154    * Stage 1 constructor - assign name, sequence, and set start and end fields.
155    * start and end are updated values from name2 if it ends with /start-end
156    *
157    * @param name2
158    * @param sequence2
159    * @param start2
160    * @param end2
161    */
 
162  0 toggle protected void initSeqAndName(String name2, char[] sequence2, int start2,
163    int end2)
164    {
165  0 this.name = name2;
166  0 this.sequence = sequence2;
167  0 this.start = start2;
168  0 this.end = end2;
169  0 parseId();
170  0 checkValidRange();
171    }
172   
173    /**
174    * If 'name' ends in /i-j, where i >= j > 0 are integers, extracts i and j as
175    * start and end respectively and removes the suffix from the name
176    */
 
177  0 toggle void parseId()
178    {
179  0 if (name == null)
180    {
181  0 jalview.bin.Console.errPrintln(
182    "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
183  0 name = "";
184    }
185  0 int slashPos = name.lastIndexOf('/');
186  0 if (slashPos > -1 && slashPos < name.length() - 1)
187    {
188  0 String suffix = name.substring(slashPos + 1);
189  0 String[] range = suffix.split("-");
190  0 if (range.length == 2)
191    {
192  0 try
193    {
194  0 int from = Integer.valueOf(range[0]);
195  0 int to = Integer.valueOf(range[1]);
196  0 if (from > 0 && to >= from)
197    {
198  0 name = name.substring(0, slashPos);
199  0 setStart(from);
200  0 setEnd(to);
201  0 checkValidRange();
202    }
203    } catch (NumberFormatException e)
204    {
205    // leave name unchanged if suffix is invalid
206    }
207    }
208    }
209    }
210   
211    /**
212    * Ensures that 'end' is not before the end of the sequence, that is,
213    * (end-start+1) is at least as long as the count of ungapped positions. Note
214    * that end is permitted to be beyond the end of the sequence data.
215    */
 
216  0 toggle void checkValidRange()
217    {
218    // Note: JAL-774 :
219    // http://issues.jalview.org/browse/JAL-774?focusedCommentId=11239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-11239
220    {
221  0 int endRes = 0;
222  0 for (int j = 0; j < sequence.length; j++)
223    {
224  0 if (!Comparison.isGap(sequence[j]))
225    {
226  0 endRes++;
227    }
228    }
229  0 if (endRes > 0)
230    {
231  0 endRes += start - 1;
232    }
233   
234  0 if (end < endRes)
235    {
236  0 end = endRes;
237    }
238    }
239   
240    }
241   
242    /**
243    * default constructor
244    */
 
245  0 toggle private Sequence()
246    {
247  0 sequenceFeatureStore = new SequenceFeatures();
248    }
249   
250    /**
251    * Creates a new Sequence object.
252    *
253    * @param name
254    * DOCUMENT ME!
255    * @param sequence
256    * DOCUMENT ME!
257    */
 
258  0 toggle public Sequence(String name, String sequence)
259    {
260  0 this(name, sequence, 1, -1);
261    }
262   
263    /**
264    * Create a new sequence object from a characters array using default values
265    * of 1 and -1 for start and end. The array used to create the sequence is
266    * copied and is not stored internally.
267    *
268    * @param name
269    * sequence name
270    * @param sequence
271    * list of residues
272    */
 
273  0 toggle public Sequence(String name, char[] sequence)
274    {
275  0 this(name, Arrays.copyOf(sequence, sequence.length), 1, -1);
276    }
277   
278    /**
279    * Creates a new Sequence object with new AlignmentAnnotations but inherits
280    * any existing dataset sequence reference. If non exists, everything is
281    * copied.
282    *
283    * @param seq
284    * if seq is a dataset sequence, behaves like a plain old copy
285    * constructor
286    */
 
287  0 toggle public Sequence(SequenceI seq)
288    {
289  0 this(seq, seq.getAnnotation());
290    }
291   
292    /**
293    * Create a new sequence object with new features, DBRefEntries, and PDBIds
294    * but inherits any existing dataset sequence reference, and duplicate of any
295    * annotation that is present in the given annotation array.
296    *
297    * @param seq
298    * the sequence to be copied
299    * @param alAnnotation
300    * an array of annotation including some associated with seq
301    */
 
302  0 toggle public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
303    {
304  0 this();
305  0 initSeqFrom(seq, alAnnotation);
306    }
307   
308    /**
309    * does the heavy lifting when cloning a dataset sequence, or coping data from
310    * dataset to a new derived sequence.
311    *
312    * @param seq
313    * - source of attributes.
314    * @param alAnnotation
315    * - alignment annotation present on seq that should be copied onto
316    * this sequence
317    */
 
318  0 toggle protected void initSeqFrom(SequenceI seq,
319    AlignmentAnnotation[] alAnnotation)
320    {
321  0 char[] oseq = seq.getSequence(); // returns a copy of the array
322  0 initSeqAndName(seq.getName(), oseq, seq.getStart(), seq.getEnd());
323   
324  0 description = seq.getDescription();
325  0 if (seq != datasetSequence)
326    {
327  0 setDatasetSequence(seq.getDatasetSequence());
328    }
329   
330    /*
331    * only copy DBRefs and seqfeatures if we really are a dataset sequence
332    */
333  0 if (datasetSequence == null)
334    {
335  0 List<DBRefEntry> dbr = seq.getDBRefs();
336  0 if (dbr != null)
337    {
338  0 for (int i = 0, n = dbr.size(); i < n; i++)
339    {
340  0 addDBRef(new DBRefEntry(dbr.get(i)));
341    }
342    }
343   
344    /*
345    * make copies of any sequence features
346    */
347  0 for (SequenceFeature sf : seq.getSequenceFeatures())
348    {
349  0 addSequenceFeature(new SequenceFeature(sf));
350    }
351    }
352   
353  0 if (seq.getAnnotation() != null)
354    {
355  0 AlignmentAnnotation[] sqann = seq.getAnnotation();
356  0 for (int i = 0; i < sqann.length; i++)
357    {
358  0 if (sqann[i] == null)
359    {
360  0 continue;
361    }
362  0 boolean found = (alAnnotation == null);
363  0 if (!found)
364    {
365  0 for (int apos = 0; !found && apos < alAnnotation.length; apos++)
366    {
367  0 found = (alAnnotation[apos] == sqann[i]);
368    }
369    }
370  0 if (found)
371    {
372    // only copy the given annotation
373  0 AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
374  0 ContactMatrixI cm = seq.getContactMatrixFor(sqann[i]);
375  0 if (cm != null)
376    {
377  0 addContactListFor(newann, cm);
378    }
379  0 addAlignmentAnnotation(newann);
380    }
381    }
382    }
383  0 if (seq.getAllPDBEntries() != null)
384    {
385  0 Vector<PDBEntry> ids = seq.getAllPDBEntries();
386  0 for (PDBEntry pdb : ids)
387    {
388  0 this.addPDBId(new PDBEntry(pdb));
389    }
390    }
391  0 if (seq.getHMM() != null)
392    {
393  0 this.hmm = new HiddenMarkovModel(seq.getHMM(), this);
394    }
395    }
396   
 
397  0 toggle @Override
398    public void setSequenceFeatures(List<SequenceFeature> features)
399    {
400  0 if (datasetSequence != null)
401    {
402  0 datasetSequence.setSequenceFeatures(features);
403  0 return;
404    }
405  0 sequenceFeatureStore = new SequenceFeatures(features);
406    }
407   
 
408  0 toggle @Override
409    public synchronized boolean addSequenceFeature(SequenceFeature sf)
410    {
411  0 if (sf.getType() == null)
412    {
413  0 jalview.bin.Console.errPrintln(
414    "SequenceFeature type may not be null: " + sf.toString());
415  0 return false;
416    }
417   
418  0 if (datasetSequence != null)
419    {
420  0 return datasetSequence.addSequenceFeature(sf);
421    }
422   
423  0 return sequenceFeatureStore.add(sf);
424    }
425   
 
426  0 toggle @Override
427    public void deleteFeature(SequenceFeature sf)
428    {
429  0 if (datasetSequence != null)
430    {
431  0 datasetSequence.deleteFeature(sf);
432    }
433    else
434    {
435  0 sequenceFeatureStore.delete(sf);
436    }
437    }
438   
439    /**
440    * {@inheritDoc}
441    *
442    * @return
443    */
 
444  0 toggle @Override
445    public List<SequenceFeature> getSequenceFeatures()
446    {
447  0 if (datasetSequence != null)
448    {
449  0 return datasetSequence.getSequenceFeatures();
450    }
451  0 return sequenceFeatureStore.getAllFeatures();
452    }
453   
 
454  0 toggle @Override
455    public SequenceFeaturesI getFeatures()
456    {
457  0 return datasetSequence != null ? datasetSequence.getFeatures()
458    : sequenceFeatureStore;
459    }
460   
 
461  0 toggle @Override
462    public boolean addPDBId(PDBEntry entry)
463    {
464  0 if (pdbIds == null)
465    {
466  0 pdbIds = new Vector<>();
467  0 pdbIds.add(entry);
468  0 return true;
469    }
470   
471  0 for (PDBEntry pdbe : pdbIds)
472    {
473  0 if (pdbe.updateFrom(entry))
474    {
475  0 return false;
476    }
477    }
478  0 pdbIds.addElement(entry);
479  0 return true;
480    }
481   
482    /**
483    * DOCUMENT ME!
484    *
485    * @param id
486    * DOCUMENT ME!
487    */
 
488  0 toggle @Override
489    public void setPDBId(Vector<PDBEntry> id)
490    {
491  0 pdbIds = id;
492    }
493   
494    /**
495    * DOCUMENT ME!
496    *
497    * @return DOCUMENT ME!
498    */
 
499  0 toggle @Override
500    public Vector<PDBEntry> getAllPDBEntries()
501    {
502  0 return pdbIds == null ? new Vector<>() : pdbIds;
503    }
504   
505    /**
506    * Answers the sequence name, with '/start-end' appended if jvsuffix is true
507    *
508    * @return
509    */
 
510  0 toggle @Override
511    public String getDisplayId(boolean jvsuffix)
512    {
513  0 if (!jvsuffix)
514    {
515  0 return name;
516    }
517  0 StringBuilder result = new StringBuilder(name);
518  0 result.append("/").append(start).append("-").append(end);
519   
520  0 return result.toString();
521    }
522   
523    /**
524    * Sets the sequence name. If the name ends in /start-end, then the start-end
525    * values are parsed out and set, and the suffix is removed from the name.
526    *
527    * @param theName
528    */
 
529  0 toggle @Override
530    public void setName(String theName)
531    {
532  0 this.name = theName;
533  0 this.parseId();
534    }
535   
536    /**
537    * DOCUMENT ME!
538    *
539    * @return DOCUMENT ME!
540    */
 
541  0 toggle @Override
542    public String getName()
543    {
544  0 return this.name;
545    }
546   
547    /**
548    * DOCUMENT ME!
549    *
550    * @param start
551    * DOCUMENT ME!
552    */
 
553  0 toggle @Override
554    public void setStart(int start)
555    {
556  0 this.start = start;
557  0 sequenceChanged();
558    }
559   
560    /**
561    * DOCUMENT ME!
562    *
563    * @return DOCUMENT ME!
564    */
 
565  0 toggle @Override
566    public int getStart()
567    {
568  0 return this.start;
569    }
570   
571    /**
572    * DOCUMENT ME!
573    *
574    * @param end
575    * DOCUMENT ME!
576    */
 
577  0 toggle @Override
578    public void setEnd(int end)
579    {
580  0 this.end = end;
581    }
582   
583    /**
584    * DOCUMENT ME!
585    *
586    * @return DOCUMENT ME!
587    */
 
588  0 toggle @Override
589    public int getEnd()
590    {
591  0 return this.end;
592    }
593   
594    /**
595    * DOCUMENT ME!
596    *
597    * @return DOCUMENT ME!
598    */
 
599  0 toggle @Override
600    public int getLength()
601    {
602  0 return this.sequence.length;
603    }
604   
 
605  0 toggle @Override
606    public void setSequence(char[] seq)
607    {
608  0 this.sequence = Arrays.copyOf(seq, seq.length);
609  0 checkValidRange();
610  0 sequenceChanged();
611    }
612   
613    /**
614    * DOCUMENT ME!
615    *
616    * @param seq
617    * DOCUMENT ME!
618    */
 
619  0 toggle @Override
620    public void setSequence(String seq)
621    {
622  0 this.sequence = seq.toCharArray();
623  0 checkValidRange();
624  0 sequenceChanged();
625    }
626   
 
627  0 toggle @Override
628    public String getSequenceAsString()
629    {
630  0 return new String(sequence);
631    }
632   
 
633  0 toggle @Override
634    public String getSequenceAsString(int start, int end)
635    {
636  0 return new String(getSequence(start, end));
637    }
638   
 
639  0 toggle @Override
640    public char[] getSequence()
641    {
642    // return sequence;
643  0 return sequence == null ? null
644    : Arrays.copyOf(sequence, sequence.length);
645    }
646   
647    /*
648    * (non-Javadoc)
649    *
650    * @see jalview.datamodel.SequenceI#getSequence(int, int)
651    */
 
652  0 toggle @Override
653    public char[] getSequence(int start, int end)
654    {
655  0 if (start < 0)
656    {
657  0 start = 0;
658    }
659    // JBPNote - left to user to pad the result here (TODO:Decide on this
660    // policy)
661  0 if (start >= sequence.length)
662    {
663  0 return new char[0];
664    }
665   
666  0 if (end >= sequence.length)
667    {
668  0 end = sequence.length;
669    }
670   
671  0 char[] reply = new char[end - start];
672  0 System.arraycopy(sequence, start, reply, 0, end - start);
673   
674  0 return reply;
675    }
676   
 
677  0 toggle @Override
678    public SequenceI getSubSequence(int start, int end)
679    {
680  0 if (start < 0)
681    {
682  0 start = 0;
683    }
684  0 char[] seq = getSequence(start, end);
685  0 if (seq.length == 0)
686    {
687  0 return null;
688    }
689  0 int nstart = findPosition(start);
690  0 int nend = findPosition(end) - 1;
691    // JBPNote - this is an incomplete copy.
692  0 SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
693  0 nseq.setDescription(description);
694  0 if (datasetSequence != null)
695    {
696  0 nseq.setDatasetSequence(datasetSequence);
697    }
698    else
699    {
700  0 nseq.setDatasetSequence(this);
701    }
702  0 return nseq;
703    }
704   
705    /**
706    * Returns the character of the aligned sequence at the given position (base
707    * zero), or space if the position is not within the sequence's bounds
708    *
709    * @return
710    */
 
711  0 toggle @Override
712    public char getCharAt(int i)
713    {
714  0 if (i >= 0 && i < sequence.length)
715    {
716  0 return sequence[i];
717    }
718    else
719    {
720  0 return ' ';
721    }
722    }
723   
724    /**
725    * Sets the sequence description, and also parses out any special formats of
726    * interest
727    *
728    * @param desc
729    */
 
730  0 toggle @Override
731    public void setDescription(String desc)
732    {
733  0 this.description = desc;
734    }
735   
 
736  0 toggle @Override
737    public void setGeneLoci(String speciesId, String assemblyId,
738    String chromosomeId, MapList map)
739    {
740  0 addDBRef(new GeneLocus(speciesId, assemblyId, chromosomeId,
741    new Mapping(map)));
742    }
743   
744    /**
745    * Returns the gene loci mapping for the sequence (may be null)
746    *
747    * @return
748    */
 
749  0 toggle @Override
750    public GeneLociI getGeneLoci()
751    {
752  0 List<DBRefEntry> refs = getDBRefs();
753  0 if (refs != null)
754    {
755  0 for (final DBRefEntry ref : refs)
756    {
757  0 if (ref instanceof GeneLociI)
758    {
759  0 return (GeneLociI) ref;
760    }
761    }
762    }
763  0 return null;
764    }
765   
766    /**
767    * Answers the description
768    *
769    * @return
770    */
 
771  0 toggle @Override
772    public String getDescription()
773    {
774  0 return this.description;
775    }
776   
777    /**
778    * {@inheritDoc}
779    */
 
780  0 toggle @Override
781    public int findIndex(int pos)
782    {
783    /*
784    * use a valid, hopefully nearby, cursor if available
785    */
786  0 if (isValidCursor(cursor))
787    {
788  0 return findIndex(pos, cursor);
789    }
790   
791  0 int j = start;
792  0 int i = 0;
793  0 int startColumn = 0;
794   
795    /*
796    * traverse sequence from the start counting gaps; make a note of
797    * the column of the first residue to save in the cursor
798    */
799  0 while ((i < sequence.length) && (j <= end) && (j <= pos))
800    {
801  0 if (!Comparison.isGap(sequence[i]))
802    {
803  0 if (j == start)
804    {
805  0 startColumn = i;
806    }
807  0 j++;
808    }
809  0 i++;
810    }
811   
812  0 if (j == end && j < pos)
813    {
814  0 return end + 1;
815    }
816   
817  0 updateCursor(pos, i, startColumn);
818  0 return i;
819    }
820   
821    /**
822    * Updates the cursor to the latest found residue and column position
823    *
824    * @param residuePos
825    * (start..)
826    * @param column
827    * (1..)
828    * @param startColumn
829    * column position of the first sequence residue
830    */
 
831  0 toggle protected void updateCursor(int residuePos, int column, int startColumn)
832    {
833    /*
834    * preserve end residue column provided cursor was valid
835    */
836  0 int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
837   
838  0 if (residuePos == this.end)
839    {
840  0 endColumn = column;
841    }
842   
843  0 cursor = new SequenceCursor(this, residuePos, column, startColumn,
844    endColumn, this.changeCount);
845    }
846   
847    /**
848    * Answers the aligned column position (1..) for the given residue position
849    * (start..) given a 'hint' of a residue/column location in the neighbourhood.
850    * The hint may be left of, at, or to the right of the required position.
851    *
852    * @param pos
853    * @param curs
854    * @return
855    */
 
856  0 toggle protected int findIndex(final int pos, SequenceCursor curs)
857    {
858  0 if (!isValidCursor(curs))
859    {
860    /*
861    * wrong or invalidated cursor, compute de novo
862    */
863  0 return findIndex(pos);
864    }
865   
866  0 if (curs.residuePosition == pos)
867    {
868  0 return curs.columnPosition;
869    }
870   
871    /*
872    * move left or right to find pos from hint.position
873    */
874  0 int col = curs.columnPosition - 1; // convert from base 1 to base 0
875  0 int newPos = curs.residuePosition;
876  0 int delta = newPos > pos ? -1 : 1;
877   
878  0 while (newPos != pos)
879    {
880  0 col += delta; // shift one column left or right
881  0 if (col < 0)
882    {
883  0 break;
884    }
885  0 if (col == sequence.length)
886    {
887  0 col--; // return last column if we failed to reach pos
888  0 break;
889    }
890  0 if (!Comparison.isGap(sequence[col]))
891    {
892  0 newPos += delta;
893    }
894    }
895   
896  0 col++; // convert back to base 1
897   
898    /*
899    * only update cursor if we found the target position
900    */
901  0 if (newPos == pos)
902    {
903  0 updateCursor(pos, col, curs.firstColumnPosition);
904    }
905   
906  0 return col;
907    }
908   
909    /**
910    * {@inheritDoc}
911    */
 
912  0 toggle @Override
913    public int findPosition(final int column)
914    {
915    /*
916    * use a valid, hopefully nearby, cursor if available
917    */
918  0 if (isValidCursor(cursor))
919    {
920  0 return findPosition(column + 1, cursor);
921    }
922   
923    // TODO recode this more naturally i.e. count residues only
924    // as they are found, not 'in anticipation'
925   
926    /*
927    * traverse the sequence counting gaps; note the column position
928    * of the first residue, to save in the cursor
929    */
930  0 int firstResidueColumn = 0;
931  0 int lastPosFound = 0;
932  0 int lastPosFoundColumn = 0;
933  0 int seqlen = sequence.length;
934   
935  0 if (seqlen > 0 && !Comparison.isGap(sequence[0]))
936    {
937  0 lastPosFound = start;
938  0 lastPosFoundColumn = 0;
939    }
940   
941  0 int j = 0;
942  0 int pos = start;
943   
944  0 while (j < column && j < seqlen)
945    {
946  0 if (!Comparison.isGap(sequence[j]))
947    {
948  0 lastPosFound = pos;
949  0 lastPosFoundColumn = j;
950  0 if (pos == this.start)
951    {
952  0 firstResidueColumn = j;
953    }
954  0 pos++;
955    }
956  0 j++;
957    }
958  0 if (j < seqlen && !Comparison.isGap(sequence[j]))
959    {
960  0 lastPosFound = pos;
961  0 lastPosFoundColumn = j;
962  0 if (pos == this.start)
963    {
964  0 firstResidueColumn = j;
965    }
966    }
967   
968    /*
969    * update the cursor to the last residue position found (if any)
970    * (converting column position to base 1)
971    */
972  0 if (lastPosFound != 0)
973    {
974  0 updateCursor(lastPosFound, lastPosFoundColumn + 1,
975    firstResidueColumn + 1);
976    }
977   
978  0 return pos;
979    }
980   
981    /**
982    * Answers true if the given cursor is not null, is for this sequence object,
983    * and has a token value that matches this object's changeCount, else false.
984    * This allows us to ignore a cursor as 'stale' if the sequence has been
985    * modified since the cursor was created.
986    *
987    * @param curs
988    * @return
989    */
 
990  0 toggle protected boolean isValidCursor(SequenceCursor curs)
991    {
992  0 if (curs == null || curs.sequence != this || curs.token != changeCount)
993    {
994  0 return false;
995    }
996    /*
997    * sanity check against range
998    */
999  0 if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
1000    {
1001  0 return false;
1002    }
1003  0 if (curs.residuePosition < start || curs.residuePosition > end)
1004    {
1005  0 return false;
1006    }
1007  0 return true;
1008    }
1009   
1010    /**
1011    * Answers the sequence position (start..) for the given aligned column
1012    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
1013    * may lie left of, at, or to the right of the column position.
1014    *
1015    * @param col
1016    * @param curs
1017    * @return
1018    */
 
1019  0 toggle protected int findPosition(final int col, SequenceCursor curs)
1020    {
1021  0 if (!isValidCursor(curs))
1022    {
1023    /*
1024    * wrong or invalidated cursor, compute de novo
1025    */
1026  0 return findPosition(col - 1);// ugh back to base 0
1027    }
1028   
1029  0 if (curs.columnPosition == col)
1030    {
1031  0 cursor = curs; // in case this method becomes public
1032  0 return curs.residuePosition; // easy case :-)
1033    }
1034   
1035  0 if (curs.lastColumnPosition > 0 && curs.lastColumnPosition < col)
1036    {
1037    /*
1038    * sequence lies entirely to the left of col
1039    * - return last residue + 1
1040    */
1041  0 return end + 1;
1042    }
1043   
1044  0 if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
1045    {
1046    /*
1047    * sequence lies entirely to the right of col
1048    * - return first residue
1049    */
1050  0 return start;
1051    }
1052   
1053    // todo could choose closest to col out of column,
1054    // firstColumnPosition, lastColumnPosition as a start point
1055   
1056    /*
1057    * move left or right to find pos from cursor position
1058    */
1059  0 int firstResidueColumn = curs.firstColumnPosition;
1060  0 int column = curs.columnPosition - 1; // to base 0
1061  0 int newPos = curs.residuePosition;
1062  0 int delta = curs.columnPosition > col ? -1 : 1;
1063  0 boolean gapped = false;
1064  0 int lastFoundPosition = curs.residuePosition;
1065  0 int lastFoundPositionColumn = curs.columnPosition;
1066   
1067  0 while (column != col - 1)
1068    {
1069  0 column += delta; // shift one column left or right
1070  0 if (column < 0 || column == sequence.length)
1071    {
1072  0 break;
1073    }
1074  0 gapped = Comparison.isGap(sequence[column]);
1075  0 if (!gapped)
1076    {
1077  0 newPos += delta;
1078  0 lastFoundPosition = newPos;
1079  0 lastFoundPositionColumn = column + 1;
1080  0 if (lastFoundPosition == this.start)
1081    {
1082  0 firstResidueColumn = column + 1;
1083    }
1084    }
1085    }
1086   
1087  0 if (cursor == null || lastFoundPosition != cursor.residuePosition)
1088    {
1089  0 updateCursor(lastFoundPosition, lastFoundPositionColumn,
1090    firstResidueColumn);
1091    }
1092   
1093    /*
1094    * hack to give position to the right if on a gap
1095    * or beyond the length of the sequence (see JAL-2562)
1096    */
1097  0 if (delta > 0 && (gapped || column >= sequence.length))
1098    {
1099  0 newPos++;
1100    }
1101   
1102  0 return newPos;
1103    }
1104   
1105    /**
1106    * {@inheritDoc}
1107    */
 
1108  0 toggle @Override
1109    public ContiguousI findPositions(int fromColumn, int toColumn)
1110    {
1111  0 fromColumn = Math.max(fromColumn, 1);
1112  0 if (toColumn < fromColumn)
1113    {
1114  0 return null;
1115    }
1116   
1117    /*
1118    * find the first non-gapped position, if any
1119    */
1120  0 int firstPosition = 0;
1121  0 int col = fromColumn - 1;
1122  0 int length = sequence.length;
1123  0 while (col < length && col < toColumn)
1124    {
1125  0 if (!Comparison.isGap(sequence[col]))
1126    {
1127  0 firstPosition = findPosition(col++);
1128  0 break;
1129    }
1130  0 col++;
1131    }
1132   
1133  0 if (firstPosition == 0)
1134    {
1135  0 return null;
1136    }
1137   
1138    /*
1139    * find the last non-gapped position
1140    */
1141  0 int lastPosition = firstPosition;
1142  0 while (col < length && col < toColumn)
1143    {
1144  0 if (!Comparison.isGap(sequence[col++]))
1145    {
1146  0 lastPosition++;
1147    }
1148    }
1149   
1150  0 return new Range(firstPosition, lastPosition);
1151    }
1152   
1153    /**
1154    * Returns an int array where indices correspond to each residue in the
1155    * sequence and the element value gives its position in the alignment
1156    *
1157    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
1158    * residues in SequenceI object
1159    */
 
1160  0 toggle @Override
1161    public int[] gapMap()
1162    {
1163  0 String seq = jalview.analysis.AlignSeq.extractGaps(
1164    jalview.util.Comparison.GapChars, new String(sequence));
1165  0 int[] map = new int[seq.length()];
1166  0 int j = 0;
1167  0 int p = 0;
1168   
1169  0 while (j < sequence.length)
1170    {
1171  0 if (!jalview.util.Comparison.isGap(sequence[j]))
1172    {
1173  0 map[p++] = j;
1174    }
1175   
1176  0 j++;
1177    }
1178   
1179  0 return map;
1180    }
1181   
1182    /**
1183    * Build a bitset corresponding to sequence gaps
1184    *
1185    * @return a BitSet where set values correspond to gaps in the sequence
1186    */
 
1187  0 toggle @Override
1188    public BitSet gapBitset()
1189    {
1190  0 BitSet gaps = new BitSet(sequence.length);
1191  0 int j = 0;
1192  0 while (j < sequence.length)
1193    {
1194  0 if (jalview.util.Comparison.isGap(sequence[j]))
1195    {
1196  0 gaps.set(j);
1197    }
1198  0 j++;
1199    }
1200  0 return gaps;
1201    }
1202   
 
1203  0 toggle @Override
1204    public int[] findPositionMap()
1205    {
1206  0 int map[] = new int[sequence.length];
1207  0 int j = 0;
1208  0 int pos = start;
1209  0 int seqlen = sequence.length;
1210  0 while ((j < seqlen))
1211    {
1212  0 map[j] = pos;
1213  0 if (!jalview.util.Comparison.isGap(sequence[j]))
1214    {
1215  0 pos++;
1216    }
1217   
1218  0 j++;
1219    }
1220  0 return map;
1221    }
1222   
 
1223  0 toggle @Override
1224    public List<int[]> getInsertions()
1225    {
1226  0 ArrayList<int[]> map = new ArrayList<>();
1227  0 int lastj = -1, j = 0;
1228    // int pos = start;
1229  0 int seqlen = sequence.length;
1230  0 while ((j < seqlen))
1231    {
1232  0 if (jalview.util.Comparison.isGap(sequence[j]))
1233    {
1234  0 if (lastj == -1)
1235    {
1236  0 lastj = j;
1237    }
1238    }
1239    else
1240    {
1241  0 if (lastj != -1)
1242    {
1243  0 map.add(new int[] { lastj, j - 1 });
1244  0 lastj = -1;
1245    }
1246    }
1247  0 j++;
1248    }
1249  0 if (lastj != -1)
1250    {
1251  0 map.add(new int[] { lastj, j - 1 });
1252  0 lastj = -1;
1253    }
1254  0 return map;
1255    }
1256   
 
1257  0 toggle @Override
1258    public BitSet getInsertionsAsBits()
1259    {
1260  0 BitSet map = new BitSet();
1261  0 int lastj = -1, j = 0;
1262    // int pos = start;
1263  0 int seqlen = sequence.length;
1264  0 while ((j < seqlen))
1265    {
1266  0 if (jalview.util.Comparison.isGap(sequence[j]))
1267    {
1268  0 if (lastj == -1)
1269    {
1270  0 lastj = j;
1271    }
1272    }
1273    else
1274    {
1275  0 if (lastj != -1)
1276    {
1277  0 map.set(lastj, j);
1278  0 lastj = -1;
1279    }
1280    }
1281  0 j++;
1282    }
1283  0 if (lastj != -1)
1284    {
1285  0 map.set(lastj, j);
1286  0 lastj = -1;
1287    }
1288  0 return map;
1289    }
1290   
 
1291  0 toggle @Override
1292    public void deleteChars(final int i, final int j)
1293    {
1294  0 int newstart = start, newend = end;
1295  0 if (i >= sequence.length || i < 0)
1296    {
1297  0 return;
1298    }
1299   
1300  0 char[] tmp = StringUtils.deleteChars(sequence, i, j);
1301  0 boolean createNewDs = false;
1302    // TODO: take a (second look) at the dataset creation validation method for
1303    // the very large sequence case
1304   
1305  0 int startIndex = findIndex(start) - 1;
1306  0 int endIndex = findIndex(end) - 1;
1307  0 int startDeleteColumn = -1; // for dataset sequence deletions
1308  0 int deleteCount = 0;
1309   
1310  0 for (int s = i; s < j && s < sequence.length; s++)
1311    {
1312  0 if (Comparison.isGap(sequence[s]))
1313    {
1314  0 continue;
1315    }
1316  0 deleteCount++;
1317  0 if (startDeleteColumn == -1)
1318    {
1319  0 startDeleteColumn = findPosition(s) - start;
1320    }
1321  0 if (createNewDs)
1322    {
1323  0 newend--;
1324    }
1325    else
1326    {
1327  0 if (startIndex == s)
1328    {
1329    /*
1330    * deleting characters from start of sequence; new start is the
1331    * sequence position of the next column (position to the right
1332    * if the column position is gapped)
1333    */
1334  0 newstart = findPosition(j);
1335  0 break;
1336    }
1337    else
1338    {
1339  0 if (endIndex < j)
1340    {
1341    /*
1342    * deleting characters at end of sequence; new end is the sequence
1343    * position of the column before the deletion; subtract 1 if this is
1344    * gapped since findPosition returns the next sequence position
1345    */
1346  0 newend = findPosition(i - 1);
1347  0 if (Comparison.isGap(sequence[i - 1]))
1348    {
1349  0 newend--;
1350    }
1351  0 break;
1352    }
1353    else
1354    {
1355  0 createNewDs = true;
1356  0 newend--;
1357    }
1358    }
1359    }
1360    }
1361   
1362  0 if (createNewDs && this.datasetSequence != null)
1363    {
1364    /*
1365    * if deletion occured in the middle of the sequence,
1366    * construct a new dataset sequence and delete the residues
1367    * that were deleted from the aligned sequence
1368    */
1369  0 Sequence ds = new Sequence(datasetSequence);
1370  0 ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
1371  0 datasetSequence = ds;
1372    // TODO: remove any non-inheritable properties ?
1373    // TODO: create a sequence mapping (since there is a relation here ?)
1374    }
1375  0 start = newstart;
1376  0 end = newend;
1377  0 sequence = tmp;
1378  0 sequenceChanged();
1379    }
1380   
 
1381  0 toggle @Override
1382    public void insertCharAt(int i, int length, char c)
1383    {
1384  0 doInsert(i, length, c);
1385  0 sequenceChanged();
1386    }
1387   
 
1388  0 toggle @Override
1389    public void insertInserts(char c, int[]...inserts)
1390    {
1391    // TODO optimise further by precomputing new char length and arrayCopies.
1392  0 if (inserts==null)
1393    {
1394  0 return;
1395    }
1396  0 for (int[] insert:inserts)
1397    {
1398  0 doInsert(insert[0],insert[1],c);
1399    }
1400  0 sequenceChanged();
1401    }
 
1402  0 toggle @Override
1403    public void doInsert(int i, int length, char c)
1404    {
1405   
1406  0 char[] tmp = new char[sequence.length + length];
1407   
1408  0 if (i >= sequence.length)
1409    {
1410  0 System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1411  0 i = sequence.length;
1412    }
1413    else
1414    {
1415  0 System.arraycopy(sequence, 0, tmp, 0, i);
1416    }
1417   
1418  0 int index = i;
1419  0 while (length > 0)
1420    {
1421  0 tmp[index++] = c;
1422  0 length--;
1423    }
1424   
1425  0 if (i < sequence.length)
1426    {
1427  0 System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1428    }
1429   
1430  0 sequence = tmp;
1431   
1432    }
1433   
 
1434  0 toggle @Override
1435    public void insertCharAt(int i, char c)
1436    {
1437  0 insertCharAt(i, 1, c);
1438    }
1439   
 
1440  0 toggle @Override
1441    public String getVamsasId()
1442    {
1443  0 return vamsasId;
1444    }
1445   
 
1446  0 toggle @Override
1447    public void setVamsasId(String id)
1448    {
1449  0 vamsasId = id;
1450    }
1451   
 
1452  0 toggle @Deprecated
1453    @Override
1454    public void setDBRefs(DBModList<DBRefEntry> newDBrefs)
1455    {
1456  0 if (dbrefs == null && datasetSequence != null
1457    && this != datasetSequence)
1458    {
1459  0 datasetSequence.setDBRefs(newDBrefs);
1460  0 return;
1461    }
1462  0 dbrefs = newDBrefs;
1463  0 refModCount = 0;
1464    }
1465   
 
1466  0 toggle @Override
1467    public DBModList<DBRefEntry> getDBRefs()
1468    {
1469  0 if (dbrefs == null && datasetSequence != null
1470    && this != datasetSequence)
1471    {
1472  0 return datasetSequence.getDBRefs();
1473    }
1474  0 return dbrefs;
1475    }
1476   
 
1477  0 toggle @Override
1478    public void addDBRef(DBRefEntry entry)
1479    {
1480    // TODO JAL-3980 maintain as sorted list
1481  0 if (datasetSequence != null)
1482    {
1483  0 datasetSequence.addDBRef(entry);
1484  0 return;
1485    }
1486   
1487  0 if (dbrefs == null)
1488    {
1489  0 dbrefs = new DBModList<>();
1490    }
1491    // TODO JAL-3979 LOOK UP RATHER THAN SWEEP FOR EFFICIENCY
1492   
1493  0 for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
1494    {
1495  0 if (dbrefs.get(ib).updateFrom(entry))
1496    {
1497    /*
1498    * found a dbref that either matched, or could be
1499    * updated from, the new entry - no need to add it
1500    */
1501  0 return;
1502    }
1503    }
1504   
1505    // /// BH OUCH!
1506    // /*
1507    // * extend the array to make room for one more
1508    // */
1509    // // TODO use an ArrayList instead
1510    // int j = dbrefs.length;
1511    // List<DBRefEntry> temp = new DBRefEntry[j + 1];
1512    // System.arraycopy(dbrefs, 0, temp, 0, j);
1513    // temp[temp.length - 1] = entry;
1514    //
1515    // dbrefs = temp;
1516   
1517  0 dbrefs.add(entry);
1518    }
1519   
 
1520  0 toggle @Override
1521    public void setDatasetSequence(SequenceI seq)
1522    {
1523  0 if (seq == this)
1524    {
1525  0 throw new IllegalArgumentException(
1526    "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1527    }
1528  0 if (seq != null && seq.getDatasetSequence() != null)
1529    {
1530  0 throw new IllegalArgumentException(
1531    "Implementation error: cascading dataset sequences are not allowed.");
1532    }
1533  0 datasetSequence = seq;
1534    }
1535   
 
1536  0 toggle @Override
1537    public SequenceI getDatasetSequence()
1538    {
1539  0 return datasetSequence;
1540    }
1541   
 
1542  0 toggle @Override
1543    public AlignmentAnnotation[] getAnnotation()
1544    {
1545  0 return annotation == null ? null
1546    : annotation
1547    .toArray(new AlignmentAnnotation[annotation.size()]);
1548    }
1549   
 
1550  0 toggle @Override
1551    public boolean hasAnnotation(AlignmentAnnotation ann)
1552    {
1553  0 return annotation == null ? false : annotation.contains(ann);
1554    }
1555   
 
1556  0 toggle @Override
1557    public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1558    {
1559  0 if (this.annotation == null)
1560    {
1561  0 this.annotation = new Vector<>();
1562    }
1563  0 if (!this.annotation.contains(annotation))
1564    {
1565  0 this.annotation.addElement(annotation);
1566    }
1567  0 annotation.setSequenceRef(this);
1568    }
1569   
 
1570  0 toggle @Override
1571    public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1572    {
1573  0 if (this.annotation != null)
1574    {
1575  0 this.annotation.removeElement(annotation);
1576  0 if (this.annotation.size() == 0)
1577    {
1578  0 this.annotation = null;
1579    }
1580    }
1581    }
1582   
1583    /**
1584    * test if this is a valid candidate for another sequence's dataset sequence.
1585    *
1586    */
 
1587  0 toggle private boolean isValidDatasetSequence()
1588    {
1589  0 if (datasetSequence != null)
1590    {
1591  0 return false;
1592    }
1593  0 for (int i = 0; i < sequence.length; i++)
1594    {
1595  0 if (jalview.util.Comparison.isGap(sequence[i]))
1596    {
1597  0 return false;
1598    }
1599    }
1600  0 return true;
1601    }
1602   
 
1603  0 toggle @Override
1604    public SequenceI deriveSequence()
1605    {
1606  0 Sequence seq = null;
1607  0 if (datasetSequence == null)
1608    {
1609  0 if (isValidDatasetSequence())
1610    {
1611    // Use this as dataset sequence
1612  0 seq = new Sequence(getName(), "", 1, -1);
1613  0 seq.setDatasetSequence(this);
1614  0 seq.initSeqFrom(this, getAnnotation());
1615  0 return seq;
1616    }
1617    else
1618    {
1619    // Create a new, valid dataset sequence
1620  0 createDatasetSequence();
1621    }
1622    }
1623  0 return new Sequence(this);
1624    }
1625   
1626    private boolean _isNa;
1627   
1628    private int _seqhash = 0;
1629   
1630    private List<DBRefEntry> primaryRefs;
1631   
1632    /**
1633    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1634    * true
1635    */
 
1636  0 toggle @Override
1637    public boolean isProtein()
1638    {
1639  0 if (datasetSequence != null)
1640    {
1641  0 return datasetSequence.isProtein();
1642    }
1643  0 if (_seqhash != sequence.hashCode())
1644    {
1645  0 _seqhash = sequence.hashCode();
1646  0 _isNa = Comparison.isNucleotide(this);
1647    }
1648  0 return !_isNa;
1649    }
1650   
1651    /*
1652    * (non-Javadoc)
1653    *
1654    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1655    */
 
1656  0 toggle @Override
1657    public SequenceI createDatasetSequence()
1658    {
1659  0 if (datasetSequence == null)
1660    {
1661  0 Sequence dsseq = new Sequence(getName(),
1662    AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
1663    getSequenceAsString()),
1664    getStart(), getEnd());
1665   
1666  0 datasetSequence = dsseq;
1667   
1668  0 dsseq.setDescription(description);
1669    // move features and database references onto dataset sequence
1670  0 dsseq.sequenceFeatureStore = sequenceFeatureStore;
1671  0 sequenceFeatureStore = null;
1672  0 dsseq.dbrefs = dbrefs;
1673  0 dbrefs = null;
1674    // TODO: search and replace any references to this sequence with
1675    // references to the dataset sequence in Mappings on dbref
1676  0 dsseq.pdbIds = pdbIds;
1677  0 pdbIds = null;
1678  0 datasetSequence.updatePDBIds();
1679  0 if (annotation != null)
1680    {
1681    // annotation is cloned rather than moved, to preserve what's currently
1682    // on the alignment
1683  0 for (AlignmentAnnotation aa : annotation)
1684    {
1685  0 AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1686  0 _aa.sequenceRef = datasetSequence;
1687  0 _aa.adjustForAlignment(); // uses annotation's own record of
1688    // sequence-column mapping
1689  0 datasetSequence.addAlignmentAnnotation(_aa);
1690   
1691  0 if (_cmholder != null)
1692    { // transfer contact matrices
1693  0 ContactMatrixI cm = _cmholder.getContactMatrixFor(aa);
1694  0 if (cm != null)
1695    {
1696  0 datasetSequence.addContactListFor(_aa, cm);
1697  0 datasetSequence.addContactListFor(aa, cm);
1698    }
1699    }
1700    }
1701    }
1702    // all matrices should have been transferred. so we clear the local holder
1703  0 _cmholder = null;
1704    }
1705  0 return datasetSequence;
1706    }
1707   
1708    /*
1709    * (non-Javadoc)
1710    *
1711    * @see
1712    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1713    * annotations)
1714    */
 
1715  0 toggle @Override
1716    public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1717    {
1718  0 if (annotation != null)
1719    {
1720  0 annotation.removeAllElements();
1721    }
1722  0 if (annotations != null)
1723    {
1724  0 for (int i = 0; i < annotations.length; i++)
1725    {
1726  0 if (annotations[i] != null)
1727    {
1728  0 addAlignmentAnnotation(annotations[i]);
1729    }
1730    }
1731    }
1732    }
1733   
 
1734  0 toggle @Override
1735    public AlignmentAnnotation[] getAnnotation(String label)
1736    {
1737  0 if (annotation == null || annotation.size() == 0)
1738    {
1739  0 return null;
1740    }
1741   
1742  0 Vector<AlignmentAnnotation> subset = new Vector<>();
1743  0 Enumeration<AlignmentAnnotation> e = annotation.elements();
1744  0 while (e.hasMoreElements())
1745    {
1746  0 AlignmentAnnotation ann = e.nextElement();
1747  0 if (ann.label != null && ann.label.equals(label))
1748    {
1749  0 subset.addElement(ann);
1750    }
1751    }
1752  0 if (subset.size() == 0)
1753    {
1754  0 return null;
1755    }
1756  0 AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1757  0 int i = 0;
1758  0 e = subset.elements();
1759  0 while (e.hasMoreElements())
1760    {
1761  0 anns[i++] = e.nextElement();
1762    }
1763  0 subset.removeAllElements();
1764  0 return anns;
1765    }
1766   
 
1767  0 toggle @Override
1768    public boolean updatePDBIds()
1769    {
1770  0 if (datasetSequence != null)
1771    {
1772    // TODO: could merge DBRefs
1773  0 return datasetSequence.updatePDBIds();
1774    }
1775  0 if (dbrefs == null || dbrefs.size() == 0)
1776    {
1777  0 return false;
1778    }
1779  0 boolean added = false;
1780  0 for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
1781    {
1782  0 DBRefEntry dbr = dbrefs.get(ib);
1783  0 if (DBRefSource.PDB.equals(dbr.getSource()))
1784    {
1785    /*
1786    * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1787    * PDB id is not already present in a 'matching' PDBEntry
1788    * Constructor parses out a chain code if appended to the accession id
1789    * (a fudge used to 'store' the chain code in the DBRef)
1790    */
1791  0 PDBEntry pdbe = new PDBEntry(dbr);
1792  0 added |= addPDBId(pdbe);
1793    }
1794    }
1795  0 return added;
1796    }
1797   
 
1798  0 toggle @Override
1799    public void transferAnnotation(SequenceI entry, Mapping mp)
1800    {
1801  0 if (datasetSequence != null)
1802    {
1803  0 datasetSequence.transferAnnotation(entry, mp);
1804  0 return;
1805    }
1806  0 if (entry.getDatasetSequence() != null)
1807    {
1808  0 transferAnnotation(entry.getDatasetSequence(), mp);
1809  0 return;
1810    }
1811    // transfer from entry to sequence
1812    // if entry has a description and sequence doesn't, then transfer
1813  0 if (entry.getDescription() != null
1814    && (description == null || description.trim().length() == 0))
1815    {
1816  0 description = entry.getDescription();
1817    }
1818   
1819    // transfer any new features from entry onto sequence
1820  0 if (entry.getSequenceFeatures() != null)
1821    {
1822   
1823  0 List<SequenceFeature> sfs = entry.getSequenceFeatures();
1824  0 for (SequenceFeature feature : sfs)
1825    {
1826  0 SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1827    : new SequenceFeature[]
1828    { new SequenceFeature(feature) };
1829  0 if (sf != null)
1830    {
1831  0 for (int sfi = 0; sfi < sf.length; sfi++)
1832    {
1833  0 addSequenceFeature(sf[sfi]);
1834    }
1835    }
1836    }
1837    }
1838   
1839    // transfer PDB entries
1840  0 if (entry.getAllPDBEntries() != null)
1841    {
1842  0 Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1843  0 while (e.hasMoreElements())
1844    {
1845  0 PDBEntry pdb = e.nextElement();
1846  0 addPDBId(pdb);
1847    }
1848    }
1849    // transfer database references
1850  0 List<DBRefEntry> entryRefs = entry.getDBRefs();
1851  0 if (entryRefs != null)
1852    {
1853  0 for (int r = 0, n = entryRefs.size(); r < n; r++)
1854    {
1855  0 DBRefEntry newref = new DBRefEntry(entryRefs.get(r));
1856  0 if (newref.getMap() != null && mp != null)
1857    {
1858    // remap ref using our local mapping
1859    }
1860    // we also assume all version string setting is done by dbSourceProxy
1861    /*
1862    * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1863    * newref.setSource(dbSource); }
1864    */
1865  0 addDBRef(newref);
1866    }
1867    }
1868    }
1869   
 
1870  0 toggle @Override
1871    public void setRNA(RNA r)
1872    {
1873  0 rna = r;
1874    }
1875   
 
1876  0 toggle @Override
1877    public RNA getRNA()
1878    {
1879  0 return rna;
1880    }
1881   
 
1882  0 toggle @Override
1883    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1884    String label)
1885    {
1886  0 return getAlignmentAnnotations(calcId, label, null, true);
1887    }
1888   
 
1889  0 toggle @Override
1890    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1891    String label, String description)
1892    {
1893  0 return getAlignmentAnnotations(calcId, label, description, false);
1894    }
1895   
 
1896  0 toggle private List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1897    String label, String description, boolean ignoreDescription)
1898    {
1899  0 List<AlignmentAnnotation> result = new ArrayList<>();
1900  0 if (this.annotation != null)
1901    {
1902  0 for (AlignmentAnnotation ann : annotation)
1903    {
1904  0 String id = ann.getCalcId();
1905  0 if ((id != null && id.equals(calcId))
1906    && (ann.label != null && ann.label.equals(label))
1907    && ((ignoreDescription && description == null)
1908    || (ann.description != null
1909    && ann.description.equals(description))))
1910    {
1911  0 result.add(ann);
1912    }
1913    }
1914    }
1915  0 return result;
1916    }
1917   
 
1918  0 toggle @Override
1919    public String toString()
1920    {
1921  0 return getDisplayId(false);
1922    }
1923   
 
1924  0 toggle @Override
1925    public PDBEntry getPDBEntry(String pdbIdStr)
1926    {
1927  0 if (getDatasetSequence() != null)
1928    {
1929  0 return getDatasetSequence().getPDBEntry(pdbIdStr);
1930    }
1931  0 if (pdbIds == null)
1932    {
1933  0 return null;
1934    }
1935  0 List<PDBEntry> entries = getAllPDBEntries();
1936  0 for (PDBEntry entry : entries)
1937    {
1938  0 if (entry.getId().equalsIgnoreCase(pdbIdStr))
1939    {
1940  0 return entry;
1941    }
1942    }
1943  0 return null;
1944    }
1945   
1946    private List<DBRefEntry> tmpList;
1947   
 
1948  0 toggle @Override
1949    public List<DBRefEntry> getPrimaryDBRefs()
1950    {
1951  0 if (datasetSequence != null)
1952    {
1953  0 return datasetSequence.getPrimaryDBRefs();
1954    }
1955  0 if (dbrefs == null || dbrefs.size() == 0)
1956    {
1957  0 return Collections.emptyList();
1958    }
1959  0 synchronized (dbrefs)
1960    {
1961  0 if (refModCount == dbrefs.getModCount() && primaryRefs != null)
1962    {
1963  0 return primaryRefs; // no changes
1964    }
1965  0 refModCount = dbrefs.getModCount();
1966  0 List<DBRefEntry> primaries = (primaryRefs == null
1967    ? (primaryRefs = new ArrayList<>())
1968    : primaryRefs);
1969  0 primaries.clear();
1970  0 if (tmpList == null)
1971    {
1972  0 tmpList = new ArrayList<>();
1973  0 tmpList.add(null); // for replacement
1974    }
1975  0 for (int i = 0, n = dbrefs.size(); i < n; i++)
1976    {
1977  0 DBRefEntry ref = dbrefs.get(i);
1978  0 if (!ref.isPrimaryCandidate())
1979    {
1980  0 continue;
1981    }
1982  0 if (ref.hasMap())
1983    {
1984  0 MapList mp = ref.getMap().getMap();
1985  0 if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1986    {
1987    // map only involves a subsequence, so cannot be primary
1988  0 continue;
1989    }
1990    }
1991    // whilst it looks like it is a primary ref, we also sanity check type
1992  0 if (DBRefSource.PDB_CANONICAL_NAME
1993    .equals(ref.getCanonicalSourceName()))
1994    {
1995    // PDB dbrefs imply there should be a PDBEntry associated
1996    // TODO: tighten PDB dbrefs
1997    // formally imply Jalview has actually downloaded and
1998    // parsed the pdb file. That means there should be a cached file
1999    // handle on the PDBEntry, and a real mapping between sequence and
2000    // extracted sequence from PDB file
2001  0 PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
2002  0 if (pdbentry == null || pdbentry.getFile() == null)
2003    {
2004  0 continue;
2005    }
2006    }
2007    else
2008    {
2009    // check standard protein or dna sources
2010  0 tmpList.set(0, ref);
2011  0 List<DBRefEntry> res = DBRefUtils.selectDbRefs(!isProtein(),
2012    tmpList);
2013  0 if (res == null || res.get(0) != tmpList.get(0))
2014    {
2015  0 continue;
2016    }
2017    }
2018  0 primaries.add(ref);
2019    }
2020   
2021    // version must be not null, as otherwise it will not be a candidate,
2022    // above
2023  0 DBRefUtils.ensurePrimaries(this, primaries);
2024  0 return primaries;
2025    }
2026    }
2027   
 
2028  0 toggle @Override
2029    public HiddenMarkovModel getHMM()
2030    {
2031  0 return hmm;
2032    }
2033   
 
2034  0 toggle @Override
2035    public void setHMM(HiddenMarkovModel hmm)
2036    {
2037  0 this.hmm = hmm;
2038    }
2039   
 
2040  0 toggle @Override
2041    public boolean hasHMMAnnotation()
2042    {
2043  0 if (this.annotation == null) {
2044  0 return false;
2045    }
2046  0 for (AlignmentAnnotation ann : annotation)
2047    {
2048  0 if (InformationThread.HMM_CALC_ID.equals(ann.getCalcId()))
2049    {
2050  0 return true;
2051    }
2052    }
2053  0 return false;
2054    }
2055    /**
2056    * {@inheritDoc}
2057    */
 
2058  0 toggle @Override
2059    public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
2060    String... types)
2061    {
2062  0 int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
2063  0 int endPos = fromColumn == toColumn ? startPos
2064    : findPosition(toColumn - 1);
2065   
2066  0 List<SequenceFeature> result = getFeatures().findFeatures(startPos,
2067    endPos, types);
2068   
2069    /*
2070    * if end column is gapped, endPos may be to the right,
2071    * and we may have included adjacent or enclosing features;
2072    * remove any that are not enclosing, non-contact features
2073    */
2074  0 boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
2075    && Comparison.isGap(sequence[toColumn - 1]);
2076  0 if (endPos > this.end || endColumnIsGapped)
2077    {
2078  0 ListIterator<SequenceFeature> it = result.listIterator();
2079  0 while (it.hasNext())
2080    {
2081  0 SequenceFeature sf = it.next();
2082  0 int sfBegin = sf.getBegin();
2083  0 int sfEnd = sf.getEnd();
2084  0 int featureStartColumn = findIndex(sfBegin);
2085  0 if (featureStartColumn > toColumn)
2086    {
2087  0 it.remove();
2088    }
2089  0 else if (featureStartColumn < fromColumn)
2090    {
2091  0 int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
2092    : findIndex(sfEnd);
2093  0 if (featureEndColumn < fromColumn)
2094    {
2095  0 it.remove();
2096    }
2097  0 else if (featureEndColumn > toColumn && sf.isContactFeature())
2098    {
2099    /*
2100    * remove an enclosing feature if it is a contact feature
2101    */
2102  0 it.remove();
2103    }
2104    }
2105    }
2106    }
2107   
2108  0 return result;
2109    }
2110   
2111    /**
2112    * Invalidates any stale cursors (forcing recalculation) by incrementing the
2113    * token that has to match the one presented by the cursor
2114    */
 
2115  0 toggle @Override
2116    public void sequenceChanged()
2117    {
2118  0 changeCount++;
2119    }
2120   
2121    /**
2122    * {@inheritDoc}
2123    */
 
2124  0 toggle @Override
2125    public int replace(char c1, char c2)
2126    {
2127  0 if (c1 == c2)
2128    {
2129  0 return 0;
2130    }
2131  0 int count = 0;
2132  0 synchronized (sequence)
2133    {
2134  0 for (int c = 0; c < sequence.length; c++)
2135    {
2136  0 if (sequence[c] == c1)
2137    {
2138  0 sequence[c] = c2;
2139  0 count++;
2140    }
2141    }
2142    }
2143  0 if (count > 0)
2144    {
2145  0 sequenceChanged();
2146    }
2147   
2148  0 return count;
2149    }
2150   
 
2151  0 toggle @Override
2152    public String getSequenceStringFromIterator(Iterator<int[]> it)
2153    {
2154  0 StringBuilder newSequence = new StringBuilder();
2155  0 while (it.hasNext())
2156    {
2157  0 int[] block = it.next();
2158  0 if (it.hasNext())
2159    {
2160  0 newSequence.append(getSequence(block[0], block[1] + 1));
2161    }
2162    else
2163    {
2164  0 newSequence.append(getSequence(block[0], block[1]));
2165    }
2166    }
2167   
2168  0 return newSequence.toString();
2169    }
2170   
 
2171  0 toggle @Override
2172    public int firstResidueOutsideIterator(Iterator<int[]> regions)
2173    {
2174  0 int start = 0;
2175   
2176  0 if (!regions.hasNext())
2177    {
2178  0 return findIndex(getStart()) - 1;
2179    }
2180   
2181    // Simply walk along the sequence whilst watching for region
2182    // boundaries
2183  0 int hideStart = getLength();
2184  0 int hideEnd = -1;
2185  0 boolean foundStart = false;
2186   
2187    // step through the non-gapped positions of the sequence
2188  0 for (int i = getStart(); i <= getEnd() && (!foundStart); i++)
2189    {
2190    // get alignment position of this residue in the sequence
2191  0 int p = findIndex(i) - 1;
2192   
2193    // update region start/end
2194  0 while (hideEnd < p && regions.hasNext())
2195    {
2196  0 int[] region = regions.next();
2197  0 hideStart = region[0];
2198  0 hideEnd = region[1];
2199    }
2200  0 if (hideEnd < p)
2201    {
2202  0 hideStart = getLength();
2203    }
2204    // update boundary for sequence
2205  0 if (p < hideStart)
2206    {
2207  0 start = p;
2208  0 foundStart = true;
2209    }
2210    }
2211   
2212  0 if (foundStart)
2213    {
2214  0 return start;
2215    }
2216    // otherwise, sequence was completely hidden
2217  0 return 0;
2218    }
2219   
2220    ////
2221    //// Contact Matrix Holder Boilerplate
2222    ////
2223    ContactMapHolderI _cmholder = null;
2224   
 
2225  0 toggle private ContactMapHolderI getContactMapHolder()
2226    {
2227  0 if (datasetSequence != null)
2228    {
2229  0 return ((Sequence) datasetSequence).getContactMapHolder();
2230    }
2231  0 if (_cmholder == null)
2232    {
2233  0 _cmholder = new ContactMapHolder();
2234    }
2235  0 return _cmholder;
2236    }
2237   
 
2238  0 toggle @Override
2239    public Collection<ContactMatrixI> getContactMaps()
2240    {
2241  0 return getContactMapHolder().getContactMaps();
2242    }
2243   
 
2244  0 toggle @Override
2245    public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann)
2246    {
2247  0 return getContactMapHolder().getContactMatrixFor(ann);
2248    }
2249   
 
2250  0 toggle @Override
2251    public ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
2252    {
2253  0 return getContactMapHolder().getContactListFor(_aa, column);
2254    }
2255   
 
2256  0 toggle @Override
2257    public AlignmentAnnotation addContactList(ContactMatrixI cm)
2258    {
2259  0 AlignmentAnnotation aa;
2260   
2261  0 if (datasetSequence != null)
2262    {
2263  0 aa = datasetSequence.addContactList(cm);
2264    // clone the annotation for the local sequence
2265  0 aa = new AlignmentAnnotation(aa);
2266  0 aa.restrict(start, end);
2267  0 aa.adjustForAlignment();
2268  0 getContactMapHolder().addContactListFor(aa, cm);
2269  0 addAlignmentAnnotation(aa);
2270  0 return aa;
2271    }
2272   
2273    // construct new annotation for matrix on dataset sequence
2274  0 aa = getContactMapHolder().addContactList(cm);
2275   
2276  0 Annotation _aa[] = new Annotation[getLength()];
2277   
2278  0 for (int i = 0; i < _aa.length; _aa[i++] = new Annotation(0.0f))
2279    {
2280  0 ;
2281    }
2282  0 aa.annotations = _aa;
2283  0 aa.setSequenceRef(this);
2284  0 if (cm instanceof MappableContactMatrix
2285    && !((MappableContactMatrix) cm).hasReferenceSeq())
2286    {
2287  0 ((MappableContactMatrix) cm).setRefSeq(this);
2288    }
2289  0 aa.createSequenceMapping(this, getStart(), false);
2290  0 addAlignmentAnnotation(aa);
2291  0 return aa;
2292    }
2293   
 
2294  0 toggle @Override
2295    public void addContactListFor(AlignmentAnnotation annotation,
2296    ContactMatrixI cm)
2297    {
2298  0 getContactMapHolder().addContactListFor(annotation, cm);
2299    }
2300   
 
2301  0 toggle @Override
2302    public boolean hasHMMProfile()
2303    {
2304  0 return hmm != null;
2305    }
2306    }