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