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