Clover icon

Coverage Report

  1. Project Clover database Thu Aug 13 2020 12:04:21 BST
  2. Package jalview.datamodel

File Sequence.java

 

Coverage histogram

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

Code metrics

388
619
83
2
2,091
1,440
325
0.53
7.46
41.5
3.92

Classes

Class Line # Actions
Sequence 47 618 324
0.937593.8%
Sequence.DBModList 59 1 1
1.0100%
 

Contributing tests

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