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