1. Project Clover database Fri Dec 6 2024 13:47:14 GMT
  2. Package jalview.datamodel

File Sequence.java

 

Coverage histogram

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

Code metrics

406
661
91
2
2,219
1,543
349
0.53
7.26
45.5
3.84

Classes

Class
Line #
Actions
Sequence 50 660 348
0.9524221495.2%
Sequence.DBModList 62 1 1
1.0100%
 

Contributing tests

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