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