Merge branch 'develop' into task/JAL-2196pdbeProperties
[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.Collections;
33 import java.util.Enumeration;
34 import java.util.List;
35 import java.util.Vector;
36
37 import fr.orsay.lri.varna.models.rna.RNA;
38
39 /**
40  * 
41  * Implements the SequenceI interface for a char[] based sequence object.
42  * 
43  * @author $author$
44  * @version $Revision$
45  */
46 public class Sequence extends ASequence implements SequenceI
47 {
48   SequenceI datasetSequence;
49
50   String name;
51
52   private char[] sequence;
53
54   String description;
55
56   int start;
57
58   int end;
59
60   Vector<PDBEntry> pdbIds;
61
62   String vamsasId;
63
64   DBRefEntry[] dbrefs;
65
66   RNA rna;
67
68   /**
69    * This annotation is displayed below the alignment but the positions are tied
70    * to the residues of this sequence
71    *
72    * TODO: change to List<>
73    */
74   Vector<AlignmentAnnotation> annotation;
75
76   /**
77    * The index of the sequence in a MSA
78    */
79   int index = -1;
80
81   /** array of sequence features - may not be null for a valid sequence object */
82   public SequenceFeature[] sequenceFeatures;
83
84   /**
85    * Creates a new Sequence object.
86    * 
87    * @param name
88    *          display name string
89    * @param sequence
90    *          string to form a possibly gapped sequence out of
91    * @param start
92    *          first position of non-gap residue in the sequence
93    * @param end
94    *          last position of ungapped residues (nearly always only used for
95    *          display purposes)
96    */
97   public Sequence(String name, String sequence, int start, int end)
98   {
99     initSeqAndName(name, sequence.toCharArray(), start, end);
100   }
101
102   public Sequence(String name, char[] sequence, int start, int end)
103   {
104     initSeqAndName(name, sequence, start, end);
105   }
106
107   /**
108    * Stage 1 constructor - assign name, sequence, and set start and end fields.
109    * start and end are updated values from name2 if it ends with /start-end
110    * 
111    * @param name2
112    * @param sequence2
113    * @param start2
114    * @param end2
115    */
116   protected void initSeqAndName(String name2, char[] sequence2, int start2,
117           int end2)
118   {
119     this.name = name2;
120     this.sequence = sequence2;
121     this.start = start2;
122     this.end = end2;
123     parseId();
124     checkValidRange();
125   }
126
127   com.stevesoft.pat.Regex limitrx = new com.stevesoft.pat.Regex(
128           "[/][0-9]{1,}[-][0-9]{1,}$");
129
130   com.stevesoft.pat.Regex endrx = new com.stevesoft.pat.Regex("[0-9]{1,}$");
131
132   void parseId()
133   {
134     if (name == null)
135     {
136       System.err
137               .println("POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
138       name = "";
139     }
140     // Does sequence have the /start-end signature?
141     if (limitrx.search(name))
142     {
143       name = limitrx.left();
144       endrx.search(limitrx.stringMatched());
145       setStart(Integer.parseInt(limitrx.stringMatched().substring(1,
146               endrx.matchedFrom() - 1)));
147       setEnd(Integer.parseInt(endrx.stringMatched()));
148     }
149   }
150
151   void checkValidRange()
152   {
153     // Note: JAL-774 :
154     // http://issues.jalview.org/browse/JAL-774?focusedCommentId=11239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-11239
155     {
156       int endRes = 0;
157       for (int j = 0; j < sequence.length; j++)
158       {
159         if (!jalview.util.Comparison.isGap(sequence[j]))
160         {
161           endRes++;
162         }
163       }
164       if (endRes > 0)
165       {
166         endRes += start - 1;
167       }
168
169       if (end < endRes)
170       {
171         end = endRes;
172       }
173     }
174
175   }
176
177   /**
178    * Creates a new Sequence object.
179    * 
180    * @param name
181    *          DOCUMENT ME!
182    * @param sequence
183    *          DOCUMENT ME!
184    */
185   public Sequence(String name, String sequence)
186   {
187     this(name, sequence, 1, -1);
188   }
189
190   /**
191    * Creates a new Sequence object with new AlignmentAnnotations but inherits
192    * any existing dataset sequence reference. If non exists, everything is
193    * copied.
194    * 
195    * @param seq
196    *          if seq is a dataset sequence, behaves like a plain old copy
197    *          constructor
198    */
199   public Sequence(SequenceI seq)
200   {
201     this(seq, seq.getAnnotation());
202   }
203
204   /**
205    * Create a new sequence object with new features, DBRefEntries, and PDBIds
206    * but inherits any existing dataset sequence reference, and duplicate of any
207    * annotation that is present in the given annotation array.
208    * 
209    * @param seq
210    *          the sequence to be copied
211    * @param alAnnotation
212    *          an array of annotation including some associated with seq
213    */
214   public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
215   {
216     initSeqFrom(seq, alAnnotation);
217
218   }
219
220   /**
221    * does the heavy lifting when cloning a dataset sequence, or coping data from
222    * dataset to a new derived sequence.
223    * 
224    * @param seq
225    *          - source of attributes.
226    * @param alAnnotation
227    *          - alignment annotation present on seq that should be copied onto
228    *          this sequence
229    */
230   protected void initSeqFrom(SequenceI seq,
231           AlignmentAnnotation[] alAnnotation)
232   {
233     {
234       char[] oseq = seq.getSequence();
235       initSeqAndName(seq.getName(), Arrays.copyOf(oseq, oseq.length),
236               seq.getStart(),
237             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
297   @Override
298   public void setSequenceFeatures(SequenceFeature[] features)
299   {
300     if (datasetSequence == null)
301     {
302       sequenceFeatures = features;
303     }
304     else
305     {
306       if (datasetSequence.getSequenceFeatures() != features
307               && datasetSequence.getSequenceFeatures() != null
308               && datasetSequence.getSequenceFeatures().length > 0)
309       {
310         new Exception(
311                 "Warning: JAL-2046 side effect ? Possible implementation error: overwriting dataset sequence features by setting sequence features on alignment")
312                 .printStackTrace();
313       }
314       datasetSequence.setSequenceFeatures(features);
315     }
316   }
317
318   @Override
319   public synchronized void addSequenceFeature(SequenceFeature sf)
320   {
321     if (sequenceFeatures==null && datasetSequence != null)
322     {
323       datasetSequence.addSequenceFeature(sf);
324       return;
325     }
326     if (sequenceFeatures == null)
327     {
328       sequenceFeatures = new SequenceFeature[0];
329     }
330
331     for (int i = 0; i < sequenceFeatures.length; i++)
332     {
333       if (sequenceFeatures[i].equals(sf))
334       {
335         return;
336       }
337     }
338
339     SequenceFeature[] temp = new SequenceFeature[sequenceFeatures.length + 1];
340     System.arraycopy(sequenceFeatures, 0, temp, 0, sequenceFeatures.length);
341     temp[sequenceFeatures.length] = sf;
342
343     sequenceFeatures = temp;
344   }
345
346   @Override
347   public void deleteFeature(SequenceFeature sf)
348   {
349     if (sequenceFeatures == null)
350     {
351       if (datasetSequence!=null) {
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 void deleteChars(int i, int j)
824   {
825     int newstart = start, newend = end;
826     if (i >= sequence.length || i < 0)
827     {
828       return;
829     }
830
831     char[] tmp = StringUtils.deleteChars(sequence, i, j);
832     boolean createNewDs = false;
833     // TODO: take a (second look) at the dataset creation validation method for
834     // the very large sequence case
835     int eindex = -1, sindex = -1;
836     boolean ecalc = false, scalc = false;
837     for (int s = i; s < j; s++)
838     {
839       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
840       {
841         if (createNewDs)
842         {
843           newend--;
844         }
845         else
846         {
847           if (!scalc)
848           {
849             sindex = findIndex(start) - 1;
850             scalc = true;
851           }
852           if (sindex == s)
853           {
854             // delete characters including start of sequence
855             newstart = findPosition(j);
856             break; // don't need to search for any more residue characters.
857           }
858           else
859           {
860             // delete characters after start.
861             if (!ecalc)
862             {
863               eindex = findIndex(end) - 1;
864               ecalc = true;
865             }
866             if (eindex < j)
867             {
868               // delete characters at end of sequence
869               newend = findPosition(i - 1);
870               break; // don't need to search for any more residue characters.
871             }
872             else
873             {
874               createNewDs = true;
875               newend--; // decrease end position by one for the deleted residue
876               // and search further
877             }
878           }
879         }
880       }
881     }
882     // deletion occured in the middle of the sequence
883     if (createNewDs && this.datasetSequence != null)
884     {
885       // construct a new sequence
886       Sequence ds = new Sequence(datasetSequence);
887       // TODO: remove any non-inheritable properties ?
888       // TODO: create a sequence mapping (since there is a relation here ?)
889       ds.deleteChars(i, j);
890       datasetSequence = ds;
891     }
892     start = newstart;
893     end = newend;
894     sequence = tmp;
895   }
896
897   @Override
898   public void insertCharAt(int i, int length, char c)
899   {
900     char[] tmp = new char[sequence.length + length];
901
902     if (i >= sequence.length)
903     {
904       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
905       i = sequence.length;
906     }
907     else
908     {
909       System.arraycopy(sequence, 0, tmp, 0, i);
910     }
911
912     int index = i;
913     while (length > 0)
914     {
915       tmp[index++] = c;
916       length--;
917     }
918
919     if (i < sequence.length)
920     {
921       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
922     }
923
924     sequence = tmp;
925   }
926
927   @Override
928   public void insertCharAt(int i, char c)
929   {
930     insertCharAt(i, 1, c);
931   }
932
933   @Override
934   public String getVamsasId()
935   {
936     return vamsasId;
937   }
938
939   @Override
940   public void setVamsasId(String id)
941   {
942     vamsasId = id;
943   }
944
945   @Override
946   public void setDBRefs(DBRefEntry[] dbref)
947   {
948     if (dbrefs == null && datasetSequence != null
949             && this != datasetSequence)
950     {
951       datasetSequence.setDBRefs(dbref);
952       return;
953     }
954     dbrefs = dbref;
955     if (dbrefs != null)
956     {
957       DBRefUtils.ensurePrimaries(this);
958     }
959   }
960
961   @Override
962   public DBRefEntry[] getDBRefs()
963   {
964     if (dbrefs == null && datasetSequence != null
965             && this != datasetSequence)
966     {
967       return datasetSequence.getDBRefs();
968     }
969     return dbrefs;
970   }
971
972   @Override
973   public void addDBRef(DBRefEntry entry)
974   {
975     if (datasetSequence != null)
976     {
977       datasetSequence.addDBRef(entry);
978       return;
979     }
980
981     if (dbrefs == null)
982     {
983       dbrefs = new DBRefEntry[0];
984     }
985
986     for (DBRefEntryI dbr : dbrefs)
987     {
988       if (dbr.updateFrom(entry))
989       {
990         /*
991          * found a dbref that either matched, or could be
992          * updated from, the new entry - no need to add it
993          */
994         return;
995       }
996     }
997
998     /*
999      * extend the array to make room for one more
1000      */
1001     // TODO use an ArrayList instead
1002     int j = dbrefs.length;
1003     DBRefEntry[] temp = new DBRefEntry[j + 1];
1004     System.arraycopy(dbrefs, 0, temp, 0, j);
1005     temp[temp.length - 1] = entry;
1006
1007     dbrefs = temp;
1008
1009     DBRefUtils.ensurePrimaries(this);
1010   }
1011
1012   @Override
1013   public void setDatasetSequence(SequenceI seq)
1014   {
1015     if (seq == this)
1016     {
1017       throw new IllegalArgumentException(
1018               "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1019     }
1020     if (seq != null && seq.getDatasetSequence() != null)
1021     {
1022       throw new IllegalArgumentException(
1023               "Implementation error: cascading dataset sequences are not allowed.");
1024     }
1025     datasetSequence = seq;
1026   }
1027
1028   @Override
1029   public SequenceI getDatasetSequence()
1030   {
1031     return datasetSequence;
1032   }
1033
1034   @Override
1035   public AlignmentAnnotation[] getAnnotation()
1036   {
1037     return annotation == null ? null : annotation
1038             .toArray(new AlignmentAnnotation[annotation.size()]);
1039   }
1040
1041   @Override
1042   public boolean hasAnnotation(AlignmentAnnotation ann)
1043   {
1044     return annotation == null ? false : annotation.contains(ann);
1045   }
1046
1047   @Override
1048   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1049   {
1050     if (this.annotation == null)
1051     {
1052       this.annotation = new Vector<AlignmentAnnotation>();
1053     }
1054     if (!this.annotation.contains(annotation))
1055     {
1056       this.annotation.addElement(annotation);
1057     }
1058     annotation.setSequenceRef(this);
1059   }
1060
1061   @Override
1062   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1063   {
1064     if (this.annotation != null)
1065     {
1066       this.annotation.removeElement(annotation);
1067       if (this.annotation.size() == 0)
1068       {
1069         this.annotation = null;
1070       }
1071     }
1072   }
1073
1074   /**
1075    * test if this is a valid candidate for another sequence's dataset sequence.
1076    * 
1077    */
1078   private boolean isValidDatasetSequence()
1079   {
1080     if (datasetSequence != null)
1081     {
1082       return false;
1083     }
1084     for (int i = 0; i < sequence.length; i++)
1085     {
1086       if (jalview.util.Comparison.isGap(sequence[i]))
1087       {
1088         return false;
1089       }
1090     }
1091     return true;
1092   }
1093
1094   @Override
1095   public SequenceI deriveSequence()
1096   {
1097     Sequence seq=null;
1098     if (datasetSequence == null)
1099     {
1100       if (isValidDatasetSequence())
1101       {
1102         // Use this as dataset sequence
1103         seq = new Sequence(getName(), "", 1, -1);
1104         seq.setDatasetSequence(this);
1105         seq.initSeqFrom(this, getAnnotation());
1106         return seq;
1107       }
1108       else
1109       {
1110         // Create a new, valid dataset sequence
1111         createDatasetSequence();
1112       }
1113     }
1114     return new Sequence(this);
1115   }
1116
1117   private boolean _isNa;
1118
1119   private long _seqhash = 0;
1120
1121   /**
1122    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1123    * true
1124    */
1125   @Override
1126   public boolean isProtein()
1127   {
1128     if (datasetSequence != null)
1129     {
1130       return datasetSequence.isProtein();
1131     }
1132     if (_seqhash != sequence.hashCode())
1133     {
1134       _seqhash = sequence.hashCode();
1135       _isNa = Comparison.isNucleotide(this);
1136     }
1137     return !_isNa;
1138   };
1139
1140   /*
1141    * (non-Javadoc)
1142    * 
1143    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1144    */
1145   @Override
1146   public SequenceI createDatasetSequence()
1147   {
1148     if (datasetSequence == null)
1149     {
1150       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1151               jalview.util.Comparison.GapChars, getSequenceAsString()),
1152               getStart(), getEnd());
1153
1154       datasetSequence = dsseq;
1155
1156       dsseq.setDescription(description);
1157       // move features and database references onto dataset sequence
1158       dsseq.sequenceFeatures = sequenceFeatures;
1159       sequenceFeatures=null;
1160       dsseq.dbrefs = dbrefs;
1161       dbrefs=null;
1162       // TODO: search and replace any references to this sequence with
1163       // references to the dataset sequence in Mappings on dbref
1164       dsseq.pdbIds = pdbIds;
1165       pdbIds = null;
1166       datasetSequence.updatePDBIds();
1167       if (annotation != null)
1168       {
1169         // annotation is cloned rather than moved, to preserve what's currently
1170         // on the alignment
1171         for (AlignmentAnnotation aa : annotation)
1172         {
1173           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1174           _aa.sequenceRef = datasetSequence;
1175           _aa.adjustForAlignment(); // uses annotation's own record of
1176                                     // sequence-column mapping
1177           datasetSequence.addAlignmentAnnotation(_aa);
1178         }
1179       }
1180     }
1181     return datasetSequence;
1182   }
1183
1184   /*
1185    * (non-Javadoc)
1186    * 
1187    * @see
1188    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1189    * annotations)
1190    */
1191   @Override
1192   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1193   {
1194     if (annotation != null)
1195     {
1196       annotation.removeAllElements();
1197     }
1198     if (annotations != null)
1199     {
1200       for (int i = 0; i < annotations.length; i++)
1201       {
1202         if (annotations[i] != null)
1203         {
1204           addAlignmentAnnotation(annotations[i]);
1205         }
1206       }
1207     }
1208   }
1209
1210   @Override
1211   public AlignmentAnnotation[] getAnnotation(String label)
1212   {
1213     if (annotation == null || annotation.size() == 0)
1214     {
1215       return null;
1216     }
1217
1218     Vector subset = new Vector();
1219     Enumeration e = annotation.elements();
1220     while (e.hasMoreElements())
1221     {
1222       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
1223       if (ann.label != null && ann.label.equals(label))
1224       {
1225         subset.addElement(ann);
1226       }
1227     }
1228     if (subset.size() == 0)
1229     {
1230       return null;
1231     }
1232     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1233     int i = 0;
1234     e = subset.elements();
1235     while (e.hasMoreElements())
1236     {
1237       anns[i++] = (AlignmentAnnotation) e.nextElement();
1238     }
1239     subset.removeAllElements();
1240     return anns;
1241   }
1242
1243   @Override
1244   public boolean updatePDBIds()
1245   {
1246     if (datasetSequence != null)
1247     {
1248       // TODO: could merge DBRefs
1249       return datasetSequence.updatePDBIds();
1250     }
1251     if (dbrefs == null || dbrefs.length == 0)
1252     {
1253       return false;
1254     }
1255     boolean added = false;
1256     for (DBRefEntry dbr : dbrefs)
1257     {
1258       if (DBRefSource.PDB.equals(dbr.getSource()))
1259       {
1260         /*
1261          * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1262          * PDB id is not already present in a 'matching' PDBEntry
1263          * Constructor parses out a chain code if appended to the accession id
1264          * (a fudge used to 'store' the chain code in the DBRef)
1265          */
1266         PDBEntry pdbe = new PDBEntry(dbr);
1267         added |= addPDBId(pdbe);
1268       }
1269     }
1270     return added;
1271   }
1272
1273   @Override
1274   public void transferAnnotation(SequenceI entry, Mapping mp)
1275   {
1276     if (datasetSequence != null)
1277     {
1278       datasetSequence.transferAnnotation(entry, mp);
1279       return;
1280     }
1281     if (entry.getDatasetSequence() != null)
1282     {
1283       transferAnnotation(entry.getDatasetSequence(), mp);
1284       return;
1285     }
1286     // transfer any new features from entry onto sequence
1287     if (entry.getSequenceFeatures() != null)
1288     {
1289
1290       SequenceFeature[] sfs = entry.getSequenceFeatures();
1291       for (int si = 0; si < sfs.length; si++)
1292       {
1293         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
1294                 : new SequenceFeature[] { new SequenceFeature(sfs[si]) };
1295         if (sf != null && sf.length > 0)
1296         {
1297           for (int sfi = 0; sfi < sf.length; sfi++)
1298           {
1299             addSequenceFeature(sf[sfi]);
1300           }
1301         }
1302       }
1303     }
1304
1305     // transfer PDB entries
1306     if (entry.getAllPDBEntries() != null)
1307     {
1308       Enumeration e = entry.getAllPDBEntries().elements();
1309       while (e.hasMoreElements())
1310       {
1311         PDBEntry pdb = (PDBEntry) e.nextElement();
1312         addPDBId(pdb);
1313       }
1314     }
1315     // transfer database references
1316     DBRefEntry[] entryRefs = entry.getDBRefs();
1317     if (entryRefs != null)
1318     {
1319       for (int r = 0; r < entryRefs.length; r++)
1320       {
1321         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1322         if (newref.getMap() != null && mp != null)
1323         {
1324           // remap ref using our local mapping
1325         }
1326         // we also assume all version string setting is done by dbSourceProxy
1327         /*
1328          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1329          * newref.setSource(dbSource); }
1330          */
1331         addDBRef(newref);
1332       }
1333     }
1334   }
1335
1336   /**
1337    * @return The index (zero-based) on this sequence in the MSA. It returns
1338    *         {@code -1} if this information is not available.
1339    */
1340   @Override
1341   public int getIndex()
1342   {
1343     return index;
1344   }
1345
1346   /**
1347    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1348    * if this information is undefined.
1349    * 
1350    * @param The
1351    *          position for this sequence. This value is zero-based (zero for
1352    *          this first sequence)
1353    */
1354   @Override
1355   public void setIndex(int value)
1356   {
1357     index = value;
1358   }
1359
1360   @Override
1361   public void setRNA(RNA r)
1362   {
1363     rna = r;
1364   }
1365
1366   @Override
1367   public RNA getRNA()
1368   {
1369     return rna;
1370   }
1371
1372   @Override
1373   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1374           String label)
1375   {
1376     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1377     if (this.annotation != null)
1378     {
1379       for (AlignmentAnnotation ann : annotation)
1380       {
1381         if (ann.calcId != null && ann.calcId.equals(calcId)
1382                 && ann.label != null && ann.label.equals(label))
1383         {
1384           result.add(ann);
1385         }
1386       }
1387     }
1388     return result;
1389   }
1390
1391   @Override
1392   public String toString()
1393   {
1394     return getDisplayId(false);
1395   }
1396
1397   @Override
1398   public PDBEntry getPDBEntry(String pdbIdStr)
1399   {
1400     if (getDatasetSequence() != null)
1401     {
1402       return getDatasetSequence().getPDBEntry(pdbIdStr);
1403     }
1404     if (pdbIds == null)
1405     {
1406       return null;
1407     }
1408     List<PDBEntry> entries = getAllPDBEntries();
1409     for (PDBEntry entry : entries)
1410     {
1411       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1412       {
1413         return entry;
1414       }
1415     }
1416     return null;
1417   }
1418
1419
1420   @Override
1421   public List<DBRefEntry> getPrimaryDBRefs()
1422   {
1423     if (datasetSequence!=null)
1424     {
1425       return datasetSequence.getPrimaryDBRefs();
1426     }
1427     if (dbrefs==null || dbrefs.length==0)
1428     {
1429       return Collections.emptyList();
1430     }
1431     synchronized (dbrefs)
1432     {
1433       List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
1434       DBRefEntry[] tmp = new DBRefEntry[1];
1435       for (DBRefEntry ref : dbrefs)
1436       {
1437         if (!ref.isPrimaryCandidate())
1438         {
1439           continue;
1440         }
1441         if (ref.hasMap())
1442         {
1443           MapList mp = ref.getMap().getMap();
1444           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1445           {
1446             // map only involves a subsequence, so cannot be primary
1447             continue;
1448           }
1449         }
1450         // whilst it looks like it is a primary ref, we also sanity check type
1451         if (DBRefUtils.getCanonicalName(DBRefSource.PDB).equals(
1452                 DBRefUtils.getCanonicalName(ref.getSource())))
1453         {
1454           // PDB dbrefs imply there should be a PDBEntry associated
1455           // TODO: tighten PDB dbrefs
1456           // formally imply Jalview has actually downloaded and
1457           // parsed the pdb file. That means there should be a cached file
1458           // handle on the PDBEntry, and a real mapping between sequence and
1459           // extracted sequence from PDB file
1460           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1461           if (pdbentry != null && pdbentry.getFile() != null)
1462           {
1463             primaries.add(ref);
1464           }
1465           continue;
1466         }
1467         // check standard protein or dna sources
1468         tmp[0] = ref;
1469         DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1470         if (res != null && res[0] == tmp[0])
1471         {
1472           primaries.add(ref);
1473           continue;
1474         }
1475       }
1476       return primaries;
1477     }
1478   }
1479
1480 }