6f1b4030d55f897ab5e474515e24b52a003200cd
[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       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1078               jalview.util.Comparison.GapChars, getSequenceAsString()),
1079               getStart(), getEnd());
1080
1081       datasetSequence = dsseq;
1082
1083       dsseq.setDescription(description);
1084       // move features and database references onto dataset sequence
1085       dsseq.sequenceFeatures = sequenceFeatures;
1086       sequenceFeatures=null;
1087       dsseq.dbrefs = dbrefs;
1088       dbrefs=null;
1089       // TODO: search and replace any references to this sequence with
1090       // references to the dataset sequence in Mappings on dbref
1091       dsseq.pdbIds = pdbIds;
1092       pdbIds = null;
1093       datasetSequence.updatePDBIds();
1094       if (annotation != null)
1095       {
1096         // annotation is cloned rather than moved, to preserve what's currently
1097         // on the alignment
1098         for (AlignmentAnnotation aa : annotation)
1099         {
1100           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1101           _aa.sequenceRef = datasetSequence;
1102           _aa.adjustForAlignment(); // uses annotation's own record of
1103                                     // sequence-column mapping
1104           datasetSequence.addAlignmentAnnotation(_aa);
1105         }
1106       }
1107     }
1108     return datasetSequence;
1109   }
1110
1111   /*
1112    * (non-Javadoc)
1113    * 
1114    * @see
1115    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1116    * annotations)
1117    */
1118   @Override
1119   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1120   {
1121     if (annotation != null)
1122     {
1123       annotation.removeAllElements();
1124     }
1125     if (annotations != null)
1126     {
1127       for (int i = 0; i < annotations.length; i++)
1128       {
1129         if (annotations[i] != null)
1130         {
1131           addAlignmentAnnotation(annotations[i]);
1132         }
1133       }
1134     }
1135   }
1136
1137   @Override
1138   public AlignmentAnnotation[] getAnnotation(String label)
1139   {
1140     if (annotation == null || annotation.size() == 0)
1141     {
1142       return null;
1143     }
1144
1145     Vector subset = new Vector();
1146     Enumeration e = annotation.elements();
1147     while (e.hasMoreElements())
1148     {
1149       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
1150       if (ann.label != null && ann.label.equals(label))
1151       {
1152         subset.addElement(ann);
1153       }
1154     }
1155     if (subset.size() == 0)
1156     {
1157       return null;
1158     }
1159     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1160     int i = 0;
1161     e = subset.elements();
1162     while (e.hasMoreElements())
1163     {
1164       anns[i++] = (AlignmentAnnotation) e.nextElement();
1165     }
1166     subset.removeAllElements();
1167     return anns;
1168   }
1169
1170   @Override
1171   public boolean updatePDBIds()
1172   {
1173     if (datasetSequence != null)
1174     {
1175       // TODO: could merge DBRefs
1176       return datasetSequence.updatePDBIds();
1177     }
1178     if (dbrefs == null || dbrefs.length == 0)
1179     {
1180       return false;
1181     }
1182     Vector newpdb = new Vector();
1183     for (int i = 0; i < dbrefs.length; i++)
1184     {
1185       if (DBRefSource.PDB.equals(dbrefs[i].getSource()))
1186       {
1187         PDBEntry pdbe = new PDBEntry();
1188         pdbe.setId(dbrefs[i].getAccessionId());
1189         if (pdbIds == null || pdbIds.size() == 0)
1190         {
1191           newpdb.addElement(pdbe);
1192         }
1193         else
1194         {
1195           Enumeration en = pdbIds.elements();
1196           boolean matched = false;
1197           while (!matched && en.hasMoreElements())
1198           {
1199             PDBEntry anentry = (PDBEntry) en.nextElement();
1200             if (anentry.getId().equals(pdbe.getId()))
1201             {
1202               matched = true;
1203             }
1204           }
1205           if (!matched)
1206           {
1207             newpdb.addElement(pdbe);
1208           }
1209         }
1210       }
1211     }
1212     if (newpdb.size() > 0)
1213     {
1214       Enumeration en = newpdb.elements();
1215       while (en.hasMoreElements())
1216       {
1217         addPDBId((PDBEntry) en.nextElement());
1218       }
1219       return true;
1220     }
1221     return false;
1222   }
1223
1224   @Override
1225   public void transferAnnotation(SequenceI entry, Mapping mp)
1226   {
1227     if (datasetSequence != null)
1228     {
1229       datasetSequence.transferAnnotation(entry, mp);
1230       return;
1231     }
1232     if (entry.getDatasetSequence() != null)
1233     {
1234       transferAnnotation(entry.getDatasetSequence(), mp);
1235       return;
1236     }
1237     // transfer any new features from entry onto sequence
1238     if (entry.getSequenceFeatures() != null)
1239     {
1240
1241       SequenceFeature[] sfs = entry.getSequenceFeatures();
1242       for (int si = 0; si < sfs.length; si++)
1243       {
1244         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
1245                 : new SequenceFeature[] { new SequenceFeature(sfs[si]) };
1246         if (sf != null && sf.length > 0)
1247         {
1248           for (int sfi = 0; sfi < sf.length; sfi++)
1249           {
1250             addSequenceFeature(sf[sfi]);
1251           }
1252         }
1253       }
1254     }
1255
1256     // transfer PDB entries
1257     if (entry.getAllPDBEntries() != null)
1258     {
1259       Enumeration e = entry.getAllPDBEntries().elements();
1260       while (e.hasMoreElements())
1261       {
1262         PDBEntry pdb = (PDBEntry) e.nextElement();
1263         addPDBId(pdb);
1264       }
1265     }
1266     // transfer database references
1267     DBRefEntry[] entryRefs = entry.getDBRefs();
1268     if (entryRefs != null)
1269     {
1270       for (int r = 0; r < entryRefs.length; r++)
1271       {
1272         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1273         if (newref.getMap() != null && mp != null)
1274         {
1275           // remap ref using our local mapping
1276         }
1277         // we also assume all version string setting is done by dbSourceProxy
1278         /*
1279          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1280          * newref.setSource(dbSource); }
1281          */
1282         addDBRef(newref);
1283       }
1284     }
1285   }
1286
1287   /**
1288    * @return The index (zero-based) on this sequence in the MSA. It returns
1289    *         {@code -1} if this information is not available.
1290    */
1291   @Override
1292   public int getIndex()
1293   {
1294     return index;
1295   }
1296
1297   /**
1298    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1299    * if this information is undefined.
1300    * 
1301    * @param The
1302    *          position for this sequence. This value is zero-based (zero for
1303    *          this first sequence)
1304    */
1305   @Override
1306   public void setIndex(int value)
1307   {
1308     index = value;
1309   }
1310
1311   @Override
1312   public void setRNA(RNA r)
1313   {
1314     rna = r;
1315   }
1316
1317   @Override
1318   public RNA getRNA()
1319   {
1320     return rna;
1321   }
1322
1323   @Override
1324   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1325           String label)
1326   {
1327     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1328     if (this.annotation != null)
1329     {
1330       for (AlignmentAnnotation ann : annotation)
1331       {
1332         if (ann.calcId != null && ann.calcId.equals(calcId)
1333                 && ann.label != null && ann.label.equals(label))
1334         {
1335           result.add(ann);
1336         }
1337       }
1338     }
1339     return result;
1340   }
1341
1342   @Override
1343   public String toString()
1344   {
1345     return getDisplayId(false);
1346   }
1347
1348   @Override
1349   public PDBEntry getPDBEntry(String pdbIdStr)
1350   {
1351     if (getDatasetSequence() == null
1352             || getDatasetSequence().getAllPDBEntries() == null)
1353     {
1354       return null;
1355     }
1356     List<PDBEntry> entries = getDatasetSequence().getAllPDBEntries();
1357     for (PDBEntry entry : entries)
1358     {
1359       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1360       {
1361         return entry;
1362       }
1363     }
1364     return null;
1365   }
1366
1367   @Override
1368   public void setSourceDBRef(DBRefEntryI dbRef)
1369   {
1370     this.sourceDBRef = dbRef;
1371   }
1372
1373   @Override
1374   public DBRefEntryI getSourceDBRef()
1375   {
1376     return this.sourceDBRef;
1377   }
1378
1379 }