Clover icon

jalviewX

  1. Project Clover database Wed Oct 31 2018 15:13:58 GMT
  2. Package jalview.datamodel

File Sequence.java

 

Coverage histogram

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

Code metrics

378
620
86
1
2,067
1,441
321
0.52
7.21
86
3.73

Classes

Class Line # Actions
Sequence 51 620 321 74
0.931734393.2%
 

Contributing tests

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