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