JAL-2046 clarify contract for copy constructor and initSeqFrom implementation
[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    * DOCUMENT ME!
298    * 
299    * @param v
300    *          DOCUMENT ME!
301    */
302   @Override
303   public void setSequenceFeatures(SequenceFeature[] features)
304   {
305     sequenceFeatures = features;
306   }
307
308   @Override
309   public synchronized void addSequenceFeature(SequenceFeature sf)
310   {
311     // TODO add to dataset sequence instead if there is one?
312     if (sequenceFeatures == null)
313     {
314       sequenceFeatures = new SequenceFeature[0];
315     }
316
317     for (int i = 0; i < sequenceFeatures.length; i++)
318     {
319       if (sequenceFeatures[i].equals(sf))
320       {
321         return;
322       }
323     }
324
325     SequenceFeature[] temp = new SequenceFeature[sequenceFeatures.length + 1];
326     System.arraycopy(sequenceFeatures, 0, temp, 0, sequenceFeatures.length);
327     temp[sequenceFeatures.length] = sf;
328
329     sequenceFeatures = temp;
330   }
331
332   @Override
333   public void deleteFeature(SequenceFeature sf)
334   {
335     if (sequenceFeatures == null)
336     {
337       return;
338     }
339
340     int index = 0;
341     for (index = 0; index < sequenceFeatures.length; index++)
342     {
343       if (sequenceFeatures[index].equals(sf))
344       {
345         break;
346       }
347     }
348
349     if (index == sequenceFeatures.length)
350     {
351       return;
352     }
353
354     int sfLength = sequenceFeatures.length;
355     if (sfLength < 2)
356     {
357       sequenceFeatures = null;
358     }
359     else
360     {
361       SequenceFeature[] temp = new SequenceFeature[sfLength - 1];
362       System.arraycopy(sequenceFeatures, 0, temp, 0, index);
363
364       if (index < sfLength)
365       {
366         System.arraycopy(sequenceFeatures, index + 1, temp, index,
367                 sequenceFeatures.length - index - 1);
368       }
369
370       sequenceFeatures = temp;
371     }
372   }
373
374   /**
375    * Returns the sequence features (if any), looking first on the sequence, then
376    * on its dataset sequence, and so on until a non-null value is found (or
377    * none). This supports retrieval of sequence features stored on the sequence
378    * (as in the applet) or on the dataset sequence (as in the Desktop version).
379    * 
380    * @return
381    */
382   @Override
383   public SequenceFeature[] getSequenceFeatures()
384   {
385     SequenceFeature[] features = sequenceFeatures;
386
387     SequenceI seq = this;
388     int count = 0; // failsafe against loop in sequence.datasetsequence...
389     while (features == null && seq.getDatasetSequence() != null
390             && count++ < 10)
391     {
392       seq = seq.getDatasetSequence();
393       features = ((Sequence) seq).sequenceFeatures;
394     }
395     return features;
396   }
397
398   @Override
399   public void addPDBId(PDBEntry entry)
400   {
401     if (pdbIds == null)
402     {
403       pdbIds = new Vector<PDBEntry>();
404     }
405     if (pdbIds.contains(entry))
406     {
407       updatePDBEntry(pdbIds.get(pdbIds.indexOf(entry)), entry);
408     }
409     else
410     {
411       pdbIds.addElement(entry);
412     }
413   }
414
415   private static void updatePDBEntry(PDBEntry oldEntry, PDBEntry newEntry)
416   {
417     if (newEntry.getFile() != null)
418     {
419       oldEntry.setFile(newEntry.getFile());
420     }
421   }
422
423   /**
424    * DOCUMENT ME!
425    * 
426    * @param id
427    *          DOCUMENT ME!
428    */
429   @Override
430   public void setPDBId(Vector<PDBEntry> id)
431   {
432     pdbIds = id;
433   }
434
435   /**
436    * DOCUMENT ME!
437    * 
438    * @return DOCUMENT ME!
439    */
440   @Override
441   public Vector<PDBEntry> getAllPDBEntries()
442   {
443     return pdbIds == null ? new Vector<PDBEntry>() : pdbIds;
444   }
445
446   /**
447    * DOCUMENT ME!
448    * 
449    * @return DOCUMENT ME!
450    */
451   @Override
452   public String getDisplayId(boolean jvsuffix)
453   {
454     StringBuffer result = new StringBuffer(name);
455     if (jvsuffix)
456     {
457       result.append("/" + start + "-" + end);
458     }
459
460     return result.toString();
461   }
462
463   /**
464    * DOCUMENT ME!
465    * 
466    * @param name
467    *          DOCUMENT ME!
468    */
469   @Override
470   public void setName(String name)
471   {
472     this.name = name;
473     this.parseId();
474   }
475
476   /**
477    * DOCUMENT ME!
478    * 
479    * @return DOCUMENT ME!
480    */
481   @Override
482   public String getName()
483   {
484     return this.name;
485   }
486
487   /**
488    * DOCUMENT ME!
489    * 
490    * @param start
491    *          DOCUMENT ME!
492    */
493   @Override
494   public void setStart(int start)
495   {
496     this.start = start;
497   }
498
499   /**
500    * DOCUMENT ME!
501    * 
502    * @return DOCUMENT ME!
503    */
504   @Override
505   public int getStart()
506   {
507     return this.start;
508   }
509
510   /**
511    * DOCUMENT ME!
512    * 
513    * @param end
514    *          DOCUMENT ME!
515    */
516   @Override
517   public void setEnd(int end)
518   {
519     this.end = end;
520   }
521
522   /**
523    * DOCUMENT ME!
524    * 
525    * @return DOCUMENT ME!
526    */
527   @Override
528   public int getEnd()
529   {
530     return this.end;
531   }
532
533   /**
534    * DOCUMENT ME!
535    * 
536    * @return DOCUMENT ME!
537    */
538   @Override
539   public int getLength()
540   {
541     return this.sequence.length;
542   }
543
544   /**
545    * DOCUMENT ME!
546    * 
547    * @param seq
548    *          DOCUMENT ME!
549    */
550   @Override
551   public void setSequence(String seq)
552   {
553     this.sequence = seq.toCharArray();
554     checkValidRange();
555   }
556
557   @Override
558   public String getSequenceAsString()
559   {
560     return new String(sequence);
561   }
562
563   @Override
564   public String getSequenceAsString(int start, int end)
565   {
566     return new String(getSequence(start, end));
567   }
568
569   @Override
570   public char[] getSequence()
571   {
572     return sequence;
573   }
574
575   /*
576    * (non-Javadoc)
577    * 
578    * @see jalview.datamodel.SequenceI#getSequence(int, int)
579    */
580   @Override
581   public char[] getSequence(int start, int end)
582   {
583     if (start < 0)
584     {
585       start = 0;
586     }
587     // JBPNote - left to user to pad the result here (TODO:Decide on this
588     // policy)
589     if (start >= sequence.length)
590     {
591       return new char[0];
592     }
593
594     if (end >= sequence.length)
595     {
596       end = sequence.length;
597     }
598
599     char[] reply = new char[end - start];
600     System.arraycopy(sequence, start, reply, 0, end - start);
601
602     return reply;
603   }
604
605   @Override
606   public SequenceI getSubSequence(int start, int end)
607   {
608     if (start < 0)
609     {
610       start = 0;
611     }
612     char[] seq = getSequence(start, end);
613     if (seq.length == 0)
614     {
615       return null;
616     }
617     int nstart = findPosition(start);
618     int nend = findPosition(end) - 1;
619     // JBPNote - this is an incomplete copy.
620     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
621     nseq.setDescription(description);
622     if (datasetSequence != null)
623     {
624       nseq.setDatasetSequence(datasetSequence);
625     }
626     else
627     {
628       nseq.setDatasetSequence(this);
629     }
630     return nseq;
631   }
632
633   /**
634    * Returns the character of the aligned sequence at the given position (base
635    * zero), or space if the position is not within the sequence's bounds
636    * 
637    * @return
638    */
639   @Override
640   public char getCharAt(int i)
641   {
642     if (i >= 0 && i < sequence.length)
643     {
644       return sequence[i];
645     }
646     else
647     {
648       return ' ';
649     }
650   }
651
652   /**
653    * DOCUMENT ME!
654    * 
655    * @param desc
656    *          DOCUMENT ME!
657    */
658   @Override
659   public void setDescription(String desc)
660   {
661     this.description = desc;
662   }
663
664   /**
665    * DOCUMENT ME!
666    * 
667    * @return DOCUMENT ME!
668    */
669   @Override
670   public String getDescription()
671   {
672     return this.description;
673   }
674
675   /*
676    * (non-Javadoc)
677    * 
678    * @see jalview.datamodel.SequenceI#findIndex(int)
679    */
680   @Override
681   public int findIndex(int pos)
682   {
683     // returns the alignment position for a residue
684     int j = start;
685     int i = 0;
686     // Rely on end being at least as long as the length of the sequence.
687     while ((i < sequence.length) && (j <= end) && (j <= pos))
688     {
689       if (!jalview.util.Comparison.isGap(sequence[i]))
690       {
691         j++;
692       }
693
694       i++;
695     }
696
697     if ((j == end) && (j < pos))
698     {
699       return end + 1;
700     }
701     else
702     {
703       return i;
704     }
705   }
706
707   @Override
708   public int findPosition(int i)
709   {
710     int j = 0;
711     int pos = start;
712     int seqlen = sequence.length;
713     while ((j < i) && (j < seqlen))
714     {
715       if (!jalview.util.Comparison.isGap(sequence[j]))
716       {
717         pos++;
718       }
719
720       j++;
721     }
722
723     return pos;
724   }
725
726   /**
727    * Returns an int array where indices correspond to each residue in the
728    * sequence and the element value gives its position in the alignment
729    * 
730    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
731    *         residues in SequenceI object
732    */
733   @Override
734   public int[] gapMap()
735   {
736     String seq = jalview.analysis.AlignSeq.extractGaps(
737             jalview.util.Comparison.GapChars, new String(sequence));
738     int[] map = new int[seq.length()];
739     int j = 0;
740     int p = 0;
741
742     while (j < sequence.length)
743     {
744       if (!jalview.util.Comparison.isGap(sequence[j]))
745       {
746         map[p++] = j;
747       }
748
749       j++;
750     }
751
752     return map;
753   }
754
755   @Override
756   public int[] findPositionMap()
757   {
758     int map[] = new int[sequence.length];
759     int j = 0;
760     int pos = start;
761     int seqlen = sequence.length;
762     while ((j < seqlen))
763     {
764       map[j] = pos;
765       if (!jalview.util.Comparison.isGap(sequence[j]))
766       {
767         pos++;
768       }
769
770       j++;
771     }
772     return map;
773   }
774
775   @Override
776   public List<int[]> getInsertions()
777   {
778     ArrayList<int[]> map = new ArrayList<int[]>();
779     int lastj = -1, j = 0;
780     int pos = start;
781     int seqlen = sequence.length;
782     while ((j < seqlen))
783     {
784       if (jalview.util.Comparison.isGap(sequence[j]))
785       {
786         if (lastj == -1)
787         {
788           lastj = j;
789         }
790       }
791       else
792       {
793         if (lastj != -1)
794         {
795           map.add(new int[] { lastj, j - 1 });
796           lastj = -1;
797         }
798       }
799       j++;
800     }
801     if (lastj != -1)
802     {
803       map.add(new int[] { lastj, j - 1 });
804       lastj = -1;
805     }
806     return map;
807   }
808
809   @Override
810   public void deleteChars(int i, int j)
811   {
812     int newstart = start, newend = end;
813     if (i >= sequence.length || i < 0)
814     {
815       return;
816     }
817
818     char[] tmp = StringUtils.deleteChars(sequence, i, j);
819     boolean createNewDs = false;
820     // TODO: take a (second look) at the dataset creation validation method for
821     // the very large sequence case
822     int eindex = -1, sindex = -1;
823     boolean ecalc = false, scalc = false;
824     for (int s = i; s < j; s++)
825     {
826       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
827       {
828         if (createNewDs)
829         {
830           newend--;
831         }
832         else
833         {
834           if (!scalc)
835           {
836             sindex = findIndex(start) - 1;
837             scalc = true;
838           }
839           if (sindex == s)
840           {
841             // delete characters including start of sequence
842             newstart = findPosition(j);
843             break; // don't need to search for any more residue characters.
844           }
845           else
846           {
847             // delete characters after start.
848             if (!ecalc)
849             {
850               eindex = findIndex(end) - 1;
851               ecalc = true;
852             }
853             if (eindex < j)
854             {
855               // delete characters at end of sequence
856               newend = findPosition(i - 1);
857               break; // don't need to search for any more residue characters.
858             }
859             else
860             {
861               createNewDs = true;
862               newend--; // decrease end position by one for the deleted residue
863               // and search further
864             }
865           }
866         }
867       }
868     }
869     // deletion occured in the middle of the sequence
870     if (createNewDs && this.datasetSequence != null)
871     {
872       // construct a new sequence
873       Sequence ds = new Sequence(datasetSequence);
874       // TODO: remove any non-inheritable properties ?
875       // TODO: create a sequence mapping (since there is a relation here ?)
876       ds.deleteChars(i, j);
877       datasetSequence = ds;
878     }
879     start = newstart;
880     end = newend;
881     sequence = tmp;
882   }
883
884   @Override
885   public void insertCharAt(int i, int length, char c)
886   {
887     char[] tmp = new char[sequence.length + length];
888
889     if (i >= sequence.length)
890     {
891       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
892       i = sequence.length;
893     }
894     else
895     {
896       System.arraycopy(sequence, 0, tmp, 0, i);
897     }
898
899     int index = i;
900     while (length > 0)
901     {
902       tmp[index++] = c;
903       length--;
904     }
905
906     if (i < sequence.length)
907     {
908       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
909     }
910
911     sequence = tmp;
912   }
913
914   @Override
915   public void insertCharAt(int i, char c)
916   {
917     insertCharAt(i, 1, c);
918   }
919
920   @Override
921   public String getVamsasId()
922   {
923     return vamsasId;
924   }
925
926   @Override
927   public void setVamsasId(String id)
928   {
929     vamsasId = id;
930   }
931
932   @Override
933   public void setDBRefs(DBRefEntry[] dbref)
934   {
935     dbrefs = dbref;
936   }
937
938   @Override
939   public DBRefEntry[] getDBRefs()
940   {
941     if (dbrefs == null && datasetSequence != null
942             && this != datasetSequence)
943     {
944       return datasetSequence.getDBRefs();
945     }
946     return dbrefs;
947   }
948
949   @Override
950   public void addDBRef(DBRefEntry entry)
951   {
952     // TODO add to dataset sequence instead if there is one?
953     if (dbrefs == null)
954     {
955       dbrefs = new DBRefEntry[0];
956     }
957
958     int i, iSize = dbrefs.length;
959
960     for (i = 0; i < iSize; i++)
961     {
962       if (dbrefs[i].equalRef(entry))
963       {
964         if (entry.getMap() != null)
965         {
966           if (dbrefs[i].getMap() == null)
967           {
968             // overwrite with 'superior' entry that contains a mapping.
969             dbrefs[i] = entry;
970           }
971         }
972         return;
973       }
974     }
975
976     DBRefEntry[] temp = new DBRefEntry[iSize + 1];
977     System.arraycopy(dbrefs, 0, temp, 0, iSize);
978     temp[temp.length - 1] = entry;
979
980     dbrefs = temp;
981   }
982
983   @Override
984   public void setDatasetSequence(SequenceI seq)
985   {
986     // TODO check for circular reference before setting?
987     datasetSequence = seq;
988   }
989
990   @Override
991   public SequenceI getDatasetSequence()
992   {
993     return datasetSequence;
994   }
995
996   @Override
997   public AlignmentAnnotation[] getAnnotation()
998   {
999     return annotation == null ? null : annotation
1000             .toArray(new AlignmentAnnotation[annotation.size()]);
1001   }
1002
1003   @Override
1004   public boolean hasAnnotation(AlignmentAnnotation ann)
1005   {
1006     return annotation == null ? false : annotation.contains(ann);
1007   }
1008
1009   @Override
1010   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1011   {
1012     if (this.annotation == null)
1013     {
1014       this.annotation = new Vector<AlignmentAnnotation>();
1015     }
1016     if (!this.annotation.contains(annotation))
1017     {
1018       this.annotation.addElement(annotation);
1019     }
1020     annotation.setSequenceRef(this);
1021   }
1022
1023   @Override
1024   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1025   {
1026     if (this.annotation != null)
1027     {
1028       this.annotation.removeElement(annotation);
1029       if (this.annotation.size() == 0)
1030       {
1031         this.annotation = null;
1032       }
1033     }
1034   }
1035
1036   /**
1037    * test if this is a valid candidate for another sequence's dataset sequence.
1038    * 
1039    */
1040   private boolean isValidDatasetSequence()
1041   {
1042     if (datasetSequence != null)
1043     {
1044       return false;
1045     }
1046     for (int i = 0; i < sequence.length; i++)
1047     {
1048       if (jalview.util.Comparison.isGap(sequence[i]))
1049       {
1050         return false;
1051       }
1052     }
1053     return true;
1054   }
1055
1056   @Override
1057   public SequenceI deriveSequence()
1058   {
1059     SequenceI seq = new Sequence(this);
1060     if (datasetSequence != null)
1061     {
1062       // duplicate current sequence with same dataset
1063       seq.setDatasetSequence(datasetSequence);
1064     }
1065     else
1066     {
1067       if (isValidDatasetSequence())
1068       {
1069         // Use this as dataset sequence
1070         seq.setDatasetSequence(this);
1071       }
1072       else
1073       {
1074         // Create a new, valid dataset sequence
1075         SequenceI ds = seq;
1076         ds.setSequence(AlignSeq.extractGaps(
1077                 jalview.util.Comparison.GapChars, new String(sequence)));
1078         setDatasetSequence(ds);
1079         ds.setSequenceFeatures(getSequenceFeatures());
1080         seq = this; // and return this sequence as the derived sequence.
1081       }
1082     }
1083     return seq;
1084   }
1085
1086   /*
1087    * (non-Javadoc)
1088    * 
1089    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1090    */
1091   @Override
1092   public SequenceI createDatasetSequence()
1093   {
1094     if (datasetSequence == null)
1095     {
1096       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1097               jalview.util.Comparison.GapChars, getSequenceAsString()),
1098               getStart(), getEnd());
1099
1100       datasetSequence = dsseq;
1101
1102       dsseq.setDescription(description);
1103       // move features and database references onto dataset sequence
1104       dsseq.sequenceFeatures = sequenceFeatures;
1105       sequenceFeatures=null;
1106       dsseq.dbrefs = dbrefs;
1107       dbrefs=null;
1108       // TODO: search and replace any references to this sequence with
1109       // references to the dataset sequence in Mappings on dbref
1110       dsseq.pdbIds = pdbIds;
1111       pdbIds = null;
1112       datasetSequence.updatePDBIds();
1113       if (annotation != null)
1114       {
1115         // annotation is cloned rather than moved, to preserve what's currently
1116         // on the alignment
1117         for (AlignmentAnnotation aa : annotation)
1118         {
1119           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1120           _aa.sequenceRef = datasetSequence;
1121           _aa.adjustForAlignment(); // uses annotation's own record of
1122                                     // sequence-column mapping
1123           datasetSequence.addAlignmentAnnotation(_aa);
1124         }
1125       }
1126     }
1127     return datasetSequence;
1128   }
1129
1130   /*
1131    * (non-Javadoc)
1132    * 
1133    * @see
1134    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1135    * annotations)
1136    */
1137   @Override
1138   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1139   {
1140     if (annotation != null)
1141     {
1142       annotation.removeAllElements();
1143     }
1144     if (annotations != null)
1145     {
1146       for (int i = 0; i < annotations.length; i++)
1147       {
1148         if (annotations[i] != null)
1149         {
1150           addAlignmentAnnotation(annotations[i]);
1151         }
1152       }
1153     }
1154   }
1155
1156   @Override
1157   public AlignmentAnnotation[] getAnnotation(String label)
1158   {
1159     if (annotation == null || annotation.size() == 0)
1160     {
1161       return null;
1162     }
1163
1164     Vector subset = new Vector();
1165     Enumeration e = annotation.elements();
1166     while (e.hasMoreElements())
1167     {
1168       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
1169       if (ann.label != null && ann.label.equals(label))
1170       {
1171         subset.addElement(ann);
1172       }
1173     }
1174     if (subset.size() == 0)
1175     {
1176       return null;
1177     }
1178     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1179     int i = 0;
1180     e = subset.elements();
1181     while (e.hasMoreElements())
1182     {
1183       anns[i++] = (AlignmentAnnotation) e.nextElement();
1184     }
1185     subset.removeAllElements();
1186     return anns;
1187   }
1188
1189   @Override
1190   public boolean updatePDBIds()
1191   {
1192     if (datasetSequence != null)
1193     {
1194       // TODO: could merge DBRefs
1195       return datasetSequence.updatePDBIds();
1196     }
1197     if (dbrefs == null || dbrefs.length == 0)
1198     {
1199       return false;
1200     }
1201     Vector newpdb = new Vector();
1202     for (int i = 0; i < dbrefs.length; i++)
1203     {
1204       if (DBRefSource.PDB.equals(dbrefs[i].getSource()))
1205       {
1206         PDBEntry pdbe = new PDBEntry();
1207         pdbe.setId(dbrefs[i].getAccessionId());
1208         if (pdbIds == null || pdbIds.size() == 0)
1209         {
1210           newpdb.addElement(pdbe);
1211         }
1212         else
1213         {
1214           Enumeration en = pdbIds.elements();
1215           boolean matched = false;
1216           while (!matched && en.hasMoreElements())
1217           {
1218             PDBEntry anentry = (PDBEntry) en.nextElement();
1219             if (anentry.getId().equals(pdbe.getId()))
1220             {
1221               matched = true;
1222             }
1223           }
1224           if (!matched)
1225           {
1226             newpdb.addElement(pdbe);
1227           }
1228         }
1229       }
1230     }
1231     if (newpdb.size() > 0)
1232     {
1233       Enumeration en = newpdb.elements();
1234       while (en.hasMoreElements())
1235       {
1236         addPDBId((PDBEntry) en.nextElement());
1237       }
1238       return true;
1239     }
1240     return false;
1241   }
1242
1243   @Override
1244   public void transferAnnotation(SequenceI entry, Mapping mp)
1245   {
1246     if (datasetSequence != null)
1247     {
1248       datasetSequence.transferAnnotation(entry, mp);
1249       return;
1250     }
1251     if (entry.getDatasetSequence() != null)
1252     {
1253       transferAnnotation(entry.getDatasetSequence(), mp);
1254       return;
1255     }
1256     // transfer any new features from entry onto sequence
1257     if (entry.getSequenceFeatures() != null)
1258     {
1259
1260       SequenceFeature[] sfs = entry.getSequenceFeatures();
1261       for (int si = 0; si < sfs.length; si++)
1262       {
1263         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
1264                 : new SequenceFeature[] { new SequenceFeature(sfs[si]) };
1265         if (sf != null && sf.length > 0)
1266         {
1267           for (int sfi = 0; sfi < sf.length; sfi++)
1268           {
1269             addSequenceFeature(sf[sfi]);
1270           }
1271         }
1272       }
1273     }
1274
1275     // transfer PDB entries
1276     if (entry.getAllPDBEntries() != null)
1277     {
1278       Enumeration e = entry.getAllPDBEntries().elements();
1279       while (e.hasMoreElements())
1280       {
1281         PDBEntry pdb = (PDBEntry) e.nextElement();
1282         addPDBId(pdb);
1283       }
1284     }
1285     // transfer database references
1286     DBRefEntry[] entryRefs = entry.getDBRefs();
1287     if (entryRefs != null)
1288     {
1289       for (int r = 0; r < entryRefs.length; r++)
1290       {
1291         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1292         if (newref.getMap() != null && mp != null)
1293         {
1294           // remap ref using our local mapping
1295         }
1296         // we also assume all version string setting is done by dbSourceProxy
1297         /*
1298          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1299          * newref.setSource(dbSource); }
1300          */
1301         addDBRef(newref);
1302       }
1303     }
1304   }
1305
1306   /**
1307    * @return The index (zero-based) on this sequence in the MSA. It returns
1308    *         {@code -1} if this information is not available.
1309    */
1310   @Override
1311   public int getIndex()
1312   {
1313     return index;
1314   }
1315
1316   /**
1317    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1318    * if this information is undefined.
1319    * 
1320    * @param The
1321    *          position for this sequence. This value is zero-based (zero for
1322    *          this first sequence)
1323    */
1324   @Override
1325   public void setIndex(int value)
1326   {
1327     index = value;
1328   }
1329
1330   @Override
1331   public void setRNA(RNA r)
1332   {
1333     rna = r;
1334   }
1335
1336   @Override
1337   public RNA getRNA()
1338   {
1339     return rna;
1340   }
1341
1342   @Override
1343   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1344           String label)
1345   {
1346     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1347     if (this.annotation != null)
1348     {
1349       for (AlignmentAnnotation ann : annotation)
1350       {
1351         if (ann.calcId != null && ann.calcId.equals(calcId)
1352                 && ann.label != null && ann.label.equals(label))
1353         {
1354           result.add(ann);
1355         }
1356       }
1357     }
1358     return result;
1359   }
1360
1361   @Override
1362   public String toString()
1363   {
1364     return getDisplayId(false);
1365   }
1366
1367   @Override
1368   public PDBEntry getPDBEntry(String pdbIdStr)
1369   {
1370     if (getDatasetSequence() == null
1371             || getDatasetSequence().getAllPDBEntries() == null)
1372     {
1373       return null;
1374     }
1375     List<PDBEntry> entries = getDatasetSequence().getAllPDBEntries();
1376     for (PDBEntry entry : entries)
1377     {
1378       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1379       {
1380         return entry;
1381       }
1382     }
1383     return null;
1384   }
1385
1386   @Override
1387   public void setSourceDBRef(DBRefEntryI dbRef)
1388   {
1389     this.sourceDBRef = dbRef;
1390   }
1391
1392   @Override
1393   public DBRefEntryI getSourceDBRef()
1394   {
1395     return this.sourceDBRef;
1396   }
1397
1398 }