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