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