develop merge
[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     sourceDBRef = seq.getSourceDBRef() == null ? null : new DBRefEntry(
223             seq.getSourceDBRef());
224     if (seq.getSequenceFeatures() != null)
225     {
226       SequenceFeature[] sf = seq.getSequenceFeatures();
227       for (int i = 0; i < sf.length; i++)
228       {
229         addSequenceFeature(new SequenceFeature(sf[i]));
230       }
231     }
232     setDatasetSequence(seq.getDatasetSequence());
233     if (datasetSequence == null && seq.getDBRefs() != null)
234     {
235       // only copy DBRefs if we really are a dataset sequence
236       DBRefEntry[] dbr = seq.getDBRefs();
237       for (int i = 0; i < dbr.length; i++)
238       {
239         addDBRef(new DBRefEntry(dbr[i]));
240       }
241     }
242     if (seq.getAnnotation() != null)
243     {
244       AlignmentAnnotation[] sqann = seq.getAnnotation();
245       for (int i = 0; i < sqann.length; i++)
246       {
247         if (sqann[i] == null)
248         {
249           continue;
250         }
251         boolean found = (alAnnotation == null);
252         if (!found)
253         {
254           for (int apos = 0; !found && apos < alAnnotation.length; apos++)
255           {
256             found = (alAnnotation[apos] == sqann[i]);
257           }
258         }
259         if (found)
260         {
261           // only copy the given annotation
262           AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
263           addAlignmentAnnotation(newann);
264         }
265       }
266     }
267     if (seq.getAllPDBEntries() != null)
268     {
269       Vector<PDBEntry> ids = seq.getAllPDBEntries();
270       for (PDBEntry pdb : ids)
271       {
272         this.addPDBId(new PDBEntry(pdb));
273       }
274     }
275   }
276
277   /**
278    * DOCUMENT ME!
279    * 
280    * @param v
281    *          DOCUMENT ME!
282    */
283   @Override
284   public void setSequenceFeatures(SequenceFeature[] features)
285   {
286     sequenceFeatures = features;
287   }
288
289   @Override
290   public synchronized void addSequenceFeature(SequenceFeature sf)
291   {
292     // TODO add to dataset sequence instead if there is one?
293     if (sequenceFeatures == null)
294     {
295       sequenceFeatures = new SequenceFeature[0];
296     }
297
298     for (int i = 0; i < sequenceFeatures.length; i++)
299     {
300       if (sequenceFeatures[i].equals(sf))
301       {
302         return;
303       }
304     }
305
306     SequenceFeature[] temp = new SequenceFeature[sequenceFeatures.length + 1];
307     System.arraycopy(sequenceFeatures, 0, temp, 0, sequenceFeatures.length);
308     temp[sequenceFeatures.length] = sf;
309
310     sequenceFeatures = temp;
311   }
312
313   @Override
314   public void deleteFeature(SequenceFeature sf)
315   {
316     if (sequenceFeatures == null)
317     {
318       return;
319     }
320
321     int index = 0;
322     for (index = 0; index < sequenceFeatures.length; index++)
323     {
324       if (sequenceFeatures[index].equals(sf))
325       {
326         break;
327       }
328     }
329
330     if (index == sequenceFeatures.length)
331     {
332       return;
333     }
334
335     int sfLength = sequenceFeatures.length;
336     if (sfLength < 2)
337     {
338       sequenceFeatures = null;
339     }
340     else
341     {
342       SequenceFeature[] temp = new SequenceFeature[sfLength - 1];
343       System.arraycopy(sequenceFeatures, 0, temp, 0, index);
344
345       if (index < sfLength)
346       {
347         System.arraycopy(sequenceFeatures, index + 1, temp, index,
348                 sequenceFeatures.length - index - 1);
349       }
350
351       sequenceFeatures = temp;
352     }
353   }
354
355   /**
356    * Returns the sequence features (if any), looking first on the sequence, then
357    * on its dataset sequence, and so on until a non-null value is found (or
358    * none). This supports retrieval of sequence features stored on the sequence
359    * (as in the applet) or on the dataset sequence (as in the Desktop version).
360    * 
361    * @return
362    */
363   @Override
364   public SequenceFeature[] getSequenceFeatures()
365   {
366     SequenceFeature[] features = sequenceFeatures;
367
368     SequenceI seq = this;
369     int count = 0; // failsafe against loop in sequence.datasetsequence...
370     while (features == null && seq.getDatasetSequence() != null
371             && count++ < 10)
372     {
373       seq = seq.getDatasetSequence();
374       features = ((Sequence) seq).sequenceFeatures;
375     }
376     return features;
377   }
378
379   @Override
380   public void addPDBId(PDBEntry entry)
381   {
382     if (pdbIds == null)
383     {
384       pdbIds = new Vector<PDBEntry>();
385     }
386     if (pdbIds.contains(entry))
387     {
388       updatePDBEntry(pdbIds.get(pdbIds.indexOf(entry)), entry);
389     }
390     else
391     {
392       pdbIds.addElement(entry);
393     }
394   }
395
396   private static void updatePDBEntry(PDBEntry oldEntry, PDBEntry newEntry)
397   {
398     if (newEntry.getFile() != null)
399     {
400       oldEntry.setFile(newEntry.getFile());
401     }
402   }
403
404   /**
405    * DOCUMENT ME!
406    * 
407    * @param id
408    *          DOCUMENT ME!
409    */
410   @Override
411   public void setPDBId(Vector<PDBEntry> id)
412   {
413     pdbIds = id;
414   }
415
416   /**
417    * DOCUMENT ME!
418    * 
419    * @return DOCUMENT ME!
420    */
421   @Override
422   public Vector<PDBEntry> getAllPDBEntries()
423   {
424     return pdbIds == null ? new Vector<PDBEntry>() : pdbIds;
425   }
426
427   /**
428    * DOCUMENT ME!
429    * 
430    * @return DOCUMENT ME!
431    */
432   @Override
433   public String getDisplayId(boolean jvsuffix)
434   {
435     StringBuffer result = new StringBuffer(name);
436     if (jvsuffix)
437     {
438       result.append("/" + start + "-" + end);
439     }
440
441     return result.toString();
442   }
443
444   /**
445    * DOCUMENT ME!
446    * 
447    * @param name
448    *          DOCUMENT ME!
449    */
450   @Override
451   public void setName(String name)
452   {
453     this.name = name;
454     this.parseId();
455   }
456
457   /**
458    * DOCUMENT ME!
459    * 
460    * @return DOCUMENT ME!
461    */
462   @Override
463   public String getName()
464   {
465     return this.name;
466   }
467
468   /**
469    * DOCUMENT ME!
470    * 
471    * @param start
472    *          DOCUMENT ME!
473    */
474   @Override
475   public void setStart(int start)
476   {
477     this.start = start;
478   }
479
480   /**
481    * DOCUMENT ME!
482    * 
483    * @return DOCUMENT ME!
484    */
485   @Override
486   public int getStart()
487   {
488     return this.start;
489   }
490
491   /**
492    * DOCUMENT ME!
493    * 
494    * @param end
495    *          DOCUMENT ME!
496    */
497   @Override
498   public void setEnd(int end)
499   {
500     this.end = end;
501   }
502
503   /**
504    * DOCUMENT ME!
505    * 
506    * @return DOCUMENT ME!
507    */
508   @Override
509   public int getEnd()
510   {
511     return this.end;
512   }
513
514   /**
515    * DOCUMENT ME!
516    * 
517    * @return DOCUMENT ME!
518    */
519   @Override
520   public int getLength()
521   {
522     return this.sequence.length;
523   }
524
525   /**
526    * DOCUMENT ME!
527    * 
528    * @param seq
529    *          DOCUMENT ME!
530    */
531   @Override
532   public void setSequence(String seq)
533   {
534     this.sequence = seq.toCharArray();
535     checkValidRange();
536   }
537
538   @Override
539   public String getSequenceAsString()
540   {
541     return new String(sequence);
542   }
543
544   @Override
545   public String getSequenceAsString(int start, int end)
546   {
547     return new String(getSequence(start, end));
548   }
549
550   @Override
551   public char[] getSequence()
552   {
553     return sequence;
554   }
555
556   /*
557    * (non-Javadoc)
558    * 
559    * @see jalview.datamodel.SequenceI#getSequence(int, int)
560    */
561   @Override
562   public char[] getSequence(int start, int end)
563   {
564     if (start < 0)
565     {
566       start = 0;
567     }
568     // JBPNote - left to user to pad the result here (TODO:Decide on this
569     // policy)
570     if (start >= sequence.length)
571     {
572       return new char[0];
573     }
574
575     if (end >= sequence.length)
576     {
577       end = sequence.length;
578     }
579
580     char[] reply = new char[end - start];
581     System.arraycopy(sequence, start, reply, 0, end - start);
582
583     return reply;
584   }
585
586   @Override
587   public SequenceI getSubSequence(int start, int end)
588   {
589     if (start < 0)
590     {
591       start = 0;
592     }
593     char[] seq = getSequence(start, end);
594     if (seq.length == 0)
595     {
596       return null;
597     }
598     int nstart = findPosition(start);
599     int nend = findPosition(end) - 1;
600     // JBPNote - this is an incomplete copy.
601     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
602     nseq.setDescription(description);
603     if (datasetSequence != null)
604     {
605       nseq.setDatasetSequence(datasetSequence);
606     }
607     else
608     {
609       nseq.setDatasetSequence(this);
610     }
611     return nseq;
612   }
613
614   /**
615    * Returns the character of the aligned sequence at the given position (base
616    * zero), or space if the position is not within the sequence's bounds
617    * 
618    * @return
619    */
620   @Override
621   public char getCharAt(int i)
622   {
623     if (i >= 0 && 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 setDBRefs(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     // TODO add to dataset sequence instead if there is one?
934     if (dbrefs == null)
935     {
936       dbrefs = new DBRefEntry[0];
937     }
938
939     int i, iSize = dbrefs.length;
940
941     for (i = 0; i < iSize; i++)
942     {
943       if (dbrefs[i].equalRef(entry))
944       {
945         if (entry.getMap() != null)
946         {
947           if (dbrefs[i].getMap() == null)
948           {
949             // overwrite with 'superior' entry that contains a mapping.
950             dbrefs[i] = entry;
951           }
952         }
953         return;
954       }
955     }
956
957     DBRefEntry[] temp = new DBRefEntry[iSize + 1];
958     System.arraycopy(dbrefs, 0, temp, 0, iSize);
959     temp[temp.length - 1] = entry;
960
961     dbrefs = temp;
962   }
963
964   @Override
965   public void setDatasetSequence(SequenceI seq)
966   {
967     // TODO check for circular reference before setting?
968     datasetSequence = seq;
969   }
970
971   @Override
972   public SequenceI getDatasetSequence()
973   {
974     return datasetSequence;
975   }
976
977   @Override
978   public AlignmentAnnotation[] getAnnotation()
979   {
980     return annotation == null ? null : annotation
981             .toArray(new AlignmentAnnotation[annotation.size()]);
982   }
983
984   @Override
985   public boolean hasAnnotation(AlignmentAnnotation ann)
986   {
987     return annotation == null ? false : annotation.contains(ann);
988   }
989
990   @Override
991   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
992   {
993     if (this.annotation == null)
994     {
995       this.annotation = new Vector<AlignmentAnnotation>();
996     }
997     if (!this.annotation.contains(annotation))
998     {
999       this.annotation.addElement(annotation);
1000     }
1001     annotation.setSequenceRef(this);
1002   }
1003
1004   @Override
1005   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1006   {
1007     if (this.annotation != null)
1008     {
1009       this.annotation.removeElement(annotation);
1010       if (this.annotation.size() == 0)
1011       {
1012         this.annotation = null;
1013       }
1014     }
1015   }
1016
1017   /**
1018    * test if this is a valid candidate for another sequence's dataset sequence.
1019    * 
1020    */
1021   private boolean isValidDatasetSequence()
1022   {
1023     if (datasetSequence != null)
1024     {
1025       return false;
1026     }
1027     for (int i = 0; i < sequence.length; i++)
1028     {
1029       if (jalview.util.Comparison.isGap(sequence[i]))
1030       {
1031         return false;
1032       }
1033     }
1034     return true;
1035   }
1036
1037   @Override
1038   public SequenceI deriveSequence()
1039   {
1040     SequenceI seq = new Sequence(this);
1041     if (datasetSequence != null)
1042     {
1043       // duplicate current sequence with same dataset
1044       seq.setDatasetSequence(datasetSequence);
1045     }
1046     else
1047     {
1048       if (isValidDatasetSequence())
1049       {
1050         // Use this as dataset sequence
1051         seq.setDatasetSequence(this);
1052       }
1053       else
1054       {
1055         // Create a new, valid dataset sequence
1056         SequenceI ds = seq;
1057         ds.setSequence(AlignSeq.extractGaps(
1058                 jalview.util.Comparison.GapChars, new String(sequence)));
1059         setDatasetSequence(ds);
1060         ds.setSequenceFeatures(getSequenceFeatures());
1061         seq = this; // and return this sequence as the derived sequence.
1062       }
1063     }
1064     return seq;
1065   }
1066
1067   /*
1068    * (non-Javadoc)
1069    * 
1070    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1071    */
1072   @Override
1073   public SequenceI createDatasetSequence()
1074   {
1075     if (datasetSequence == null)
1076     {
1077       datasetSequence = new Sequence(getName(), AlignSeq.extractGaps(
1078               jalview.util.Comparison.GapChars, getSequenceAsString()),
1079               getStart(), getEnd());
1080       datasetSequence.setSequenceFeatures(getSequenceFeatures());
1081       datasetSequence.setDescription(getDescription());
1082       setSequenceFeatures(null);
1083       // move database references onto dataset sequence
1084       datasetSequence.setDBRefs(getDBRefs());
1085       setDBRefs(null);
1086       datasetSequence.setPDBId(getAllPDBEntries());
1087       setPDBId(null);
1088       datasetSequence.updatePDBIds();
1089       if (annotation != null)
1090       {
1091         for (AlignmentAnnotation aa : annotation)
1092         {
1093           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1094           _aa.sequenceRef = datasetSequence;
1095           _aa.adjustForAlignment(); // uses annotation's own record of
1096                                     // sequence-column mapping
1097           datasetSequence.addAlignmentAnnotation(_aa);
1098         }
1099       }
1100     }
1101     return datasetSequence;
1102   }
1103
1104   /*
1105    * (non-Javadoc)
1106    * 
1107    * @see
1108    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1109    * annotations)
1110    */
1111   @Override
1112   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1113   {
1114     if (annotation != null)
1115     {
1116       annotation.removeAllElements();
1117     }
1118     if (annotations != null)
1119     {
1120       for (int i = 0; i < annotations.length; i++)
1121       {
1122         if (annotations[i] != null)
1123         {
1124           addAlignmentAnnotation(annotations[i]);
1125         }
1126       }
1127     }
1128   }
1129
1130   @Override
1131   public AlignmentAnnotation[] getAnnotation(String label)
1132   {
1133     if (annotation == null || annotation.size() == 0)
1134     {
1135       return null;
1136     }
1137
1138     Vector subset = new Vector();
1139     Enumeration e = annotation.elements();
1140     while (e.hasMoreElements())
1141     {
1142       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
1143       if (ann.label != null && ann.label.equals(label))
1144       {
1145         subset.addElement(ann);
1146       }
1147     }
1148     if (subset.size() == 0)
1149     {
1150       return null;
1151     }
1152     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1153     int i = 0;
1154     e = subset.elements();
1155     while (e.hasMoreElements())
1156     {
1157       anns[i++] = (AlignmentAnnotation) e.nextElement();
1158     }
1159     subset.removeAllElements();
1160     return anns;
1161   }
1162
1163   @Override
1164   public boolean updatePDBIds()
1165   {
1166     if (datasetSequence != null)
1167     {
1168       // TODO: could merge DBRefs
1169       return datasetSequence.updatePDBIds();
1170     }
1171     if (dbrefs == null || dbrefs.length == 0)
1172     {
1173       return false;
1174     }
1175     Vector newpdb = new Vector();
1176     for (int i = 0; i < dbrefs.length; i++)
1177     {
1178       if (DBRefSource.PDB.equals(dbrefs[i].getSource()))
1179       {
1180         PDBEntry pdbe = new PDBEntry();
1181         pdbe.setId(dbrefs[i].getAccessionId());
1182         if (pdbIds == null || pdbIds.size() == 0)
1183         {
1184           newpdb.addElement(pdbe);
1185         }
1186         else
1187         {
1188           Enumeration en = pdbIds.elements();
1189           boolean matched = false;
1190           while (!matched && en.hasMoreElements())
1191           {
1192             PDBEntry anentry = (PDBEntry) en.nextElement();
1193             if (anentry.getId().equals(pdbe.getId()))
1194             {
1195               matched = true;
1196             }
1197           }
1198           if (!matched)
1199           {
1200             newpdb.addElement(pdbe);
1201           }
1202         }
1203       }
1204     }
1205     if (newpdb.size() > 0)
1206     {
1207       Enumeration en = newpdb.elements();
1208       while (en.hasMoreElements())
1209       {
1210         addPDBId((PDBEntry) en.nextElement());
1211       }
1212       return true;
1213     }
1214     return false;
1215   }
1216
1217   @Override
1218   public void transferAnnotation(SequenceI entry, Mapping mp)
1219   {
1220     if (datasetSequence != null)
1221     {
1222       datasetSequence.transferAnnotation(entry, mp);
1223       return;
1224     }
1225     if (entry.getDatasetSequence() != null)
1226     {
1227       transferAnnotation(entry.getDatasetSequence(), mp);
1228       return;
1229     }
1230     // transfer any new features from entry onto sequence
1231     if (entry.getSequenceFeatures() != null)
1232     {
1233
1234       SequenceFeature[] sfs = entry.getSequenceFeatures();
1235       for (int si = 0; si < sfs.length; si++)
1236       {
1237         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
1238                 : new SequenceFeature[] { new SequenceFeature(sfs[si]) };
1239         if (sf != null && sf.length > 0)
1240         {
1241           for (int sfi = 0; sfi < sf.length; sfi++)
1242           {
1243             addSequenceFeature(sf[sfi]);
1244           }
1245         }
1246       }
1247     }
1248
1249     // transfer PDB entries
1250     if (entry.getAllPDBEntries() != null)
1251     {
1252       Enumeration e = entry.getAllPDBEntries().elements();
1253       while (e.hasMoreElements())
1254       {
1255         PDBEntry pdb = (PDBEntry) e.nextElement();
1256         addPDBId(pdb);
1257       }
1258     }
1259     // transfer database references
1260     DBRefEntry[] entryRefs = entry.getDBRefs();
1261     if (entryRefs != null)
1262     {
1263       for (int r = 0; r < entryRefs.length; r++)
1264       {
1265         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1266         if (newref.getMap() != null && mp != null)
1267         {
1268           // remap ref using our local mapping
1269         }
1270         // we also assume all version string setting is done by dbSourceProxy
1271         /*
1272          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1273          * newref.setSource(dbSource); }
1274          */
1275         addDBRef(newref);
1276       }
1277     }
1278   }
1279
1280   /**
1281    * @return The index (zero-based) on this sequence in the MSA. It returns
1282    *         {@code -1} if this information is not available.
1283    */
1284   @Override
1285   public int getIndex()
1286   {
1287     return index;
1288   }
1289
1290   /**
1291    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1292    * if this information is undefined.
1293    * 
1294    * @param The
1295    *          position for this sequence. This value is zero-based (zero for
1296    *          this first sequence)
1297    */
1298   @Override
1299   public void setIndex(int value)
1300   {
1301     index = value;
1302   }
1303
1304   @Override
1305   public void setRNA(RNA r)
1306   {
1307     rna = r;
1308   }
1309
1310   @Override
1311   public RNA getRNA()
1312   {
1313     return rna;
1314   }
1315
1316   @Override
1317   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1318           String label)
1319   {
1320     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1321     if (this.annotation != null)
1322     {
1323       for (AlignmentAnnotation ann : annotation)
1324       {
1325         if (ann.calcId != null && ann.calcId.equals(calcId)
1326                 && ann.label != null && ann.label.equals(label))
1327         {
1328           result.add(ann);
1329         }
1330       }
1331     }
1332     return result;
1333   }
1334
1335   @Override
1336   public String toString()
1337   {
1338     return getDisplayId(false);
1339   }
1340
1341   @Override
1342   public PDBEntry getPDBEntry(String pdbIdStr)
1343   {
1344     if (getDatasetSequence() == null
1345             || getDatasetSequence().getAllPDBEntries() == null)
1346     {
1347       return null;
1348     }
1349     List<PDBEntry> entries = getDatasetSequence().getAllPDBEntries();
1350     for (PDBEntry entry : entries)
1351     {
1352       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1353       {
1354         return entry;
1355       }
1356     }
1357     return null;
1358   }
1359
1360   @Override
1361   public void setSourceDBRef(DBRefEntryI dbRef)
1362   {
1363     this.sourceDBRef = dbRef;
1364   }
1365
1366   @Override
1367   public DBRefEntryI getSourceDBRef()
1368   {
1369     return this.sourceDBRef;
1370   }
1371
1372 }