JAL-2106 tighten DBRefEntry primary seq test for PDB
[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   DBRefEntryI sourceDBRef;
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 void addPDBId(PDBEntry entry)
417   {
418     if (pdbIds == null)
419     {
420       pdbIds = new Vector<PDBEntry>();
421     }
422     if (pdbIds.contains(entry))
423     {
424       updatePDBEntry(pdbIds.get(pdbIds.indexOf(entry)), entry);
425     }
426     else
427     {
428       pdbIds.addElement(entry);
429     }
430   }
431
432   private static void updatePDBEntry(PDBEntry oldEntry, PDBEntry newEntry)
433   {
434     if (newEntry.getFile() != null)
435     {
436       oldEntry.setFile(newEntry.getFile());
437     }
438   }
439
440   /**
441    * DOCUMENT ME!
442    * 
443    * @param id
444    *          DOCUMENT ME!
445    */
446   @Override
447   public void setPDBId(Vector<PDBEntry> id)
448   {
449     pdbIds = id;
450   }
451
452   /**
453    * DOCUMENT ME!
454    * 
455    * @return DOCUMENT ME!
456    */
457   @Override
458   public Vector<PDBEntry> getAllPDBEntries()
459   {
460     return pdbIds == null ? new Vector<PDBEntry>() : pdbIds;
461   }
462
463   /**
464    * DOCUMENT ME!
465    * 
466    * @return DOCUMENT ME!
467    */
468   @Override
469   public String getDisplayId(boolean jvsuffix)
470   {
471     StringBuffer result = new StringBuffer(name);
472     if (jvsuffix)
473     {
474       result.append("/" + start + "-" + end);
475     }
476
477     return result.toString();
478   }
479
480   /**
481    * DOCUMENT ME!
482    * 
483    * @param name
484    *          DOCUMENT ME!
485    */
486   @Override
487   public void setName(String name)
488   {
489     this.name = name;
490     this.parseId();
491   }
492
493   /**
494    * DOCUMENT ME!
495    * 
496    * @return DOCUMENT ME!
497    */
498   @Override
499   public String getName()
500   {
501     return this.name;
502   }
503
504   /**
505    * DOCUMENT ME!
506    * 
507    * @param start
508    *          DOCUMENT ME!
509    */
510   @Override
511   public void setStart(int start)
512   {
513     this.start = start;
514   }
515
516   /**
517    * DOCUMENT ME!
518    * 
519    * @return DOCUMENT ME!
520    */
521   @Override
522   public int getStart()
523   {
524     return this.start;
525   }
526
527   /**
528    * DOCUMENT ME!
529    * 
530    * @param end
531    *          DOCUMENT ME!
532    */
533   @Override
534   public void setEnd(int end)
535   {
536     this.end = end;
537   }
538
539   /**
540    * DOCUMENT ME!
541    * 
542    * @return DOCUMENT ME!
543    */
544   @Override
545   public int getEnd()
546   {
547     return this.end;
548   }
549
550   /**
551    * DOCUMENT ME!
552    * 
553    * @return DOCUMENT ME!
554    */
555   @Override
556   public int getLength()
557   {
558     return this.sequence.length;
559   }
560
561   /**
562    * DOCUMENT ME!
563    * 
564    * @param seq
565    *          DOCUMENT ME!
566    */
567   @Override
568   public void setSequence(String seq)
569   {
570     this.sequence = seq.toCharArray();
571     checkValidRange();
572   }
573
574   @Override
575   public String getSequenceAsString()
576   {
577     return new String(sequence);
578   }
579
580   @Override
581   public String getSequenceAsString(int start, int end)
582   {
583     return new String(getSequence(start, end));
584   }
585
586   @Override
587   public char[] getSequence()
588   {
589     return sequence;
590   }
591
592   /*
593    * (non-Javadoc)
594    * 
595    * @see jalview.datamodel.SequenceI#getSequence(int, int)
596    */
597   @Override
598   public char[] getSequence(int start, int end)
599   {
600     if (start < 0)
601     {
602       start = 0;
603     }
604     // JBPNote - left to user to pad the result here (TODO:Decide on this
605     // policy)
606     if (start >= sequence.length)
607     {
608       return new char[0];
609     }
610
611     if (end >= sequence.length)
612     {
613       end = sequence.length;
614     }
615
616     char[] reply = new char[end - start];
617     System.arraycopy(sequence, start, reply, 0, end - start);
618
619     return reply;
620   }
621
622   @Override
623   public SequenceI getSubSequence(int start, int end)
624   {
625     if (start < 0)
626     {
627       start = 0;
628     }
629     char[] seq = getSequence(start, end);
630     if (seq.length == 0)
631     {
632       return null;
633     }
634     int nstart = findPosition(start);
635     int nend = findPosition(end) - 1;
636     // JBPNote - this is an incomplete copy.
637     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
638     nseq.setDescription(description);
639     if (datasetSequence != null)
640     {
641       nseq.setDatasetSequence(datasetSequence);
642     }
643     else
644     {
645       nseq.setDatasetSequence(this);
646     }
647     return nseq;
648   }
649
650   /**
651    * Returns the character of the aligned sequence at the given position (base
652    * zero), or space if the position is not within the sequence's bounds
653    * 
654    * @return
655    */
656   @Override
657   public char getCharAt(int i)
658   {
659     if (i >= 0 && i < sequence.length)
660     {
661       return sequence[i];
662     }
663     else
664     {
665       return ' ';
666     }
667   }
668
669   /**
670    * DOCUMENT ME!
671    * 
672    * @param desc
673    *          DOCUMENT ME!
674    */
675   @Override
676   public void setDescription(String desc)
677   {
678     this.description = desc;
679   }
680
681   /**
682    * DOCUMENT ME!
683    * 
684    * @return DOCUMENT ME!
685    */
686   @Override
687   public String getDescription()
688   {
689     return this.description;
690   }
691
692   /*
693    * (non-Javadoc)
694    * 
695    * @see jalview.datamodel.SequenceI#findIndex(int)
696    */
697   @Override
698   public int findIndex(int pos)
699   {
700     // returns the alignment position for a residue
701     int j = start;
702     int i = 0;
703     // Rely on end being at least as long as the length of the sequence.
704     while ((i < sequence.length) && (j <= end) && (j <= pos))
705     {
706       if (!jalview.util.Comparison.isGap(sequence[i]))
707       {
708         j++;
709       }
710
711       i++;
712     }
713
714     if ((j == end) && (j < pos))
715     {
716       return end + 1;
717     }
718     else
719     {
720       return i;
721     }
722   }
723
724   @Override
725   public int findPosition(int i)
726   {
727     int j = 0;
728     int pos = start;
729     int seqlen = sequence.length;
730     while ((j < i) && (j < seqlen))
731     {
732       if (!jalview.util.Comparison.isGap(sequence[j]))
733       {
734         pos++;
735       }
736
737       j++;
738     }
739
740     return pos;
741   }
742
743   /**
744    * Returns an int array where indices correspond to each residue in the
745    * sequence and the element value gives its position in the alignment
746    * 
747    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
748    *         residues in SequenceI object
749    */
750   @Override
751   public int[] gapMap()
752   {
753     String seq = jalview.analysis.AlignSeq.extractGaps(
754             jalview.util.Comparison.GapChars, new String(sequence));
755     int[] map = new int[seq.length()];
756     int j = 0;
757     int p = 0;
758
759     while (j < sequence.length)
760     {
761       if (!jalview.util.Comparison.isGap(sequence[j]))
762       {
763         map[p++] = j;
764       }
765
766       j++;
767     }
768
769     return map;
770   }
771
772   @Override
773   public int[] findPositionMap()
774   {
775     int map[] = new int[sequence.length];
776     int j = 0;
777     int pos = start;
778     int seqlen = sequence.length;
779     while ((j < seqlen))
780     {
781       map[j] = pos;
782       if (!jalview.util.Comparison.isGap(sequence[j]))
783       {
784         pos++;
785       }
786
787       j++;
788     }
789     return map;
790   }
791
792   @Override
793   public List<int[]> getInsertions()
794   {
795     ArrayList<int[]> map = new ArrayList<int[]>();
796     int lastj = -1, j = 0;
797     int pos = start;
798     int seqlen = sequence.length;
799     while ((j < seqlen))
800     {
801       if (jalview.util.Comparison.isGap(sequence[j]))
802       {
803         if (lastj == -1)
804         {
805           lastj = j;
806         }
807       }
808       else
809       {
810         if (lastj != -1)
811         {
812           map.add(new int[] { lastj, j - 1 });
813           lastj = -1;
814         }
815       }
816       j++;
817     }
818     if (lastj != -1)
819     {
820       map.add(new int[] { lastj, j - 1 });
821       lastj = -1;
822     }
823     return map;
824   }
825
826   @Override
827   public void deleteChars(int i, int j)
828   {
829     int newstart = start, newend = end;
830     if (i >= sequence.length || i < 0)
831     {
832       return;
833     }
834
835     char[] tmp = StringUtils.deleteChars(sequence, i, j);
836     boolean createNewDs = false;
837     // TODO: take a (second look) at the dataset creation validation method for
838     // the very large sequence case
839     int eindex = -1, sindex = -1;
840     boolean ecalc = false, scalc = false;
841     for (int s = i; s < j; s++)
842     {
843       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
844       {
845         if (createNewDs)
846         {
847           newend--;
848         }
849         else
850         {
851           if (!scalc)
852           {
853             sindex = findIndex(start) - 1;
854             scalc = true;
855           }
856           if (sindex == s)
857           {
858             // delete characters including start of sequence
859             newstart = findPosition(j);
860             break; // don't need to search for any more residue characters.
861           }
862           else
863           {
864             // delete characters after start.
865             if (!ecalc)
866             {
867               eindex = findIndex(end) - 1;
868               ecalc = true;
869             }
870             if (eindex < j)
871             {
872               // delete characters at end of sequence
873               newend = findPosition(i - 1);
874               break; // don't need to search for any more residue characters.
875             }
876             else
877             {
878               createNewDs = true;
879               newend--; // decrease end position by one for the deleted residue
880               // and search further
881             }
882           }
883         }
884       }
885     }
886     // deletion occured in the middle of the sequence
887     if (createNewDs && this.datasetSequence != null)
888     {
889       // construct a new sequence
890       Sequence ds = new Sequence(datasetSequence);
891       // TODO: remove any non-inheritable properties ?
892       // TODO: create a sequence mapping (since there is a relation here ?)
893       ds.deleteChars(i, j);
894       datasetSequence = ds;
895     }
896     start = newstart;
897     end = newend;
898     sequence = tmp;
899   }
900
901   @Override
902   public void insertCharAt(int i, int length, char c)
903   {
904     char[] tmp = new char[sequence.length + length];
905
906     if (i >= sequence.length)
907     {
908       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
909       i = sequence.length;
910     }
911     else
912     {
913       System.arraycopy(sequence, 0, tmp, 0, i);
914     }
915
916     int index = i;
917     while (length > 0)
918     {
919       tmp[index++] = c;
920       length--;
921     }
922
923     if (i < sequence.length)
924     {
925       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
926     }
927
928     sequence = tmp;
929   }
930
931   @Override
932   public void insertCharAt(int i, char c)
933   {
934     insertCharAt(i, 1, c);
935   }
936
937   @Override
938   public String getVamsasId()
939   {
940     return vamsasId;
941   }
942
943   @Override
944   public void setVamsasId(String id)
945   {
946     vamsasId = id;
947   }
948
949   @Override
950   public void setDBRefs(DBRefEntry[] dbref)
951   {
952     dbrefs = dbref;
953   }
954
955   @Override
956   public DBRefEntry[] getDBRefs()
957   {
958     if (dbrefs == null && datasetSequence != null
959             && this != datasetSequence)
960     {
961       return datasetSequence.getDBRefs();
962     }
963     return dbrefs;
964   }
965
966   @Override
967   public void addDBRef(DBRefEntry entry)
968   {
969     // TODO add to dataset sequence instead if there is one?
970     if (dbrefs == null)
971     {
972       dbrefs = new DBRefEntry[0];
973     }
974
975     for (DBRefEntryI dbr : dbrefs)
976     {
977       if (dbr.updateFrom(entry))
978       {
979         /*
980          * found a dbref that either matched, or could be
981          * updated from, the new entry - no need to add it
982          */
983         return;
984       }
985     }
986
987     /*
988      * extend the array to make room for one more
989      */
990     // TODO use an ArrayList instead
991     int j = dbrefs.length;
992     DBRefEntry[] temp = new DBRefEntry[j + 1];
993     System.arraycopy(dbrefs, 0, temp, 0, j);
994     temp[temp.length - 1] = entry;
995
996     dbrefs = temp;
997   }
998
999   @Override
1000   public void setDatasetSequence(SequenceI seq)
1001   {
1002     // TODO check for circular reference before setting?
1003     datasetSequence = seq;
1004   }
1005
1006   @Override
1007   public SequenceI getDatasetSequence()
1008   {
1009     return datasetSequence;
1010   }
1011
1012   @Override
1013   public AlignmentAnnotation[] getAnnotation()
1014   {
1015     return annotation == null ? null : annotation
1016             .toArray(new AlignmentAnnotation[annotation.size()]);
1017   }
1018
1019   @Override
1020   public boolean hasAnnotation(AlignmentAnnotation ann)
1021   {
1022     return annotation == null ? false : annotation.contains(ann);
1023   }
1024
1025   @Override
1026   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1027   {
1028     if (this.annotation == null)
1029     {
1030       this.annotation = new Vector<AlignmentAnnotation>();
1031     }
1032     if (!this.annotation.contains(annotation))
1033     {
1034       this.annotation.addElement(annotation);
1035     }
1036     annotation.setSequenceRef(this);
1037   }
1038
1039   @Override
1040   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1041   {
1042     if (this.annotation != null)
1043     {
1044       this.annotation.removeElement(annotation);
1045       if (this.annotation.size() == 0)
1046       {
1047         this.annotation = null;
1048       }
1049     }
1050   }
1051
1052   /**
1053    * test if this is a valid candidate for another sequence's dataset sequence.
1054    * 
1055    */
1056   private boolean isValidDatasetSequence()
1057   {
1058     if (datasetSequence != null)
1059     {
1060       return false;
1061     }
1062     for (int i = 0; i < sequence.length; i++)
1063     {
1064       if (jalview.util.Comparison.isGap(sequence[i]))
1065       {
1066         return false;
1067       }
1068     }
1069     return true;
1070   }
1071
1072   @Override
1073   public SequenceI deriveSequence()
1074   {
1075     Sequence seq=null;
1076     if (datasetSequence == null)
1077     {
1078       if (isValidDatasetSequence())
1079       {
1080         // Use this as dataset sequence
1081         seq = new Sequence(getName(), "", 1, -1);
1082         seq.setDatasetSequence(this);
1083         seq.initSeqFrom(this, getAnnotation());
1084         return seq;
1085       }
1086       else
1087       {
1088         // Create a new, valid dataset sequence
1089         createDatasetSequence();
1090       }
1091     }
1092     return new Sequence(this);
1093   }
1094
1095   private boolean _isNa;
1096
1097   private long _seqhash = 0;
1098
1099   @Override
1100   public boolean isProtein()
1101   {
1102     if (datasetSequence != null)
1103     {
1104       return datasetSequence.isProtein();
1105     }
1106     if (_seqhash != sequence.hashCode())
1107     {
1108       _seqhash = sequence.hashCode();
1109       _isNa=jalview.util.Comparison.isNucleotide(new SequenceI[] { this });
1110     }
1111     return !_isNa;
1112   };
1113
1114   /*
1115    * (non-Javadoc)
1116    * 
1117    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1118    */
1119   @Override
1120   public SequenceI createDatasetSequence()
1121   {
1122     if (datasetSequence == null)
1123     {
1124       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1125               jalview.util.Comparison.GapChars, getSequenceAsString()),
1126               getStart(), getEnd());
1127
1128       datasetSequence = dsseq;
1129
1130       dsseq.setDescription(description);
1131       // move features and database references onto dataset sequence
1132       dsseq.sequenceFeatures = sequenceFeatures;
1133       sequenceFeatures=null;
1134       dsseq.dbrefs = dbrefs;
1135       dbrefs=null;
1136       // TODO: search and replace any references to this sequence with
1137       // references to the dataset sequence in Mappings on dbref
1138       dsseq.pdbIds = pdbIds;
1139       pdbIds = null;
1140       datasetSequence.updatePDBIds();
1141       if (annotation != null)
1142       {
1143         // annotation is cloned rather than moved, to preserve what's currently
1144         // on the alignment
1145         for (AlignmentAnnotation aa : annotation)
1146         {
1147           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1148           _aa.sequenceRef = datasetSequence;
1149           _aa.adjustForAlignment(); // uses annotation's own record of
1150                                     // sequence-column mapping
1151           datasetSequence.addAlignmentAnnotation(_aa);
1152         }
1153       }
1154     }
1155     return datasetSequence;
1156   }
1157
1158   /*
1159    * (non-Javadoc)
1160    * 
1161    * @see
1162    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1163    * annotations)
1164    */
1165   @Override
1166   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1167   {
1168     if (annotation != null)
1169     {
1170       annotation.removeAllElements();
1171     }
1172     if (annotations != null)
1173     {
1174       for (int i = 0; i < annotations.length; i++)
1175       {
1176         if (annotations[i] != null)
1177         {
1178           addAlignmentAnnotation(annotations[i]);
1179         }
1180       }
1181     }
1182   }
1183
1184   @Override
1185   public AlignmentAnnotation[] getAnnotation(String label)
1186   {
1187     if (annotation == null || annotation.size() == 0)
1188     {
1189       return null;
1190     }
1191
1192     Vector subset = new Vector();
1193     Enumeration e = annotation.elements();
1194     while (e.hasMoreElements())
1195     {
1196       AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
1197       if (ann.label != null && ann.label.equals(label))
1198       {
1199         subset.addElement(ann);
1200       }
1201     }
1202     if (subset.size() == 0)
1203     {
1204       return null;
1205     }
1206     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1207     int i = 0;
1208     e = subset.elements();
1209     while (e.hasMoreElements())
1210     {
1211       anns[i++] = (AlignmentAnnotation) e.nextElement();
1212     }
1213     subset.removeAllElements();
1214     return anns;
1215   }
1216
1217   @Override
1218   public boolean updatePDBIds()
1219   {
1220     if (datasetSequence != null)
1221     {
1222       // TODO: could merge DBRefs
1223       return datasetSequence.updatePDBIds();
1224     }
1225     if (dbrefs == null || dbrefs.length == 0)
1226     {
1227       return false;
1228     }
1229     Vector newpdb = new Vector();
1230     for (int i = 0; i < dbrefs.length; i++)
1231     {
1232       if (DBRefSource.PDB.equals(dbrefs[i].getSource()))
1233       {
1234         PDBEntry pdbe = new PDBEntry();
1235         pdbe.setId(dbrefs[i].getAccessionId());
1236         if (pdbIds == null || pdbIds.size() == 0)
1237         {
1238           newpdb.addElement(pdbe);
1239         }
1240         else
1241         {
1242           Enumeration en = pdbIds.elements();
1243           boolean matched = false;
1244           while (!matched && en.hasMoreElements())
1245           {
1246             PDBEntry anentry = (PDBEntry) en.nextElement();
1247             if (anentry.getId().equals(pdbe.getId()))
1248             {
1249               matched = true;
1250             }
1251           }
1252           if (!matched)
1253           {
1254             newpdb.addElement(pdbe);
1255           }
1256         }
1257       }
1258     }
1259     if (newpdb.size() > 0)
1260     {
1261       Enumeration en = newpdb.elements();
1262       while (en.hasMoreElements())
1263       {
1264         addPDBId((PDBEntry) en.nextElement());
1265       }
1266       return true;
1267     }
1268     return false;
1269   }
1270
1271   @Override
1272   public void transferAnnotation(SequenceI entry, Mapping mp)
1273   {
1274     if (datasetSequence != null)
1275     {
1276       datasetSequence.transferAnnotation(entry, mp);
1277       return;
1278     }
1279     if (entry.getDatasetSequence() != null)
1280     {
1281       transferAnnotation(entry.getDatasetSequence(), mp);
1282       return;
1283     }
1284     // transfer any new features from entry onto sequence
1285     if (entry.getSequenceFeatures() != null)
1286     {
1287
1288       SequenceFeature[] sfs = entry.getSequenceFeatures();
1289       for (int si = 0; si < sfs.length; si++)
1290       {
1291         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
1292                 : new SequenceFeature[] { new SequenceFeature(sfs[si]) };
1293         if (sf != null && sf.length > 0)
1294         {
1295           for (int sfi = 0; sfi < sf.length; sfi++)
1296           {
1297             addSequenceFeature(sf[sfi]);
1298           }
1299         }
1300       }
1301     }
1302
1303     // transfer PDB entries
1304     if (entry.getAllPDBEntries() != null)
1305     {
1306       Enumeration e = entry.getAllPDBEntries().elements();
1307       while (e.hasMoreElements())
1308       {
1309         PDBEntry pdb = (PDBEntry) e.nextElement();
1310         addPDBId(pdb);
1311       }
1312     }
1313     // transfer database references
1314     DBRefEntry[] entryRefs = entry.getDBRefs();
1315     if (entryRefs != null)
1316     {
1317       for (int r = 0; r < entryRefs.length; r++)
1318       {
1319         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1320         if (newref.getMap() != null && mp != null)
1321         {
1322           // remap ref using our local mapping
1323         }
1324         // we also assume all version string setting is done by dbSourceProxy
1325         /*
1326          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1327          * newref.setSource(dbSource); }
1328          */
1329         addDBRef(newref);
1330       }
1331     }
1332   }
1333
1334   /**
1335    * @return The index (zero-based) on this sequence in the MSA. It returns
1336    *         {@code -1} if this information is not available.
1337    */
1338   @Override
1339   public int getIndex()
1340   {
1341     return index;
1342   }
1343
1344   /**
1345    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1346    * if this information is undefined.
1347    * 
1348    * @param The
1349    *          position for this sequence. This value is zero-based (zero for
1350    *          this first sequence)
1351    */
1352   @Override
1353   public void setIndex(int value)
1354   {
1355     index = value;
1356   }
1357
1358   @Override
1359   public void setRNA(RNA r)
1360   {
1361     rna = r;
1362   }
1363
1364   @Override
1365   public RNA getRNA()
1366   {
1367     return rna;
1368   }
1369
1370   @Override
1371   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1372           String label)
1373   {
1374     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1375     if (this.annotation != null)
1376     {
1377       for (AlignmentAnnotation ann : annotation)
1378       {
1379         if (ann.calcId != null && ann.calcId.equals(calcId)
1380                 && ann.label != null && ann.label.equals(label))
1381         {
1382           result.add(ann);
1383         }
1384       }
1385     }
1386     return result;
1387   }
1388
1389   @Override
1390   public String toString()
1391   {
1392     return getDisplayId(false);
1393   }
1394
1395   @Override
1396   public PDBEntry getPDBEntry(String pdbIdStr)
1397   {
1398     if (getDatasetSequence() != null)
1399     {
1400       return getDatasetSequence().getPDBEntry(pdbIdStr);
1401     }
1402     if (pdbIds == null)
1403     {
1404       return null;
1405     }
1406     List<PDBEntry> entries = getAllPDBEntries();
1407     for (PDBEntry entry : entries)
1408     {
1409       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1410       {
1411         return entry;
1412       }
1413     }
1414     return null;
1415   }
1416
1417
1418   @Override
1419   public List<DBRefEntry> getPrimaryDBRefs()
1420   {
1421     if (datasetSequence!=null)
1422     {
1423       return datasetSequence.getPrimaryDBRefs();
1424     }
1425     if (dbrefs==null || dbrefs.length==0)
1426     {
1427       return Arrays.asList(new DBRefEntry[0]);
1428     }
1429     synchronized (dbrefs)
1430     {
1431       List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
1432       DBRefEntry tmp[] = new DBRefEntry[1], res[] = null;
1433       for (DBRefEntry ref : dbrefs)
1434       {
1435         if (!ref.isPrimary())
1436         {
1437           continue;
1438         }
1439         if (ref.hasMap())
1440         {
1441           MapList mp = ref.getMap().getMap();
1442           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1443           {
1444             // map only involves a subsequence, so cannot be primary
1445             continue;
1446           }
1447         }
1448         // whilst it looks like it is a primary ref, we also sanity check type
1449         if (DBRefUtils.getCanonicalName(DBRefSource.PDB).equals(
1450                 DBRefUtils.getCanonicalName(ref.getSource())))
1451         {
1452           // PDB dbrefs imply there should be a PDBEntry associated
1453           // TODO: tighten PDB dbrefs
1454           // formally imply Jalview has actually downlaoded and
1455           // parsed the pdb file. That means there should be a cached file
1456           // handle on the PDBEntry, and a real mapping between sequence and
1457           // extracted sequence from PDB file
1458           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1459           if (pdbentry != null && pdbentry.getType() != null
1460                   && pdbentry.getType().equalsIgnoreCase("PDB"))
1461           {
1462             primaries.add(ref);
1463           }
1464           continue;
1465         }
1466         // check standard protein or dna sources
1467         tmp[0] = ref;
1468         res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1469         if (res != null && res[0] == tmp[0])
1470         {
1471           primaries.add(ref);
1472           continue;
1473         }
1474       }
1475       return primaries;
1476     }
1477   }
1478
1479 }