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