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