7ecb07a9f4edf237e4728c875ea0aa8f3c58bd0f
[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   }
547
548   @Override
549   public String getSequenceAsString()
550   {
551     return new String(sequence);
552   }
553
554   @Override
555   public String getSequenceAsString(int start, int end)
556   {
557     return new String(getSequence(start, end));
558   }
559
560   @Override
561   public char[] getSequence()
562   {
563     return sequence;
564   }
565
566   /*
567    * (non-Javadoc)
568    * 
569    * @see jalview.datamodel.SequenceI#getSequence(int, int)
570    */
571   @Override
572   public char[] getSequence(int start, int end)
573   {
574     if (start < 0)
575     {
576       start = 0;
577     }
578     // JBPNote - left to user to pad the result here (TODO:Decide on this
579     // policy)
580     if (start >= sequence.length)
581     {
582       return new char[0];
583     }
584
585     if (end >= sequence.length)
586     {
587       end = sequence.length;
588     }
589
590     char[] reply = new char[end - start];
591     System.arraycopy(sequence, start, reply, 0, end - start);
592
593     return reply;
594   }
595
596   @Override
597   public SequenceI getSubSequence(int start, int end)
598   {
599     if (start < 0)
600     {
601       start = 0;
602     }
603     char[] seq = getSequence(start, end);
604     if (seq.length == 0)
605     {
606       return null;
607     }
608     int nstart = findPosition(start);
609     int nend = findPosition(end) - 1;
610     // JBPNote - this is an incomplete copy.
611     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
612     nseq.setDescription(description);
613     if (datasetSequence != null)
614     {
615       nseq.setDatasetSequence(datasetSequence);
616     }
617     else
618     {
619       nseq.setDatasetSequence(this);
620     }
621     return nseq;
622   }
623
624   /**
625    * Returns the character of the aligned sequence at the given position (base
626    * zero), or space if the position is not within the sequence's bounds
627    * 
628    * @return
629    */
630   @Override
631   public char getCharAt(int i)
632   {
633     if (i >= 0 && i < sequence.length)
634     {
635       return sequence[i];
636     }
637     else
638     {
639       return ' ';
640     }
641   }
642
643   /**
644    * DOCUMENT ME!
645    * 
646    * @param desc
647    *          DOCUMENT ME!
648    */
649   @Override
650   public void setDescription(String desc)
651   {
652     this.description = desc;
653   }
654
655   /**
656    * DOCUMENT ME!
657    * 
658    * @return DOCUMENT ME!
659    */
660   @Override
661   public String getDescription()
662   {
663     return this.description;
664   }
665
666   /*
667    * (non-Javadoc)
668    * 
669    * @see jalview.datamodel.SequenceI#findIndex(int)
670    */
671   @Override
672   public int findIndex(int pos)
673   {
674     /*
675      * use a valid nearby cursor if available
676      */
677     if (cursor != null && cursor.sequence == this
678             && cursor.token == changeCount)
679     {
680       return findIndex(pos, cursor);
681     }
682
683     int j = start;
684     int i = 0;
685     // Rely on end being at least as long as the length of the sequence.
686     while ((i < sequence.length) && (j <= end) && (j <= pos))
687     {
688       if (!Comparison.isGap(sequence[i]))
689       {
690         j++;
691       }
692       i++;
693     }
694
695     if ((j == end) && (j < pos))
696     {
697       return end + 1;
698     }
699     else
700     {
701       updateCursor(pos, i);
702       return i;
703     }
704   }
705
706   protected void updateCursor(int residuePos, int column)
707   {
708     // TODO probably want to synchronize this on something
709     cursor = new SequenceCursor(this, residuePos, column, this.changeCount);
710   }
711
712   /**
713    * Answers the aligned column position (1..) for the given residue position
714    * (start..) given a 'hint' of a residue/column location in the neighbourhood.
715    * The hint may be left of, at, or to the right of the required position.
716    * 
717    * @param pos
718    * @param curs
719    * @return
720    */
721   protected int findIndex(int pos, SequenceCursor curs)
722   {
723     if (curs.sequence != this || curs.token != changeCount)
724     {
725       /*
726        * wrong or invalidated cursor, compute de novo
727        */
728       return findIndex(pos);
729     }
730
731     if (curs.residuePosition == pos)
732     {
733       return curs.columnPosition;
734     }
735
736     /*
737      * move left or right to find pos from hint.position
738      */
739     int col = curs.columnPosition - 1; // convert from base 1 to 0-based array
740                                        // index
741     int newPos = curs.residuePosition;
742     int delta = newPos > pos ? -1 : 1;
743
744     while (newPos != pos)
745     {
746       col += delta; // shift one column left or right
747       if (col < 0 || col == sequence.length)
748       {
749         break;
750       }
751       if (!Comparison.isGap(sequence[col]))
752       {
753         newPos += delta;
754       }
755     }
756
757     col++; // convert back to base 1
758     updateCursor(pos, col);
759
760     return col;
761   }
762
763   @Override
764   public int findPosition(final int i)
765   {
766     /*
767      * use a valid nearby cursor if available
768      */
769     if (cursor != null && cursor.sequence == this
770             && cursor.token == changeCount)
771     {
772       return findPosition(i + 1, cursor);
773     }
774
775     int j = 0;
776     int pos = start;
777     int seqlen = sequence.length;
778     while ((j < i) && (j < seqlen))
779     {
780       if (!Comparison.isGap(sequence[j]))
781       {
782         pos++;
783       }
784
785       j++;
786     }
787
788     if (j == i && !Comparison.isGap(sequence[i]))
789     {
790       updateCursor(pos, i + 1);
791     }
792
793     return pos;
794   }
795
796   /**
797    * Answers the sequence position (start..) for the given aligned column
798    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
799    * may lie left of, at, or to the right of the column position.
800    * 
801    * @param col
802    * @param curs
803    * @return
804    */
805   protected int findPosition(final int col, SequenceCursor curs)
806   {
807     if (curs.sequence != this || curs.token != changeCount)
808     {
809       /*
810        * wrong or invalidated cursor, compute de novo
811        */
812       return findPosition(col - 1);// ugh back to base 0
813     }
814
815     if (curs.columnPosition == col)
816     {
817       return curs.residuePosition; // easy case :-)
818     }
819
820     /*
821      * move left or right to find pos from cursor position
822      */
823     int column = curs.columnPosition - 1; // to base 0
824     int newPos = curs.residuePosition;
825     int delta = curs.columnPosition > col ? -1 : 1;
826     boolean gapped = false;
827
828     while (column != col - 1)
829     {
830       column += delta; // shift one column left or right
831       if (column < 0 || column == sequence.length)
832       {
833         break;
834       }
835       gapped = Comparison.isGap(sequence[column]);
836       if (!gapped)
837       {
838         newPos += delta;
839       }
840     }
841
842     /*
843      * hack to give position to the right if on a gap
844      * pending resolution of JAL-2562
845      */
846     if (delta > 0 && gapped)
847     {
848       newPos++;
849     }
850
851     return newPos;
852   }
853
854   /**
855    * {@inheritDoc}
856    */
857   @Override
858   public Range findPositions(int fromCol, int toCol)
859   {
860     if (cursor != null && cursor.sequence == this
861             && cursor.token == changeCount)
862     {
863       return findPositions(fromCol, toCol, cursor);
864     }
865
866     /*
867      * count residues before fromCol
868      */
869     int j = 0;
870     int count = 0;
871     int seqlen = sequence.length;
872     while (j < fromCol && j < seqlen)
873     {
874       if (!Comparison.isGap(sequence[j]))
875       {
876         count++;
877       }
878       j++;
879     }
880
881     /*
882      * find first and last residues between fromCol and toCol
883      */
884     int firstPos = 0;
885     int lastPos = 0;
886     boolean foundFirst = false;
887     
888     while (j <= toCol && j < seqlen)
889     {
890       if (!Comparison.isGap(sequence[j]))
891       {
892         count++;
893         if (!foundFirst)
894         {
895           firstPos = count;
896           foundFirst = true;
897         }
898         lastPos = count;
899       }
900       j++;
901     }
902
903     if (firstPos == 0)
904     {
905       /*
906        * no residues in this range
907        */
908       return null;
909     }
910
911     /*
912      * adjust for sequence start coordinate
913      */
914     firstPos += start - 1;
915     lastPos += start - 1;
916
917     return new Range(firstPos, lastPos);
918   }
919
920   /**
921    * Returns the range of sequence positions included in the given alignment
922    * position range. If no positions are included (the range is entirely gaps),
923    * then returns null. The cursor parameter may provide a starting position in
924    * the neighbourhood of the search (which may be left of, right of, or
925    * overlapping the search region).
926    * 
927    * @param fromCol
928    *          start column of region (0..)
929    * @param toCol
930    *          end column of region (0..)
931    * @param curs
932    * @return
933    */
934   protected Range findPositions(int fromCol, int toCol, SequenceCursor curs)
935   {
936     if (curs.sequence != this || curs.token != changeCount)
937     {
938       /*
939        * wrong or invalidated cursor, compute de novo
940        */
941       return findPositions(fromCol, toCol);
942     }
943
944     /*
945      * keep this simple...first step from cursor to fromCol...
946      */
947     final int seqlen = sequence.length;
948     int resNo = curs.residuePosition;
949     int col = curs.columnPosition - 1; // from base 1 to base 0
950     if (col != fromCol)
951     {
952       int delta = col > fromCol ? -1 : 1;
953       while (col != fromCol && col >= 0 && col < seqlen)
954       {
955         if (!Comparison.isGap(sequence[col]))
956         {
957           resNo += delta;
958         }
959         col += delta;
960       }
961     }
962
963     if (col < fromCol || col == seqlen)
964     {
965       /*
966        * sequence lies to the left of the target region
967        */
968       return null;
969     }
970
971     /*
972      * resNo is now the residue at fromCol (if not gapped), else the one
973      * before it (if delta == 1), else the one after (if delta == -1);
974      * we want the residue before fromCol
975      */
976     if (!Comparison.isGap(sequence[fromCol]))
977     {
978       resNo--;
979     }
980     else if (curs.columnPosition > fromCol)
981     {
982       resNo -= 2;
983     }
984
985     /*
986      * now first and last residues between fromCol and toCol
987      */
988     int firstPos = 0;
989     int lastPos = 0;
990     boolean foundFirst = false;
991
992     while (col <= toCol && col < seqlen)
993     {
994       if (!Comparison.isGap(sequence[col]))
995       {
996         resNo++;
997         if (!foundFirst)
998         {
999           firstPos = resNo;
1000           foundFirst = true;
1001         }
1002         lastPos = resNo;
1003       }
1004       col++;
1005     }
1006
1007     if (firstPos == 0)
1008     {
1009       /*
1010        * no residues in this range
1011        */
1012       return null;
1013     }
1014
1015     return new Range(firstPos, lastPos);
1016   }
1017
1018   /**
1019    * Returns an int array where indices correspond to each residue in the
1020    * sequence and the element value gives its position in the alignment
1021    * 
1022    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
1023    *         residues in SequenceI object
1024    */
1025   @Override
1026   public int[] gapMap()
1027   {
1028     String seq = jalview.analysis.AlignSeq.extractGaps(
1029             jalview.util.Comparison.GapChars, new String(sequence));
1030     int[] map = new int[seq.length()];
1031     int j = 0;
1032     int p = 0;
1033
1034     while (j < sequence.length)
1035     {
1036       if (!jalview.util.Comparison.isGap(sequence[j]))
1037       {
1038         map[p++] = j;
1039       }
1040
1041       j++;
1042     }
1043
1044     return map;
1045   }
1046
1047   @Override
1048   public int[] findPositionMap()
1049   {
1050     int map[] = new int[sequence.length];
1051     int j = 0;
1052     int pos = start;
1053     int seqlen = sequence.length;
1054     while ((j < seqlen))
1055     {
1056       map[j] = pos;
1057       if (!jalview.util.Comparison.isGap(sequence[j]))
1058       {
1059         pos++;
1060       }
1061
1062       j++;
1063     }
1064     return map;
1065   }
1066
1067   @Override
1068   public List<int[]> getInsertions()
1069   {
1070     ArrayList<int[]> map = new ArrayList<int[]>();
1071     int lastj = -1, j = 0;
1072     int pos = start;
1073     int seqlen = sequence.length;
1074     while ((j < seqlen))
1075     {
1076       if (jalview.util.Comparison.isGap(sequence[j]))
1077       {
1078         if (lastj == -1)
1079         {
1080           lastj = j;
1081         }
1082       }
1083       else
1084       {
1085         if (lastj != -1)
1086         {
1087           map.add(new int[] { lastj, j - 1 });
1088           lastj = -1;
1089         }
1090       }
1091       j++;
1092     }
1093     if (lastj != -1)
1094     {
1095       map.add(new int[] { lastj, j - 1 });
1096       lastj = -1;
1097     }
1098     return map;
1099   }
1100
1101   @Override
1102   public void deleteChars(int i, int j)
1103   {
1104     int newstart = start, newend = end;
1105     if (i >= sequence.length || i < 0)
1106     {
1107       return;
1108     }
1109
1110     char[] tmp = StringUtils.deleteChars(sequence, i, j);
1111     boolean createNewDs = false;
1112     // TODO: take a (second look) at the dataset creation validation method for
1113     // the very large sequence case
1114     int eindex = -1, sindex = -1;
1115     boolean ecalc = false, scalc = false;
1116     for (int s = i; s < j; s++)
1117     {
1118       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
1119       {
1120         if (createNewDs)
1121         {
1122           newend--;
1123         }
1124         else
1125         {
1126           if (!scalc)
1127           {
1128             sindex = findIndex(start) - 1;
1129             scalc = true;
1130           }
1131           if (sindex == s)
1132           {
1133             // delete characters including start of sequence
1134             newstart = findPosition(j);
1135             break; // don't need to search for any more residue characters.
1136           }
1137           else
1138           {
1139             // delete characters after start.
1140             if (!ecalc)
1141             {
1142               eindex = findIndex(end) - 1;
1143               ecalc = true;
1144             }
1145             if (eindex < j)
1146             {
1147               // delete characters at end of sequence
1148               newend = findPosition(i - 1);
1149               break; // don't need to search for any more residue characters.
1150             }
1151             else
1152             {
1153               createNewDs = true;
1154               newend--; // decrease end position by one for the deleted residue
1155               // and search further
1156             }
1157           }
1158         }
1159       }
1160     }
1161     // deletion occured in the middle of the sequence
1162     if (createNewDs && this.datasetSequence != null)
1163     {
1164       // construct a new sequence
1165       Sequence ds = new Sequence(datasetSequence);
1166       // TODO: remove any non-inheritable properties ?
1167       // TODO: create a sequence mapping (since there is a relation here ?)
1168       ds.deleteChars(i, j);
1169       datasetSequence = ds;
1170     }
1171     start = newstart;
1172     end = newend;
1173     sequence = tmp;
1174   }
1175
1176   @Override
1177   public void insertCharAt(int i, int length, char c)
1178   {
1179     char[] tmp = new char[sequence.length + length];
1180
1181     if (i >= sequence.length)
1182     {
1183       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1184       i = sequence.length;
1185     }
1186     else
1187     {
1188       System.arraycopy(sequence, 0, tmp, 0, i);
1189     }
1190
1191     int index = i;
1192     while (length > 0)
1193     {
1194       tmp[index++] = c;
1195       length--;
1196     }
1197
1198     if (i < sequence.length)
1199     {
1200       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1201     }
1202
1203     sequence = tmp;
1204   }
1205
1206   @Override
1207   public void insertCharAt(int i, char c)
1208   {
1209     insertCharAt(i, 1, c);
1210   }
1211
1212   @Override
1213   public String getVamsasId()
1214   {
1215     return vamsasId;
1216   }
1217
1218   @Override
1219   public void setVamsasId(String id)
1220   {
1221     vamsasId = id;
1222   }
1223
1224   @Override
1225   public void setDBRefs(DBRefEntry[] dbref)
1226   {
1227     if (dbrefs == null && datasetSequence != null
1228             && this != datasetSequence)
1229     {
1230       datasetSequence.setDBRefs(dbref);
1231       return;
1232     }
1233     dbrefs = dbref;
1234     if (dbrefs != null)
1235     {
1236       DBRefUtils.ensurePrimaries(this);
1237     }
1238   }
1239
1240   @Override
1241   public DBRefEntry[] getDBRefs()
1242   {
1243     if (dbrefs == null && datasetSequence != null
1244             && this != datasetSequence)
1245     {
1246       return datasetSequence.getDBRefs();
1247     }
1248     return dbrefs;
1249   }
1250
1251   @Override
1252   public void addDBRef(DBRefEntry entry)
1253   {
1254     if (datasetSequence != null)
1255     {
1256       datasetSequence.addDBRef(entry);
1257       return;
1258     }
1259
1260     if (dbrefs == null)
1261     {
1262       dbrefs = new DBRefEntry[0];
1263     }
1264
1265     for (DBRefEntryI dbr : dbrefs)
1266     {
1267       if (dbr.updateFrom(entry))
1268       {
1269         /*
1270          * found a dbref that either matched, or could be
1271          * updated from, the new entry - no need to add it
1272          */
1273         return;
1274       }
1275     }
1276
1277     /*
1278      * extend the array to make room for one more
1279      */
1280     // TODO use an ArrayList instead
1281     int j = dbrefs.length;
1282     DBRefEntry[] temp = new DBRefEntry[j + 1];
1283     System.arraycopy(dbrefs, 0, temp, 0, j);
1284     temp[temp.length - 1] = entry;
1285
1286     dbrefs = temp;
1287
1288     DBRefUtils.ensurePrimaries(this);
1289   }
1290
1291   @Override
1292   public void setDatasetSequence(SequenceI seq)
1293   {
1294     if (seq == this)
1295     {
1296       throw new IllegalArgumentException(
1297               "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1298     }
1299     if (seq != null && seq.getDatasetSequence() != null)
1300     {
1301       throw new IllegalArgumentException(
1302               "Implementation error: cascading dataset sequences are not allowed.");
1303     }
1304     datasetSequence = seq;
1305   }
1306
1307   @Override
1308   public SequenceI getDatasetSequence()
1309   {
1310     return datasetSequence;
1311   }
1312
1313   @Override
1314   public AlignmentAnnotation[] getAnnotation()
1315   {
1316     return annotation == null ? null : annotation
1317             .toArray(new AlignmentAnnotation[annotation.size()]);
1318   }
1319
1320   @Override
1321   public boolean hasAnnotation(AlignmentAnnotation ann)
1322   {
1323     return annotation == null ? false : annotation.contains(ann);
1324   }
1325
1326   @Override
1327   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1328   {
1329     if (this.annotation == null)
1330     {
1331       this.annotation = new Vector<AlignmentAnnotation>();
1332     }
1333     if (!this.annotation.contains(annotation))
1334     {
1335       this.annotation.addElement(annotation);
1336     }
1337     annotation.setSequenceRef(this);
1338   }
1339
1340   @Override
1341   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1342   {
1343     if (this.annotation != null)
1344     {
1345       this.annotation.removeElement(annotation);
1346       if (this.annotation.size() == 0)
1347       {
1348         this.annotation = null;
1349       }
1350     }
1351   }
1352
1353   /**
1354    * test if this is a valid candidate for another sequence's dataset sequence.
1355    * 
1356    */
1357   private boolean isValidDatasetSequence()
1358   {
1359     if (datasetSequence != null)
1360     {
1361       return false;
1362     }
1363     for (int i = 0; i < sequence.length; i++)
1364     {
1365       if (jalview.util.Comparison.isGap(sequence[i]))
1366       {
1367         return false;
1368       }
1369     }
1370     return true;
1371   }
1372
1373   @Override
1374   public SequenceI deriveSequence()
1375   {
1376     Sequence seq = null;
1377     if (datasetSequence == null)
1378     {
1379       if (isValidDatasetSequence())
1380       {
1381         // Use this as dataset sequence
1382         seq = new Sequence(getName(), "", 1, -1);
1383         seq.setDatasetSequence(this);
1384         seq.initSeqFrom(this, getAnnotation());
1385         return seq;
1386       }
1387       else
1388       {
1389         // Create a new, valid dataset sequence
1390         createDatasetSequence();
1391       }
1392     }
1393     return new Sequence(this);
1394   }
1395
1396   private boolean _isNa;
1397
1398   private long _seqhash = 0;
1399
1400   /**
1401    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1402    * true
1403    */
1404   @Override
1405   public boolean isProtein()
1406   {
1407     if (datasetSequence != null)
1408     {
1409       return datasetSequence.isProtein();
1410     }
1411     if (_seqhash != sequence.hashCode())
1412     {
1413       _seqhash = sequence.hashCode();
1414       _isNa = Comparison.isNucleotide(this);
1415     }
1416     return !_isNa;
1417   };
1418
1419   /*
1420    * (non-Javadoc)
1421    * 
1422    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1423    */
1424   @Override
1425   public SequenceI createDatasetSequence()
1426   {
1427     if (datasetSequence == null)
1428     {
1429       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1430               jalview.util.Comparison.GapChars, getSequenceAsString()),
1431               getStart(), getEnd());
1432
1433       datasetSequence = dsseq;
1434
1435       dsseq.setDescription(description);
1436       // move features and database references onto dataset sequence
1437       dsseq.sequenceFeatureStore = sequenceFeatureStore;
1438       sequenceFeatureStore = null;
1439       dsseq.dbrefs = dbrefs;
1440       dbrefs = null;
1441       // TODO: search and replace any references to this sequence with
1442       // references to the dataset sequence in Mappings on dbref
1443       dsseq.pdbIds = pdbIds;
1444       pdbIds = null;
1445       datasetSequence.updatePDBIds();
1446       if (annotation != null)
1447       {
1448         // annotation is cloned rather than moved, to preserve what's currently
1449         // on the alignment
1450         for (AlignmentAnnotation aa : annotation)
1451         {
1452           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1453           _aa.sequenceRef = datasetSequence;
1454           _aa.adjustForAlignment(); // uses annotation's own record of
1455                                     // sequence-column mapping
1456           datasetSequence.addAlignmentAnnotation(_aa);
1457         }
1458       }
1459     }
1460     return datasetSequence;
1461   }
1462
1463   /*
1464    * (non-Javadoc)
1465    * 
1466    * @see
1467    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1468    * annotations)
1469    */
1470   @Override
1471   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1472   {
1473     if (annotation != null)
1474     {
1475       annotation.removeAllElements();
1476     }
1477     if (annotations != null)
1478     {
1479       for (int i = 0; i < annotations.length; i++)
1480       {
1481         if (annotations[i] != null)
1482         {
1483           addAlignmentAnnotation(annotations[i]);
1484         }
1485       }
1486     }
1487   }
1488
1489   @Override
1490   public AlignmentAnnotation[] getAnnotation(String label)
1491   {
1492     if (annotation == null || annotation.size() == 0)
1493     {
1494       return null;
1495     }
1496
1497     Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
1498     Enumeration<AlignmentAnnotation> e = annotation.elements();
1499     while (e.hasMoreElements())
1500     {
1501       AlignmentAnnotation ann = e.nextElement();
1502       if (ann.label != null && ann.label.equals(label))
1503       {
1504         subset.addElement(ann);
1505       }
1506     }
1507     if (subset.size() == 0)
1508     {
1509       return null;
1510     }
1511     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1512     int i = 0;
1513     e = subset.elements();
1514     while (e.hasMoreElements())
1515     {
1516       anns[i++] = e.nextElement();
1517     }
1518     subset.removeAllElements();
1519     return anns;
1520   }
1521
1522   @Override
1523   public boolean updatePDBIds()
1524   {
1525     if (datasetSequence != null)
1526     {
1527       // TODO: could merge DBRefs
1528       return datasetSequence.updatePDBIds();
1529     }
1530     if (dbrefs == null || dbrefs.length == 0)
1531     {
1532       return false;
1533     }
1534     boolean added = false;
1535     for (DBRefEntry dbr : dbrefs)
1536     {
1537       if (DBRefSource.PDB.equals(dbr.getSource()))
1538       {
1539         /*
1540          * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1541          * PDB id is not already present in a 'matching' PDBEntry
1542          * Constructor parses out a chain code if appended to the accession id
1543          * (a fudge used to 'store' the chain code in the DBRef)
1544          */
1545         PDBEntry pdbe = new PDBEntry(dbr);
1546         added |= addPDBId(pdbe);
1547       }
1548     }
1549     return added;
1550   }
1551
1552   @Override
1553   public void transferAnnotation(SequenceI entry, Mapping mp)
1554   {
1555     if (datasetSequence != null)
1556     {
1557       datasetSequence.transferAnnotation(entry, mp);
1558       return;
1559     }
1560     if (entry.getDatasetSequence() != null)
1561     {
1562       transferAnnotation(entry.getDatasetSequence(), mp);
1563       return;
1564     }
1565     // transfer any new features from entry onto sequence
1566     if (entry.getSequenceFeatures() != null)
1567     {
1568
1569       List<SequenceFeature> sfs = entry.getSequenceFeatures();
1570       for (SequenceFeature feature : sfs)
1571       {
1572         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1573                 : new SequenceFeature[] { new SequenceFeature(feature) };
1574         if (sf != null)
1575         {
1576           for (int sfi = 0; sfi < sf.length; sfi++)
1577           {
1578             addSequenceFeature(sf[sfi]);
1579           }
1580         }
1581       }
1582     }
1583
1584     // transfer PDB entries
1585     if (entry.getAllPDBEntries() != null)
1586     {
1587       Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1588       while (e.hasMoreElements())
1589       {
1590         PDBEntry pdb = e.nextElement();
1591         addPDBId(pdb);
1592       }
1593     }
1594     // transfer database references
1595     DBRefEntry[] entryRefs = entry.getDBRefs();
1596     if (entryRefs != null)
1597     {
1598       for (int r = 0; r < entryRefs.length; r++)
1599       {
1600         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1601         if (newref.getMap() != null && mp != null)
1602         {
1603           // remap ref using our local mapping
1604         }
1605         // we also assume all version string setting is done by dbSourceProxy
1606         /*
1607          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1608          * newref.setSource(dbSource); }
1609          */
1610         addDBRef(newref);
1611       }
1612     }
1613   }
1614
1615   /**
1616    * @return The index (zero-based) on this sequence in the MSA. It returns
1617    *         {@code -1} if this information is not available.
1618    */
1619   @Override
1620   public int getIndex()
1621   {
1622     return index;
1623   }
1624
1625   /**
1626    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1627    * if this information is undefined.
1628    * 
1629    * @param The
1630    *          position for this sequence. This value is zero-based (zero for
1631    *          this first sequence)
1632    */
1633   @Override
1634   public void setIndex(int value)
1635   {
1636     index = value;
1637   }
1638
1639   @Override
1640   public void setRNA(RNA r)
1641   {
1642     rna = r;
1643   }
1644
1645   @Override
1646   public RNA getRNA()
1647   {
1648     return rna;
1649   }
1650
1651   @Override
1652   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1653           String label)
1654   {
1655     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1656     if (this.annotation != null)
1657     {
1658       for (AlignmentAnnotation ann : annotation)
1659       {
1660         if (ann.calcId != null && ann.calcId.equals(calcId)
1661                 && ann.label != null && ann.label.equals(label))
1662         {
1663           result.add(ann);
1664         }
1665       }
1666     }
1667     return result;
1668   }
1669
1670   @Override
1671   public String toString()
1672   {
1673     return getDisplayId(false);
1674   }
1675
1676   @Override
1677   public PDBEntry getPDBEntry(String pdbIdStr)
1678   {
1679     if (getDatasetSequence() != null)
1680     {
1681       return getDatasetSequence().getPDBEntry(pdbIdStr);
1682     }
1683     if (pdbIds == null)
1684     {
1685       return null;
1686     }
1687     List<PDBEntry> entries = getAllPDBEntries();
1688     for (PDBEntry entry : entries)
1689     {
1690       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1691       {
1692         return entry;
1693       }
1694     }
1695     return null;
1696   }
1697
1698   @Override
1699   public List<DBRefEntry> getPrimaryDBRefs()
1700   {
1701     if (datasetSequence != null)
1702     {
1703       return datasetSequence.getPrimaryDBRefs();
1704     }
1705     if (dbrefs == null || dbrefs.length == 0)
1706     {
1707       return Collections.emptyList();
1708     }
1709     synchronized (dbrefs)
1710     {
1711       List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
1712       DBRefEntry[] tmp = new DBRefEntry[1];
1713       for (DBRefEntry ref : dbrefs)
1714       {
1715         if (!ref.isPrimaryCandidate())
1716         {
1717           continue;
1718         }
1719         if (ref.hasMap())
1720         {
1721           MapList mp = ref.getMap().getMap();
1722           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1723           {
1724             // map only involves a subsequence, so cannot be primary
1725             continue;
1726           }
1727         }
1728         // whilst it looks like it is a primary ref, we also sanity check type
1729         if (DBRefUtils.getCanonicalName(DBRefSource.PDB).equals(
1730                 DBRefUtils.getCanonicalName(ref.getSource())))
1731         {
1732           // PDB dbrefs imply there should be a PDBEntry associated
1733           // TODO: tighten PDB dbrefs
1734           // formally imply Jalview has actually downloaded and
1735           // parsed the pdb file. That means there should be a cached file
1736           // handle on the PDBEntry, and a real mapping between sequence and
1737           // extracted sequence from PDB file
1738           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1739           if (pdbentry != null && pdbentry.getFile() != null)
1740           {
1741             primaries.add(ref);
1742           }
1743           continue;
1744         }
1745         // check standard protein or dna sources
1746         tmp[0] = ref;
1747         DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1748         if (res != null && res[0] == tmp[0])
1749         {
1750           primaries.add(ref);
1751           continue;
1752         }
1753       }
1754       return primaries;
1755     }
1756   }
1757
1758   /**
1759    * {@inheritDoc}
1760    */
1761   @Override
1762   public List<SequenceFeature> findFeatures(int from, int to,
1763           String... types)
1764   {
1765     if (datasetSequence != null)
1766     {
1767       return datasetSequence.findFeatures(from, to, types);
1768     }
1769     return sequenceFeatureStore.findFeatures(from, to, types);
1770   }
1771
1772   /**
1773    * Invalidates any stale cursors (forcing recalculation) by incrementing the
1774    * token that has to match the one presented by the cursor
1775    */
1776   @Override
1777   public void zapCursor()
1778   {
1779     changeCount++;
1780   }
1781 }