JAL-3253 jalview.bin.Instance handles all singleton instances -
[jalview.git] / test / jalview / io / CrossRef2xmlTests.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.io;
22
23 import jalview.analysis.CrossRef;
24 import jalview.api.AlignmentViewPanel;
25 import jalview.bin.Instance;
26 import jalview.datamodel.AlignedCodonFrame;
27 import jalview.datamodel.AlignmentI;
28 import jalview.datamodel.AlignmentTest;
29 import jalview.datamodel.SequenceI;
30 import jalview.gui.AlignFrame;
31 import jalview.gui.CrossRefAction;
32 import jalview.gui.Desktop;
33 import jalview.gui.JvOptionPane;
34 import jalview.gui.SequenceFetcher;
35 import jalview.project.Jalview2XML;
36 import jalview.util.DBRefUtils;
37
38 import java.io.File;
39 import java.io.IOException;
40 import java.util.ArrayList;
41 import java.util.Arrays;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45
46 import org.testng.Assert;
47 import org.testng.annotations.BeforeClass;
48 import org.testng.annotations.DataProvider;
49 import org.testng.annotations.Test;
50
51 import junit.extensions.PA;
52
53 @Test(singleThreaded = true)
54 public class CrossRef2xmlTests extends Jalview2xmlBase
55 {
56
57   @Override
58   @BeforeClass(alwaysRun = true)
59   public void setUpJvOptionPane()
60   {
61     JvOptionPane.setInteractiveMode(false);
62     JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
63   }
64
65   @DataProvider(name = "initialAccessions")
66   static Object[][] getAccessions()
67   {
68     return new String[][] { { "UNIPROT", "P00338" },
69         { "UNIPROT", "Q8Z9G6" },
70         { "ENSEMBLGENOMES", "CAD01290" } };
71   }
72
73   /**
74    * test store and recovery of all reachable cross refs from all reachable
75    * crossrefs for one or more fetched db refs. Currently, this test has a known
76    * failure case.
77    * 
78    * @throws Exception
79    */
80   @Test(
81     groups =
82     { "Operational" },
83     dataProvider = "initialAccessions",
84     enabled = true)
85   public void testRetrieveAndShowCrossref(String forSource,
86           String forAccession) throws Exception
87   {
88
89     List<String> failedDBRetr = new ArrayList<>();
90     List<String> failedXrefMenuItems = new ArrayList<>();
91     List<String> failedProjectRecoveries = new ArrayList<>();
92     // only search for ensembl or Uniprot crossrefs
93     List<String> limit = Arrays
94             .asList(new String[]
95             { DBRefUtils.getCanonicalName("ENSEMBL"),
96                 DBRefUtils.getCanonicalName("Uniprot") });
97     // for every set of db queries
98     // retrieve db query
99     // verify presence of expected xrefs
100     // show xrefs - verify expected type of frame is shown for each xref
101     // show xrefs again
102     // - verify original -> xref -> xref(original) recovers frame containing at
103     // least the first retrieved sequence
104     // store
105     // 1. whole project
106     // 2. individual frames
107     // 3. load each one back and verify
108     // . aligned sequences (.toString() )
109     // . xrefs (.toString() )
110     // . codonframes
111     //
112     //
113     Map<String, String> dbtoviewBit = new HashMap<>();
114     List<String> keyseq = new ArrayList<>();
115     Map<String, File> savedProjects = new HashMap<>();
116
117     // for (String[] did : new String[][] { { "UNIPROT", "P00338" } })
118     // {
119     // pass counters - 0 - first pass, 1 means retrieve project rather than
120     // perform action
121     int pass1 = 0, pass2 = 0, pass3 = 0;
122     // each do loop performs two iterations in the first outer loop pass, but
123     // only performs one iteration on the second outer loop
124     // ie. pass 1 = 0 {pass 2= 0 { pass 3 = 0,1 }, pass 2=1 { pass 3 = 0 }}, 1
125     // { pass 2 = 0 { pass 3 = 0 } }
126     do
127     {
128       String first = forSource + " " + forAccession;// did[0] + " " + did[1];
129       AlignFrame af = null;
130       boolean dna;
131       AlignmentI retral;
132       AlignmentI dataset;
133       SequenceI[] seqs;
134       List<String> ptypes = null;
135       if (pass1 == 0)
136       {
137         // retrieve dbref
138
139         SequenceFetcher sf = new SequenceFetcher(Instance.getDesktop(),
140                 forSource, forAccession);
141         sf.run();
142         AlignFrame[] afs = Desktop.getAlignFrames();
143         if (afs.length == 0)
144         {
145           failedDBRetr.add("Didn't retrieve " + first);
146           break;
147         }
148         keyseq.add(first);
149         af = afs[0];
150
151         // verify references for retrieved data
152         AlignmentTest.assertAlignmentDatasetRefs(
153                 af.getViewport().getAlignment(), "Pass (" + pass1 + ","
154                         + pass2 + "," + pass3 + "): Fetch " + first + ":");
155         assertDatasetIsNormalisedKnownDefect(
156                 af.getViewport().getAlignment(), "Pass (" + pass1 + ","
157                         + pass2 + "," + pass3 + "): Fetch " + first + ":");
158         dna = af.getViewport().getAlignment().isNucleotide();
159         retral = af.getViewport().getAlignment();
160         dataset = retral.getDataset();
161         seqs = retral.getSequencesArray();
162
163       }
164       else
165       {
166         Instance.getDesktop().closeAll_actionPerformed(null);
167         // recover stored project
168         af = new FileLoader(false).LoadFileWaitTillLoaded(
169                 savedProjects.get(first).toString(), DataSourceType.FILE);
170         System.out.println("Recovered view for '" + first + "' from '"
171                 + savedProjects.get(first).toString() + "'");
172         dna = af.getViewport().getAlignment().isNucleotide();
173         retral = af.getViewport().getAlignment();
174         dataset = retral.getDataset();
175         seqs = retral.getSequencesArray();
176
177         // verify references for recovered data
178         AlignmentTest.assertAlignmentDatasetRefs(
179                 af.getViewport().getAlignment(),
180                 "Pass (" + pass1 + "," + pass2 + "," + pass3 + "): Recover "
181                         + first + ":");
182         assertDatasetIsNormalisedKnownDefect(
183                 af.getViewport().getAlignment(),
184                 "Pass (" + pass1 + "," + pass2 + "," + pass3 + "): Recover "
185                         + first + ":");
186
187       }
188
189       // store project on first pass, compare next pass
190       stringify(dbtoviewBit, savedProjects, first, af.alignPanel);
191
192       ptypes = (seqs == null || seqs.length == 0) ? null
193               : new CrossRef(seqs, dataset)
194                       .findXrefSourcesForSequences(dna);
195       filterDbRefs(ptypes, limit);
196
197       // start of pass2: retrieve each cross-ref for fetched or restored
198       // project.
199       do // first cross ref and recover crossref loop
200       {
201
202         for (String db : ptypes)
203         {
204           // counter for splitframe views retrieved via crossref
205           int firstcr_ap = 0;
206           // build next key so we an retrieve all views
207           String nextxref = first + " -> " + db + "{" + firstcr_ap + "}";
208           // perform crossref action, or retrieve stored project
209           List<AlignmentViewPanel> cra_views = new ArrayList<>();
210           CrossRefAction cra = null;
211
212           if (pass2 == 0)
213           { // retrieve and show cross-refs in this thread
214             cra = CrossRefAction.getHandlerFor(seqs, dna, db, af);
215             cra.run();
216             cra_views = (List<AlignmentViewPanel>) PA.getValue(cra,
217                     "xrefViews");
218             if (cra_views.size() == 0)
219             {
220               failedXrefMenuItems.add(
221                       "No crossrefs retrieved for " + first + " -> " + db);
222               continue;
223             }
224             assertNucleotide(cra_views.get(0),
225                     "Nucleotide panel included proteins for " + first
226                             + " -> " + db);
227             assertProtein(cra_views.get(1),
228                     "Protein panel included nucleotides for " + first
229                             + " -> " + db);
230           }
231           else
232           {
233             Instance.getDesktop().closeAll_actionPerformed(null);
234             pass3 = 0;
235             // recover stored project
236             File storedProject = savedProjects.get(nextxref);
237             if (storedProject == null)
238             {
239               failedProjectRecoveries
240                       .add("Failed to store a view for '" + nextxref + "'");
241               continue;
242             }
243
244             // recover stored project
245             AlignFrame af2 = new FileLoader(false).LoadFileWaitTillLoaded(
246                     savedProjects.get(nextxref).toString(),
247                     DataSourceType.FILE);
248             System.out
249                     .println("Recovered view for '" + nextxref + "' from '"
250                             + savedProjects.get(nextxref).toString() + "'");
251             // gymnastics to recover the alignPanel/Complementary alignPanel
252             if (af2.getViewport().isNucleotide())
253             {
254               // top view, then bottom
255               cra_views.add(af2.getViewport().getAlignPanel());
256               cra_views.add(((jalview.gui.AlignViewport) af2.getViewport()
257                       .getCodingComplement()).getAlignPanel());
258
259             }
260             else
261             {
262               // bottom view, then top
263               cra_views.add(((jalview.gui.AlignViewport) af2.getViewport()
264                       .getCodingComplement()).getAlignPanel());
265               cra_views.add(af2.getViewport().getAlignPanel());
266
267             }
268           }
269           HashMap<String, List<String>> xrptypes = new HashMap<>();
270           // first save/verify views.
271           for (AlignmentViewPanel avp : cra_views)
272           {
273             nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
274             // verify references for this panel
275             AlignmentTest.assertAlignmentDatasetRefs(avp.getAlignment(),
276                     "Pass (" + pass1 + "," + pass2 + "," + pass3
277                             + "): before start of pass3: " + nextxref
278                             + ":");
279             assertDatasetIsNormalisedKnownDefect(avp.getAlignment(),
280                     "Pass (" + pass1 + "," + pass2 + "," + pass3
281                             + "): before start of pass3: " + nextxref
282                             + ":");
283
284             SequenceI[] xrseqs = avp.getAlignment().getSequencesArray();
285
286             List<String> _xrptypes = (seqs == null || seqs.length == 0)
287                     ? null
288                     : new CrossRef(xrseqs, dataset)
289                             .findXrefSourcesForSequences(
290                                     avp.getAlignViewport().isNucleotide());
291
292             stringify(dbtoviewBit, savedProjects, nextxref, avp);
293             xrptypes.put(nextxref, _xrptypes);
294
295           }
296
297           // now do the second xref pass starting from either saved or just
298           // recovered split pane, in sequence
299           do // retrieve second set of cross refs or recover and verify
300           {
301             firstcr_ap = 0;
302             for (AlignmentViewPanel avp : cra_views)
303             {
304               nextxref = first + " -> " + db + "{" + firstcr_ap++ + "}";
305               for (String xrefdb : xrptypes.get(nextxref))
306               {
307                 List<AlignmentViewPanel> cra_views2 = new ArrayList<>();
308                 int q = 0;
309                 String nextnextxref = nextxref + " -> " + xrefdb + "{" + q
310                         + "}";
311
312                 if (pass3 == 0)
313                 {
314                   SequenceI[] xrseqs = avp.getAlignment()
315                           .getSequencesArray();
316                   AlignFrame nextaf = Desktop
317                           .getAlignFrameFor(avp.getAlignViewport());
318
319                   cra = CrossRefAction.getHandlerFor(xrseqs,
320                           avp.getAlignViewport().isNucleotide(), xrefdb,
321                           nextaf);
322                   cra.run();
323                   cra_views2 = (List<AlignmentViewPanel>) PA.getValue(cra,
324                           "xrefViews");
325                   if (cra_views2.size() == 0)
326                   {
327                     failedXrefMenuItems.add("No crossrefs retrieved for '"
328                             + nextxref + "' to " + xrefdb + " via '"
329                             + nextaf.getTitle() + "'");
330                     continue;
331                   }
332                   assertNucleotide(cra_views2.get(0),
333                           "Nucleotide panel included proteins for '"
334                                   + nextxref + "' to " + xrefdb + " via '"
335                                   + nextaf.getTitle() + "'");
336                   assertProtein(cra_views2.get(1),
337                           "Protein panel included nucleotides for '"
338                                   + nextxref + "' to " + xrefdb + " via '"
339                                   + nextaf.getTitle() + "'");
340
341                 }
342                 else
343                 {
344                   Instance.getDesktop().closeAll_actionPerformed(null);
345                   // recover stored project
346                   File storedProject = savedProjects.get(nextnextxref);
347                   if (storedProject == null)
348                   {
349                     failedProjectRecoveries
350                             .add("Failed to store a view for '"
351                                     + nextnextxref + "'");
352                     continue;
353                   }
354                   AlignFrame af2 = new FileLoader(false)
355                           .LoadFileWaitTillLoaded(savedProjects
356                                   .get(nextnextxref).toString(),
357                                   DataSourceType.FILE);
358                   System.out
359                           .println("Recovered view for '" + nextnextxref
360                                   + "' from '" + savedProjects
361                                           .get(nextnextxref).toString()
362                                   + "'");
363                   // gymnastics to recover the alignPanel/Complementary
364                   // alignPanel
365                   if (af2.getViewport().isNucleotide())
366                   {
367                     // top view, then bottom
368                     cra_views2.add(af2.getViewport().getAlignPanel());
369                     cra_views2.add(((jalview.gui.AlignViewport) af2
370                             .getViewport().getCodingComplement())
371                                     .getAlignPanel());
372
373                   }
374                   else
375                   {
376                     // bottom view, then top
377                     cra_views2.add(((jalview.gui.AlignViewport) af2
378                             .getViewport().getCodingComplement())
379                                     .getAlignPanel());
380                     cra_views2.add(af2.getViewport().getAlignPanel());
381                   }
382                   Assert.assertEquals(cra_views2.size(), 2);
383                   Assert.assertNotNull(cra_views2.get(0));
384                   Assert.assertNotNull(cra_views2.get(1));
385                 }
386
387                 for (AlignmentViewPanel nextavp : cra_views2)
388                 {
389                   nextnextxref = nextxref + " -> " + xrefdb + "{" + q++
390                           + "}";
391
392                   // verify references for this panel
393                   AlignmentTest.assertAlignmentDatasetRefs(
394                           nextavp.getAlignment(),
395                           "" + "Pass (" + pass1 + "," + pass2 + "): For "
396                                   + nextnextxref + ":");
397                   assertDatasetIsNormalisedKnownDefect(
398                           nextavp.getAlignment(),
399                           "" + "Pass (" + pass1 + "," + pass2 + "): For "
400                                   + nextnextxref + ":");
401
402                   stringify(dbtoviewBit, savedProjects, nextnextxref,
403                           nextavp);
404                   keyseq.add(nextnextxref);
405                 }
406               } // end of loop around showing all xrefdb for crossrf2
407
408             } // end of loop around all viewpanels from crossrf1
409           } while (pass2 == 2 && pass3++ < 2);
410           // fetchdb->crossref1->crossref-2->verify for xrefs we
411           // either loop twice when pass2=0, or just once when pass2=1
412           // (recovered project from previous crossref)
413
414         } // end of loop over db-xrefs for crossref-2
415
416         // fetchdb-->crossref1
417         // for each xref we try to retrieve xref, store and verify when
418         // pass1=0, or just retrieve and verify when pass1=1
419       } while (pass1 == 1 && pass2++ < 2);
420       // fetchdb
421       // for each ref we
422       // loop twice: first, do the retrieve, second recover from saved project
423
424       // increment pass counters, so we repeat traversal starting from the
425       // oldest saved project first.
426       if (pass1 == 0)
427       {
428         // verify stored projects for first set of cross references
429         pass1 = 1;
430         // and verify cross-references retrieved from stored projects
431         pass2 = 0;
432         pass3 = 0;
433       }
434       else
435       {
436         pass1++;
437       }
438     } while (pass1 < 3);
439
440     if (failedXrefMenuItems.size() > 0)
441     {
442       for (String s : failedXrefMenuItems)
443       {
444         System.err.println(s);
445       }
446       Assert.fail("Faulty xref menu (" + failedXrefMenuItems.size()
447               + " counts)");
448     }
449     if (failedProjectRecoveries.size() > 0)
450     {
451
452       for (String s : failedProjectRecoveries)
453       {
454         System.err.println(s);
455       }
456       Assert.fail(
457               "Didn't recover projects for some retrievals (did they retrieve ?) ("
458                       + failedProjectRecoveries.size() + " counts)");
459     }
460     if (failedDBRetr.size() > 0)
461     {
462       for (String s : failedProjectRecoveries)
463       {
464         System.err.println(s);
465       }
466       Assert.fail("Didn't retrieve some db refs for checking cross-refs ("
467               + failedDBRetr.size() + " counts)");
468     }
469   }
470
471   private void filterDbRefs(List<String> ptypes, List<String> limit)
472   {
473     if (limit != null)
474     {
475       int p = 0;
476       while (ptypes.size() > p)
477       {
478         if (!limit.contains(ptypes.get(p)))
479         {
480           ptypes.remove(p);
481         }
482         else
483         {
484           p++;
485         }
486       }
487     }
488   }
489
490   /**
491    * wrapper to trap known defect for AH002001 testcase
492    * 
493    * @param alignment
494    * @param string
495    */
496   private void assertDatasetIsNormalisedKnownDefect(AlignmentI al,
497           String message)
498   {
499     try
500     {
501       AlignmentTest.assertDatasetIsNormalised(al, message);
502     } catch (AssertionError ae)
503     {
504       if (!ae.getMessage().endsWith("EMBL|AH002001"))
505       {
506         throw ae;
507       }
508       else
509       {
510         System.out.println("Ignored exception for known defect: JAL-2179 : "
511                 + message);
512       }
513
514     }
515   }
516
517   private void assertProtein(AlignmentViewPanel alignmentViewPanel,
518           String message)
519   {
520     assertType(true, alignmentViewPanel, message);
521   }
522
523   private void assertNucleotide(AlignmentViewPanel alignmentViewPanel,
524           String message)
525   {
526     assertType(false, alignmentViewPanel, message);
527   }
528
529   private void assertType(boolean expectProtein,
530           AlignmentViewPanel alignmentViewPanel, String message)
531   {
532     List<SequenceI> nonType = new ArrayList<>();
533     for (SequenceI sq : alignmentViewPanel.getAlignViewport().getAlignment()
534             .getSequences())
535     {
536       if (sq.isProtein() != expectProtein)
537       {
538         nonType.add(sq);
539       }
540     }
541     if (nonType.size() > 0)
542     {
543       Assert.fail(message + " [ "
544               + (expectProtein ? "nucleotides were " : "proteins were ")
545               + nonType.toString() + " ]");
546     }
547   }
548
549   /**
550    * first time called, record strings derived from alignment and
551    * alignedcodonframes, and save view to a project file. Second time called,
552    * compare strings to existing ones. org.testng.Assert.assertTrue on
553    * stringmatch
554    * 
555    * @param dbtoviewBit
556    *          map between xrefpath and view string
557    * @param savedProjects
558    *          - map from xrefpath to saved project filename (createTempFile)
559    * @param xrefpath
560    *          - xrefpath - unique ID for this context (composed of sequence of
561    *          db-fetch/cross-ref actions preceeding state)
562    * @param avp
563    *          - viewpanel to store (for viewpanels in splitframe, the same
564    *          project should be written for both panels, only one needs
565    *          recovering for comparison on the next stringify call, but each
566    *          viewpanel needs to be called with a distinct xrefpath to ensure
567    *          each one's strings are compared)
568    */
569   private void stringify(Map<String, String> dbtoviewBit,
570           Map<String, File> savedProjects, String xrefpath,
571           AlignmentViewPanel avp)
572   {
573     if (savedProjects != null)
574     {
575       if (savedProjects.get(xrefpath) == null)
576       {
577         // write a project file for this view. On the second pass, this will be
578         // recovered and cross-references verified
579         try
580         {
581           File prfile = File.createTempFile("crossRefTest", ".jvp");
582           AlignFrame af = Desktop.getAlignFrameFor(avp.getAlignViewport());
583           new Jalview2XML(false).saveAlignment(af, prfile.toString(),
584                   af.getTitle());
585           System.out.println("Written view from '" + xrefpath + "' as '"
586                   + prfile.getAbsolutePath() + "'");
587           savedProjects.put(xrefpath, prfile);
588         } catch (IOException q)
589         {
590           Assert.fail("Unexpected IO Exception", q);
591         }
592       }
593       else
594       {
595         System.out.println("Stringify check on view from '" + xrefpath
596                 + "' [ possibly retrieved from '"
597                 + savedProjects.get(xrefpath).getAbsolutePath() + "' ]");
598
599       }
600     }
601
602     StringBuilder sbr = new StringBuilder();
603     sbr.append(avp.getAlignment().toString());
604     sbr.append("\n");
605     sbr.append("<End of alignment>");
606     sbr.append("\n");
607     sbr.append(avp.getAlignment().getDataset());
608     sbr.append("\n");
609     sbr.append("<End of dataset>");
610     sbr.append("\n");
611     int p = 0;
612     if (avp.getAlignment().getCodonFrames() != null)
613     {
614       for (AlignedCodonFrame ac : avp.getAlignment().getCodonFrames())
615       {
616         sbr.append("<AlignedCodonFrame " + p++ + ">");
617         sbr.append("\n");
618         sbr.append(ac.toString());
619         sbr.append("\n");
620       }
621     }
622     String dbt = dbtoviewBit.get(xrefpath);
623     if (dbt == null)
624     {
625       dbtoviewBit.put(xrefpath, sbr.toString());
626     }
627     else
628     {
629       Assert.assertEquals(sbr.toString(), dbt,
630               "stringify mismatch for " + xrefpath);
631     }
632   }
633 }