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