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