Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 14:43:25 GMT
  2. Package jalview.datamodel

File Sequence.java

 

Coverage histogram

../../img/srcFileCovDistChart10.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.9447698694.5%
Sequence.DBModList 63 1 1
1.0100%
 

Contributing tests

This file is covered by 878 tests. .

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  581 toggle protected int getModCount()
67    {
68  581 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  52483 toggle public Sequence(String name, String sequence, int start, int end)
142    {
143  52483 this();
144  52483 initSeqAndName(name, sequence.toCharArray(), start, end);
145    }
146   
 
147  126 toggle public Sequence(String name, char[] sequence, int start, int end)
148    {
149  126 this();
150  126 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  53188 toggle protected void initSeqAndName(String name2, char[] sequence2, int start2,
163    int end2)
164    {
165  53188 this.name = name2;
166  53188 this.sequence = sequence2;
167  53188 this.start = start2;
168  53188 this.end = end2;
169  53188 parseId();
170  53188 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  54503 toggle void parseId()
178    {
179  54503 if (name == null)
180    {
181  3 jalview.bin.Console.errPrintln(
182    "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
183  3 name = "";
184    }
185  54503 int slashPos = name.lastIndexOf('/');
186  54503 if (slashPos > -1 && slashPos < name.length() - 1)
187    {
188  1641 String suffix = name.substring(slashPos + 1);
189  1641 String[] range = suffix.split("-");
190  1641 if (range.length == 2)
191    {
192  1638 try
193    {
194  1638 int from = Integer.valueOf(range[0]);
195  1629 int to = Integer.valueOf(range[1]);
196  1629 if (from > 0 && to >= from)
197    {
198  1627 name = name.substring(0, slashPos);
199  1627 setStart(from);
200  1627 setEnd(to);
201  1627 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  68068 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  68068 int endRes = 0;
222  17132342 for (int j = 0; j < sequence.length; j++)
223    {
224  17064274 if (!Comparison.isGap(sequence[j]))
225    {
226  7804909 endRes++;
227    }
228    }
229  68068 if (endRes > 0)
230    {
231  60689 endRes += start - 1;
232    }
233   
234  68068 if (end < endRes)
235    {
236  46951 end = endRes;
237    }
238    }
239   
240    }
241   
242    /**
243    * default constructor
244    */
 
245  52912 toggle private Sequence()
246    {
247  52912 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  41577 toggle public Sequence(String name, String sequence)
259    {
260  41577 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  33 toggle public Sequence(String name, char[] sequence)
274    {
275  33 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  299 toggle public Sequence(SequenceI seq)
288    {
289  299 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  303 toggle public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
303    {
304  303 this();
305  303 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  579 toggle protected void initSeqFrom(SequenceI seq,
319    AlignmentAnnotation[] alAnnotation)
320    {
321  579 char[] oseq = seq.getSequence(); // returns a copy of the array
322  579 initSeqAndName(seq.getName(), oseq, seq.getStart(), seq.getEnd());
323   
324  579 description = seq.getDescription();
325  579 if (seq != datasetSequence)
326    {
327  312 setDatasetSequence(seq.getDatasetSequence());
328    }
329   
330    /*
331    * only copy DBRefs and seqfeatures if we really are a dataset sequence
332    */
333  579 if (datasetSequence == null)
334    {
335  109 List<DBRefEntry> dbr = seq.getDBRefs();
336  109 if (dbr != null)
337    {
338  4 for (int i = 0, n = dbr.size(); i < n; i++)
339    {
340  2 addDBRef(new DBRefEntry(dbr.get(i)));
341    }
342    }
343   
344    /*
345    * make copies of any sequence features
346    */
347  109 for (SequenceFeature sf : seq.getSequenceFeatures())
348    {
349  195 addSequenceFeature(new SequenceFeature(sf));
350    }
351    }
352   
353  579 if (seq.getAnnotation() != null)
354    {
355  11 AlignmentAnnotation[] sqann = seq.getAnnotation();
356  22 for (int i = 0; i < sqann.length; i++)
357    {
358  11 if (sqann[i] == null)
359    {
360  0 continue;
361    }
362  11 boolean found = (alAnnotation == null);
363  11 if (!found)
364    {
365  22 for (int apos = 0; !found && apos < alAnnotation.length; apos++)
366    {
367  11 found = (alAnnotation[apos] == sqann[i]);
368    }
369    }
370  11 if (found)
371    {
372    // only copy the given annotation
373  11 AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
374  11 ContactMatrixI cm = seq.getContactMatrixFor(sqann[i]);
375  11 if (cm != null)
376    {
377  5 addContactListFor(newann, cm);
378    }
379  11 addAlignmentAnnotation(newann);
380    }
381    }
382    }
383  579 if (seq.getAllPDBEntries() != null)
384    {
385  579 Vector<PDBEntry> ids = seq.getAllPDBEntries();
386  579 for (PDBEntry pdb : ids)
387    {
388  146 this.addPDBId(new PDBEntry(pdb));
389    }
390    }
391  579 if (seq.getHMM() != null)
392    {
393  0 this.hmm = new HiddenMarkovModel(seq.getHMM(), this);
394    }
395    }
396   
 
397  44 toggle @Override
398    public void setSequenceFeatures(List<SequenceFeature> features)
399    {
400  44 if (datasetSequence != null)
401    {
402  1 datasetSequence.setSequenceFeatures(features);
403  1 return;
404    }
405  43 sequenceFeatureStore = new SequenceFeatures(features);
406    }
407   
 
408  293899 toggle @Override
409    public synchronized boolean addSequenceFeature(SequenceFeature sf)
410    {
411  293899 if (sf.getType() == null)
412    {
413  1 jalview.bin.Console.errPrintln(
414    "SequenceFeature type may not be null: " + sf.toString());
415  1 return false;
416    }
417   
418  293898 if (datasetSequence != null)
419    {
420  72446 return datasetSequence.addSequenceFeature(sf);
421    }
422   
423  221452 return sequenceFeatureStore.add(sf);
424    }
425   
 
426  308 toggle @Override
427    public void deleteFeature(SequenceFeature sf)
428    {
429  308 if (datasetSequence != null)
430    {
431  109 datasetSequence.deleteFeature(sf);
432    }
433    else
434    {
435  199 sequenceFeatureStore.delete(sf);
436    }
437    }
438   
439    /**
440    * {@inheritDoc}
441    *
442    * @return
443    */
 
444  16457 toggle @Override
445    public List<SequenceFeature> getSequenceFeatures()
446    {
447  16457 if (datasetSequence != null)
448    {
449  3514 return datasetSequence.getSequenceFeatures();
450    }
451  12943 return sequenceFeatureStore.getAllFeatures();
452    }
453   
 
454  384913 toggle @Override
455    public SequenceFeaturesI getFeatures()
456    {
457  384949 return datasetSequence != null ? datasetSequence.getFeatures()
458    : sequenceFeatureStore;
459    }
460   
 
461  1416 toggle @Override
462    public boolean addPDBId(PDBEntry entry)
463    {
464  1416 if (pdbIds == null)
465    {
466  475 pdbIds = new Vector<>();
467  475 pdbIds.add(entry);
468  475 return true;
469    }
470   
471  941 for (PDBEntry pdbe : pdbIds)
472    {
473  17764 if (pdbe.updateFrom(entry))
474    {
475  567 return false;
476    }
477    }
478  374 pdbIds.addElement(entry);
479  374 return true;
480    }
481   
482    /**
483    * DOCUMENT ME!
484    *
485    * @param id
486    * DOCUMENT ME!
487    */
 
488  24 toggle @Override
489    public void setPDBId(Vector<PDBEntry> id)
490    {
491  24 pdbIds = id;
492    }
493   
494    /**
495    * DOCUMENT ME!
496    *
497    * @return DOCUMENT ME!
498    */
 
499  24396 toggle @Override
500    public Vector<PDBEntry> getAllPDBEntries()
501    {
502  24396 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  52386 toggle @Override
511    public String getDisplayId(boolean jvsuffix)
512    {
513  52386 if (!jvsuffix)
514    {
515  676 return name;
516    }
517  51710 StringBuilder result = new StringBuilder(name);
518  51710 result.append("/").append(start).append("-").append(end);
519   
520  51710 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  1315 toggle @Override
530    public void setName(String theName)
531    {
532  1315 this.name = theName;
533  1315 this.parseId();
534    }
535   
536    /**
537    * DOCUMENT ME!
538    *
539    * @return DOCUMENT ME!
540    */
 
541  1052499 toggle @Override
542    public String getName()
543    {
544  1052499 return this.name;
545    }
546   
547    /**
548    * DOCUMENT ME!
549    *
550    * @param start
551    * DOCUMENT ME!
552    */
 
553  19327 toggle @Override
554    public void setStart(int start)
555    {
556  19327 this.start = start;
557  19327 sequenceChanged();
558    }
559   
560    /**
561    * DOCUMENT ME!
562    *
563    * @return DOCUMENT ME!
564    */
 
565  327374 toggle @Override
566    public int getStart()
567    {
568  327374 return this.start;
569    }
570   
571    /**
572    * DOCUMENT ME!
573    *
574    * @param end
575    * DOCUMENT ME!
576    */
 
577  19357 toggle @Override
578    public void setEnd(int end)
579    {
580  19357 this.end = end;
581    }
582   
583    /**
584    * DOCUMENT ME!
585    *
586    * @return DOCUMENT ME!
587    */
 
588  606028 toggle @Override
589    public int getEnd()
590    {
591  606029 return this.end;
592    }
593   
594    /**
595    * DOCUMENT ME!
596    *
597    * @return DOCUMENT ME!
598    */
 
599  18300640 toggle @Override
600    public int getLength()
601    {
602  18355087 return this.sequence.length;
603    }
604   
 
605  33 toggle @Override
606    public void setSequence(char[] seq)
607    {
608  33 this.sequence = Arrays.copyOf(seq, seq.length);
609  33 checkValidRange();
610  33 sequenceChanged();
611    }
612   
613    /**
614    * DOCUMENT ME!
615    *
616    * @param seq
617    * DOCUMENT ME!
618    */
 
619  13218 toggle @Override
620    public void setSequence(String seq)
621    {
622  13218 this.sequence = seq.toCharArray();
623  13218 checkValidRange();
624  13218 sequenceChanged();
625    }
626   
 
627  37229 toggle @Override
628    public String getSequenceAsString()
629    {
630  37229 return new String(sequence);
631    }
632   
 
633  21776 toggle @Override
634    public String getSequenceAsString(int start, int end)
635    {
636  21776 return new String(getSequence(start, end));
637    }
638   
 
639  16848 toggle @Override
640    public char[] getSequence()
641    {
642    // return sequence;
643  16848 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  23803 toggle @Override
653    public char[] getSequence(int start, int end)
654    {
655  23803 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  23803 if (start >= sequence.length)
662    {
663  6 return new char[0];
664    }
665   
666  23797 if (end >= sequence.length)
667    {
668  3539 end = sequence.length;
669    }
670   
671  23797 char[] reply = new char[end - start];
672  23797 System.arraycopy(sequence, start, reply, 0, end - start);
673   
674  23797 return reply;
675    }
676   
 
677  41 toggle @Override
678    public SequenceI getSubSequence(int start, int end)
679    {
680  41 if (start < 0)
681    {
682  0 start = 0;
683    }
684  41 char[] seq = getSequence(start, end);
685  41 if (seq.length == 0)
686    {
687  0 return null;
688    }
689  41 int nstart = findPosition(start);
690  41 int nend = findPosition(end) - 1;
691    // JBPNote - this is an incomplete copy.
692  41 SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
693  41 nseq.setDescription(description);
694  41 if (datasetSequence != null)
695    {
696  25 nseq.setDatasetSequence(datasetSequence);
697    }
698    else
699    {
700  16 nseq.setDatasetSequence(this);
701    }
702  41 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  30400311 toggle @Override
712    public char getCharAt(int i)
713    {
714  30519404 if (i >= 0 && i < sequence.length)
715    {
716  30504993 return sequence[i];
717    }
718    else
719    {
720  32900 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  27391 toggle @Override
731    public void setDescription(String desc)
732    {
733  27391 this.description = desc;
734    }
735   
 
736  35 toggle @Override
737    public void setGeneLoci(String speciesId, String assemblyId,
738    String chromosomeId, MapList map)
739    {
740  35 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  68 toggle @Override
750    public GeneLociI getGeneLoci()
751    {
752  68 List<DBRefEntry> refs = getDBRefs();
753  68 if (refs != null)
754    {
755  50 for (final DBRefEntry ref : refs)
756    {
757  63 if (ref instanceof GeneLociI)
758    {
759  35 return (GeneLociI) ref;
760    }
761    }
762    }
763  33 return null;
764    }
765   
766    /**
767    * Answers the description
768    *
769    * @return
770    */
 
771  13366 toggle @Override
772    public String getDescription()
773    {
774  13366 return this.description;
775    }
776   
777    /**
778    * {@inheritDoc}
779    */
 
780  730004 toggle @Override
781    public int findIndex(int pos)
782    {
783    /*
784    * use a valid, hopefully nearby, cursor if available
785    */
786  730006 if (isValidCursor(cursor))
787    {
788  728630 return findIndex(pos, cursor);
789    }
790   
791  1376 int j = start;
792  1376 int i = 0;
793  1376 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  432959 while ((i < sequence.length) && (j <= end) && (j <= pos))
800    {
801  431583 if (!Comparison.isGap(sequence[i]))
802    {
803  2570 if (j == start)
804    {
805  1370 startColumn = i;
806    }
807  2570 j++;
808    }
809  431583 i++;
810    }
811   
812  1376 if (j == end && j < pos)
813    {
814  0 return end + 1;
815    }
816   
817  1376 updateCursor(pos, i, startColumn);
818  1376 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  1058351 toggle protected void updateCursor(int residuePos, int column, int startColumn)
832    {
833    /*
834    * preserve end residue column provided cursor was valid
835    */
836  1058373 int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
837   
838  1058386 if (residuePos == this.end)
839    {
840  11713 endColumn = column;
841    }
842   
843  1058387 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  728725 toggle protected int findIndex(final int pos, SequenceCursor curs)
857    {
858  728724 if (!isValidCursor(curs))
859    {
860    /*
861    * wrong or invalidated cursor, compute de novo
862    */
863  0 return findIndex(pos);
864    }
865   
866  728726 if (curs.residuePosition == pos)
867    {
868  28937 return curs.columnPosition;
869    }
870   
871    /*
872    * move left or right to find pos from hint.position
873    */
874  699789 int col = curs.columnPosition - 1; // convert from base 1 to base 0
875  699789 int newPos = curs.residuePosition;
876  699796 int delta = newPos > pos ? -1 : 1;
877   
878  2870303 while (newPos != pos)
879    {
880  2170565 col += delta; // shift one column left or right
881  2170564 if (col < 0)
882    {
883  11 break;
884    }
885  2170565 if (col == sequence.length)
886    {
887  50 col--; // return last column if we failed to reach pos
888  50 break;
889    }
890  2170523 if (!Comparison.isGap(sequence[col]))
891    {
892  1579369 newPos += delta;
893    }
894    }
895   
896  699799 col++; // convert back to base 1
897   
898    /*
899    * only update cursor if we found the target position
900    */
901  699799 if (newPos == pos)
902    {
903  699740 updateCursor(pos, col, curs.firstColumnPosition);
904    }
905   
906  699809 return col;
907    }
908   
909    /**
910    * {@inheritDoc}
911    */
 
912  565278 toggle @Override
913    public int findPosition(final int column)
914    {
915    /*
916    * use a valid, hopefully nearby, cursor if available
917    */
918  565292 if (isValidCursor(cursor))
919    {
920  562843 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  2447 int firstResidueColumn = 0;
931  2447 int lastPosFound = 0;
932  2447 int lastPosFoundColumn = 0;
933  2447 int seqlen = sequence.length;
934   
935  2447 if (seqlen > 0 && !Comparison.isGap(sequence[0]))
936    {
937  1953 lastPosFound = start;
938  1953 lastPosFoundColumn = 0;
939    }
940   
941  2447 int j = 0;
942  2447 int pos = start;
943   
944  49536 while (j < column && j < seqlen)
945    {
946  47089 if (!Comparison.isGap(sequence[j]))
947    {
948  34701 lastPosFound = pos;
949  34701 lastPosFoundColumn = j;
950  34701 if (pos == this.start)
951    {
952  726 firstResidueColumn = j;
953    }
954  34701 pos++;
955    }
956  47089 j++;
957    }
958  2447 if (j < seqlen && !Comparison.isGap(sequence[j]))
959    {
960  2045 lastPosFound = pos;
961  2045 lastPosFoundColumn = j;
962  2045 if (pos == this.start)
963    {
964  1603 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  2447 if (lastPosFound != 0)
973    {
974  2328 updateCursor(lastPosFound, lastPosFoundColumn + 1,
975    firstResidueColumn + 1);
976    }
977   
978  2447 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  3644637 toggle protected boolean isValidCursor(SequenceCursor curs)
991    {
992  3644751 if (curs == null || curs.sequence != this || curs.token != changeCount)
993    {
994  7533 return false;
995    }
996    /*
997    * sanity check against range
998    */
999  3637247 if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
1000    {
1001  0 return false;
1002    }
1003  3637326 if (curs.residuePosition < start || curs.residuePosition > end)
1004    {
1005  0 return false;
1006    }
1007  3637375 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  562851 toggle protected int findPosition(final int col, SequenceCursor curs)
1020    {
1021  562854 if (!isValidCursor(curs))
1022    {
1023    /*
1024    * wrong or invalidated cursor, compute de novo
1025    */
1026  2 return findPosition(col - 1);// ugh back to base 0
1027    }
1028   
1029  562910 if (curs.columnPosition == col)
1030    {
1031  200177 cursor = curs; // in case this method becomes public
1032  200181 return curs.residuePosition; // easy case :-)
1033    }
1034   
1035  362733 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  86 return end + 1;
1042    }
1043   
1044  362648 if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
1045    {
1046    /*
1047    * sequence lies entirely to the right of col
1048    * - return first residue
1049    */
1050  91 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  362561 int firstResidueColumn = curs.firstColumnPosition;
1060  362567 int column = curs.columnPosition - 1; // to base 0
1061  362568 int newPos = curs.residuePosition;
1062  362583 int delta = curs.columnPosition > col ? -1 : 1;
1063  362573 boolean gapped = false;
1064  362575 int lastFoundPosition = curs.residuePosition;
1065  362575 int lastFoundPositionColumn = curs.columnPosition;
1066   
1067  2587369 while (column != col - 1)
1068    {
1069  2225042 column += delta; // shift one column left or right
1070  2224606 if (column < 0 || column == sequence.length)
1071    {
1072  176 break;
1073    }
1074  2224537 gapped = Comparison.isGap(sequence[column]);
1075  2224700 if (!gapped)
1076    {
1077  1696074 newPos += delta;
1078  1696118 lastFoundPosition = newPos;
1079  1696168 lastFoundPositionColumn = column + 1;
1080  1696482 if (lastFoundPosition == this.start)
1081    {
1082  2122 firstResidueColumn = column + 1;
1083    }
1084    }
1085    }
1086   
1087  362579 if (cursor == null || lastFoundPosition != cursor.residuePosition)
1088    {
1089  354989 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  362602 if (delta > 0 && (gapped || column >= sequence.length))
1098    {
1099  7784 newPos++;
1100    }
1101   
1102  362601 return newPos;
1103    }
1104   
1105    /**
1106    * {@inheritDoc}
1107    */
 
1108  1944 toggle @Override
1109    public ContiguousI findPositions(int fromColumn, int toColumn)
1110    {
1111  1944 fromColumn = Math.max(fromColumn, 1);
1112  1944 if (toColumn < fromColumn)
1113    {
1114  8 return null;
1115    }
1116   
1117    /*
1118    * find the first non-gapped position, if any
1119    */
1120  1936 int firstPosition = 0;
1121  1936 int col = fromColumn - 1;
1122  1936 int length = sequence.length;
1123  13419 while (col < length && col < toColumn)
1124    {
1125  13287 if (!Comparison.isGap(sequence[col]))
1126    {
1127  1804 firstPosition = findPosition(col++);
1128  1804 break;
1129    }
1130  11483 col++;
1131    }
1132   
1133  1936 if (firstPosition == 0)
1134    {
1135  132 return null;
1136    }
1137   
1138    /*
1139    * find the last non-gapped position
1140    */
1141  1804 int lastPosition = firstPosition;
1142  126205 while (col < length && col < toColumn)
1143    {
1144  124401 if (!Comparison.isGap(sequence[col++]))
1145    {
1146  118297 lastPosition++;
1147    }
1148    }
1149   
1150  1804 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  29 toggle @Override
1161    public int[] gapMap()
1162    {
1163  29 String seq = jalview.analysis.AlignSeq.extractGaps(
1164    jalview.util.Comparison.GapChars, new String(sequence));
1165  29 int[] map = new int[seq.length()];
1166  29 int j = 0;
1167  29 int p = 0;
1168   
1169  702 while (j < sequence.length)
1170    {
1171  673 if (!jalview.util.Comparison.isGap(sequence[j]))
1172    {
1173  616 map[p++] = j;
1174    }
1175   
1176  673 j++;
1177    }
1178   
1179  29 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  37 toggle @Override
1188    public BitSet gapBitset()
1189    {
1190  37 BitSet gaps = new BitSet(sequence.length);
1191  37 int j = 0;
1192  576 while (j < sequence.length)
1193    {
1194  539 if (jalview.util.Comparison.isGap(sequence[j]))
1195    {
1196  48 gaps.set(j);
1197    }
1198  539 j++;
1199    }
1200  37 return gaps;
1201    }
1202   
 
1203  327 toggle @Override
1204    public int[] findPositionMap()
1205    {
1206  327 int map[] = new int[sequence.length];
1207  327 int j = 0;
1208  327 int pos = start;
1209  327 int seqlen = sequence.length;
1210  24789 while ((j < seqlen))
1211    {
1212  24462 map[j] = pos;
1213  24462 if (!jalview.util.Comparison.isGap(sequence[j]))
1214    {
1215  16901 pos++;
1216    }
1217   
1218  24462 j++;
1219    }
1220  327 return map;
1221    }
1222   
 
1223  4 toggle @Override
1224    public List<int[]> getInsertions()
1225    {
1226  4 ArrayList<int[]> map = new ArrayList<>();
1227  4 int lastj = -1, j = 0;
1228    // int pos = start;
1229  4 int seqlen = sequence.length;
1230  220 while ((j < seqlen))
1231    {
1232  216 if (jalview.util.Comparison.isGap(sequence[j]))
1233    {
1234  25 if (lastj == -1)
1235    {
1236  11 lastj = j;
1237    }
1238    }
1239    else
1240    {
1241  191 if (lastj != -1)
1242    {
1243  10 map.add(new int[] { lastj, j - 1 });
1244  10 lastj = -1;
1245    }
1246    }
1247  216 j++;
1248    }
1249  4 if (lastj != -1)
1250    {
1251  1 map.add(new int[] { lastj, j - 1 });
1252  1 lastj = -1;
1253    }
1254  4 return map;
1255    }
1256   
 
1257  5 toggle @Override
1258    public BitSet getInsertionsAsBits()
1259    {
1260  5 BitSet map = new BitSet();
1261  5 int lastj = -1, j = 0;
1262    // int pos = start;
1263  5 int seqlen = sequence.length;
1264  187 while ((j < seqlen))
1265    {
1266  182 if (jalview.util.Comparison.isGap(sequence[j]))
1267    {
1268  43 if (lastj == -1)
1269    {
1270  16 lastj = j;
1271    }
1272    }
1273    else
1274    {
1275  139 if (lastj != -1)
1276    {
1277  15 map.set(lastj, j);
1278  15 lastj = -1;
1279    }
1280    }
1281  182 j++;
1282    }
1283  5 if (lastj != -1)
1284    {
1285  1 map.set(lastj, j);
1286  1 lastj = -1;
1287    }
1288  5 return map;
1289    }
1290   
 
1291  175 toggle @Override
1292    public void deleteChars(final int i, final int j)
1293    {
1294  175 int newstart = start, newend = end;
1295  175 if (i >= sequence.length || i < 0)
1296    {
1297  3 return;
1298    }
1299   
1300  172 char[] tmp = StringUtils.deleteChars(sequence, i, j);
1301  172 boolean createNewDs = false;
1302    // TODO: take a (second look) at the dataset creation validation method for
1303    // the very large sequence case
1304   
1305  172 int startIndex = findIndex(start) - 1;
1306  172 int endIndex = findIndex(end) - 1;
1307  172 int startDeleteColumn = -1; // for dataset sequence deletions
1308  172 int deleteCount = 0;
1309   
1310  527 for (int s = i; s < j && s < sequence.length; s++)
1311    {
1312  397 if (Comparison.isGap(sequence[s]))
1313    {
1314  30 continue;
1315    }
1316  367 deleteCount++;
1317  367 if (startDeleteColumn == -1)
1318    {
1319  154 startDeleteColumn = findPosition(s) - start;
1320    }
1321  367 if (createNewDs)
1322    {
1323  213 newend--;
1324    }
1325    else
1326    {
1327  154 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  21 newstart = findPosition(j);
1335  21 break;
1336    }
1337    else
1338    {
1339  133 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  21 newend = findPosition(i - 1);
1347  21 if (Comparison.isGap(sequence[i - 1]))
1348    {
1349  1 newend--;
1350    }
1351  21 break;
1352    }
1353    else
1354    {
1355  112 createNewDs = true;
1356  112 newend--;
1357    }
1358    }
1359    }
1360    }
1361   
1362  172 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  55 Sequence ds = new Sequence(datasetSequence);
1370  55 ds.deleteChars(startDeleteColumn, startDeleteColumn + deleteCount);
1371  55 datasetSequence = ds;
1372    // TODO: remove any non-inheritable properties ?
1373    // TODO: create a sequence mapping (since there is a relation here ?)
1374    }
1375  172 start = newstart;
1376  172 end = newend;
1377  172 sequence = tmp;
1378  172 sequenceChanged();
1379    }
1380   
 
1381  719 toggle @Override
1382    public void insertCharAt(int i, int length, char c)
1383    {
1384  719 doInsert(i, length, c);
1385  719 sequenceChanged();
1386    }
1387   
 
1388  1 toggle @Override
1389    public void insertInserts(char c, int[]...inserts)
1390    {
1391    // TODO optimise further by precomputing new char length and arrayCopies.
1392  1 if (inserts==null)
1393    {
1394  0 return;
1395    }
1396  1 for (int[] insert:inserts)
1397    {
1398  2 doInsert(insert[0],insert[1],c);
1399    }
1400  1 sequenceChanged();
1401    }
 
1402  6391 toggle @Override
1403    public void doInsert(int i, int length, char c)
1404    {
1405   
1406  6391 char[] tmp = new char[sequence.length + length];
1407   
1408  6391 if (i >= sequence.length)
1409    {
1410  66 System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1411  66 i = sequence.length;
1412    }
1413    else
1414    {
1415  6325 System.arraycopy(sequence, 0, tmp, 0, i);
1416    }
1417   
1418  6391 int index = i;
1419  35501 while (length > 0)
1420    {
1421  29110 tmp[index++] = c;
1422  29110 length--;
1423    }
1424   
1425  6391 if (i < sequence.length)
1426    {
1427  6325 System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1428    }
1429   
1430  6391 sequence = tmp;
1431   
1432    }
1433   
 
1434  608 toggle @Override
1435    public void insertCharAt(int i, char c)
1436    {
1437  608 insertCharAt(i, 1, c);
1438    }
1439   
 
1440  0 toggle @Override
1441    public String getVamsasId()
1442    {
1443  0 return vamsasId;
1444    }
1445   
 
1446  15608 toggle @Override
1447    public void setVamsasId(String id)
1448    {
1449  15608 vamsasId = id;
1450    }
1451   
 
1452  8 toggle @Deprecated
1453    @Override
1454    public void setDBRefs(DBModList<DBRefEntry> newDBrefs)
1455    {
1456  8 if (dbrefs == null && datasetSequence != null
1457    && this != datasetSequence)
1458    {
1459  2 datasetSequence.setDBRefs(newDBrefs);
1460  2 return;
1461    }
1462  6 dbrefs = newDBrefs;
1463  6 refModCount = 0;
1464    }
1465   
 
1466  48719 toggle @Override
1467    public DBModList<DBRefEntry> getDBRefs()
1468    {
1469  48719 if (dbrefs == null && datasetSequence != null
1470    && this != datasetSequence)
1471    {
1472  14730 return datasetSequence.getDBRefs();
1473    }
1474  33989 return dbrefs;
1475    }
1476   
 
1477  5496 toggle @Override
1478    public void addDBRef(DBRefEntry entry)
1479    {
1480    // TODO JAL-3980 maintain as sorted list
1481  5496 if (datasetSequence != null)
1482    {
1483  196 datasetSequence.addDBRef(entry);
1484  196 return;
1485    }
1486   
1487  5300 if (dbrefs == null)
1488    {
1489  1694 dbrefs = new DBModList<>();
1490    }
1491    // TODO JAL-3979 LOOK UP RATHER THAN SWEEP FOR EFFICIENCY
1492   
1493  110525 for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
1494    {
1495  105264 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  39 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  5261 dbrefs.add(entry);
1518    }
1519   
 
1520  1907 toggle @Override
1521    public void setDatasetSequence(SequenceI seq)
1522    {
1523  1907 if (seq == this)
1524    {
1525  1 throw new IllegalArgumentException(
1526    "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1527    }
1528  1906 if (seq != null && seq.getDatasetSequence() != null)
1529    {
1530  2 throw new IllegalArgumentException(
1531    "Implementation error: cascading dataset sequences are not allowed.");
1532    }
1533  1904 datasetSequence = seq;
1534    }
1535   
 
1536  958294 toggle @Override
1537    public SequenceI getDatasetSequence()
1538    {
1539  958419 return datasetSequence;
1540    }
1541   
 
1542  24277 toggle @Override
1543    public AlignmentAnnotation[] getAnnotation()
1544    {
1545  24277 return annotation == null ? null
1546    : annotation
1547    .toArray(new AlignmentAnnotation[annotation.size()]);
1548    }
1549   
 
1550  105 toggle @Override
1551    public boolean hasAnnotation(AlignmentAnnotation ann)
1552    {
1553  105 return annotation == null ? false : annotation.contains(ann);
1554    }
1555   
 
1556  4736 toggle @Override
1557    public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1558    {
1559  4736 if (this.annotation == null)
1560    {
1561  2219 this.annotation = new Vector<>();
1562    }
1563  4736 if (!this.annotation.contains(annotation))
1564    {
1565  4734 this.annotation.addElement(annotation);
1566    }
1567  4736 annotation.setSequenceRef(this);
1568    }
1569   
 
1570  21 toggle @Override
1571    public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1572    {
1573  21 if (this.annotation != null)
1574    {
1575  21 this.annotation.removeElement(annotation);
1576  21 if (this.annotation.size() == 0)
1577    {
1578  21 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  298 toggle private boolean isValidDatasetSequence()
1588    {
1589  298 if (datasetSequence != null)
1590    {
1591  0 return false;
1592    }
1593  66841 for (int i = 0; i < sequence.length; i++)
1594    {
1595  66574 if (jalview.util.Comparison.isGap(sequence[i]))
1596    {
1597  31 return false;
1598    }
1599    }
1600  267 return true;
1601    }
1602   
 
1603  431 toggle @Override
1604    public SequenceI deriveSequence()
1605    {
1606  431 Sequence seq = null;
1607  431 if (datasetSequence == null)
1608    {
1609  298 if (isValidDatasetSequence())
1610    {
1611    // Use this as dataset sequence
1612  267 seq = new Sequence(getName(), "", 1, -1);
1613  267 seq.setDatasetSequence(this);
1614  267 seq.initSeqFrom(this, getAnnotation());
1615  267 return seq;
1616    }
1617    else
1618    {
1619    // Create a new, valid dataset sequence
1620  31 createDatasetSequence();
1621    }
1622    }
1623  164 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  290049 toggle @Override
1637    public boolean isProtein()
1638    {
1639  290049 if (datasetSequence != null)
1640    {
1641  1118 return datasetSequence.isProtein();
1642    }
1643  288931 if (_seqhash != sequence.hashCode())
1644    {
1645  17467 _seqhash = sequence.hashCode();
1646  17467 _isNa = Comparison.isNucleotide(this);
1647    }
1648  288931 return !_isNa;
1649    }
1650   
1651    /*
1652    * (non-Javadoc)
1653    *
1654    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1655    */
 
1656  6979 toggle @Override
1657    public SequenceI createDatasetSequence()
1658    {
1659  6979 if (datasetSequence == null)
1660    {
1661  6978 Sequence dsseq = new Sequence(getName(),
1662    AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
1663    getSequenceAsString()),
1664    getStart(), getEnd());
1665   
1666  6978 datasetSequence = dsseq;
1667   
1668  6978 dsseq.setDescription(description);
1669    // move features and database references onto dataset sequence
1670  6978 dsseq.sequenceFeatureStore = sequenceFeatureStore;
1671  6978 sequenceFeatureStore = null;
1672  6978 dsseq.dbrefs = dbrefs;
1673  6978 dbrefs = null;
1674    // TODO: search and replace any references to this sequence with
1675    // references to the dataset sequence in Mappings on dbref
1676  6978 dsseq.pdbIds = pdbIds;
1677  6978 pdbIds = null;
1678  6978 datasetSequence.updatePDBIds();
1679  6978 if (annotation != null)
1680    {
1681    // annotation is cloned rather than moved, to preserve what's currently
1682    // on the alignment
1683  284 for (AlignmentAnnotation aa : annotation)
1684    {
1685  314 AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1686  314 _aa.sequenceRef = datasetSequence;
1687  314 _aa.adjustForAlignment(); // uses annotation's own record of
1688    // sequence-column mapping
1689  314 datasetSequence.addAlignmentAnnotation(_aa);
1690   
1691  314 if (_cmholder != null)
1692    { // transfer contact matrices
1693  1 ContactMatrixI cm = _cmholder.getContactMatrixFor(aa);
1694  1 if (cm != null)
1695    {
1696  1 datasetSequence.addContactListFor(_aa, cm);
1697  1 datasetSequence.addContactListFor(aa, cm);
1698    }
1699    }
1700    }
1701    }
1702    // all matrices should have been transferred. so we clear the local holder
1703  6978 _cmholder = null;
1704    }
1705  6979 return datasetSequence;
1706    }
1707   
1708    /*
1709    * (non-Javadoc)
1710    *
1711    * @see
1712    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1713    * annotations)
1714    */
 
1715  2 toggle @Override
1716    public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1717    {
1718  2 if (annotation != null)
1719    {
1720  2 annotation.removeAllElements();
1721    }
1722  2 if (annotations != null)
1723    {
1724  2 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  12535335 toggle @Override
1735    public AlignmentAnnotation[] getAnnotation(String label)
1736    {
1737  12536312 if (annotation == null || annotation.size() == 0)
1738    {
1739  12220714 return null;
1740    }
1741   
1742  315888 Vector<AlignmentAnnotation> subset = new Vector<>();
1743  315870 Enumeration<AlignmentAnnotation> e = annotation.elements();
1744  1689684 while (e.hasMoreElements())
1745    {
1746  1373928 AlignmentAnnotation ann = e.nextElement();
1747  1374175 if (ann.label != null && ann.label.equals(label))
1748    {
1749  204638 subset.addElement(ann);
1750    }
1751    }
1752  315905 if (subset.size() == 0)
1753    {
1754  146084 return null;
1755    }
1756  169832 AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1757  169823 int i = 0;
1758  169822 e = subset.elements();
1759  374456 while (e.hasMoreElements())
1760    {
1761  204631 anns[i++] = e.nextElement();
1762    }
1763  169828 subset.removeAllElements();
1764  169832 return anns;
1765    }
1766   
 
1767  7023 toggle @Override
1768    public boolean updatePDBIds()
1769    {
1770  7023 if (datasetSequence != null)
1771    {
1772    // TODO: could merge DBRefs
1773  22 return datasetSequence.updatePDBIds();
1774    }
1775  7001 if (dbrefs == null || dbrefs.size() == 0)
1776    {
1777  6480 return false;
1778    }
1779  521 boolean added = false;
1780  1930 for (int ib = 0, nb = dbrefs.size(); ib < nb; ib++)
1781    {
1782  1409 DBRefEntry dbr = dbrefs.get(ib);
1783  1409 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  194 PDBEntry pdbe = new PDBEntry(dbr);
1792  194 added |= addPDBId(pdbe);
1793    }
1794    }
1795  521 return added;
1796    }
1797   
 
1798  17 toggle @Override
1799    public void transferAnnotation(SequenceI entry, Mapping mp)
1800    {
1801  17 if (datasetSequence != null)
1802    {
1803  0 datasetSequence.transferAnnotation(entry, mp);
1804  0 return;
1805    }
1806  17 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  17 if (entry.getDescription() != null
1814    && (description == null || description.trim().length() == 0))
1815    {
1816  1 description = entry.getDescription();
1817    }
1818   
1819    // transfer any new features from entry onto sequence
1820  17 if (entry.getSequenceFeatures() != null)
1821    {
1822   
1823  17 List<SequenceFeature> sfs = entry.getSequenceFeatures();
1824  17 for (SequenceFeature feature : sfs)
1825    {
1826  3352 SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1827    : new SequenceFeature[]
1828    { new SequenceFeature(feature) };
1829  3352 if (sf != null)
1830    {
1831  6704 for (int sfi = 0; sfi < sf.length; sfi++)
1832    {
1833  3352 addSequenceFeature(sf[sfi]);
1834    }
1835    }
1836    }
1837    }
1838   
1839    // transfer PDB entries
1840  17 if (entry.getAllPDBEntries() != null)
1841    {
1842  17 Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1843  31 while (e.hasMoreElements())
1844    {
1845  14 PDBEntry pdb = e.nextElement();
1846  14 addPDBId(pdb);
1847    }
1848    }
1849    // transfer database references
1850  17 List<DBRefEntry> entryRefs = entry.getDBRefs();
1851  17 if (entryRefs != null)
1852    {
1853  34 for (int r = 0, n = entryRefs.size(); r < n; r++)
1854    {
1855  17 DBRefEntry newref = new DBRefEntry(entryRefs.get(r));
1856  17 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  17 addDBRef(newref);
1866    }
1867    }
1868    }
1869   
 
1870  3 toggle @Override
1871    public void setRNA(RNA r)
1872    {
1873  3 rna = r;
1874    }
1875   
 
1876  0 toggle @Override
1877    public RNA getRNA()
1878    {
1879  0 return rna;
1880    }
1881   
 
1882  249 toggle @Override
1883    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1884    String label)
1885    {
1886  249 return getAlignmentAnnotations(calcId, label, null, true);
1887    }
1888   
 
1889  436 toggle @Override
1890    public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1891    String label, String description)
1892    {
1893  436 return getAlignmentAnnotations(calcId, label, description, false);
1894    }
1895   
 
1896  685 toggle private List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1897    String label, String description, boolean ignoreDescription)
1898    {
1899  685 List<AlignmentAnnotation> result = new ArrayList<>();
1900  685 if (this.annotation != null)
1901    {
1902  518 for (AlignmentAnnotation ann : annotation)
1903    {
1904  1389 String id = ann.getCalcId();
1905  1389 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  236 result.add(ann);
1912    }
1913    }
1914    }
1915  685 return result;
1916    }
1917   
 
1918  239 toggle @Override
1919    public String toString()
1920    {
1921  239 return getDisplayId(false);
1922    }
1923   
 
1924  32 toggle @Override
1925    public PDBEntry getPDBEntry(String pdbIdStr)
1926    {
1927  32 if (getDatasetSequence() != null)
1928    {
1929  3 return getDatasetSequence().getPDBEntry(pdbIdStr);
1930    }
1931  29 if (pdbIds == null)
1932    {
1933  3 return null;
1934    }
1935  26 List<PDBEntry> entries = getAllPDBEntries();
1936  26 for (PDBEntry entry : entries)
1937    {
1938  40 if (entry.getId().equalsIgnoreCase(pdbIdStr))
1939    {
1940  10 return entry;
1941    }
1942    }
1943  16 return null;
1944    }
1945   
1946    private List<DBRefEntry> tmpList;
1947   
 
1948  578 toggle @Override
1949    public List<DBRefEntry> getPrimaryDBRefs()
1950    {
1951  578 if (datasetSequence != null)
1952    {
1953  276 return datasetSequence.getPrimaryDBRefs();
1954    }
1955  302 if (dbrefs == null || dbrefs.size() == 0)
1956    {
1957  9 return Collections.emptyList();
1958    }
1959  293 synchronized (dbrefs)
1960    {
1961  293 if (refModCount == dbrefs.getModCount() && primaryRefs != null)
1962    {
1963  5 return primaryRefs; // no changes
1964    }
1965  288 refModCount = dbrefs.getModCount();
1966  288 List<DBRefEntry> primaries = (primaryRefs == null
1967    ? (primaryRefs = new ArrayList<>())
1968    : primaryRefs);
1969  288 primaries.clear();
1970  288 if (tmpList == null)
1971    {
1972  286 tmpList = new ArrayList<>();
1973  286 tmpList.add(null); // for replacement
1974    }
1975  1147 for (int i = 0, n = dbrefs.size(); i < n; i++)
1976    {
1977  859 DBRefEntry ref = dbrefs.get(i);
1978  859 if (!ref.isPrimaryCandidate())
1979    {
1980  807 continue;
1981    }
1982  52 if (ref.hasMap())
1983    {
1984  5 MapList mp = ref.getMap().getMap();
1985  5 if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1986    {
1987    // map only involves a subsequence, so cannot be primary
1988  2 continue;
1989    }
1990    }
1991    // whilst it looks like it is a primary ref, we also sanity check type
1992  50 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  21 PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
2002  21 if (pdbentry == null || pdbentry.getFile() == null)
2003    {
2004  18 continue;
2005    }
2006    }
2007    else
2008    {
2009    // check standard protein or dna sources
2010  29 tmpList.set(0, ref);
2011  29 List<DBRefEntry> res = DBRefUtils.selectDbRefs(!isProtein(),
2012    tmpList);
2013  29 if (res == null || res.get(0) != tmpList.get(0))
2014    {
2015  6 continue;
2016    }
2017    }
2018  26 primaries.add(ref);
2019    }
2020   
2021    // version must be not null, as otherwise it will not be a candidate,
2022    // above
2023  288 DBRefUtils.ensurePrimaries(this, primaries);
2024  288 return primaries;
2025    }
2026    }
2027   
 
2028  596 toggle @Override
2029    public HiddenMarkovModel getHMM()
2030    {
2031  596 return hmm;
2032    }
2033   
 
2034  19 toggle @Override
2035    public void setHMM(HiddenMarkovModel hmm)
2036    {
2037  19 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  176448 toggle @Override
2059    public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
2060    String... types)
2061    {
2062  176448 int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
2063  176448 int endPos = fromColumn == toColumn ? startPos
2064    : findPosition(toColumn - 1);
2065   
2066  176448 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  176448 boolean endColumnIsGapped = toColumn > 0 && toColumn <= sequence.length
2075    && Comparison.isGap(sequence[toColumn - 1]);
2076  176448 if (endPos > this.end || endColumnIsGapped)
2077    {
2078  65 ListIterator<SequenceFeature> it = result.listIterator();
2079  474 while (it.hasNext())
2080    {
2081  409 SequenceFeature sf = it.next();
2082  409 int sfBegin = sf.getBegin();
2083  409 int sfEnd = sf.getEnd();
2084  409 int featureStartColumn = findIndex(sfBegin);
2085  409 if (featureStartColumn > toColumn)
2086    {
2087  3 it.remove();
2088    }
2089  406 else if (featureStartColumn < fromColumn)
2090    {
2091  7 int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
2092    : findIndex(sfEnd);
2093  7 if (featureEndColumn < fromColumn)
2094    {
2095  0 it.remove();
2096    }
2097  7 else if (featureEndColumn > toColumn && sf.isContactFeature())
2098    {
2099    /*
2100    * remove an enclosing feature if it is a contact feature
2101    */
2102  3 it.remove();
2103    }
2104    }
2105    }
2106    }
2107   
2108  176448 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  33586 toggle @Override
2116    public void sequenceChanged()
2117    {
2118  33586 changeCount++;
2119    }
2120   
2121    /**
2122    * {@inheritDoc}
2123    */
 
2124  6 toggle @Override
2125    public int replace(char c1, char c2)
2126    {
2127  6 if (c1 == c2)
2128    {
2129  1 return 0;
2130    }
2131  5 int count = 0;
2132  5 synchronized (sequence)
2133    {
2134  57 for (int c = 0; c < sequence.length; c++)
2135    {
2136  52 if (sequence[c] == c1)
2137    {
2138  12 sequence[c] = c2;
2139  12 count++;
2140    }
2141    }
2142    }
2143  5 if (count > 0)
2144    {
2145  4 sequenceChanged();
2146    }
2147   
2148  5 return count;
2149    }
2150   
 
2151  189 toggle @Override
2152    public String getSequenceStringFromIterator(Iterator<int[]> it)
2153    {
2154  189 StringBuilder newSequence = new StringBuilder();
2155  382 while (it.hasNext())
2156    {
2157  193 int[] block = it.next();
2158  193 if (it.hasNext())
2159    {
2160  4 newSequence.append(getSequence(block[0], block[1] + 1));
2161    }
2162    else
2163    {
2164  189 newSequence.append(getSequence(block[0], block[1]));
2165    }
2166    }
2167   
2168  189 return newSequence.toString();
2169    }
2170   
 
2171  43 toggle @Override
2172    public int firstResidueOutsideIterator(Iterator<int[]> regions)
2173    {
2174  43 int start = 0;
2175   
2176  43 if (!regions.hasNext())
2177    {
2178  16 return findIndex(getStart()) - 1;
2179    }
2180   
2181    // Simply walk along the sequence whilst watching for region
2182    // boundaries
2183  27 int hideStart = getLength();
2184  27 int hideEnd = -1;
2185  27 boolean foundStart = false;
2186   
2187    // step through the non-gapped positions of the sequence
2188  187 for (int i = getStart(); i <= getEnd() && (!foundStart); i++)
2189    {
2190    // get alignment position of this residue in the sequence
2191  160 int p = findIndex(i) - 1;
2192   
2193    // update region start/end
2194  200 while (hideEnd < p && regions.hasNext())
2195    {
2196  40 int[] region = regions.next();
2197  40 hideStart = region[0];
2198  40 hideEnd = region[1];
2199    }
2200  160 if (hideEnd < p)
2201    {
2202  7 hideStart = getLength();
2203    }
2204    // update boundary for sequence
2205  160 if (p < hideStart)
2206    {
2207  15 start = p;
2208  15 foundStart = true;
2209    }
2210    }
2211   
2212  27 if (foundStart)
2213    {
2214  15 return start;
2215    }
2216    // otherwise, sequence was completely hidden
2217  12 return 0;
2218    }
2219   
2220    ////
2221    //// Contact Matrix Holder Boilerplate
2222    ////
2223    ContactMapHolderI _cmholder = null;
2224   
 
2225  13609 toggle private ContactMapHolderI getContactMapHolder()
2226    {
2227  13609 if (datasetSequence != null)
2228    {
2229  6396 return ((Sequence) datasetSequence).getContactMapHolder();
2230    }
2231  7213 if (_cmholder == null)
2232    {
2233  267 _cmholder = new ContactMapHolder();
2234    }
2235  7213 return _cmholder;
2236    }
2237   
 
2238  30 toggle @Override
2239    public Collection<ContactMatrixI> getContactMaps()
2240    {
2241  30 return getContactMapHolder().getContactMaps();
2242    }
2243   
 
2244  760 toggle @Override
2245    public ContactMatrixI getContactMatrixFor(AlignmentAnnotation ann)
2246    {
2247  760 return getContactMapHolder().getContactMatrixFor(ann);
2248    }
2249   
 
2250  6146 toggle @Override
2251    public ContactListI getContactListFor(AlignmentAnnotation _aa, int column)
2252    {
2253  6146 return getContactMapHolder().getContactListFor(_aa, column);
2254    }
2255   
 
2256  159 toggle @Override
2257    public AlignmentAnnotation addContactList(ContactMatrixI cm)
2258    {
2259  159 AlignmentAnnotation aa;
2260   
2261  159 if (datasetSequence != null)
2262    {
2263  2 aa = datasetSequence.addContactList(cm);
2264    // clone the annotation for the local sequence
2265  2 aa = new AlignmentAnnotation(aa);
2266  2 aa.restrict(start, end);
2267  2 aa.adjustForAlignment();
2268  2 getContactMapHolder().addContactListFor(aa, cm);
2269  2 addAlignmentAnnotation(aa);
2270  2 return aa;
2271    }
2272   
2273    // construct new annotation for matrix on dataset sequence
2274  157 aa = getContactMapHolder().addContactList(cm);
2275   
2276  157 Annotation _aa[] = new Annotation[getLength()];
2277   
2278  66037 for (int i = 0; i < _aa.length; _aa[i++] = new Annotation(0.0f))
2279    {
2280  65880 ;
2281    }
2282  157 aa.annotations = _aa;
2283  157 aa.setSequenceRef(this);
2284  157 if (cm instanceof MappableContactMatrix
2285    && !((MappableContactMatrix) cm).hasReferenceSeq())
2286    {
2287  63 ((MappableContactMatrix) cm).setRefSeq(this);
2288    }
2289  157 aa.createSequenceMapping(this, getStart(), false);
2290  157 addAlignmentAnnotation(aa);
2291  157 return aa;
2292    }
2293   
 
2294  118 toggle @Override
2295    public void addContactListFor(AlignmentAnnotation annotation,
2296    ContactMatrixI cm)
2297    {
2298  118 getContactMapHolder().addContactListFor(annotation, cm);
2299    }
2300   
 
2301  28124 toggle @Override
2302    public boolean hasHMMProfile()
2303    {
2304  28124 return hmm != null;
2305    }
2306    }