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