cc4a0dca39ce909e4b3eb97f7029f903fce14aa6
[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.datamodel.features.SequenceFeatures;
26 import jalview.datamodel.features.SequenceFeaturesI;
27 import jalview.util.Comparison;
28 import jalview.util.DBRefUtils;
29 import jalview.util.MapList;
30 import jalview.util.StringUtils;
31
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.Collections;
35 import java.util.Enumeration;
36 import java.util.List;
37 import java.util.Vector;
38
39 import com.stevesoft.pat.Regex;
40
41 import fr.orsay.lri.varna.models.rna.RNA;
42
43 /**
44  * 
45  * Implements the SequenceI interface for a char[] based sequence object.
46  * 
47  * @author $author$
48  * @version $Revision$
49  */
50 public class Sequence extends ASequence implements SequenceI
51 {
52   private static final Regex limitrx = new Regex(
53           "[/][0-9]{1,}[-][0-9]{1,}$");
54
55   private static final Regex endrx = new Regex("[0-9]{1,}$");
56
57   SequenceI datasetSequence;
58
59   String name;
60
61   private char[] sequence;
62
63   String description;
64
65   int start;
66
67   int end;
68
69   Vector<PDBEntry> pdbIds;
70
71   String vamsasId;
72
73   DBRefEntry[] dbrefs;
74
75   RNA rna;
76
77   /**
78    * This annotation is displayed below the alignment but the positions are tied
79    * to the residues of this sequence
80    *
81    * TODO: change to List<>
82    */
83   Vector<AlignmentAnnotation> annotation;
84
85   /**
86    * The index of the sequence in a MSA
87    */
88   int index = -1;
89
90   private SequenceFeatures sequenceFeatureStore;
91
92   /*
93    * A cursor holding the approximate current view position to the sequence,
94    * as determined by findIndex or findPosition or findPositions.
95    * Using a cursor as a hint allows these methods to be more performant for
96    * large sequences.
97    */
98   private SequenceCursor cursor;
99
100   /*
101    * A number that should be incremented whenever the sequence is edited.
102    * If the value matches the cursor token, then we can trust the cursor,
103    * if not then it should be recomputed. 
104    */
105   private int changeCount;
106
107   /**
108    * Creates a new Sequence object.
109    * 
110    * @param name
111    *          display name string
112    * @param sequence
113    *          string to form a possibly gapped sequence out of
114    * @param start
115    *          first position of non-gap residue in the sequence
116    * @param end
117    *          last position of ungapped residues (nearly always only used for
118    *          display purposes)
119    */
120   public Sequence(String name, String sequence, int start, int end)
121   {
122     this();
123     initSeqAndName(name, sequence.toCharArray(), start, end);
124   }
125
126   public Sequence(String name, char[] sequence, int start, int end)
127   {
128     this();
129     initSeqAndName(name, sequence, start, end);
130   }
131
132   /**
133    * Stage 1 constructor - assign name, sequence, and set start and end fields.
134    * start and end are updated values from name2 if it ends with /start-end
135    * 
136    * @param name2
137    * @param sequence2
138    * @param start2
139    * @param end2
140    */
141   protected void initSeqAndName(String name2, char[] sequence2, int start2,
142           int end2)
143   {
144     this.name = name2;
145     this.sequence = sequence2;
146     this.start = start2;
147     this.end = end2;
148     parseId();
149     checkValidRange();
150   }
151
152   void parseId()
153   {
154     if (name == null)
155     {
156       System.err
157               .println("POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
158       name = "";
159     }
160     // Does sequence have the /start-end signature?
161     if (limitrx.search(name))
162     {
163       name = limitrx.left();
164       endrx.search(limitrx.stringMatched());
165       setStart(Integer.parseInt(limitrx.stringMatched().substring(1,
166               endrx.matchedFrom() - 1)));
167       setEnd(Integer.parseInt(endrx.stringMatched()));
168     }
169   }
170
171   void checkValidRange()
172   {
173     // Note: JAL-774 :
174     // http://issues.jalview.org/browse/JAL-774?focusedCommentId=11239&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-11239
175     {
176       int endRes = 0;
177       for (int j = 0; j < sequence.length; j++)
178       {
179         if (!jalview.util.Comparison.isGap(sequence[j]))
180         {
181           endRes++;
182         }
183       }
184       if (endRes > 0)
185       {
186         endRes += start - 1;
187       }
188
189       if (end < endRes)
190       {
191         end = endRes;
192       }
193     }
194
195   }
196
197   /**
198    * default constructor
199    */
200   private Sequence()
201   {
202     sequenceFeatureStore = new SequenceFeatures();
203   }
204
205   /**
206    * Creates a new Sequence object.
207    * 
208    * @param name
209    *          DOCUMENT ME!
210    * @param sequence
211    *          DOCUMENT ME!
212    */
213   public Sequence(String name, String sequence)
214   {
215     this(name, sequence, 1, -1);
216   }
217
218   /**
219    * Creates a new Sequence object with new AlignmentAnnotations but inherits
220    * any existing dataset sequence reference. If non exists, everything is
221    * copied.
222    * 
223    * @param seq
224    *          if seq is a dataset sequence, behaves like a plain old copy
225    *          constructor
226    */
227   public Sequence(SequenceI seq)
228   {
229     this(seq, seq.getAnnotation());
230   }
231
232   /**
233    * Create a new sequence object with new features, DBRefEntries, and PDBIds
234    * but inherits any existing dataset sequence reference, and duplicate of any
235    * annotation that is present in the given annotation array.
236    * 
237    * @param seq
238    *          the sequence to be copied
239    * @param alAnnotation
240    *          an array of annotation including some associated with seq
241    */
242   public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
243   {
244     this();
245     initSeqFrom(seq, alAnnotation);
246   }
247
248   /**
249    * does the heavy lifting when cloning a dataset sequence, or coping data from
250    * dataset to a new derived sequence.
251    * 
252    * @param seq
253    *          - source of attributes.
254    * @param alAnnotation
255    *          - alignment annotation present on seq that should be copied onto
256    *          this sequence
257    */
258   protected void initSeqFrom(SequenceI seq,
259           AlignmentAnnotation[] alAnnotation)
260   {
261     char[] oseq = seq.getSequence();
262     initSeqAndName(seq.getName(), Arrays.copyOf(oseq, oseq.length),
263             seq.getStart(), seq.getEnd());
264
265     description = seq.getDescription();
266     if (seq != datasetSequence)
267     {
268       setDatasetSequence(seq.getDatasetSequence());
269     }
270     
271     /*
272      * only copy DBRefs and seqfeatures if we really are a dataset sequence
273      */
274     if (datasetSequence == null)
275     {
276       if (seq.getDBRefs() != null)
277       {
278         DBRefEntry[] dbr = seq.getDBRefs();
279         for (int i = 0; i < dbr.length; i++)
280         {
281           addDBRef(new DBRefEntry(dbr[i]));
282         }
283       }
284
285       /*
286        * make copies of any sequence features
287        */
288       for (SequenceFeature sf : seq.getSequenceFeatures())
289       {
290         addSequenceFeature(new SequenceFeature(sf));
291       }
292     }
293
294     if (seq.getAnnotation() != null)
295     {
296       AlignmentAnnotation[] sqann = seq.getAnnotation();
297       for (int i = 0; i < sqann.length; i++)
298       {
299         if (sqann[i] == null)
300         {
301           continue;
302         }
303         boolean found = (alAnnotation == null);
304         if (!found)
305         {
306           for (int apos = 0; !found && apos < alAnnotation.length; apos++)
307           {
308             found = (alAnnotation[apos] == sqann[i]);
309           }
310         }
311         if (found)
312         {
313           // only copy the given annotation
314           AlignmentAnnotation newann = new AlignmentAnnotation(sqann[i]);
315           addAlignmentAnnotation(newann);
316         }
317       }
318     }
319     if (seq.getAllPDBEntries() != null)
320     {
321       Vector<PDBEntry> ids = seq.getAllPDBEntries();
322       for (PDBEntry pdb : ids)
323       {
324         this.addPDBId(new PDBEntry(pdb));
325       }
326     }
327   }
328
329   @Override
330   public void setSequenceFeatures(List<SequenceFeature> features)
331   {
332     if (datasetSequence != null)
333     {
334       datasetSequence.setSequenceFeatures(features);
335       return;
336     }
337     sequenceFeatureStore = new SequenceFeatures(features);
338   }
339
340   @Override
341   public synchronized boolean addSequenceFeature(SequenceFeature sf)
342   {
343     if (sf.getType() == null)
344     {
345       System.err.println("SequenceFeature type may not be null: "
346               + sf.toString());
347       return false;
348     }
349
350     if (datasetSequence != null)
351     {
352       return datasetSequence.addSequenceFeature(sf);
353     }
354
355     return sequenceFeatureStore.add(sf);
356   }
357
358   @Override
359   public void deleteFeature(SequenceFeature sf)
360   {
361     if (datasetSequence != null)
362     {
363       datasetSequence.deleteFeature(sf);
364     }
365     else
366     {
367       sequenceFeatureStore.delete(sf);
368     }
369   }
370
371   /**
372    * {@inheritDoc}
373    * 
374    * @return
375    */
376   @Override
377   public List<SequenceFeature> getSequenceFeatures()
378   {
379     if (datasetSequence != null)
380     {
381       return datasetSequence.getSequenceFeatures();
382     }
383     return sequenceFeatureStore.getAllFeatures();
384   }
385
386   @Override
387   public SequenceFeaturesI getFeatures()
388   {
389     return datasetSequence != null ? datasetSequence.getFeatures()
390             : sequenceFeatureStore;
391   }
392
393   @Override
394   public boolean addPDBId(PDBEntry entry)
395   {
396     if (pdbIds == null)
397     {
398       pdbIds = new Vector<PDBEntry>();
399       pdbIds.add(entry);
400       return true;
401     }
402
403     for (PDBEntry pdbe : pdbIds)
404     {
405       if (pdbe.updateFrom(entry))
406       {
407         return false;
408       }
409     }
410     pdbIds.addElement(entry);
411     return true;
412   }
413
414   /**
415    * DOCUMENT ME!
416    * 
417    * @param id
418    *          DOCUMENT ME!
419    */
420   @Override
421   public void setPDBId(Vector<PDBEntry> id)
422   {
423     pdbIds = id;
424   }
425
426   /**
427    * DOCUMENT ME!
428    * 
429    * @return DOCUMENT ME!
430    */
431   @Override
432   public Vector<PDBEntry> getAllPDBEntries()
433   {
434     return pdbIds == null ? new Vector<PDBEntry>() : pdbIds;
435   }
436
437   /**
438    * DOCUMENT ME!
439    * 
440    * @return DOCUMENT ME!
441    */
442   @Override
443   public String getDisplayId(boolean jvsuffix)
444   {
445     StringBuffer result = new StringBuffer(name);
446     if (jvsuffix)
447     {
448       result.append("/" + start + "-" + end);
449     }
450
451     return result.toString();
452   }
453
454   /**
455    * DOCUMENT ME!
456    * 
457    * @param name
458    *          DOCUMENT ME!
459    */
460   @Override
461   public void setName(String name)
462   {
463     this.name = name;
464     this.parseId();
465   }
466
467   /**
468    * DOCUMENT ME!
469    * 
470    * @return DOCUMENT ME!
471    */
472   @Override
473   public String getName()
474   {
475     return this.name;
476   }
477
478   /**
479    * DOCUMENT ME!
480    * 
481    * @param start
482    *          DOCUMENT ME!
483    */
484   @Override
485   public void setStart(int start)
486   {
487     this.start = start;
488   }
489
490   /**
491    * DOCUMENT ME!
492    * 
493    * @return DOCUMENT ME!
494    */
495   @Override
496   public int getStart()
497   {
498     return this.start;
499   }
500
501   /**
502    * DOCUMENT ME!
503    * 
504    * @param end
505    *          DOCUMENT ME!
506    */
507   @Override
508   public void setEnd(int end)
509   {
510     this.end = end;
511   }
512
513   /**
514    * DOCUMENT ME!
515    * 
516    * @return DOCUMENT ME!
517    */
518   @Override
519   public int getEnd()
520   {
521     return this.end;
522   }
523
524   /**
525    * DOCUMENT ME!
526    * 
527    * @return DOCUMENT ME!
528    */
529   @Override
530   public int getLength()
531   {
532     return this.sequence.length;
533   }
534
535   /**
536    * DOCUMENT ME!
537    * 
538    * @param seq
539    *          DOCUMENT ME!
540    */
541   @Override
542   public void setSequence(String seq)
543   {
544     this.sequence = seq.toCharArray();
545     checkValidRange();
546     sequenceChanged();
547   }
548
549   @Override
550   public String getSequenceAsString()
551   {
552     return new String(sequence);
553   }
554
555   @Override
556   public String getSequenceAsString(int start, int end)
557   {
558     return new String(getSequence(start, end));
559   }
560
561   @Override
562   public char[] getSequence()
563   {
564     return sequence;
565   }
566
567   /*
568    * (non-Javadoc)
569    * 
570    * @see jalview.datamodel.SequenceI#getSequence(int, int)
571    */
572   @Override
573   public char[] getSequence(int start, int end)
574   {
575     if (start < 0)
576     {
577       start = 0;
578     }
579     // JBPNote - left to user to pad the result here (TODO:Decide on this
580     // policy)
581     if (start >= sequence.length)
582     {
583       return new char[0];
584     }
585
586     if (end >= sequence.length)
587     {
588       end = sequence.length;
589     }
590
591     char[] reply = new char[end - start];
592     System.arraycopy(sequence, start, reply, 0, end - start);
593
594     return reply;
595   }
596
597   @Override
598   public SequenceI getSubSequence(int start, int end)
599   {
600     if (start < 0)
601     {
602       start = 0;
603     }
604     char[] seq = getSequence(start, end);
605     if (seq.length == 0)
606     {
607       return null;
608     }
609     int nstart = findPosition(start);
610     int nend = findPosition(end) - 1;
611     // JBPNote - this is an incomplete copy.
612     SequenceI nseq = new Sequence(this.getName(), seq, nstart, nend);
613     nseq.setDescription(description);
614     if (datasetSequence != null)
615     {
616       nseq.setDatasetSequence(datasetSequence);
617     }
618     else
619     {
620       nseq.setDatasetSequence(this);
621     }
622     return nseq;
623   }
624
625   /**
626    * Returns the character of the aligned sequence at the given position (base
627    * zero), or space if the position is not within the sequence's bounds
628    * 
629    * @return
630    */
631   @Override
632   public char getCharAt(int i)
633   {
634     if (i >= 0 && i < sequence.length)
635     {
636       return sequence[i];
637     }
638     else
639     {
640       return ' ';
641     }
642   }
643
644   /**
645    * DOCUMENT ME!
646    * 
647    * @param desc
648    *          DOCUMENT ME!
649    */
650   @Override
651   public void setDescription(String desc)
652   {
653     this.description = desc;
654   }
655
656   /**
657    * DOCUMENT ME!
658    * 
659    * @return DOCUMENT ME!
660    */
661   @Override
662   public String getDescription()
663   {
664     return this.description;
665   }
666
667   /*
668    * (non-Javadoc)
669    * 
670    * @see jalview.datamodel.SequenceI#findIndex(int)
671    */
672   @Override
673   public int findIndex(int pos)
674   {
675     /*
676      * use a valid nearby cursor if available
677      */
678     if (cursor != null && cursor.sequence == this
679             && cursor.token == changeCount)
680     {
681       return findIndex(pos, cursor);
682     }
683
684     int j = start;
685     int i = 0;
686     // Rely on end being at least as long as the length of the sequence.
687     while ((i < sequence.length) && (j <= end) && (j <= pos))
688     {
689       if (!Comparison.isGap(sequence[i]))
690       {
691         j++;
692       }
693       i++;
694     }
695
696     if ((j == end) && (j < pos))
697     {
698       return end + 1;
699     }
700     else
701     {
702       updateCursor(pos, i);
703       return i;
704     }
705   }
706
707   protected void updateCursor(int residuePos, int column)
708   {
709     // TODO probably want to synchronize this on something
710     cursor = new SequenceCursor(this, residuePos, column, this.changeCount);
711   }
712
713   /**
714    * Answers the aligned column position (1..) for the given residue position
715    * (start..) given a 'hint' of a residue/column location in the neighbourhood.
716    * The hint may be left of, at, or to the right of the required position.
717    * 
718    * @param pos
719    * @param curs
720    * @return
721    */
722   protected int findIndex(int pos, SequenceCursor curs)
723   {
724     if (curs.sequence != this || curs.token != changeCount)
725     {
726       /*
727        * wrong or invalidated cursor, compute de novo
728        */
729       return findIndex(pos);
730     }
731
732     if (curs.residuePosition == pos)
733     {
734       return curs.columnPosition;
735     }
736
737     /*
738      * move left or right to find pos from hint.position
739      */
740     int col = curs.columnPosition - 1; // convert from base 1 to 0-based array
741                                        // index
742     int newPos = curs.residuePosition;
743     int delta = newPos > pos ? -1 : 1;
744
745     while (newPos != pos)
746     {
747       col += delta; // shift one column left or right
748       if (col < 0 || col == sequence.length)
749       {
750         break;
751       }
752       if (!Comparison.isGap(sequence[col]))
753       {
754         newPos += delta;
755       }
756     }
757
758     col++; // convert back to base 1
759     updateCursor(pos, col);
760
761     return col;
762   }
763
764   @Override
765   public int findPosition(final int i)
766   {
767     /*
768      * use a valid nearby cursor if available
769      */
770     if (cursor != null && cursor.sequence == this
771             && cursor.token == changeCount)
772     {
773       return findPosition(i + 1, cursor);
774     }
775
776     int j = 0;
777     int pos = start;
778     int seqlen = sequence.length;
779     while ((j < i) && (j < seqlen))
780     {
781       if (!Comparison.isGap(sequence[j]))
782       {
783         pos++;
784       }
785
786       j++;
787     }
788
789     if (j == i && !Comparison.isGap(sequence[i]))
790     {
791       updateCursor(pos, i + 1);
792     }
793
794     return pos;
795   }
796
797   /**
798    * Answers the sequence position (start..) for the given aligned column
799    * position (1..), given a hint of a cursor in the neighbourhood. The cursor
800    * may lie left of, at, or to the right of the column position.
801    * 
802    * @param col
803    * @param curs
804    * @return
805    */
806   protected int findPosition(final int col, SequenceCursor curs)
807   {
808     if (curs.sequence != this || curs.token != changeCount)
809     {
810       /*
811        * wrong or invalidated cursor, compute de novo
812        */
813       return findPosition(col - 1);// ugh back to base 0
814     }
815
816     if (curs.columnPosition == col)
817     {
818       return curs.residuePosition; // easy case :-)
819     }
820
821     /*
822      * move left or right to find pos from cursor position
823      */
824     int column = curs.columnPosition - 1; // to base 0
825     int newPos = curs.residuePosition;
826     int delta = curs.columnPosition > col ? -1 : 1;
827     boolean gapped = false;
828
829     while (column != col - 1)
830     {
831       column += delta; // shift one column left or right
832       if (column < 0 || column == sequence.length)
833       {
834         break;
835       }
836       gapped = Comparison.isGap(sequence[column]);
837       if (!gapped)
838       {
839         newPos += delta;
840       }
841     }
842
843     /*
844      * hack to give position to the right if on a gap
845      * pending resolution of JAL-2562
846      */
847     if (delta > 0 && gapped)
848     {
849       newPos++;
850     }
851
852     return newPos;
853   }
854
855   /**
856    * {@inheritDoc}
857    */
858   @Override
859   public Range findPositions(int fromCol, int toCol)
860   {
861     if (cursor != null && cursor.sequence == this
862             && cursor.token == changeCount)
863     {
864       return findPositions(fromCol, toCol, cursor);
865     }
866
867     /*
868      * count residues before fromCol
869      */
870     int j = 0;
871     int count = 0;
872     int seqlen = sequence.length;
873     while (j < fromCol && j < seqlen)
874     {
875       if (!Comparison.isGap(sequence[j]))
876       {
877         count++;
878       }
879       j++;
880     }
881
882     /*
883      * find first and last residues between fromCol and toCol
884      */
885     int firstPos = 0;
886     int lastPos = 0;
887     boolean foundFirst = false;
888     
889     while (j <= toCol && j < seqlen)
890     {
891       if (!Comparison.isGap(sequence[j]))
892       {
893         count++;
894         if (!foundFirst)
895         {
896           firstPos = count;
897           foundFirst = true;
898         }
899         lastPos = count;
900       }
901       j++;
902     }
903
904     if (firstPos == 0)
905     {
906       /*
907        * no residues in this range
908        */
909       return null;
910     }
911
912     /*
913      * adjust for sequence start coordinate
914      */
915     firstPos += start - 1;
916     lastPos += start - 1;
917
918     return new Range(firstPos, lastPos);
919   }
920
921   /**
922    * Returns the range of sequence positions included in the given alignment
923    * position range. If no positions are included (the range is entirely gaps),
924    * then returns null. The cursor parameter may provide a starting position in
925    * the neighbourhood of the search (which may be left of, right of, or
926    * overlapping the search region).
927    * 
928    * @param fromCol
929    *          start column of region (0..)
930    * @param toCol
931    *          end column of region (0..)
932    * @param curs
933    * @return
934    */
935   protected Range findPositions(int fromCol, int toCol, SequenceCursor curs)
936   {
937     if (curs.sequence != this || curs.token != changeCount)
938     {
939       /*
940        * wrong or invalidated cursor, compute de novo
941        */
942       return findPositions(fromCol, toCol);
943     }
944
945     /*
946      * keep this simple...first step from cursor to fromCol...
947      */
948     final int seqlen = sequence.length;
949     int resNo = curs.residuePosition;
950     int col = curs.columnPosition - 1; // from base 1 to base 0
951     if (col != fromCol)
952     {
953       int delta = col > fromCol ? -1 : 1;
954       while (col != fromCol && col >= 0 && col < seqlen)
955       {
956         if (!Comparison.isGap(sequence[col]))
957         {
958           resNo += delta;
959         }
960         col += delta;
961       }
962     }
963
964     if (col < fromCol || col == seqlen)
965     {
966       /*
967        * sequence lies to the left of the target region
968        */
969       return null;
970     }
971
972     /*
973      * resNo is now the residue at fromCol (if not gapped), else the one
974      * before it (if delta == 1), else the one after (if delta == -1);
975      * we want the residue before fromCol
976      */
977     if (!Comparison.isGap(sequence[fromCol]))
978     {
979       resNo--;
980     }
981     else if (curs.columnPosition > fromCol)
982     {
983       resNo -= 2;
984     }
985
986     /*
987      * now first and last residues between fromCol and toCol
988      */
989     int firstPos = 0;
990     int lastPos = 0;
991     boolean foundFirst = false;
992
993     while (col <= toCol && col < seqlen)
994     {
995       if (!Comparison.isGap(sequence[col]))
996       {
997         resNo++;
998         if (!foundFirst)
999         {
1000           firstPos = resNo;
1001           foundFirst = true;
1002         }
1003         lastPos = resNo;
1004       }
1005       col++;
1006     }
1007
1008     if (firstPos == 0)
1009     {
1010       /*
1011        * no residues in this range
1012        */
1013       return null;
1014     }
1015
1016     return new Range(firstPos, lastPos);
1017   }
1018
1019   /**
1020    * Returns an int array where indices correspond to each residue in the
1021    * sequence and the element value gives its position in the alignment
1022    * 
1023    * @return int[SequenceI.getEnd()-SequenceI.getStart()+1] or null if no
1024    *         residues in SequenceI object
1025    */
1026   @Override
1027   public int[] gapMap()
1028   {
1029     String seq = jalview.analysis.AlignSeq.extractGaps(
1030             jalview.util.Comparison.GapChars, new String(sequence));
1031     int[] map = new int[seq.length()];
1032     int j = 0;
1033     int p = 0;
1034
1035     while (j < sequence.length)
1036     {
1037       if (!jalview.util.Comparison.isGap(sequence[j]))
1038       {
1039         map[p++] = j;
1040       }
1041
1042       j++;
1043     }
1044
1045     return map;
1046   }
1047
1048   @Override
1049   public int[] findPositionMap()
1050   {
1051     int map[] = new int[sequence.length];
1052     int j = 0;
1053     int pos = start;
1054     int seqlen = sequence.length;
1055     while ((j < seqlen))
1056     {
1057       map[j] = pos;
1058       if (!jalview.util.Comparison.isGap(sequence[j]))
1059       {
1060         pos++;
1061       }
1062
1063       j++;
1064     }
1065     return map;
1066   }
1067
1068   @Override
1069   public List<int[]> getInsertions()
1070   {
1071     ArrayList<int[]> map = new ArrayList<int[]>();
1072     int lastj = -1, j = 0;
1073     int pos = start;
1074     int seqlen = sequence.length;
1075     while ((j < seqlen))
1076     {
1077       if (jalview.util.Comparison.isGap(sequence[j]))
1078       {
1079         if (lastj == -1)
1080         {
1081           lastj = j;
1082         }
1083       }
1084       else
1085       {
1086         if (lastj != -1)
1087         {
1088           map.add(new int[] { lastj, j - 1 });
1089           lastj = -1;
1090         }
1091       }
1092       j++;
1093     }
1094     if (lastj != -1)
1095     {
1096       map.add(new int[] { lastj, j - 1 });
1097       lastj = -1;
1098     }
1099     return map;
1100   }
1101
1102   @Override
1103   public void deleteChars(int i, int j)
1104   {
1105     int newstart = start, newend = end;
1106     if (i >= sequence.length || i < 0)
1107     {
1108       return;
1109     }
1110
1111     char[] tmp = StringUtils.deleteChars(sequence, i, j);
1112     boolean createNewDs = false;
1113     // TODO: take a (second look) at the dataset creation validation method for
1114     // the very large sequence case
1115     int eindex = -1, sindex = -1;
1116     boolean ecalc = false, scalc = false;
1117     for (int s = i; s < j; s++)
1118     {
1119       if (jalview.schemes.ResidueProperties.aaIndex[sequence[s]] != 23)
1120       {
1121         if (createNewDs)
1122         {
1123           newend--;
1124         }
1125         else
1126         {
1127           if (!scalc)
1128           {
1129             sindex = findIndex(start) - 1;
1130             scalc = true;
1131           }
1132           if (sindex == s)
1133           {
1134             // delete characters including start of sequence
1135             newstart = findPosition(j);
1136             break; // don't need to search for any more residue characters.
1137           }
1138           else
1139           {
1140             // delete characters after start.
1141             if (!ecalc)
1142             {
1143               eindex = findIndex(end) - 1;
1144               ecalc = true;
1145             }
1146             if (eindex < j)
1147             {
1148               // delete characters at end of sequence
1149               newend = findPosition(i - 1);
1150               break; // don't need to search for any more residue characters.
1151             }
1152             else
1153             {
1154               createNewDs = true;
1155               newend--; // decrease end position by one for the deleted residue
1156               // and search further
1157             }
1158           }
1159         }
1160       }
1161     }
1162     // deletion occured in the middle of the sequence
1163     if (createNewDs && this.datasetSequence != null)
1164     {
1165       // construct a new sequence
1166       Sequence ds = new Sequence(datasetSequence);
1167       // TODO: remove any non-inheritable properties ?
1168       // TODO: create a sequence mapping (since there is a relation here ?)
1169       ds.deleteChars(i, j);
1170       datasetSequence = ds;
1171     }
1172     start = newstart;
1173     end = newend;
1174     sequence = tmp;
1175     sequenceChanged();
1176   }
1177
1178   @Override
1179   public void insertCharAt(int i, int length, char c)
1180   {
1181     char[] tmp = new char[sequence.length + length];
1182
1183     if (i >= sequence.length)
1184     {
1185       System.arraycopy(sequence, 0, tmp, 0, sequence.length);
1186       i = sequence.length;
1187     }
1188     else
1189     {
1190       System.arraycopy(sequence, 0, tmp, 0, i);
1191     }
1192
1193     int index = i;
1194     while (length > 0)
1195     {
1196       tmp[index++] = c;
1197       length--;
1198     }
1199
1200     if (i < sequence.length)
1201     {
1202       System.arraycopy(sequence, i, tmp, index, sequence.length - i);
1203     }
1204
1205     sequence = tmp;
1206     sequenceChanged();
1207   }
1208
1209   @Override
1210   public void insertCharAt(int i, char c)
1211   {
1212     insertCharAt(i, 1, c);
1213   }
1214
1215   @Override
1216   public String getVamsasId()
1217   {
1218     return vamsasId;
1219   }
1220
1221   @Override
1222   public void setVamsasId(String id)
1223   {
1224     vamsasId = id;
1225   }
1226
1227   @Override
1228   public void setDBRefs(DBRefEntry[] dbref)
1229   {
1230     if (dbrefs == null && datasetSequence != null
1231             && this != datasetSequence)
1232     {
1233       datasetSequence.setDBRefs(dbref);
1234       return;
1235     }
1236     dbrefs = dbref;
1237     if (dbrefs != null)
1238     {
1239       DBRefUtils.ensurePrimaries(this);
1240     }
1241   }
1242
1243   @Override
1244   public DBRefEntry[] getDBRefs()
1245   {
1246     if (dbrefs == null && datasetSequence != null
1247             && this != datasetSequence)
1248     {
1249       return datasetSequence.getDBRefs();
1250     }
1251     return dbrefs;
1252   }
1253
1254   @Override
1255   public void addDBRef(DBRefEntry entry)
1256   {
1257     if (datasetSequence != null)
1258     {
1259       datasetSequence.addDBRef(entry);
1260       return;
1261     }
1262
1263     if (dbrefs == null)
1264     {
1265       dbrefs = new DBRefEntry[0];
1266     }
1267
1268     for (DBRefEntryI dbr : dbrefs)
1269     {
1270       if (dbr.updateFrom(entry))
1271       {
1272         /*
1273          * found a dbref that either matched, or could be
1274          * updated from, the new entry - no need to add it
1275          */
1276         return;
1277       }
1278     }
1279
1280     /*
1281      * extend the array to make room for one more
1282      */
1283     // TODO use an ArrayList instead
1284     int j = dbrefs.length;
1285     DBRefEntry[] temp = new DBRefEntry[j + 1];
1286     System.arraycopy(dbrefs, 0, temp, 0, j);
1287     temp[temp.length - 1] = entry;
1288
1289     dbrefs = temp;
1290
1291     DBRefUtils.ensurePrimaries(this);
1292   }
1293
1294   @Override
1295   public void setDatasetSequence(SequenceI seq)
1296   {
1297     if (seq == this)
1298     {
1299       throw new IllegalArgumentException(
1300               "Implementation Error: self reference passed to SequenceI.setDatasetSequence");
1301     }
1302     if (seq != null && seq.getDatasetSequence() != null)
1303     {
1304       throw new IllegalArgumentException(
1305               "Implementation error: cascading dataset sequences are not allowed.");
1306     }
1307     datasetSequence = seq;
1308   }
1309
1310   @Override
1311   public SequenceI getDatasetSequence()
1312   {
1313     return datasetSequence;
1314   }
1315
1316   @Override
1317   public AlignmentAnnotation[] getAnnotation()
1318   {
1319     return annotation == null ? null : annotation
1320             .toArray(new AlignmentAnnotation[annotation.size()]);
1321   }
1322
1323   @Override
1324   public boolean hasAnnotation(AlignmentAnnotation ann)
1325   {
1326     return annotation == null ? false : annotation.contains(ann);
1327   }
1328
1329   @Override
1330   public void addAlignmentAnnotation(AlignmentAnnotation annotation)
1331   {
1332     if (this.annotation == null)
1333     {
1334       this.annotation = new Vector<AlignmentAnnotation>();
1335     }
1336     if (!this.annotation.contains(annotation))
1337     {
1338       this.annotation.addElement(annotation);
1339     }
1340     annotation.setSequenceRef(this);
1341   }
1342
1343   @Override
1344   public void removeAlignmentAnnotation(AlignmentAnnotation annotation)
1345   {
1346     if (this.annotation != null)
1347     {
1348       this.annotation.removeElement(annotation);
1349       if (this.annotation.size() == 0)
1350       {
1351         this.annotation = null;
1352       }
1353     }
1354   }
1355
1356   /**
1357    * test if this is a valid candidate for another sequence's dataset sequence.
1358    * 
1359    */
1360   private boolean isValidDatasetSequence()
1361   {
1362     if (datasetSequence != null)
1363     {
1364       return false;
1365     }
1366     for (int i = 0; i < sequence.length; i++)
1367     {
1368       if (jalview.util.Comparison.isGap(sequence[i]))
1369       {
1370         return false;
1371       }
1372     }
1373     return true;
1374   }
1375
1376   @Override
1377   public SequenceI deriveSequence()
1378   {
1379     Sequence seq = null;
1380     if (datasetSequence == null)
1381     {
1382       if (isValidDatasetSequence())
1383       {
1384         // Use this as dataset sequence
1385         seq = new Sequence(getName(), "", 1, -1);
1386         seq.setDatasetSequence(this);
1387         seq.initSeqFrom(this, getAnnotation());
1388         return seq;
1389       }
1390       else
1391       {
1392         // Create a new, valid dataset sequence
1393         createDatasetSequence();
1394       }
1395     }
1396     return new Sequence(this);
1397   }
1398
1399   private boolean _isNa;
1400
1401   private long _seqhash = 0;
1402
1403   /**
1404    * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
1405    * true
1406    */
1407   @Override
1408   public boolean isProtein()
1409   {
1410     if (datasetSequence != null)
1411     {
1412       return datasetSequence.isProtein();
1413     }
1414     if (_seqhash != sequence.hashCode())
1415     {
1416       _seqhash = sequence.hashCode();
1417       _isNa = Comparison.isNucleotide(this);
1418     }
1419     return !_isNa;
1420   };
1421
1422   /*
1423    * (non-Javadoc)
1424    * 
1425    * @see jalview.datamodel.SequenceI#createDatasetSequence()
1426    */
1427   @Override
1428   public SequenceI createDatasetSequence()
1429   {
1430     if (datasetSequence == null)
1431     {
1432       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
1433               jalview.util.Comparison.GapChars, getSequenceAsString()),
1434               getStart(), getEnd());
1435
1436       datasetSequence = dsseq;
1437
1438       dsseq.setDescription(description);
1439       // move features and database references onto dataset sequence
1440       dsseq.sequenceFeatureStore = sequenceFeatureStore;
1441       sequenceFeatureStore = null;
1442       dsseq.dbrefs = dbrefs;
1443       dbrefs = null;
1444       // TODO: search and replace any references to this sequence with
1445       // references to the dataset sequence in Mappings on dbref
1446       dsseq.pdbIds = pdbIds;
1447       pdbIds = null;
1448       datasetSequence.updatePDBIds();
1449       if (annotation != null)
1450       {
1451         // annotation is cloned rather than moved, to preserve what's currently
1452         // on the alignment
1453         for (AlignmentAnnotation aa : annotation)
1454         {
1455           AlignmentAnnotation _aa = new AlignmentAnnotation(aa);
1456           _aa.sequenceRef = datasetSequence;
1457           _aa.adjustForAlignment(); // uses annotation's own record of
1458                                     // sequence-column mapping
1459           datasetSequence.addAlignmentAnnotation(_aa);
1460         }
1461       }
1462     }
1463     return datasetSequence;
1464   }
1465
1466   /*
1467    * (non-Javadoc)
1468    * 
1469    * @see
1470    * jalview.datamodel.SequenceI#setAlignmentAnnotation(AlignmmentAnnotation[]
1471    * annotations)
1472    */
1473   @Override
1474   public void setAlignmentAnnotation(AlignmentAnnotation[] annotations)
1475   {
1476     if (annotation != null)
1477     {
1478       annotation.removeAllElements();
1479     }
1480     if (annotations != null)
1481     {
1482       for (int i = 0; i < annotations.length; i++)
1483       {
1484         if (annotations[i] != null)
1485         {
1486           addAlignmentAnnotation(annotations[i]);
1487         }
1488       }
1489     }
1490   }
1491
1492   @Override
1493   public AlignmentAnnotation[] getAnnotation(String label)
1494   {
1495     if (annotation == null || annotation.size() == 0)
1496     {
1497       return null;
1498     }
1499
1500     Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
1501     Enumeration<AlignmentAnnotation> e = annotation.elements();
1502     while (e.hasMoreElements())
1503     {
1504       AlignmentAnnotation ann = e.nextElement();
1505       if (ann.label != null && ann.label.equals(label))
1506       {
1507         subset.addElement(ann);
1508       }
1509     }
1510     if (subset.size() == 0)
1511     {
1512       return null;
1513     }
1514     AlignmentAnnotation[] anns = new AlignmentAnnotation[subset.size()];
1515     int i = 0;
1516     e = subset.elements();
1517     while (e.hasMoreElements())
1518     {
1519       anns[i++] = e.nextElement();
1520     }
1521     subset.removeAllElements();
1522     return anns;
1523   }
1524
1525   @Override
1526   public boolean updatePDBIds()
1527   {
1528     if (datasetSequence != null)
1529     {
1530       // TODO: could merge DBRefs
1531       return datasetSequence.updatePDBIds();
1532     }
1533     if (dbrefs == null || dbrefs.length == 0)
1534     {
1535       return false;
1536     }
1537     boolean added = false;
1538     for (DBRefEntry dbr : dbrefs)
1539     {
1540       if (DBRefSource.PDB.equals(dbr.getSource()))
1541       {
1542         /*
1543          * 'Add' any PDB dbrefs as a PDBEntry - add is only performed if the
1544          * PDB id is not already present in a 'matching' PDBEntry
1545          * Constructor parses out a chain code if appended to the accession id
1546          * (a fudge used to 'store' the chain code in the DBRef)
1547          */
1548         PDBEntry pdbe = new PDBEntry(dbr);
1549         added |= addPDBId(pdbe);
1550       }
1551     }
1552     return added;
1553   }
1554
1555   @Override
1556   public void transferAnnotation(SequenceI entry, Mapping mp)
1557   {
1558     if (datasetSequence != null)
1559     {
1560       datasetSequence.transferAnnotation(entry, mp);
1561       return;
1562     }
1563     if (entry.getDatasetSequence() != null)
1564     {
1565       transferAnnotation(entry.getDatasetSequence(), mp);
1566       return;
1567     }
1568     // transfer any new features from entry onto sequence
1569     if (entry.getSequenceFeatures() != null)
1570     {
1571
1572       List<SequenceFeature> sfs = entry.getSequenceFeatures();
1573       for (SequenceFeature feature : sfs)
1574       {
1575         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
1576                 : new SequenceFeature[] { new SequenceFeature(feature) };
1577         if (sf != null)
1578         {
1579           for (int sfi = 0; sfi < sf.length; sfi++)
1580           {
1581             addSequenceFeature(sf[sfi]);
1582           }
1583         }
1584       }
1585     }
1586
1587     // transfer PDB entries
1588     if (entry.getAllPDBEntries() != null)
1589     {
1590       Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
1591       while (e.hasMoreElements())
1592       {
1593         PDBEntry pdb = e.nextElement();
1594         addPDBId(pdb);
1595       }
1596     }
1597     // transfer database references
1598     DBRefEntry[] entryRefs = entry.getDBRefs();
1599     if (entryRefs != null)
1600     {
1601       for (int r = 0; r < entryRefs.length; r++)
1602       {
1603         DBRefEntry newref = new DBRefEntry(entryRefs[r]);
1604         if (newref.getMap() != null && mp != null)
1605         {
1606           // remap ref using our local mapping
1607         }
1608         // we also assume all version string setting is done by dbSourceProxy
1609         /*
1610          * if (!newref.getSource().equalsIgnoreCase(dbSource)) {
1611          * newref.setSource(dbSource); }
1612          */
1613         addDBRef(newref);
1614       }
1615     }
1616   }
1617
1618   /**
1619    * @return The index (zero-based) on this sequence in the MSA. It returns
1620    *         {@code -1} if this information is not available.
1621    */
1622   @Override
1623   public int getIndex()
1624   {
1625     return index;
1626   }
1627
1628   /**
1629    * Defines the position of this sequence in the MSA. Use the value {@code -1}
1630    * if this information is undefined.
1631    * 
1632    * @param The
1633    *          position for this sequence. This value is zero-based (zero for
1634    *          this first sequence)
1635    */
1636   @Override
1637   public void setIndex(int value)
1638   {
1639     index = value;
1640   }
1641
1642   @Override
1643   public void setRNA(RNA r)
1644   {
1645     rna = r;
1646   }
1647
1648   @Override
1649   public RNA getRNA()
1650   {
1651     return rna;
1652   }
1653
1654   @Override
1655   public List<AlignmentAnnotation> getAlignmentAnnotations(String calcId,
1656           String label)
1657   {
1658     List<AlignmentAnnotation> result = new ArrayList<AlignmentAnnotation>();
1659     if (this.annotation != null)
1660     {
1661       for (AlignmentAnnotation ann : annotation)
1662       {
1663         if (ann.calcId != null && ann.calcId.equals(calcId)
1664                 && ann.label != null && ann.label.equals(label))
1665         {
1666           result.add(ann);
1667         }
1668       }
1669     }
1670     return result;
1671   }
1672
1673   @Override
1674   public String toString()
1675   {
1676     return getDisplayId(false);
1677   }
1678
1679   @Override
1680   public PDBEntry getPDBEntry(String pdbIdStr)
1681   {
1682     if (getDatasetSequence() != null)
1683     {
1684       return getDatasetSequence().getPDBEntry(pdbIdStr);
1685     }
1686     if (pdbIds == null)
1687     {
1688       return null;
1689     }
1690     List<PDBEntry> entries = getAllPDBEntries();
1691     for (PDBEntry entry : entries)
1692     {
1693       if (entry.getId().equalsIgnoreCase(pdbIdStr))
1694       {
1695         return entry;
1696       }
1697     }
1698     return null;
1699   }
1700
1701   @Override
1702   public List<DBRefEntry> getPrimaryDBRefs()
1703   {
1704     if (datasetSequence != null)
1705     {
1706       return datasetSequence.getPrimaryDBRefs();
1707     }
1708     if (dbrefs == null || dbrefs.length == 0)
1709     {
1710       return Collections.emptyList();
1711     }
1712     synchronized (dbrefs)
1713     {
1714       List<DBRefEntry> primaries = new ArrayList<DBRefEntry>();
1715       DBRefEntry[] tmp = new DBRefEntry[1];
1716       for (DBRefEntry ref : dbrefs)
1717       {
1718         if (!ref.isPrimaryCandidate())
1719         {
1720           continue;
1721         }
1722         if (ref.hasMap())
1723         {
1724           MapList mp = ref.getMap().getMap();
1725           if (mp.getFromLowest() > start || mp.getFromHighest() < end)
1726           {
1727             // map only involves a subsequence, so cannot be primary
1728             continue;
1729           }
1730         }
1731         // whilst it looks like it is a primary ref, we also sanity check type
1732         if (DBRefUtils.getCanonicalName(DBRefSource.PDB).equals(
1733                 DBRefUtils.getCanonicalName(ref.getSource())))
1734         {
1735           // PDB dbrefs imply there should be a PDBEntry associated
1736           // TODO: tighten PDB dbrefs
1737           // formally imply Jalview has actually downloaded and
1738           // parsed the pdb file. That means there should be a cached file
1739           // handle on the PDBEntry, and a real mapping between sequence and
1740           // extracted sequence from PDB file
1741           PDBEntry pdbentry = getPDBEntry(ref.getAccessionId());
1742           if (pdbentry != null && pdbentry.getFile() != null)
1743           {
1744             primaries.add(ref);
1745           }
1746           continue;
1747         }
1748         // check standard protein or dna sources
1749         tmp[0] = ref;
1750         DBRefEntry[] res = DBRefUtils.selectDbRefs(!isProtein(), tmp);
1751         if (res != null && res[0] == tmp[0])
1752         {
1753           primaries.add(ref);
1754           continue;
1755         }
1756       }
1757       return primaries;
1758     }
1759   }
1760
1761   /**
1762    * {@inheritDoc}
1763    */
1764   @Override
1765   public List<SequenceFeature> findFeatures(int from, int to,
1766           String... types)
1767   {
1768     if (datasetSequence != null)
1769     {
1770       return datasetSequence.findFeatures(from, to, types);
1771     }
1772     return sequenceFeatureStore.findFeatures(from, to, types);
1773   }
1774
1775   /**
1776    * Invalidates any stale cursors (forcing recalculation) by incrementing the
1777    * token that has to match the one presented by the cursor
1778    */
1779   @Override
1780   public void sequenceChanged()
1781   {
1782     changeCount++;
1783   }
1784 }