JAL-3438 spotless for 2.11.2.0
[jalview.git] / test / jalview / datamodel / SequenceTest.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 java.util.Locale;
24
25 import static org.testng.AssertJUnit.assertEquals;
26 import static org.testng.AssertJUnit.assertFalse;
27 import static org.testng.AssertJUnit.assertNotNull;
28 import static org.testng.AssertJUnit.assertNotSame;
29 import static org.testng.AssertJUnit.assertNull;
30 import static org.testng.AssertJUnit.assertSame;
31 import static org.testng.AssertJUnit.assertTrue;
32
33 import jalview.analysis.AlignmentGenerator;
34 import jalview.commands.EditCommand;
35 import jalview.commands.EditCommand.Action;
36 import jalview.datamodel.PDBEntry.Type;
37 import jalview.gui.JvOptionPane;
38 import jalview.util.MapList;
39 import jalview.ws.params.InvalidArgumentException;
40
41 import java.io.File;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.BitSet;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Vector;
48
49 import org.testng.Assert;
50 import org.testng.annotations.BeforeClass;
51 import org.testng.annotations.BeforeMethod;
52 import org.testng.annotations.Test;
53
54 import junit.extensions.PA;
55
56 public class SequenceTest
57 {
58   @BeforeClass(alwaysRun = true)
59   public void setUpJvOptionPane()
60   {
61     JvOptionPane.setInteractiveMode(false);
62     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
63   }
64
65   Sequence seq;
66
67   @BeforeMethod(alwaysRun = true)
68   public void setUp()
69   {
70     seq = new Sequence("FER1", "AKPNGVL");
71   }
72
73   @Test(groups = { "Functional" })
74   public void testInsertGapsAndGapmaps()
75   {
76     SequenceI aseq = seq.deriveSequence();
77     aseq.insertCharAt(2, 3, '-');
78     aseq.insertCharAt(6, 3, '-');
79     assertEquals("Gap insertions not correct", "AK---P---NGVL",
80             aseq.getSequenceAsString());
81     List<int[]> gapInt = aseq.getInsertions();
82     assertEquals("Gap interval 1 start wrong", 2, gapInt.get(0)[0]);
83     assertEquals("Gap interval 1 end wrong", 4, gapInt.get(0)[1]);
84     assertEquals("Gap interval 2 start wrong", 6, gapInt.get(1)[0]);
85     assertEquals("Gap interval 2 end wrong", 8, gapInt.get(1)[1]);
86
87     BitSet gapfield = aseq.getInsertionsAsBits();
88     BitSet expectedgaps = new BitSet();
89     expectedgaps.set(2, 5);
90     expectedgaps.set(6, 9);
91
92     assertEquals(6, expectedgaps.cardinality());
93
94     assertEquals("getInsertionsAsBits didn't mark expected number of gaps",
95             6, gapfield.cardinality());
96
97     assertEquals("getInsertionsAsBits not correct.", expectedgaps,
98             gapfield);
99   }
100
101   @Test(groups = ("Functional"))
102   public void testIsProtein()
103   {
104     // test Protein
105     assertTrue(new Sequence("prot", "ASDFASDFASDF").isProtein());
106     // test DNA
107     assertFalse(new Sequence("prot", "ACGTACGTACGT").isProtein());
108     // test RNA
109     SequenceI sq = new Sequence("prot", "ACGUACGUACGU");
110     assertFalse(sq.isProtein());
111     // change sequence, should trigger an update of cached result
112     sq.setSequence("ASDFASDFADSF");
113     assertTrue(sq.isProtein());
114   }
115
116   @Test(groups = ("Functional"))
117   public void testIsProteinWithXorNAmbiguityCodes()
118   {
119     // test Protein with N - poly asparagine
120     assertTrue(new Sequence("prot", "ASDFASDFASDFNNNNNNNNN").isProtein());
121     assertTrue(new Sequence("prot", "NNNNNNNNNNNNNNNNNNNNN").isProtein());
122     // test Protein with X
123     assertTrue(new Sequence("prot", "ASDFASDFASDFXXXXXXXXX").isProtein());
124     // test DNA with X
125     assertFalse(new Sequence("prot", "ACGTACGTACGTXXXXXXXX").isProtein());
126     // test DNA with N
127     assertFalse(new Sequence("prot", "ACGTACGTACGTNNNNNNNN").isProtein());
128     // test RNA with X
129     assertFalse(new Sequence("prot", "ACGUACGUACGUXXXXXXXXX").isProtein());
130     assertFalse(new Sequence("prot", "ACGUACGUACGUNNNNNNNNN").isProtein());
131   }
132
133   @Test(groups = { "Functional" })
134   public void testGetAnnotation()
135   {
136     // initial state returns null not an empty array
137     assertNull(seq.getAnnotation());
138     AlignmentAnnotation ann = addAnnotation("label1", "desc1", "calcId1",
139             1f);
140     AlignmentAnnotation[] anns = seq.getAnnotation();
141     assertEquals(1, anns.length);
142     assertSame(ann, anns[0]);
143
144     // removing all annotations reverts array to null
145     seq.removeAlignmentAnnotation(ann);
146     assertNull(seq.getAnnotation());
147   }
148
149   @Test(groups = { "Functional" })
150   public void testGetAnnotation_forLabel()
151   {
152     AlignmentAnnotation ann1 = addAnnotation("label1", "desc1", "calcId1",
153             1f);
154     addAnnotation("label2", "desc2", "calcId2", 1f);
155     AlignmentAnnotation ann3 = addAnnotation("label1", "desc3", "calcId3",
156             1f);
157     AlignmentAnnotation[] anns = seq.getAnnotation("label1");
158     assertEquals(2, anns.length);
159     assertSame(ann1, anns[0]);
160     assertSame(ann3, anns[1]);
161   }
162
163   private AlignmentAnnotation addAnnotation(String label,
164           String description, String calcId, float value)
165   {
166     final AlignmentAnnotation annotation = new AlignmentAnnotation(label,
167             description, value);
168     annotation.setCalcId(calcId);
169     seq.addAlignmentAnnotation(annotation);
170     return annotation;
171   }
172
173   @Test(groups = { "Functional" })
174   public void testGetAlignmentAnnotations_forCalcIdAndLabel()
175   {
176     addAnnotation("label1", "desc1", "calcId1", 1f);
177     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
178             1f);
179     addAnnotation("label2", "desc3", "calcId3", 1f);
180     AlignmentAnnotation ann4 = addAnnotation("label2", "desc3", "calcId2",
181             1f);
182     addAnnotation("label5", "desc3", null, 1f);
183     addAnnotation(null, "desc3", "calcId3", 1f);
184
185     List<AlignmentAnnotation> anns = seq.getAlignmentAnnotations("calcId2",
186             "label2");
187     assertEquals(2, anns.size());
188     assertSame(ann2, anns.get(0));
189     assertSame(ann4, anns.get(1));
190
191     assertTrue(seq.getAlignmentAnnotations("calcId2", "label3").isEmpty());
192     assertTrue(seq.getAlignmentAnnotations("calcId3", "label5").isEmpty());
193     assertTrue(seq.getAlignmentAnnotations("calcId2", null).isEmpty());
194     assertTrue(seq.getAlignmentAnnotations(null, "label3").isEmpty());
195     assertTrue(seq.getAlignmentAnnotations(null, null).isEmpty());
196   }
197
198   @Test(groups = { "Functional" })
199   public void testGetAlignmentAnnotations_forCalcIdLabelAndDescription()
200   {
201     addAnnotation("label1", "desc1", "calcId1", 1f);
202     AlignmentAnnotation ann2 = addAnnotation("label2", "desc2", "calcId2",
203             1f);
204     addAnnotation("label2", "desc3", "calcId3", 1f);
205     AlignmentAnnotation ann4 = addAnnotation("label2", "desc3", "calcId2",
206             1f);
207     addAnnotation("label5", "desc3", null, 1f);
208     addAnnotation(null, "desc3", "calcId3", 1f);
209
210     List<AlignmentAnnotation> anns = seq.getAlignmentAnnotations("calcId2",
211             "label2", "desc3");
212     assertEquals(1, anns.size());
213     assertSame(ann4, anns.get(0));
214     /**
215      * null matching should fail
216      */
217     assertTrue(seq.getAlignmentAnnotations("calcId3", "label2", null)
218             .isEmpty());
219
220     assertTrue(seq.getAlignmentAnnotations("calcId2", "label3", null)
221             .isEmpty());
222     assertTrue(seq.getAlignmentAnnotations("calcId3", "label5", null)
223             .isEmpty());
224     assertTrue(
225             seq.getAlignmentAnnotations("calcId2", null, null).isEmpty());
226     assertTrue(seq.getAlignmentAnnotations(null, "label3", null).isEmpty());
227     assertTrue(seq.getAlignmentAnnotations(null, null, null).isEmpty());
228   }
229
230   /**
231    * Tests for addAlignmentAnnotation. Note this method has the side-effect of
232    * setting the sequenceRef on the annotation. Adding the same annotation twice
233    * should be ignored.
234    */
235   @Test(groups = { "Functional" })
236   public void testAddAlignmentAnnotation()
237   {
238     assertNull(seq.getAnnotation());
239     final AlignmentAnnotation annotation = new AlignmentAnnotation("a", "b",
240             2d);
241     assertNull(annotation.sequenceRef);
242     seq.addAlignmentAnnotation(annotation);
243     assertSame(seq, annotation.sequenceRef);
244     AlignmentAnnotation[] anns = seq.getAnnotation();
245     assertEquals(1, anns.length);
246     assertSame(annotation, anns[0]);
247
248     // re-adding does nothing
249     seq.addAlignmentAnnotation(annotation);
250     anns = seq.getAnnotation();
251     assertEquals(1, anns.length);
252     assertSame(annotation, anns[0]);
253
254     // an identical but different annotation can be added
255     final AlignmentAnnotation annotation2 = new AlignmentAnnotation("a",
256             "b", 2d);
257     seq.addAlignmentAnnotation(annotation2);
258     anns = seq.getAnnotation();
259     assertEquals(2, anns.length);
260     assertSame(annotation, anns[0]);
261     assertSame(annotation2, anns[1]);
262   }
263
264   @Test(groups = { "Functional" })
265   public void testGetStartGetEnd()
266   {
267     SequenceI sq = new Sequence("test", "ABCDEF");
268     assertEquals(1, sq.getStart());
269     assertEquals(6, sq.getEnd());
270
271     sq = new Sequence("test", "--AB-C-DEF--");
272     assertEquals(1, sq.getStart());
273     assertEquals(6, sq.getEnd());
274
275     sq = new Sequence("test", "----");
276     assertEquals(1, sq.getStart());
277     assertEquals(0, sq.getEnd()); // ??
278   }
279
280   /**
281    * Tests for the method that returns an alignment column position (base 1) for
282    * a given sequence position (base 1).
283    */
284   @Test(groups = { "Functional" })
285   public void testFindIndex()
286   {
287     /* 
288      * call sequenceChanged() after each test to invalidate any cursor,
289      * forcing the 1-arg findIndex to be executed
290      */
291     SequenceI sq = new Sequence("test", "ABCDEF");
292     assertEquals(0, sq.findIndex(0));
293     sq.sequenceChanged();
294     assertEquals(1, sq.findIndex(1));
295     sq.sequenceChanged();
296     assertEquals(5, sq.findIndex(5));
297     sq.sequenceChanged();
298     assertEquals(6, sq.findIndex(6));
299     sq.sequenceChanged();
300     assertEquals(6, sq.findIndex(9));
301
302     final String aligned = "-A--B-C-D-E-F--";
303     assertEquals(15, aligned.length());
304     sq = new Sequence("test/8-13", aligned);
305     assertEquals(2, sq.findIndex(8));
306     sq.sequenceChanged();
307     assertEquals(5, sq.findIndex(9));
308     sq.sequenceChanged();
309     assertEquals(7, sq.findIndex(10));
310
311     // before start returns 0
312     sq.sequenceChanged();
313     assertEquals(0, sq.findIndex(0));
314     sq.sequenceChanged();
315     assertEquals(0, sq.findIndex(-1));
316
317     // beyond end returns last residue column
318     sq.sequenceChanged();
319     assertEquals(13, sq.findIndex(99));
320
321     /*
322      * residue before sequence 'end' but beyond end of sequence returns 
323      * length of sequence (last column) (rightly or wrongly!)
324      */
325     sq = new Sequence("test/8-15", "A-B-C-"); // trailing gap case
326     assertEquals(6, sq.getLength());
327     sq.sequenceChanged();
328     assertEquals(sq.getLength(), sq.findIndex(14));
329     sq = new Sequence("test/8-99", "-A--B-C-D"); // trailing residue case
330     sq.sequenceChanged();
331     assertEquals(sq.getLength(), sq.findIndex(65));
332
333     /*
334      * residue after sequence 'start' but before first residue returns 
335      * zero (before first column) (rightly or wrongly!)
336      */
337     sq = new Sequence("test/8-15", "-A-B-C-"); // leading gap case
338     sq.sequenceChanged();
339     assertEquals(0, sq.findIndex(3));
340     sq = new Sequence("test/8-15", "A-B-C-"); // leading residue case
341     sq.sequenceChanged();
342     assertEquals(0, sq.findIndex(2));
343   }
344
345   @Test(groups = { "Functional" })
346   public void testFindPositions()
347   {
348     SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
349
350     /*
351      * invalid inputs
352      */
353     assertNull(sq.findPositions(6, 5));
354     assertNull(sq.findPositions(0, 5));
355     assertNull(sq.findPositions(-1, 5));
356
357     /*
358      * all gapped ranges
359      */
360     assertNull(sq.findPositions(1, 1)); // 1-based columns
361     assertNull(sq.findPositions(5, 5));
362     assertNull(sq.findPositions(5, 6));
363     assertNull(sq.findPositions(5, 7));
364
365     /*
366      * all ungapped ranges
367      */
368     assertEquals(new Range(8, 8), sq.findPositions(2, 2)); // A
369     assertEquals(new Range(8, 9), sq.findPositions(2, 3)); // AB
370     assertEquals(new Range(8, 10), sq.findPositions(2, 4)); // ABC
371     assertEquals(new Range(9, 10), sq.findPositions(3, 4)); // BC
372
373     /*
374      * gap to ungapped range
375      */
376     assertEquals(new Range(8, 10), sq.findPositions(1, 4)); // ABC
377     assertEquals(new Range(11, 12), sq.findPositions(6, 9)); // DE
378
379     /*
380      * ungapped to gapped range
381      */
382     assertEquals(new Range(10, 10), sq.findPositions(4, 5)); // C
383     assertEquals(new Range(9, 13), sq.findPositions(3, 11)); // BCDEF
384
385     /*
386      * ungapped to ungapped enclosing gaps
387      */
388     assertEquals(new Range(10, 11), sq.findPositions(4, 8)); // CD
389     assertEquals(new Range(8, 13), sq.findPositions(2, 11)); // ABCDEF
390
391     /*
392      * gapped to gapped enclosing ungapped
393      */
394     assertEquals(new Range(8, 10), sq.findPositions(1, 5)); // ABC
395     assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
396     assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
397     assertEquals(new Range(8, 13), sq.findPositions(1, 99));
398   }
399
400   /**
401    * Tests for the method that returns a dataset sequence position (start..) for
402    * an aligned column position (base 0).
403    */
404   @Test(groups = { "Functional" })
405   public void testFindPosition()
406   {
407     /* 
408      * call sequenceChanged() after each test to invalidate any cursor,
409      * forcing the 1-arg findPosition to be executed
410      */
411     SequenceI sq = new Sequence("test/8-13", "ABCDEF");
412     assertEquals(8, sq.findPosition(0));
413     // Sequence should now hold a cursor at [8, 0]
414     assertEquals("test:Pos8:Col1:startCol1:endCol0:tok1",
415             PA.getValue(sq, "cursor").toString());
416     SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
417     int token = (int) PA.getValue(sq, "changeCount");
418     assertEquals(new SequenceCursor(sq, 8, 1, token), cursor);
419
420     sq.sequenceChanged();
421
422     /*
423      * find F13 at column offset 5, cursor should update to [13, 6]
424      * endColumn is found and saved in cursor
425      */
426     assertEquals(13, sq.findPosition(5));
427     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
428     assertEquals(++token, (int) PA.getValue(sq, "changeCount"));
429     assertEquals(new SequenceCursor(sq, 13, 6, token), cursor);
430     assertEquals("test:Pos13:Col6:startCol1:endCol6:tok2",
431             PA.getValue(sq, "cursor").toString());
432
433     // assertEquals(-1, seq.findPosition(6)); // fails
434
435     sq = new Sequence("test/8-11", "AB-C-D--");
436     token = (int) PA.getValue(sq, "changeCount"); // 1 for setStart
437     assertEquals(8, sq.findPosition(0));
438     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
439     assertEquals(new SequenceCursor(sq, 8, 1, token), cursor);
440     assertEquals("test:Pos8:Col1:startCol1:endCol0:tok1",
441             PA.getValue(sq, "cursor").toString());
442
443     sq.sequenceChanged();
444     assertEquals(9, sq.findPosition(1));
445     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
446     assertEquals(new SequenceCursor(sq, 9, 2, ++token), cursor);
447     assertEquals("test:Pos9:Col2:startCol1:endCol0:tok2",
448             PA.getValue(sq, "cursor").toString());
449
450     sq.sequenceChanged();
451     // gap position 'finds' residue to the right (not the left as per javadoc)
452     // cursor is set to the last residue position found [B 2]
453     assertEquals(10, sq.findPosition(2));
454     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
455     assertEquals(new SequenceCursor(sq, 9, 2, ++token), cursor);
456     assertEquals("test:Pos9:Col2:startCol1:endCol0:tok3",
457             PA.getValue(sq, "cursor").toString());
458
459     sq.sequenceChanged();
460     assertEquals(10, sq.findPosition(3));
461     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
462     assertEquals(new SequenceCursor(sq, 10, 4, ++token), cursor);
463     assertEquals("test:Pos10:Col4:startCol1:endCol0:tok4",
464             PA.getValue(sq, "cursor").toString());
465
466     sq.sequenceChanged();
467     // column[4] is the gap after C - returns D11
468     // cursor is set to [C 4]
469     assertEquals(11, sq.findPosition(4));
470     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
471     assertEquals(new SequenceCursor(sq, 10, 4, ++token), cursor);
472     assertEquals("test:Pos10:Col4:startCol1:endCol0:tok5",
473             PA.getValue(sq, "cursor").toString());
474
475     sq.sequenceChanged();
476     assertEquals(11, sq.findPosition(5)); // D
477     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
478     assertEquals(new SequenceCursor(sq, 11, 6, ++token), cursor);
479     // lastCol has been found and saved in the cursor
480     assertEquals("test:Pos11:Col6:startCol1:endCol6:tok6",
481             PA.getValue(sq, "cursor").toString());
482
483     sq.sequenceChanged();
484     // returns 1 more than sequence length if off the end ?!?
485     assertEquals(12, sq.findPosition(6));
486
487     sq.sequenceChanged();
488     assertEquals(12, sq.findPosition(7));
489
490     /*
491      * first findPosition should also set firstResCol in cursor
492      */
493     sq = new Sequence("test/8-13", "--AB-C-DEF--");
494     assertEquals(8, sq.findPosition(0));
495     assertNull(PA.getValue(sq, "cursor"));
496     assertEquals(1, PA.getValue(sq, "changeCount"));
497
498     sq.sequenceChanged();
499     assertEquals(8, sq.findPosition(1));
500     assertNull(PA.getValue(sq, "cursor"));
501
502     sq.sequenceChanged();
503     assertEquals(8, sq.findPosition(2));
504     assertEquals("test:Pos8:Col3:startCol3:endCol0:tok3",
505             PA.getValue(sq, "cursor").toString());
506
507     sq.sequenceChanged();
508     assertEquals(9, sq.findPosition(3));
509     assertEquals("test:Pos9:Col4:startCol3:endCol0:tok4",
510             PA.getValue(sq, "cursor").toString());
511
512     sq.sequenceChanged();
513     // column[4] is a gap, returns next residue pos (C10)
514     // cursor is set to last residue found [B]
515     assertEquals(10, sq.findPosition(4));
516     assertEquals("test:Pos9:Col4:startCol3:endCol0:tok5",
517             PA.getValue(sq, "cursor").toString());
518
519     sq.sequenceChanged();
520     assertEquals(10, sq.findPosition(5));
521     assertEquals("test:Pos10:Col6:startCol3:endCol0:tok6",
522             PA.getValue(sq, "cursor").toString());
523
524     sq.sequenceChanged();
525     // column[6] is a gap, returns next residue pos (D11)
526     // cursor is set to last residue found [C]
527     assertEquals(11, sq.findPosition(6));
528     assertEquals("test:Pos10:Col6:startCol3:endCol0:tok7",
529             PA.getValue(sq, "cursor").toString());
530
531     sq.sequenceChanged();
532     assertEquals(11, sq.findPosition(7));
533     assertEquals("test:Pos11:Col8:startCol3:endCol0:tok8",
534             PA.getValue(sq, "cursor").toString());
535
536     sq.sequenceChanged();
537     assertEquals(12, sq.findPosition(8));
538     assertEquals("test:Pos12:Col9:startCol3:endCol0:tok9",
539             PA.getValue(sq, "cursor").toString());
540
541     /*
542      * when the last residue column is found, it is set in the cursor
543      */
544     sq.sequenceChanged();
545     assertEquals(13, sq.findPosition(9));
546     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok10",
547             PA.getValue(sq, "cursor").toString());
548
549     sq.sequenceChanged();
550     assertEquals(14, sq.findPosition(10));
551     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok11",
552             PA.getValue(sq, "cursor").toString());
553
554     /*
555      * findPosition for column beyond sequence length
556      * returns 1 more than last residue position
557      */
558     sq.sequenceChanged();
559     assertEquals(14, sq.findPosition(11));
560     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok12",
561             PA.getValue(sq, "cursor").toString());
562
563     sq.sequenceChanged();
564     assertEquals(14, sq.findPosition(99));
565     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok13",
566             PA.getValue(sq, "cursor").toString());
567
568     /*
569      * gapped sequence ending in non-gap
570      */
571     sq = new Sequence("test/8-13", "--AB-C-DEF");
572     assertEquals(13, sq.findPosition(9));
573     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok1",
574             PA.getValue(sq, "cursor").toString());
575     sq.sequenceChanged();
576     assertEquals(12, sq.findPosition(8)); // E12
577     // sequenceChanged() invalidates cursor.lastResidueColumn
578     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
579     assertEquals("test:Pos12:Col9:startCol3:endCol0:tok2",
580             cursor.toString());
581     // findPosition with cursor accepts base 1 column values
582     assertEquals(13, ((Sequence) sq).findPosition(10, cursor));
583     assertEquals(13, sq.findPosition(9)); // F13
584     // lastResidueColumn has now been found and saved in cursor
585     assertEquals("test:Pos13:Col10:startCol3:endCol10:tok2",
586             PA.getValue(sq, "cursor").toString());
587   }
588
589   @Test(groups = { "Functional" })
590   public void testDeleteChars()
591   {
592     /*
593      * internal delete
594      */
595     SequenceI sq = new Sequence("test", "ABCDEF");
596     assertNull(PA.getValue(sq, "datasetSequence"));
597     assertEquals(1, sq.getStart());
598     assertEquals(6, sq.getEnd());
599     sq.deleteChars(2, 3);
600     assertEquals("ABDEF", sq.getSequenceAsString());
601     assertEquals(1, sq.getStart());
602     assertEquals(5, sq.getEnd());
603     assertNull(PA.getValue(sq, "datasetSequence"));
604
605     /*
606      * delete at start
607      */
608     sq = new Sequence("test", "ABCDEF");
609     sq.deleteChars(0, 2);
610     assertEquals("CDEF", sq.getSequenceAsString());
611     assertEquals(3, sq.getStart());
612     assertEquals(6, sq.getEnd());
613     assertNull(PA.getValue(sq, "datasetSequence"));
614
615     sq = new Sequence("test", "ABCDE");
616     sq.deleteChars(0, 3);
617     assertEquals("DE", sq.getSequenceAsString());
618     assertEquals(4, sq.getStart());
619     assertEquals(5, sq.getEnd());
620     assertNull(PA.getValue(sq, "datasetSequence"));
621
622     /*
623      * delete at end
624      */
625     sq = new Sequence("test", "ABCDEF");
626     sq.deleteChars(4, 6);
627     assertEquals("ABCD", sq.getSequenceAsString());
628     assertEquals(1, sq.getStart());
629     assertEquals(4, sq.getEnd());
630     assertNull(PA.getValue(sq, "datasetSequence"));
631
632     /*
633      * delete more positions than there are
634      */
635     sq = new Sequence("test/8-11", "ABCD");
636     sq.deleteChars(0, 99);
637     assertEquals("", sq.getSequenceAsString());
638     assertEquals(12, sq.getStart()); // = findPosition(99) ?!?
639     assertEquals(11, sq.getEnd());
640
641     sq = new Sequence("test/8-11", "----");
642     sq.deleteChars(0, 99); // ArrayIndexOutOfBoundsException <= 2.10.2
643     assertEquals("", sq.getSequenceAsString());
644     assertEquals(8, sq.getStart());
645     assertEquals(11, sq.getEnd());
646   }
647
648   @Test(groups = { "Functional" })
649   public void testDeleteChars_withDbRefsAndFeatures()
650   {
651     /*
652      * internal delete - new dataset sequence created
653      * gets a copy of any dbrefs
654      */
655     SequenceI sq = new Sequence("test", "ABCDEF");
656     sq.createDatasetSequence();
657     DBRefEntry dbr1 = new DBRefEntry("Uniprot", "0", "a123");
658     sq.addDBRef(dbr1);
659     Object ds = PA.getValue(sq, "datasetSequence");
660     assertNotNull(ds);
661     assertEquals(1, sq.getStart());
662     assertEquals(6, sq.getEnd());
663     sq.deleteChars(2, 3);
664     assertEquals("ABDEF", sq.getSequenceAsString());
665     assertEquals(1, sq.getStart());
666     assertEquals(5, sq.getEnd());
667     Object newDs = PA.getValue(sq, "datasetSequence");
668     assertNotNull(newDs);
669     assertNotSame(ds, newDs);
670     assertNotNull(sq.getDBRefs());
671     assertEquals(1, sq.getDBRefs().size());
672     assertNotSame(dbr1, sq.getDBRefs().get(0));
673     assertEquals(dbr1, sq.getDBRefs().get(0));
674
675     /*
676      * internal delete with sequence features
677      * (failure case for JAL-2541)
678      */
679     sq = new Sequence("test", "ABCDEF");
680     sq.createDatasetSequence();
681     SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 2, 4, 2f,
682             "CathGroup");
683     sq.addSequenceFeature(sf1);
684     ds = PA.getValue(sq, "datasetSequence");
685     assertNotNull(ds);
686     assertEquals(1, sq.getStart());
687     assertEquals(6, sq.getEnd());
688     sq.deleteChars(2, 4);
689     assertEquals("ABEF", sq.getSequenceAsString());
690     assertEquals(1, sq.getStart());
691     assertEquals(4, sq.getEnd());
692     newDs = PA.getValue(sq, "datasetSequence");
693     assertNotNull(newDs);
694     assertNotSame(ds, newDs);
695     List<SequenceFeature> sfs = sq.getSequenceFeatures();
696     assertEquals(1, sfs.size());
697     assertNotSame(sf1, sfs.get(0));
698     assertEquals(sf1, sfs.get(0));
699
700     /*
701      * delete at start - no new dataset sequence created
702      * any sequence features remain as before
703      */
704     sq = new Sequence("test", "ABCDEF");
705     sq.createDatasetSequence();
706     ds = PA.getValue(sq, "datasetSequence");
707     sf1 = new SequenceFeature("Cath", "desc", 2, 4, 2f, "CathGroup");
708     sq.addSequenceFeature(sf1);
709     sq.deleteChars(0, 2);
710     assertEquals("CDEF", sq.getSequenceAsString());
711     assertEquals(3, sq.getStart());
712     assertEquals(6, sq.getEnd());
713     assertSame(ds, PA.getValue(sq, "datasetSequence"));
714     sfs = sq.getSequenceFeatures();
715     assertNotNull(sfs);
716     assertEquals(1, sfs.size());
717     assertSame(sf1, sfs.get(0));
718
719     /*
720      * delete at end - no new dataset sequence created
721      * any dbrefs remain as before
722      */
723     sq = new Sequence("test", "ABCDEF");
724     sq.createDatasetSequence();
725     ds = PA.getValue(sq, "datasetSequence");
726     dbr1 = new DBRefEntry("Uniprot", "0", "a123");
727     sq.addDBRef(dbr1);
728     sq.deleteChars(4, 6);
729     assertEquals("ABCD", sq.getSequenceAsString());
730     assertEquals(1, sq.getStart());
731     assertEquals(4, sq.getEnd());
732     assertSame(ds, PA.getValue(sq, "datasetSequence"));
733     assertNotNull(sq.getDBRefs());
734     assertEquals(1, sq.getDBRefs().size());
735     assertSame(dbr1, sq.getDBRefs().get(0));
736   }
737
738   @Test(groups = { "Functional" })
739   public void testInsertCharAt()
740   {
741     // non-static methods:
742     SequenceI sq = new Sequence("test", "ABCDEF");
743     sq.insertCharAt(0, 'z');
744     assertEquals("zABCDEF", sq.getSequenceAsString());
745     sq.insertCharAt(2, 2, 'x');
746     assertEquals("zAxxBCDEF", sq.getSequenceAsString());
747
748     // for static method see StringUtilsTest
749   }
750
751   /**
752    * Test the method that returns an array of aligned sequence positions where
753    * the array index is the data sequence position (both base 0).
754    */
755   @Test(groups = { "Functional" })
756   public void testGapMap()
757   {
758     SequenceI sq = new Sequence("test", "-A--B-CD-E--F-");
759     sq.createDatasetSequence();
760     assertEquals("[1, 4, 6, 7, 9, 12]", Arrays.toString(sq.gapMap()));
761   }
762
763   /**
764    * Test the method that gets sequence features, either from the sequence or
765    * its dataset.
766    */
767   @Test(groups = { "Functional" })
768   public void testGetSequenceFeatures()
769   {
770     SequenceI sq = new Sequence("test", "GATCAT");
771     sq.createDatasetSequence();
772
773     assertTrue(sq.getSequenceFeatures().isEmpty());
774
775     /*
776      * SequenceFeature on sequence
777      */
778     SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 4, 2f,
779             null);
780     sq.addSequenceFeature(sf);
781     List<SequenceFeature> sfs = sq.getSequenceFeatures();
782     assertEquals(1, sfs.size());
783     assertSame(sf, sfs.get(0));
784
785     /*
786      * SequenceFeature on sequence and dataset sequence; returns that on
787      * sequence
788      * 
789      * Note JAL-2046: spurious: we have no use case for this at the moment.
790      * This test also buggy - as sf2.equals(sf), no new feature is added
791      */
792     SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 2, 4, 2f,
793             null);
794     sq.getDatasetSequence().addSequenceFeature(sf2);
795     sfs = sq.getSequenceFeatures();
796     assertEquals(1, sfs.size());
797     assertSame(sf, sfs.get(0));
798
799     /*
800      * SequenceFeature on dataset sequence only
801      * Note JAL-2046: spurious: we have no use case for setting a non-dataset sequence's feature array to null at the moment.
802      */
803     sq.setSequenceFeatures(null);
804     assertTrue(sq.getDatasetSequence().getSequenceFeatures().isEmpty());
805
806     /*
807      * Corrupt case - no SequenceFeature, dataset's dataset is the original
808      * sequence. Test shows no infinite loop results.
809      */
810     sq.getDatasetSequence().setSequenceFeatures(null);
811     /**
812      * is there a usecase for this ? setDatasetSequence should throw an error if
813      * this actually occurs.
814      */
815     try
816     {
817       sq.getDatasetSequence().setDatasetSequence(sq); // loop!
818       Assert.fail(
819               "Expected Error to be raised when calling setDatasetSequence with self reference");
820     } catch (IllegalArgumentException e)
821     {
822       // TODO Jalview error/exception class for raising implementation errors
823       assertTrue(e.getMessage().toLowerCase(Locale.ROOT)
824               .contains("implementation error"));
825     }
826     assertTrue(sq.getSequenceFeatures().isEmpty());
827   }
828
829   /**
830    * Test the method that returns an array, indexed by sequence position, whose
831    * entries are the residue positions at the sequence position (or to the right
832    * if a gap)
833    */
834   @Test(groups = { "Functional" })
835   public void testFindPositionMap()
836   {
837     /*
838      * Note: Javadoc for findPosition says it returns the residue position to
839      * the left of a gapped position; in fact it returns the position to the
840      * right. Also it returns a non-existent residue position for a gap beyond
841      * the sequence.
842      */
843     Sequence sq = new Sequence("TestSeq", "AB.C-D E.");
844     int[] map = sq.findPositionMap();
845     assertEquals(Arrays.toString(new int[] { 1, 2, 3, 3, 4, 4, 5, 5, 6 }),
846             Arrays.toString(map));
847   }
848
849   /**
850    * Test for getSubsequence
851    */
852   @Test(groups = { "Functional" })
853   public void testGetSubsequence()
854   {
855     SequenceI sq = new Sequence("TestSeq", "ABCDEFG");
856     sq.createDatasetSequence();
857
858     // positions are base 0, end position is exclusive
859     SequenceI subseq = sq.getSubSequence(2, 4);
860
861     assertEquals("CD", subseq.getSequenceAsString());
862     // start/end are base 1 positions
863     assertEquals(3, subseq.getStart());
864     assertEquals(4, subseq.getEnd());
865     // subsequence shares the full dataset sequence
866     assertSame(sq.getDatasetSequence(), subseq.getDatasetSequence());
867   }
868
869   /**
870    * test createDatasetSequence behaves to doc
871    */
872   @Test(groups = { "Functional" })
873   public void testCreateDatasetSequence()
874   {
875     SequenceI sq = new Sequence("my", "ASDASD");
876     sq.addSequenceFeature(
877             new SequenceFeature("type", "desc", 1, 10, 1f, "group"));
878     sq.addDBRef(new DBRefEntry("source", "version", "accession"));
879     assertNull(sq.getDatasetSequence());
880     assertNotNull(PA.getValue(sq, "sequenceFeatureStore"));
881     assertNotNull(PA.getValue(sq, "dbrefs"));
882
883     SequenceI rds = sq.createDatasetSequence();
884     assertNotNull(rds);
885     assertNull(rds.getDatasetSequence());
886     assertSame(sq.getDatasetSequence(), rds);
887
888     // sequence features and dbrefs transferred to dataset sequence
889     assertNull(PA.getValue(sq, "sequenceFeatureStore"));
890     assertNull(PA.getValue(sq, "dbrefs"));
891     assertNotNull(PA.getValue(rds, "sequenceFeatureStore"));
892     assertNotNull(PA.getValue(rds, "dbrefs"));
893   }
894
895   /**
896    * Test for deriveSequence applied to a sequence with a dataset
897    */
898   @Test(groups = { "Functional" })
899   public void testDeriveSequence_existingDataset()
900   {
901     Sequence sq = new Sequence("Seq1", "CD");
902     sq.setDatasetSequence(new Sequence("Seq1", "ABCDEF"));
903     sq.getDatasetSequence().addSequenceFeature(
904             new SequenceFeature("", "", 1, 2, 0f, null));
905     sq.setStart(3);
906     sq.setEnd(4);
907
908     sq.setDescription("Test sequence description..");
909     sq.setVamsasId("TestVamsasId");
910     sq.addDBRef(new DBRefEntry("PDB", "version0", "1TST"));
911
912     sq.addDBRef(new DBRefEntry("PDB", "version1", "1PDB"));
913     sq.addDBRef(new DBRefEntry("PDB", "version2", "2PDB"));
914     sq.addDBRef(new DBRefEntry("PDB", "version3", "3PDB"));
915     sq.addDBRef(new DBRefEntry("PDB", "version4", "4PDB"));
916
917     sq.addPDBId(new PDBEntry("1PDB", "A", Type.PDB, "filePath/test1"));
918     sq.addPDBId(new PDBEntry("1PDB", "B", Type.PDB, "filePath/test1"));
919     sq.addPDBId(new PDBEntry("2PDB", "A", Type.MMCIF, "filePath/test2"));
920     sq.addPDBId(new PDBEntry("2PDB", "B", Type.MMCIF, "filePath/test2"));
921
922     // these are the same as ones already added
923     DBRefEntry pdb1pdb = new DBRefEntry("PDB", "version1", "1PDB");
924     DBRefEntry pdb2pdb = new DBRefEntry("PDB", "version2", "2PDB");
925
926     List<DBRefEntry> primRefs = Arrays
927             .asList(new DBRefEntry[]
928             { pdb1pdb, pdb2pdb });
929
930     sq.getDatasetSequence().addDBRef(pdb1pdb); // should do nothing
931     sq.getDatasetSequence().addDBRef(pdb2pdb); // should do nothing
932     sq.getDatasetSequence()
933             .addDBRef(new DBRefEntry("PDB", "version3", "3PDB")); // should do
934                                                                   // nothing
935     sq.getDatasetSequence()
936             .addDBRef(new DBRefEntry("PDB", "version4", "4PDB")); // should do
937                                                                   // nothing
938
939     PDBEntry pdbe1a = new PDBEntry("1PDB", "A", Type.PDB, "filePath/test1");
940     PDBEntry pdbe1b = new PDBEntry("1PDB", "B", Type.PDB, "filePath/test1");
941     PDBEntry pdbe2a = new PDBEntry("2PDB", "A", Type.MMCIF,
942             "filePath/test2");
943     PDBEntry pdbe2b = new PDBEntry("2PDB", "B", Type.MMCIF,
944             "filePath/test2");
945     sq.getDatasetSequence().addPDBId(pdbe1a);
946     sq.getDatasetSequence().addPDBId(pdbe1b);
947     sq.getDatasetSequence().addPDBId(pdbe2a);
948     sq.getDatasetSequence().addPDBId(pdbe2b);
949
950     /*
951      * test we added pdb entries to the dataset sequence
952      */
953     Assert.assertEquals(sq.getDatasetSequence().getAllPDBEntries(),
954             Arrays.asList(new PDBEntry[]
955             { pdbe1a, pdbe1b, pdbe2a, pdbe2b }),
956             "PDB Entries were not found on dataset sequence.");
957
958     /*
959      * we should recover a pdb entry that is on the dataset sequence via PDBEntry
960      */
961     Assert.assertEquals(pdbe1a, sq.getDatasetSequence().getPDBEntry("1PDB"),
962             "PDB Entry '1PDB' not found on dataset sequence via getPDBEntry.");
963     ArrayList<Annotation> annotsList = new ArrayList<>();
964     System.out.println(">>>>>> " + sq.getSequenceAsString().length());
965     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
966     annotsList.add(new Annotation("A", "A", 'X', 0.1f));
967     Annotation[] annots = annotsList.toArray(new Annotation[0]);
968     sq.addAlignmentAnnotation(new AlignmentAnnotation("Test annot",
969             "Test annot description", annots));
970     sq.getDatasetSequence().addAlignmentAnnotation(new AlignmentAnnotation(
971             "Test annot", "Test annot description", annots));
972     Assert.assertEquals(sq.getDescription(), "Test sequence description..");
973     Assert.assertEquals(sq.getDBRefs().size(), 5); // DBRefs are on dataset
974                                                    // sequence
975     Assert.assertEquals(sq.getAllPDBEntries().size(), 4);
976     Assert.assertNotNull(sq.getAnnotation());
977     Assert.assertEquals(sq.getAnnotation()[0].annotations.length, 2);
978     Assert.assertEquals(sq.getDatasetSequence().getDBRefs().size(), 5); // same
979                                                                         // as
980                                                                         // sq.getDBRefs()
981     Assert.assertEquals(sq.getDatasetSequence().getAllPDBEntries().size(),
982             4);
983     Assert.assertNotNull(sq.getDatasetSequence().getAnnotation());
984
985     Sequence derived = (Sequence) sq.deriveSequence();
986
987     Assert.assertEquals(derived.getDescription(),
988             "Test sequence description..");
989     Assert.assertEquals(derived.getDBRefs().size(), 5); // come from dataset
990     Assert.assertEquals(derived.getAllPDBEntries().size(), 4);
991     Assert.assertNotNull(derived.getAnnotation());
992     Assert.assertEquals(derived.getAnnotation()[0].annotations.length, 2);
993     Assert.assertEquals(derived.getDatasetSequence().getDBRefs().size(), 5);
994     Assert.assertEquals(
995             derived.getDatasetSequence().getAllPDBEntries().size(), 4);
996     Assert.assertNotNull(derived.getDatasetSequence().getAnnotation());
997
998     assertEquals("CD", derived.getSequenceAsString());
999     assertSame(sq.getDatasetSequence(), derived.getDatasetSequence());
1000
1001     // derived sequence should access dataset sequence features
1002     assertNotNull(sq.getSequenceFeatures());
1003     assertEquals(sq.getSequenceFeatures(), derived.getSequenceFeatures());
1004
1005     /*
1006      *  verify we have primary db refs *just* for PDB IDs with associated
1007      *  PDBEntry objects
1008      */
1009
1010     assertEquals(primRefs, sq.getPrimaryDBRefs());
1011     assertEquals(primRefs, sq.getDatasetSequence().getPrimaryDBRefs());
1012
1013     assertEquals(sq.getPrimaryDBRefs(), derived.getPrimaryDBRefs());
1014
1015   }
1016
1017   /**
1018    * Test for deriveSequence applied to an ungapped sequence with no dataset
1019    */
1020   @Test(groups = { "Functional" })
1021   public void testDeriveSequence_noDatasetUngapped()
1022   {
1023     SequenceI sq = new Sequence("Seq1", "ABCDEF");
1024     assertEquals(1, sq.getStart());
1025     assertEquals(6, sq.getEnd());
1026     SequenceI derived = sq.deriveSequence();
1027     assertEquals("ABCDEF", derived.getSequenceAsString());
1028     assertEquals("ABCDEF",
1029             derived.getDatasetSequence().getSequenceAsString());
1030   }
1031
1032   /**
1033    * Test for deriveSequence applied to a gapped sequence with no dataset
1034    */
1035   @Test(groups = { "Functional" })
1036   public void testDeriveSequence_noDatasetGapped()
1037   {
1038     SequenceI sq = new Sequence("Seq1", "AB-C.D EF");
1039     assertEquals(1, sq.getStart());
1040     assertEquals(6, sq.getEnd());
1041     assertNull(sq.getDatasetSequence());
1042     SequenceI derived = sq.deriveSequence();
1043     assertEquals("AB-C.D EF", derived.getSequenceAsString());
1044     assertEquals("ABCDEF",
1045             derived.getDatasetSequence().getSequenceAsString());
1046   }
1047
1048   @Test(groups = { "Functional" })
1049   public void testCopyConstructor_noDataset()
1050   {
1051     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
1052     seq1.setDescription("description");
1053     seq1.addAlignmentAnnotation(
1054             new AlignmentAnnotation("label", "desc", 1.3d));
1055     seq1.addSequenceFeature(
1056             new SequenceFeature("type", "desc", 22, 33, 12.4f, "group"));
1057     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
1058     seq1.addDBRef(new DBRefEntry("EMBL", "1.2", "AZ12345"));
1059
1060     SequenceI copy = new Sequence(seq1);
1061
1062     assertNull(copy.getDatasetSequence());
1063
1064     verifyCopiedSequence(seq1, copy);
1065
1066     // copy has a copy of the DBRefEntry
1067     // this is murky - DBrefs are only copied for dataset sequences
1068     // where the test for 'dataset sequence' is 'dataset is null'
1069     // but that doesn't distinguish it from an aligned sequence
1070     // which has not yet generated a dataset sequence
1071     // NB getDBRef looks inside dataset sequence if not null
1072     List<DBRefEntry> dbrefs = copy.getDBRefs();
1073     assertEquals(1, dbrefs.size());
1074     assertFalse(dbrefs.get(0) == seq1.getDBRefs().get(0));
1075     assertTrue(dbrefs.get(0).equals(seq1.getDBRefs().get(0)));
1076   }
1077
1078   @Test(groups = { "Functional" })
1079   public void testCopyConstructor_withDataset()
1080   {
1081     SequenceI seq1 = new Sequence("Seq1", "AB-C.D EF");
1082     seq1.createDatasetSequence();
1083     seq1.setDescription("description");
1084     seq1.addAlignmentAnnotation(
1085             new AlignmentAnnotation("label", "desc", 1.3d));
1086     // JAL-2046 - what is the contract for using a derived sequence's
1087     // addSequenceFeature ?
1088     seq1.addSequenceFeature(
1089             new SequenceFeature("type", "desc", 22, 33, 12.4f, "group"));
1090     seq1.addPDBId(new PDBEntry("1A70", "B", Type.PDB, "File"));
1091     // here we add DBRef to the dataset sequence:
1092     seq1.getDatasetSequence()
1093             .addDBRef(new DBRefEntry("EMBL", "1.2", "AZ12345"));
1094
1095     SequenceI copy = new Sequence(seq1);
1096
1097     assertNotNull(copy.getDatasetSequence());
1098     assertSame(copy.getDatasetSequence(), seq1.getDatasetSequence());
1099
1100     verifyCopiedSequence(seq1, copy);
1101
1102     // getDBRef looks inside dataset sequence and this is shared,
1103     // so holds the same dbref objects
1104     List<DBRefEntry> dbrefs = copy.getDBRefs();
1105     assertEquals(1, dbrefs.size());
1106     assertSame(dbrefs.get(0), seq1.getDBRefs().get(0));
1107   }
1108
1109   /**
1110    * Helper to make assertions about a copied sequence
1111    * 
1112    * @param seq1
1113    * @param copy
1114    */
1115   protected void verifyCopiedSequence(SequenceI seq1, SequenceI copy)
1116   {
1117     // verify basic properties:
1118     assertEquals(copy.getName(), seq1.getName());
1119     assertEquals(copy.getDescription(), seq1.getDescription());
1120     assertEquals(copy.getStart(), seq1.getStart());
1121     assertEquals(copy.getEnd(), seq1.getEnd());
1122     assertEquals(copy.getSequenceAsString(), seq1.getSequenceAsString());
1123
1124     // copy has a copy of the annotation:
1125     AlignmentAnnotation[] anns = copy.getAnnotation();
1126     assertEquals(1, anns.length);
1127     assertFalse(anns[0] == seq1.getAnnotation()[0]);
1128     assertEquals(anns[0].label, seq1.getAnnotation()[0].label);
1129     assertEquals(anns[0].description, seq1.getAnnotation()[0].description);
1130     assertEquals(anns[0].score, seq1.getAnnotation()[0].score);
1131
1132     // copy has a copy of the sequence feature:
1133     List<SequenceFeature> sfs = copy.getSequenceFeatures();
1134     assertEquals(1, sfs.size());
1135     if (seq1.getDatasetSequence() != null
1136             && copy.getDatasetSequence() == seq1.getDatasetSequence())
1137     {
1138       assertSame(sfs.get(0), seq1.getSequenceFeatures().get(0));
1139     }
1140     else
1141     {
1142       assertNotSame(sfs.get(0), seq1.getSequenceFeatures().get(0));
1143     }
1144     assertEquals(sfs.get(0), seq1.getSequenceFeatures().get(0));
1145
1146     // copy has a copy of the PDB entry
1147     Vector<PDBEntry> pdbs = copy.getAllPDBEntries();
1148     assertEquals(1, pdbs.size());
1149     assertFalse(pdbs.get(0) == seq1.getAllPDBEntries().get(0));
1150     assertTrue(pdbs.get(0).equals(seq1.getAllPDBEntries().get(0)));
1151   }
1152
1153   @Test(groups = "Functional")
1154   public void testGetCharAt()
1155   {
1156     SequenceI sq = new Sequence("", "abcde");
1157     assertEquals('a', sq.getCharAt(0));
1158     assertEquals('e', sq.getCharAt(4));
1159     assertEquals(' ', sq.getCharAt(5));
1160     assertEquals(' ', sq.getCharAt(-1));
1161   }
1162
1163   @Test(groups = { "Functional" })
1164   public void testAddSequenceFeatures()
1165   {
1166     SequenceI sq = new Sequence("", "abcde");
1167     // type may not be null
1168     assertFalse(sq.addSequenceFeature(
1169             new SequenceFeature(null, "desc", 4, 8, 0f, null)));
1170     assertTrue(sq.addSequenceFeature(
1171             new SequenceFeature("Cath", "desc", 4, 8, 0f, null)));
1172     // can't add a duplicate feature
1173     assertFalse(sq.addSequenceFeature(
1174             new SequenceFeature("Cath", "desc", 4, 8, 0f, null)));
1175     // can add a different feature
1176     assertTrue(sq.addSequenceFeature(
1177             new SequenceFeature("Scop", "desc", 4, 8, 0f, null))); // different
1178                                                                    // type
1179     assertTrue(sq.addSequenceFeature(
1180             new SequenceFeature("Cath", "description", 4, 8, 0f, null)));// different
1181                                                                          // description
1182     assertTrue(sq.addSequenceFeature(
1183             new SequenceFeature("Cath", "desc", 3, 8, 0f, null))); // different
1184                                                                    // start
1185                                                                    // position
1186     assertTrue(sq.addSequenceFeature(
1187             new SequenceFeature("Cath", "desc", 4, 9, 0f, null))); // different
1188                                                                    // end
1189                                                                    // position
1190     assertTrue(sq.addSequenceFeature(
1191             new SequenceFeature("Cath", "desc", 4, 8, 1f, null))); // different
1192                                                                    // score
1193     assertTrue(sq.addSequenceFeature(
1194             new SequenceFeature("Cath", "desc", 4, 8, Float.NaN, null))); // score
1195                                                                           // NaN
1196     assertTrue(sq.addSequenceFeature(
1197             new SequenceFeature("Cath", "desc", 4, 8, 0f, "Metal"))); // different
1198                                                                       // group
1199     assertEquals(8, sq.getFeatures().getAllFeatures().size());
1200   }
1201
1202   /**
1203    * Tests for adding (or updating) dbrefs
1204    * 
1205    * @see DBRefEntry#updateFrom(DBRefEntry)
1206    */
1207   @Test(groups = { "Functional" })
1208   public void testAddDBRef()
1209   {
1210     SequenceI sq = new Sequence("", "abcde");
1211     assertNull(sq.getDBRefs());
1212     DBRefEntry dbref = new DBRefEntry("Uniprot", "1", "P00340");
1213     sq.addDBRef(dbref);
1214     assertEquals(1, sq.getDBRefs().size());
1215     assertSame(dbref, sq.getDBRefs().get(0));
1216
1217     /*
1218      * change of version - new entry
1219      */
1220     DBRefEntry dbref2 = new DBRefEntry("Uniprot", "2", "P00340");
1221     sq.addDBRef(dbref2);
1222     assertEquals(2, sq.getDBRefs().size());
1223     assertSame(dbref, sq.getDBRefs().get(0));
1224     assertSame(dbref2, sq.getDBRefs().get(1));
1225
1226     /*
1227      * matches existing entry - not added
1228      */
1229     sq.addDBRef(new DBRefEntry("UNIPROT", "1", "p00340"));
1230     assertEquals(2, sq.getDBRefs().size());
1231
1232     /*
1233      * different source = new entry
1234      */
1235     DBRefEntry dbref3 = new DBRefEntry("UniRef", "1", "p00340");
1236     sq.addDBRef(dbref3);
1237     assertEquals(3, sq.getDBRefs().size());
1238     assertSame(dbref3, sq.getDBRefs().get(2));
1239
1240     /*
1241      * different ref = new entry
1242      */
1243     DBRefEntry dbref4 = new DBRefEntry("UniRef", "1", "p00341");
1244     sq.addDBRef(dbref4);
1245     assertEquals(4, sq.getDBRefs().size());
1246     assertSame(dbref4, sq.getDBRefs().get(3));
1247
1248     /*
1249      * matching ref with a mapping - map updated
1250      */
1251     DBRefEntry dbref5 = new DBRefEntry("UniRef", "1", "p00341");
1252     Mapping map = new Mapping(
1253             new MapList(new int[]
1254             { 1, 3 }, new int[] { 1, 1 }, 3, 1));
1255     dbref5.setMap(map);
1256     sq.addDBRef(dbref5);
1257     assertEquals(4, sq.getDBRefs().size());
1258     assertSame(dbref4, sq.getDBRefs().get(3));
1259     assertSame(map, dbref4.getMap());
1260
1261     /*
1262      * 'real' version replaces "0" version
1263      */
1264     dbref2.setVersion("0");
1265     DBRefEntry dbref6 = new DBRefEntry(dbref2.getSource(), "3",
1266             dbref2.getAccessionId());
1267     sq.addDBRef(dbref6);
1268     assertEquals(4, sq.getDBRefs().size());
1269     assertSame(dbref2, sq.getDBRefs().get(1));
1270     assertEquals("3", dbref2.getVersion());
1271
1272     /*
1273      * 'real' version replaces "source:0" version
1274      */
1275     dbref3.setVersion("Uniprot:0");
1276     DBRefEntry dbref7 = new DBRefEntry(dbref3.getSource(), "3",
1277             dbref3.getAccessionId());
1278     sq.addDBRef(dbref7);
1279     assertEquals(4, sq.getDBRefs().size());
1280     assertSame(dbref3, sq.getDBRefs().get(2));
1281     assertEquals("3", dbref2.getVersion());
1282   }
1283
1284   @Test(groups = { "Functional" })
1285   public void testGetPrimaryDBRefs_peptide()
1286   {
1287     SequenceI sq = new Sequence("aseq", "ASDFKYLMQPRST", 10, 22);
1288
1289     // no dbrefs
1290     List<DBRefEntry> primaryDBRefs = sq.getPrimaryDBRefs();
1291     assertTrue(primaryDBRefs.isEmpty());
1292
1293     // empty dbrefs
1294     sq.setDBRefs(null);
1295     primaryDBRefs = sq.getPrimaryDBRefs();
1296     assertTrue(primaryDBRefs.isEmpty());
1297
1298     // primary - uniprot
1299     DBRefEntry upentry1 = new DBRefEntry("UNIPROT", "0", "Q04760");
1300     sq.addDBRef(upentry1);
1301
1302     // primary - uniprot with congruent map
1303     DBRefEntry upentry2 = new DBRefEntry("UNIPROT", "0", "Q04762");
1304     upentry2.setMap(
1305             new Mapping(null, new MapList(new int[]
1306             { 10, 22 }, new int[] { 10, 22 }, 1, 1)));
1307     sq.addDBRef(upentry2);
1308
1309     // primary - uniprot with map of enclosing sequence
1310     DBRefEntry upentry3 = new DBRefEntry("UNIPROT", "0", "Q04763");
1311     upentry3.setMap(
1312             new Mapping(null, new MapList(new int[]
1313             { 8, 24 }, new int[] { 8, 24 }, 1, 1)));
1314     sq.addDBRef(upentry3);
1315
1316     // not primary - uniprot with map of sub-sequence (5')
1317     DBRefEntry upentry4 = new DBRefEntry("UNIPROT", "0", "Q04764");
1318     upentry4.setMap(
1319             new Mapping(null, new MapList(new int[]
1320             { 10, 18 }, new int[] { 10, 18 }, 1, 1)));
1321     sq.addDBRef(upentry4);
1322
1323     // not primary - uniprot with map that overlaps 3'
1324     DBRefEntry upentry5 = new DBRefEntry("UNIPROT", "0", "Q04765");
1325     upentry5.setMap(
1326             new Mapping(null, new MapList(new int[]
1327             { 12, 22 }, new int[] { 12, 22 }, 1, 1)));
1328     sq.addDBRef(upentry5);
1329
1330     // not primary - uniprot with map to different coordinates frame
1331     DBRefEntry upentry6 = new DBRefEntry("UNIPROT", "0", "Q04766");
1332     upentry6.setMap(
1333             new Mapping(null, new MapList(new int[]
1334             { 12, 18 }, new int[] { 112, 118 }, 1, 1)));
1335     sq.addDBRef(upentry6);
1336
1337     // not primary - dbref to 'non-core' database
1338     DBRefEntry upentry7 = new DBRefEntry("Pfam", "0", "PF00903");
1339     sq.addDBRef(upentry7);
1340
1341     // primary - type is PDB
1342     DBRefEntry pdbentry = new DBRefEntry("PDB", "0", "1qip");
1343     sq.addDBRef(pdbentry);
1344
1345     // not primary - PDBEntry has no file
1346     sq.addDBRef(new DBRefEntry("PDB", "0", "1AAA"));
1347
1348     // not primary - no PDBEntry
1349     sq.addDBRef(new DBRefEntry("PDB", "0", "1DDD"));
1350
1351     // add corroborating PDB entry for primary DBref -
1352     // needs to have a file as well as matching ID
1353     // note PDB ID is not treated as case sensitive
1354     sq.addPDBId(new PDBEntry("1QIP", null, Type.PDB,
1355             new File("/blah").toString()));
1356
1357     // not valid DBRef - no file..
1358     sq.addPDBId(new PDBEntry("1AAA", null, null, null));
1359
1360     primaryDBRefs = sq.getPrimaryDBRefs();
1361     assertEquals(4, primaryDBRefs.size());
1362     assertTrue("Couldn't find simple primary reference (UNIPROT)",
1363             primaryDBRefs.contains(upentry1));
1364     assertTrue("Couldn't find mapped primary reference (UNIPROT)",
1365             primaryDBRefs.contains(upentry2));
1366     assertTrue("Couldn't find mapped context reference (UNIPROT)",
1367             primaryDBRefs.contains(upentry3));
1368     assertTrue("Couldn't find expected PDB primary reference",
1369             primaryDBRefs.contains(pdbentry));
1370   }
1371
1372   @Test(groups = { "Functional" })
1373   public void testGetPrimaryDBRefs_nucleotide()
1374   {
1375     SequenceI sq = new Sequence("aseq", "TGATCACTCGACTAGCATCAGCATA", 10,
1376             34);
1377
1378     // primary - Ensembl
1379     DBRefEntry dbr1 = new DBRefEntry("ENSEMBL", "0", "ENSG1234");
1380     sq.addDBRef(dbr1);
1381
1382     // not primary - Ensembl 'transcript' mapping of sub-sequence
1383     DBRefEntry dbr2 = new DBRefEntry("ENSEMBL", "0", "ENST1234");
1384     dbr2.setMap(
1385             new Mapping(null, new MapList(new int[]
1386             { 15, 25 }, new int[] { 1, 11 }, 1, 1)));
1387     sq.addDBRef(dbr2);
1388
1389     // primary - EMBL with congruent map
1390     DBRefEntry dbr3 = new DBRefEntry("EMBL", "0", "J1234");
1391     dbr3.setMap(
1392             new Mapping(null, new MapList(new int[]
1393             { 10, 34 }, new int[] { 10, 34 }, 1, 1)));
1394     sq.addDBRef(dbr3);
1395
1396     // not primary - to non-core database
1397     DBRefEntry dbr4 = new DBRefEntry("CCDS", "0", "J1234");
1398     sq.addDBRef(dbr4);
1399
1400     // not primary - to protein
1401     DBRefEntry dbr5 = new DBRefEntry("UNIPROT", "0", "Q87654");
1402     sq.addDBRef(dbr5);
1403
1404     List<DBRefEntry> primaryDBRefs = sq.getPrimaryDBRefs();
1405     assertEquals(2, primaryDBRefs.size());
1406     assertTrue(primaryDBRefs.contains(dbr1));
1407     assertTrue(primaryDBRefs.contains(dbr3));
1408   }
1409
1410   /**
1411    * Test the method that updates the list of PDBEntry from any new DBRefEntry
1412    * for PDB
1413    */
1414   @Test(groups = { "Functional" })
1415   public void testUpdatePDBIds()
1416   {
1417     PDBEntry pdbe1 = new PDBEntry("3A6S", null, null, null);
1418     seq.addPDBId(pdbe1);
1419     seq.addDBRef(new DBRefEntry("Ensembl", "8", "ENST1234"));
1420     seq.addDBRef(new DBRefEntry("PDB", "0", "1A70"));
1421     seq.addDBRef(new DBRefEntry("PDB", "0", "4BQGa"));
1422     seq.addDBRef(new DBRefEntry("PDB", "0", "3a6sB"));
1423     // 7 is not a valid chain code:
1424     seq.addDBRef(new DBRefEntry("PDB", "0", "2GIS7"));
1425
1426     seq.updatePDBIds();
1427     List<PDBEntry> pdbIds = seq.getAllPDBEntries();
1428     assertEquals(4, pdbIds.size());
1429     assertSame(pdbe1, pdbIds.get(0));
1430     // chain code got added to 3A6S:
1431     assertEquals("B", pdbe1.getChainCode());
1432     assertEquals("1A70", pdbIds.get(1).getId());
1433     // 4BQGA is parsed into id + chain
1434     assertEquals("4BQG", pdbIds.get(2).getId());
1435     assertEquals("a", pdbIds.get(2).getChainCode());
1436     assertEquals("2GIS7", pdbIds.get(3).getId());
1437     assertNull(pdbIds.get(3).getChainCode());
1438   }
1439
1440   /**
1441    * Test the method that either adds a pdbid or updates an existing one
1442    */
1443   @Test(groups = { "Functional" })
1444   public void testAddPDBId()
1445   {
1446     PDBEntry pdbe = new PDBEntry("3A6S", null, null, null);
1447     seq.addPDBId(pdbe);
1448     assertEquals(1, seq.getAllPDBEntries().size());
1449     assertSame(pdbe, seq.getPDBEntry("3A6S"));
1450     assertSame(pdbe, seq.getPDBEntry("3a6s")); // case-insensitive
1451
1452     // add the same entry
1453     seq.addPDBId(pdbe);
1454     assertEquals(1, seq.getAllPDBEntries().size());
1455     assertSame(pdbe, seq.getPDBEntry("3A6S"));
1456
1457     // add an identical entry
1458     seq.addPDBId(new PDBEntry("3A6S", null, null, null));
1459     assertEquals(1, seq.getAllPDBEntries().size());
1460     assertSame(pdbe, seq.getPDBEntry("3A6S"));
1461
1462     // add a different entry
1463     PDBEntry pdbe2 = new PDBEntry("1A70", null, null, null);
1464     seq.addPDBId(pdbe2);
1465     assertEquals(2, seq.getAllPDBEntries().size());
1466     assertSame(pdbe, seq.getAllPDBEntries().get(0));
1467     assertSame(pdbe2, seq.getAllPDBEntries().get(1));
1468
1469     // update pdbe with chain code, file, type
1470     PDBEntry pdbe3 = new PDBEntry("3a6s", "A", Type.PDB, "filepath");
1471     seq.addPDBId(pdbe3);
1472     assertEquals(2, seq.getAllPDBEntries().size());
1473     assertSame(pdbe, seq.getAllPDBEntries().get(0)); // updated in situ
1474     assertEquals("3A6S", pdbe.getId()); // unchanged
1475     assertEquals("A", pdbe.getChainCode()); // updated
1476     assertEquals(Type.PDB.toString(), pdbe.getType()); // updated
1477     assertEquals("filepath", pdbe.getFile()); // updated
1478     assertSame(pdbe2, seq.getAllPDBEntries().get(1));
1479
1480     // add with a different file path
1481     PDBEntry pdbe4 = new PDBEntry("3a6s", "A", Type.PDB, "filepath2");
1482     seq.addPDBId(pdbe4);
1483     assertEquals(3, seq.getAllPDBEntries().size());
1484     assertSame(pdbe4, seq.getAllPDBEntries().get(2));
1485
1486     // add with a different chain code
1487     PDBEntry pdbe5 = new PDBEntry("3a6s", "B", Type.PDB, "filepath");
1488     seq.addPDBId(pdbe5);
1489     assertEquals(4, seq.getAllPDBEntries().size());
1490     assertSame(pdbe5, seq.getAllPDBEntries().get(3));
1491
1492     // add with a fake pdbid
1493     // (models don't have an embedded ID)
1494     String realId = "RealIDQ";
1495     PDBEntry pdbe6 = new PDBEntry(realId, null, Type.PDB, "real/localpath");
1496     PDBEntry pdbe7 = new PDBEntry("RealID/real/localpath", "C", Type.MMCIF,
1497             "real/localpath");
1498     pdbe7.setFakedPDBId(true);
1499     seq.addPDBId(pdbe6);
1500     assertEquals(5, seq.getAllPDBEntries().size());
1501     seq.addPDBId(pdbe7);
1502     assertEquals(5, seq.getAllPDBEntries().size());
1503     assertFalse(pdbe6.fakedPDBId());
1504     assertSame(pdbe6, seq.getAllPDBEntries().get(4));
1505     assertEquals("C", pdbe6.getChainCode());
1506     assertEquals(realId, pdbe6.getId());
1507   }
1508
1509   @Test(
1510     groups =
1511     { "Functional" },
1512     expectedExceptions =
1513     { IllegalArgumentException.class })
1514   public void testSetDatasetSequence_toSelf()
1515   {
1516     seq.setDatasetSequence(seq);
1517   }
1518
1519   @Test(
1520     groups =
1521     { "Functional" },
1522     expectedExceptions =
1523     { IllegalArgumentException.class })
1524   public void testSetDatasetSequence_cascading()
1525   {
1526     SequenceI seq2 = new Sequence("Seq2", "xyz");
1527     seq2.createDatasetSequence();
1528     seq.setDatasetSequence(seq2);
1529   }
1530
1531   @Test(groups = { "Functional" })
1532   public void testFindFeatures()
1533   {
1534     SequenceI sq = new Sequence("test/8-16", "-ABC--DEF--GHI--");
1535     sq.createDatasetSequence();
1536
1537     assertTrue(sq.findFeatures(1, 99).isEmpty());
1538
1539     // add non-positional feature
1540     SequenceFeature sf0 = new SequenceFeature("Cath", "desc", 0, 0, 2f,
1541             null);
1542     sq.addSequenceFeature(sf0);
1543     // add feature on BCD
1544     SequenceFeature sfBCD = new SequenceFeature("Cath", "desc", 9, 11, 2f,
1545             null);
1546     sq.addSequenceFeature(sfBCD);
1547     // add feature on DE
1548     SequenceFeature sfDE = new SequenceFeature("Cath", "desc", 11, 12, 2f,
1549             null);
1550     sq.addSequenceFeature(sfDE);
1551     // add contact feature at [B, H]
1552     SequenceFeature sfContactBH = new SequenceFeature("Disulphide bond",
1553             "desc", 9, 15, 2f, null);
1554     sq.addSequenceFeature(sfContactBH);
1555     // add contact feature at [F, G]
1556     SequenceFeature sfContactFG = new SequenceFeature("Disulfide Bond",
1557             "desc", 13, 14, 2f, null);
1558     sq.addSequenceFeature(sfContactFG);
1559     // add single position feature at [I]
1560     SequenceFeature sfI = new SequenceFeature("Disulfide Bond", "desc", 16,
1561             16, null);
1562     sq.addSequenceFeature(sfI);
1563
1564     // no features in columns 1-2 (-A)
1565     List<SequenceFeature> found = sq.findFeatures(1, 2);
1566     assertTrue(found.isEmpty());
1567
1568     // columns 1-6 (-ABC--) includes BCD and B/H feature but not DE
1569     found = sq.findFeatures(1, 6);
1570     assertEquals(2, found.size());
1571     assertTrue(found.contains(sfBCD));
1572     assertTrue(found.contains(sfContactBH));
1573
1574     // columns 5-6 (--) includes (enclosing) BCD but not (contact) B/H feature
1575     found = sq.findFeatures(5, 6);
1576     assertEquals(1, found.size());
1577     assertTrue(found.contains(sfBCD));
1578
1579     // columns 7-10 (DEF-) includes BCD, DE, F/G but not B/H feature
1580     found = sq.findFeatures(7, 10);
1581     assertEquals(3, found.size());
1582     assertTrue(found.contains(sfBCD));
1583     assertTrue(found.contains(sfDE));
1584     assertTrue(found.contains(sfContactFG));
1585
1586     // columns 10-11 (--) should find nothing
1587     found = sq.findFeatures(10, 11);
1588     assertEquals(0, found.size());
1589
1590     // columns 14-14 (I) should find variant feature
1591     found = sq.findFeatures(14, 14);
1592     assertEquals(1, found.size());
1593     assertTrue(found.contains(sfI));
1594   }
1595
1596   @Test(groups = { "Functional" })
1597   public void testFindIndex_withCursor()
1598   {
1599     Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
1600     final int tok = (int) PA.getValue(sq, "changeCount");
1601     assertEquals(1, tok);
1602
1603     // find F given A, check cursor is now at the found position
1604     assertEquals(10, sq.findIndex(13, new SequenceCursor(sq, 8, 2, tok)));
1605     SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1606     assertEquals(13, cursor.residuePosition);
1607     assertEquals(10, cursor.columnPosition);
1608
1609     // find A given F
1610     assertEquals(2, sq.findIndex(8, new SequenceCursor(sq, 13, 10, tok)));
1611     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1612     assertEquals(8, cursor.residuePosition);
1613     assertEquals(2, cursor.columnPosition);
1614
1615     // find C given C (no cursor update is done for this case)
1616     assertEquals(6, sq.findIndex(10, new SequenceCursor(sq, 10, 6, tok)));
1617     SequenceCursor cursor2 = (SequenceCursor) PA.getValue(sq, "cursor");
1618     assertSame(cursor2, cursor);
1619
1620     /*
1621      * sequence 'end' beyond end of sequence returns length of sequence 
1622      *  (for compatibility with pre-cursor code)
1623      *  - also verify the cursor is left in a valid state
1624      */
1625     sq = new Sequence("test/8-99", "-A--B-C-D-E-F--"); // trailing gap case
1626     assertEquals(7, sq.findIndex(10)); // establishes a cursor
1627     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1628     assertEquals(10, cursor.residuePosition);
1629     assertEquals(7, cursor.columnPosition);
1630     assertEquals(sq.getLength(), sq.findIndex(65));
1631     cursor2 = (SequenceCursor) PA.getValue(sq, "cursor");
1632     assertSame(cursor, cursor2); // not updated for this case!
1633
1634     sq = new Sequence("test/8-99", "-A--B-C-D-E-F"); // trailing residue case
1635     sq.findIndex(10); // establishes a cursor
1636     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1637     assertEquals(sq.getLength(), sq.findIndex(65));
1638     cursor2 = (SequenceCursor) PA.getValue(sq, "cursor");
1639     assertSame(cursor, cursor2); // not updated for this case!
1640
1641     /*
1642      * residue after sequence 'start' but before first residue should return 
1643      * zero (for compatibility with pre-cursor code)
1644      */
1645     sq = new Sequence("test/8-15", "-A-B-C-"); // leading gap case
1646     sq.findIndex(10); // establishes a cursor
1647     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1648     assertEquals(0, sq.findIndex(3));
1649     cursor2 = (SequenceCursor) PA.getValue(sq, "cursor");
1650     assertSame(cursor, cursor2); // not updated for this case!
1651
1652     sq = new Sequence("test/8-15", "A-B-C-"); // leading residue case
1653     sq.findIndex(10); // establishes a cursor
1654     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1655     assertEquals(0, sq.findIndex(2));
1656     cursor2 = (SequenceCursor) PA.getValue(sq, "cursor");
1657     assertSame(cursor, cursor2); // not updated for this case!
1658   }
1659
1660   @Test(groups = { "Functional" })
1661   public void testFindPosition_withCursor()
1662   {
1663     Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
1664     final int tok = (int) PA.getValue(sq, "changeCount");
1665     assertEquals(1, tok);
1666
1667     // find F pos given A - lastCol gets set in cursor
1668     assertEquals(13,
1669             sq.findPosition(10, new SequenceCursor(sq, 8, 2, tok)));
1670     assertEquals("test:Pos13:Col10:startCol0:endCol10:tok1",
1671             PA.getValue(sq, "cursor").toString());
1672
1673     // find A pos given F - first residue column is saved in cursor
1674     assertEquals(8,
1675             sq.findPosition(2, new SequenceCursor(sq, 13, 10, tok)));
1676     assertEquals("test:Pos8:Col2:startCol2:endCol10:tok1",
1677             PA.getValue(sq, "cursor").toString());
1678
1679     // find C pos given C (neither startCol nor endCol is set)
1680     assertEquals(10,
1681             sq.findPosition(6, new SequenceCursor(sq, 10, 6, tok)));
1682     assertEquals("test:Pos10:Col6:startCol0:endCol0:tok1",
1683             PA.getValue(sq, "cursor").toString());
1684
1685     // now the grey area - what residue position for a gapped column? JAL-2562
1686
1687     // find 'residue' for column 3 given cursor for D (so working left)
1688     // returns B9; cursor is updated to [B 5]
1689     assertEquals(9, sq.findPosition(3, new SequenceCursor(sq, 11, 7, tok)));
1690     assertEquals("test:Pos9:Col5:startCol0:endCol0:tok1",
1691             PA.getValue(sq, "cursor").toString());
1692
1693     // find 'residue' for column 8 given cursor for D (so working right)
1694     // returns E12; cursor is updated to [D 7]
1695     assertEquals(12,
1696             sq.findPosition(8, new SequenceCursor(sq, 11, 7, tok)));
1697     assertEquals("test:Pos11:Col7:startCol0:endCol0:tok1",
1698             PA.getValue(sq, "cursor").toString());
1699
1700     // find 'residue' for column 12 given cursor for B
1701     // returns 1 more than last residue position; cursor is updated to [F 10]
1702     // lastCol position is saved in cursor
1703     assertEquals(14,
1704             sq.findPosition(12, new SequenceCursor(sq, 9, 5, tok)));
1705     assertEquals("test:Pos13:Col10:startCol0:endCol10:tok1",
1706             PA.getValue(sq, "cursor").toString());
1707
1708     /*
1709      * findPosition for column beyond length of sequence
1710      * returns 1 more than the last residue position
1711      * cursor is set to last real residue position [F 10]
1712      */
1713     assertEquals(14,
1714             sq.findPosition(99, new SequenceCursor(sq, 8, 2, tok)));
1715     assertEquals("test:Pos13:Col10:startCol0:endCol10:tok1",
1716             PA.getValue(sq, "cursor").toString());
1717
1718     /*
1719      * and the case without a trailing gap
1720      */
1721     sq = new Sequence("test/8-13", "-A--BCD-EF");
1722     // first find C from A
1723     assertEquals(10, sq.findPosition(6, new SequenceCursor(sq, 8, 2, tok)));
1724     SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1725     assertEquals("test:Pos10:Col6:startCol0:endCol0:tok1",
1726             cursor.toString());
1727     // now 'find' 99 from C
1728     // cursor is set to [F 10] and saved lastCol
1729     assertEquals(14, sq.findPosition(99, cursor));
1730     assertEquals("test:Pos13:Col10:startCol0:endCol10:tok1",
1731             PA.getValue(sq, "cursor").toString());
1732   }
1733
1734   @Test
1735   public void testIsValidCursor()
1736   {
1737     Sequence sq = new Sequence("Seq", "ABC--DE-F", 8, 13);
1738     assertFalse(sq.isValidCursor(null));
1739
1740     /*
1741      * cursor is valid if it has valid sequence ref and changeCount token
1742      * and positions within the range of the sequence
1743      */
1744     int changeCount = (int) PA.getValue(sq, "changeCount");
1745     SequenceCursor cursor = new SequenceCursor(sq, 13, 1, changeCount);
1746     assertTrue(sq.isValidCursor(cursor));
1747
1748     /*
1749      * column position outside [0 - length] is rejected
1750      */
1751     cursor = new SequenceCursor(sq, 13, -1, changeCount);
1752     assertFalse(sq.isValidCursor(cursor));
1753     cursor = new SequenceCursor(sq, 13, 10, changeCount);
1754     assertFalse(sq.isValidCursor(cursor));
1755     cursor = new SequenceCursor(sq, 7, 8, changeCount);
1756     assertFalse(sq.isValidCursor(cursor));
1757     cursor = new SequenceCursor(sq, 14, 2, changeCount);
1758     assertFalse(sq.isValidCursor(cursor));
1759
1760     /*
1761      * wrong sequence is rejected
1762      */
1763     cursor = new SequenceCursor(null, 13, 1, changeCount);
1764     assertFalse(sq.isValidCursor(cursor));
1765     cursor = new SequenceCursor(new Sequence("Seq", "abc"), 13, 1,
1766             changeCount);
1767     assertFalse(sq.isValidCursor(cursor));
1768
1769     /*
1770      * wrong token value is rejected
1771      */
1772     cursor = new SequenceCursor(sq, 13, 1, changeCount + 1);
1773     assertFalse(sq.isValidCursor(cursor));
1774     cursor = new SequenceCursor(sq, 13, 1, changeCount - 1);
1775     assertFalse(sq.isValidCursor(cursor));
1776   }
1777
1778   @Test(groups = { "Functional" })
1779   public void testFindPosition_withCursorAndEdits()
1780   {
1781     Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
1782
1783     // find F pos given A
1784     assertEquals(13, sq.findPosition(10, new SequenceCursor(sq, 8, 2, 0)));
1785     int token = (int) PA.getValue(sq, "changeCount"); // 0
1786     SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1787     assertEquals(new SequenceCursor(sq, 13, 10, token), cursor);
1788
1789     /*
1790      * setSequence should invalidate the cursor cached by the sequence
1791      */
1792     sq.setSequence("-A-BCD-EF---"); // one gap removed
1793     assertEquals(8, sq.getStart()); // sanity check
1794     assertEquals(11, sq.findPosition(5)); // D11
1795     // cursor should now be at [D 6]
1796     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1797     assertEquals(new SequenceCursor(sq, 11, 6, ++token), cursor);
1798     assertEquals(0, cursor.lastColumnPosition); // not yet found
1799     assertEquals(13, sq.findPosition(8)); // E13
1800     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1801     assertEquals(9, cursor.lastColumnPosition); // found
1802
1803     /*
1804      * deleteChars should invalidate the cached cursor
1805      */
1806     sq.deleteChars(2, 5); // delete -BC
1807     assertEquals("-AD-EF---", sq.getSequenceAsString());
1808     assertEquals(8, sq.getStart()); // sanity check
1809     assertEquals(10, sq.findPosition(4)); // E10
1810     // cursor should now be at [E 5]
1811     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1812     assertEquals(new SequenceCursor(sq, 10, 5, ++token), cursor);
1813
1814     /*
1815      * Edit to insert gaps should invalidate the cached cursor
1816      * insert 2 gaps at column[3] to make -AD---EF---
1817      */
1818     SequenceI[] seqs = new SequenceI[] { sq };
1819     AlignmentI al = new Alignment(seqs);
1820     new EditCommand().appendEdit(Action.INSERT_GAP, seqs, 3, 2, al, true);
1821     assertEquals("-AD---EF---", sq.getSequenceAsString());
1822     assertEquals(10, sq.findPosition(4)); // E10
1823     // cursor should now be at [D 3]
1824     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1825     assertEquals(new SequenceCursor(sq, 9, 3, ++token), cursor);
1826
1827     /*
1828      * insertCharAt should invalidate the cached cursor
1829      * insert CC at column[4] to make -AD-CC--EF---
1830      */
1831     sq.insertCharAt(4, 2, 'C');
1832     assertEquals("-AD-CC--EF---", sq.getSequenceAsString());
1833     assertEquals(13, sq.findPosition(9)); // F13
1834     // cursor should now be at [F 10]
1835     cursor = (SequenceCursor) PA.getValue(sq, "cursor");
1836     assertEquals(new SequenceCursor(sq, 13, 10, ++token), cursor);
1837
1838     /*
1839      * changing sequence start should invalidate cursor
1840      */
1841     sq = new Sequence("test/8-13", "-A--BCD-EF--");
1842     assertEquals(8, sq.getStart());
1843     assertEquals(9, sq.findPosition(4)); // B(9)
1844     sq.setStart(7);
1845     assertEquals(8, sq.findPosition(4)); // is now B(8)
1846     sq.setStart(10);
1847     assertEquals(11, sq.findPosition(4)); // is now B(11)
1848   }
1849
1850   @Test(groups = { "Functional" })
1851   public void testGetSequence()
1852   {
1853     String seqstring = "-A--BCD-EF--";
1854     Sequence sq = new Sequence("test/8-13", seqstring);
1855     sq.createDatasetSequence();
1856     assertTrue(Arrays.equals(sq.getSequence(), seqstring.toCharArray()));
1857     assertTrue(Arrays.equals(sq.getDatasetSequence().getSequence(),
1858             "ABCDEF".toCharArray()));
1859
1860     // verify a copy of the sequence array is returned
1861     char[] theSeq = (char[]) PA.getValue(sq, "sequence");
1862     assertNotSame(theSeq, sq.getSequence());
1863     theSeq = (char[]) PA.getValue(sq.getDatasetSequence(), "sequence");
1864     assertNotSame(theSeq, sq.getDatasetSequence().getSequence());
1865   }
1866
1867   @Test(groups = { "Functional" })
1868   public void testReplace()
1869   {
1870     String seqstring = "-A--BCD-EF--";
1871     SequenceI sq = new Sequence("test/8-13", seqstring);
1872     // changeCount is incremented for setStart
1873     assertEquals(1, PA.getValue(sq, "changeCount"));
1874
1875     assertEquals(0, sq.replace('A', 'A')); // same char
1876     assertEquals(seqstring, sq.getSequenceAsString());
1877     assertEquals(1, PA.getValue(sq, "changeCount"));
1878
1879     assertEquals(0, sq.replace('X', 'Y')); // not there
1880     assertEquals(seqstring, sq.getSequenceAsString());
1881     assertEquals(1, PA.getValue(sq, "changeCount"));
1882
1883     assertEquals(1, sq.replace('A', 'K'));
1884     assertEquals("-K--BCD-EF--", sq.getSequenceAsString());
1885     assertEquals(2, PA.getValue(sq, "changeCount"));
1886
1887     assertEquals(6, sq.replace('-', '.'));
1888     assertEquals(".K..BCD.EF..", sq.getSequenceAsString());
1889     assertEquals(3, PA.getValue(sq, "changeCount"));
1890   }
1891
1892   @Test(groups = { "Functional" })
1893   public void testGapBitset()
1894   {
1895     SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
1896     BitSet bs = sq.gapBitset();
1897     BitSet expected = new BitSet();
1898     expected.set(0);
1899     expected.set(4, 7);
1900     expected.set(9);
1901     expected.set(11, 13);
1902
1903     assertTrue(bs.equals(expected));
1904
1905   }
1906
1907   public void testFindFeatures_largeEndPos()
1908   {
1909     /*
1910      * imitate a PDB sequence where end is larger than end position
1911      */
1912     SequenceI sq = new Sequence("test", "-ABC--DEF--", 1, 20);
1913     sq.createDatasetSequence();
1914
1915     assertTrue(sq.findFeatures(1, 9).isEmpty());
1916     // should be no array bounds exception - JAL-2772
1917     assertTrue(sq.findFeatures(1, 15).isEmpty());
1918
1919     // add feature on BCD
1920     SequenceFeature sfBCD = new SequenceFeature("Cath", "desc", 2, 4, 2f,
1921             null);
1922     sq.addSequenceFeature(sfBCD);
1923
1924     // no features in columns 1-2 (-A)
1925     List<SequenceFeature> found = sq.findFeatures(1, 2);
1926     assertTrue(found.isEmpty());
1927
1928     // columns 1-6 (-ABC--) includes BCD
1929     found = sq.findFeatures(1, 6);
1930     assertEquals(1, found.size());
1931     assertTrue(found.contains(sfBCD));
1932
1933     // columns 10-11 (--) should find nothing
1934     found = sq.findFeatures(10, 11);
1935     assertEquals(0, found.size());
1936   }
1937
1938   @Test(groups = { "Functional" })
1939   public void testSetName()
1940   {
1941     SequenceI sq = new Sequence("test", "-ABC---DE-F--");
1942     assertEquals("test", sq.getName());
1943     assertEquals(1, sq.getStart());
1944     assertEquals(6, sq.getEnd());
1945
1946     sq.setName("testing");
1947     assertEquals("testing", sq.getName());
1948
1949     sq.setName("test/8-10");
1950     assertEquals("test", sq.getName());
1951     assertEquals(8, sq.getStart());
1952     assertEquals(13, sq.getEnd()); // note end is recomputed
1953
1954     sq.setName("testing/7-99");
1955     assertEquals("testing", sq.getName());
1956     assertEquals(7, sq.getStart());
1957     assertEquals(99, sq.getEnd()); // end may be beyond physical end
1958
1959     sq.setName("/2-3");
1960     assertEquals("", sq.getName());
1961     assertEquals(2, sq.getStart());
1962     assertEquals(7, sq.getEnd());
1963
1964     sq.setName("test/"); // invalid
1965     assertEquals("test/", sq.getName());
1966     assertEquals(2, sq.getStart());
1967     assertEquals(7, sq.getEnd());
1968
1969     sq.setName("test/6-13/7-99");
1970     assertEquals("test/6-13", sq.getName());
1971     assertEquals(7, sq.getStart());
1972     assertEquals(99, sq.getEnd());
1973
1974     sq.setName("test/0-5"); // 0 is invalid - ignored
1975     assertEquals("test/0-5", sq.getName());
1976     assertEquals(7, sq.getStart());
1977     assertEquals(99, sq.getEnd());
1978
1979     sq.setName("test/a-5"); // a is invalid - ignored
1980     assertEquals("test/a-5", sq.getName());
1981     assertEquals(7, sq.getStart());
1982     assertEquals(99, sq.getEnd());
1983
1984     sq.setName("test/6-5"); // start > end is invalid - ignored
1985     assertEquals("test/6-5", sq.getName());
1986     assertEquals(7, sq.getStart());
1987     assertEquals(99, sq.getEnd());
1988
1989     sq.setName("test/5"); // invalid - ignored
1990     assertEquals("test/5", sq.getName());
1991     assertEquals(7, sq.getStart());
1992     assertEquals(99, sq.getEnd());
1993
1994     sq.setName("test/-5"); // invalid - ignored
1995     assertEquals("test/-5", sq.getName());
1996     assertEquals(7, sq.getStart());
1997     assertEquals(99, sq.getEnd());
1998
1999     sq.setName("test/5-"); // invalid - ignored
2000     assertEquals("test/5-", sq.getName());
2001     assertEquals(7, sq.getStart());
2002     assertEquals(99, sq.getEnd());
2003
2004     sq.setName("test/5-6-7"); // invalid - ignored
2005     assertEquals("test/5-6-7", sq.getName());
2006     assertEquals(7, sq.getStart());
2007     assertEquals(99, sq.getEnd());
2008
2009     sq.setName(null); // invalid, gets converted to space
2010     assertEquals("", sq.getName());
2011     assertEquals(7, sq.getStart());
2012     assertEquals(99, sq.getEnd());
2013   }
2014
2015   @Test(groups = { "Functional" })
2016   public void testCheckValidRange()
2017   {
2018     Sequence sq = new Sequence("test/7-12", "-ABC---DE-F--");
2019     assertEquals(7, sq.getStart());
2020     assertEquals(12, sq.getEnd());
2021
2022     /*
2023      * checkValidRange ensures end is at least the last residue position
2024      */
2025     PA.setValue(sq, "end", 2);
2026     sq.checkValidRange();
2027     assertEquals(12, sq.getEnd());
2028
2029     /*
2030      * end may be beyond the last residue position
2031      */
2032     PA.setValue(sq, "end", 22);
2033     sq.checkValidRange();
2034     assertEquals(22, sq.getEnd());
2035   }
2036
2037   @Test(groups = { "Functional" })
2038   public void testDeleteChars_withGaps()
2039   {
2040     /*
2041      * delete gaps only
2042      */
2043     SequenceI sq = new Sequence("test/8-10", "A-B-C");
2044     sq.createDatasetSequence();
2045     assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
2046     sq.deleteChars(1, 2); // delete first gap
2047     assertEquals("AB-C", sq.getSequenceAsString());
2048     assertEquals(8, sq.getStart());
2049     assertEquals(10, sq.getEnd());
2050     assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
2051
2052     /*
2053      * delete gaps and residues at start (no new dataset sequence)
2054      */
2055     sq = new Sequence("test/8-10", "A-B-C");
2056     sq.createDatasetSequence();
2057     sq.deleteChars(0, 3); // delete A-B
2058     assertEquals("-C", sq.getSequenceAsString());
2059     assertEquals(10, sq.getStart());
2060     assertEquals(10, sq.getEnd());
2061     assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
2062
2063     /*
2064      * delete gaps and residues at end (no new dataset sequence)
2065      */
2066     sq = new Sequence("test/8-10", "A-B-C");
2067     sq.createDatasetSequence();
2068     sq.deleteChars(2, 5); // delete B-C
2069     assertEquals("A-", sq.getSequenceAsString());
2070     assertEquals(8, sq.getStart());
2071     assertEquals(8, sq.getEnd());
2072     assertEquals("ABC", sq.getDatasetSequence().getSequenceAsString());
2073
2074     /*
2075      * delete gaps and residues internally (new dataset sequence)
2076      * first delete from gap to residue
2077      */
2078     sq = new Sequence("test/8-10", "A-B-C");
2079     sq.createDatasetSequence();
2080     sq.deleteChars(1, 3); // delete -B
2081     assertEquals("A-C", sq.getSequenceAsString());
2082     assertEquals(8, sq.getStart());
2083     assertEquals(9, sq.getEnd());
2084     assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
2085     assertEquals(8, sq.getDatasetSequence().getStart());
2086     assertEquals(9, sq.getDatasetSequence().getEnd());
2087
2088     /*
2089      * internal delete from gap to gap
2090      */
2091     sq = new Sequence("test/8-10", "A-B-C");
2092     sq.createDatasetSequence();
2093     sq.deleteChars(1, 4); // delete -B-
2094     assertEquals("AC", sq.getSequenceAsString());
2095     assertEquals(8, sq.getStart());
2096     assertEquals(9, sq.getEnd());
2097     assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
2098     assertEquals(8, sq.getDatasetSequence().getStart());
2099     assertEquals(9, sq.getDatasetSequence().getEnd());
2100
2101     /*
2102      * internal delete from residue to residue
2103      */
2104     sq = new Sequence("test/8-10", "A-B-C");
2105     sq.createDatasetSequence();
2106     sq.deleteChars(2, 3); // delete B
2107     assertEquals("A--C", sq.getSequenceAsString());
2108     assertEquals(8, sq.getStart());
2109     assertEquals(9, sq.getEnd());
2110     assertEquals("AC", sq.getDatasetSequence().getSequenceAsString());
2111     assertEquals(8, sq.getDatasetSequence().getStart());
2112     assertEquals(9, sq.getDatasetSequence().getEnd());
2113   }
2114
2115   /**
2116    * Test the code used to locate the reference sequence ruler origin
2117    */
2118   @Test(groups = { "Functional" })
2119   public void testLocateVisibleStartofSequence()
2120   {
2121     // create random alignment
2122     AlignmentGenerator gen = new AlignmentGenerator(false);
2123     AlignmentI al = gen.generate(50, 20, 123, 5, 5);
2124
2125     HiddenColumns cs = al.getHiddenColumns();
2126     ColumnSelection colsel = new ColumnSelection();
2127
2128     SequenceI seq = new Sequence("RefSeq", "-A-SD-ASD--E---");
2129     assertEquals(2, seq.findIndex(seq.getStart()));
2130
2131     // no hidden columns
2132     assertEquals(seq.findIndex(seq.getStart()) - 1,
2133             seq.firstResidueOutsideIterator(cs.iterator()));
2134
2135     // hidden column on gap after end of sequence - should not affect bounds
2136     colsel.hideSelectedColumns(13, al.getHiddenColumns());
2137     assertEquals(seq.findIndex(seq.getStart()) - 1,
2138             seq.firstResidueOutsideIterator(cs.iterator()));
2139
2140     cs.revealAllHiddenColumns(colsel);
2141     // hidden column on gap before beginning of sequence - should vis bounds by
2142     // one
2143     colsel.hideSelectedColumns(0, al.getHiddenColumns());
2144     assertEquals(seq.findIndex(seq.getStart()) - 2,
2145             cs.absoluteToVisibleColumn(
2146                     seq.firstResidueOutsideIterator(cs.iterator())));
2147
2148     cs.revealAllHiddenColumns(colsel);
2149     // hide columns around most of sequence - leave one residue remaining
2150     cs.hideColumns(1, 3);
2151     cs.hideColumns(6, 11);
2152
2153     Iterator<int[]> it = cs.getVisContigsIterator(0, 6, false);
2154
2155     assertEquals("-D", seq.getSequenceStringFromIterator(it));
2156     // cs.getVisibleSequenceStrings(0, 5, new SequenceI[]
2157     // { seq })[0]);
2158
2159     assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator()));
2160     cs.revealAllHiddenColumns(colsel);
2161
2162     // hide whole sequence - should just get location of hidden region
2163     // containing sequence
2164     cs.hideColumns(1, 11);
2165     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2166
2167     cs.revealAllHiddenColumns(colsel);
2168     cs.hideColumns(0, 15);
2169     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2170
2171     SequenceI seq2 = new Sequence("RefSeq2", "-------A-SD-ASD--E---");
2172
2173     cs.revealAllHiddenColumns(colsel);
2174     cs.hideColumns(7, 17);
2175     assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
2176
2177     cs.revealAllHiddenColumns(colsel);
2178     cs.hideColumns(3, 17);
2179     assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
2180
2181     cs.revealAllHiddenColumns(colsel);
2182     cs.hideColumns(3, 19);
2183     assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
2184
2185     cs.revealAllHiddenColumns(colsel);
2186     cs.hideColumns(0, 0);
2187     assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
2188
2189     cs.revealAllHiddenColumns(colsel);
2190     cs.hideColumns(0, 1);
2191     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2192
2193     cs.revealAllHiddenColumns(colsel);
2194     cs.hideColumns(0, 2);
2195     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2196
2197     cs.revealAllHiddenColumns(colsel);
2198     cs.hideColumns(1, 1);
2199     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2200
2201     cs.revealAllHiddenColumns(colsel);
2202     cs.hideColumns(1, 2);
2203     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2204
2205     cs.revealAllHiddenColumns(colsel);
2206     cs.hideColumns(1, 3);
2207     assertEquals(4, seq.firstResidueOutsideIterator(cs.iterator()));
2208
2209     cs.revealAllHiddenColumns(colsel);
2210     cs.hideColumns(0, 2);
2211     cs.hideColumns(5, 6);
2212     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2213
2214     cs.revealAllHiddenColumns(colsel);
2215     cs.hideColumns(0, 2);
2216     cs.hideColumns(5, 6);
2217     cs.hideColumns(9, 10);
2218     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2219
2220     cs.revealAllHiddenColumns(colsel);
2221     cs.hideColumns(0, 2);
2222     cs.hideColumns(7, 11);
2223     assertEquals(3, seq.firstResidueOutsideIterator(cs.iterator()));
2224
2225     cs.revealAllHiddenColumns(colsel);
2226     cs.hideColumns(2, 4);
2227     cs.hideColumns(7, 11);
2228     assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
2229
2230     cs.revealAllHiddenColumns(colsel);
2231     cs.hideColumns(2, 4);
2232     cs.hideColumns(7, 12);
2233     assertEquals(1, seq.firstResidueOutsideIterator(cs.iterator()));
2234
2235     cs.revealAllHiddenColumns(colsel);
2236     cs.hideColumns(1, 11);
2237     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2238
2239     cs.revealAllHiddenColumns(colsel);
2240     cs.hideColumns(0, 12);
2241     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2242
2243     cs.revealAllHiddenColumns(colsel);
2244     cs.hideColumns(0, 4);
2245     cs.hideColumns(6, 12);
2246     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2247
2248     cs.revealAllHiddenColumns(colsel);
2249     cs.hideColumns(0, 1);
2250     cs.hideColumns(3, 12);
2251     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2252
2253     cs.revealAllHiddenColumns(colsel);
2254     cs.hideColumns(3, 14);
2255     cs.hideColumns(17, 19);
2256     assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
2257
2258     cs.revealAllHiddenColumns(colsel);
2259     cs.hideColumns(3, 7);
2260     cs.hideColumns(9, 14);
2261     cs.hideColumns(17, 19);
2262     assertEquals(0, seq2.firstResidueOutsideIterator(cs.iterator()));
2263
2264     cs.revealAllHiddenColumns(colsel);
2265     cs.hideColumns(0, 1);
2266     cs.hideColumns(3, 4);
2267     cs.hideColumns(6, 8);
2268     cs.hideColumns(10, 12);
2269     assertEquals(0, seq.firstResidueOutsideIterator(cs.iterator()));
2270
2271   }
2272
2273   @Test(groups = { "Functional" })
2274   public void testTransferAnnotation()
2275   {
2276     Sequence origSeq = new Sequence("MYSEQ", "THISISASEQ");
2277     Sequence toSeq = new Sequence("MYSEQ", "THISISASEQ");
2278     origSeq.addDBRef(new DBRefEntry("UNIPROT", "0", "Q12345", null, true));
2279     toSeq.transferAnnotation(origSeq, null);
2280     assertTrue(toSeq.getDBRefs().size() == 1);
2281
2282     assertTrue(toSeq.getDBRefs().get(0).isCanonical());
2283
2284     // check for promotion of non-canonical
2285     // to canonical (e.g. fetch-db-refs on a jalview project pre 2.11.2)
2286     toSeq.setDBRefs(null);
2287     toSeq.addDBRef(new DBRefEntry("UNIPROT", "0", "Q12345", null, false));
2288     toSeq.transferAnnotation(origSeq, null);
2289     assertTrue(toSeq.getDBRefs().size() == 1);
2290
2291     assertTrue("Promotion of non-canonical DBRefEntry failed",
2292             toSeq.getDBRefs().get(0).isCanonical());
2293
2294   }
2295 }