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