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