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