Jalview-JS/JAL-3253-applet JAL-3192 secondary core files
[jalview.git] / src / jalview / bin / JalviewAppLoader.java
1 package jalview.bin;
2
3 import jalview.api.JalviewApp;
4 import jalview.api.StructureSelectionManagerProvider;
5 import jalview.datamodel.PDBEntry;
6 import jalview.datamodel.Sequence;
7 import jalview.datamodel.SequenceI;
8 import jalview.io.AnnotationFile;
9 import jalview.io.DataSourceType;
10 import jalview.io.JPredFile;
11 import jalview.io.JnetAnnotationMaker;
12 import jalview.io.NewickFile;
13 import jalview.structure.StructureSelectionManager;
14 import jalview.util.HttpUtils;
15 import jalview.util.MessageManager;
16
17 import java.net.URL;
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.StringTokenizer;
21 import java.util.Vector;
22
23 /**
24  * A class to load parameters for either JalviewLite or Jalview
25  * 
26  * @author hansonr
27  *
28  */
29 public class JalviewAppLoader
30 {
31
32   private JalviewApp app; // Jalview or JalviewJS or JalviewLite
33
34   private boolean debug;
35
36   private String separator;
37
38   public JalviewAppLoader(boolean debug)
39   {
40     this.debug = debug;
41   }
42
43   public void load(JalviewApp app)
44   {
45
46     this.app = app;
47
48     String sep = app.getParameter("separator");
49     if (sep != null)
50     {
51       if (sep.length() > 0)
52       {
53         separator = sep;
54       }
55       else
56       {
57         throw new Error(MessageManager
58                 .getString("error.invalid_separator_parameter"));
59       }
60     }
61
62     loadTree();
63     loadScoreFile();
64     loadFeatures();
65     loadAnnotations();
66     loadJnetFile();
67     loadPdbFiles();
68     callInitCallback();
69   }
70
71   /**
72    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
73    * else false.
74    * 
75    * @param loaderFrame
76    * @return
77    */
78   protected boolean loadPdbFiles()
79   {
80     boolean result = false;
81     /*
82      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
83      * related to JAL-434
84      */
85
86     boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
87     app.setAlignPdbStructures(doAlign);
88     /*
89      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
90      * PDB|1GAQ|1GAQ|C">
91      * 
92      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
93      * 
94      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
95      */
96
97     // Accumulate pdbs here if they are heading for the same view (if
98     // alignPdbStructures is true)
99     Vector<Object[]> pdbs = new Vector<>();
100     // create a lazy matcher if we're asked to
101     jalview.analysis.SequenceIdMatcher matcher = (app
102             .getDefaultParameter("relaxedidmatch", false))
103                     ? new jalview.analysis.SequenceIdMatcher(
104                             app.getViewport().getAlignment()
105                                     .getSequencesArray())
106                     : null;
107
108     int pdbFileCount = 0;
109     String param;
110     do
111     {
112       if (pdbFileCount > 0)
113       {
114         param = app.getParameter("PDBFILE" + pdbFileCount);
115       }
116       else
117       {
118         param = app.getParameter("PDBFILE");
119       }
120
121       if (param != null)
122       {
123         PDBEntry pdb = new PDBEntry();
124
125         String seqstring;
126         SequenceI[] seqs = null;
127         String[] chains = null;
128
129         StringTokenizer st = new StringTokenizer(param, " ");
130
131         if (st.countTokens() < 2)
132         {
133           String sequence = app.getParameter("PDBSEQ");
134           if (sequence != null)
135           {
136             seqs = new SequenceI[] { matcher == null
137                     ? (Sequence) app.getViewport().getAlignment()
138                             .findName(sequence)
139                     : matcher.findIdMatch(sequence) };
140           }
141
142         }
143         else
144         {
145           param = st.nextToken();
146           List<SequenceI> tmp = new ArrayList<>();
147           List<String> tmp2 = new ArrayList<>();
148
149           while (st.hasMoreTokens())
150           {
151             seqstring = st.nextToken();
152             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
153             if (st2.countTokens() > 1)
154             {
155               // This is the chain
156               tmp2.add(st2.nextToken());
157               seqstring = st2.nextToken();
158             }
159             tmp.add(matcher == null
160                     ? (Sequence) app.getViewport().getAlignment()
161                             .findName(seqstring)
162                     : matcher.findIdMatch(seqstring));
163           }
164
165           seqs = tmp.toArray(new SequenceI[tmp.size()]);
166           if (tmp2.size() == tmp.size())
167           {
168             chains = tmp2.toArray(new String[tmp2.size()]);
169           }
170         }
171         pdb.setId(param);
172         ret[0] = param;
173         DataSourceType protocol = resolveFileProtocol(app, ret);
174         // TODO check JAL-357 for files in a jar (CLASSLOADER)
175         pdb.setFile(ret[0]);
176
177         if (seqs != null)
178         {
179           for (int i = 0; i < seqs.length; i++)
180           {
181             if (seqs[i] != null)
182             {
183               ((Sequence) seqs[i]).addPDBId(pdb);
184               StructureSelectionManager
185                       .getStructureSelectionManager(
186                               (StructureSelectionManagerProvider) app)
187                       .registerPDBEntry(pdb);
188             }
189             else
190             {
191               if (debug)
192               {
193                 // this may not really be a problem but we give a warning
194                 // anyway
195                 System.err.println(
196                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
197                                 + i + ")");
198               }
199             }
200           }
201
202           if (doAlign)
203           {
204             pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
205           }
206           else
207           {
208             app.newStructureView(pdb, seqs, chains, protocol);
209           }
210         }
211       }
212
213       pdbFileCount++;
214     } while (param != null || pdbFileCount < 10);
215     if (pdbs.size() > 0)
216     {
217       SequenceI[][] seqs = new SequenceI[pdbs.size()][];
218       PDBEntry[] pdb = new PDBEntry[pdbs.size()];
219       String[][] chains = new String[pdbs.size()][];
220       String[] protocols = new String[pdbs.size()];
221       for (int pdbsi = 0, pdbsiSize = pdbs
222               .size(); pdbsi < pdbsiSize; pdbsi++)
223       {
224         Object[] o = pdbs.elementAt(pdbsi);
225         pdb[pdbsi] = (PDBEntry) o[0];
226         seqs[pdbsi] = (SequenceI[]) o[1];
227         chains[pdbsi] = (String[]) o[2];
228         protocols[pdbsi] = (String) o[3];
229       }
230       app.alignedStructureView(pdb, seqs, chains, protocols);
231       result = true;
232     }
233     return result;
234   }
235
236   /**
237    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
238    * false.
239    * 
240    * @param alignFrame
241    * @return
242    */
243   protected boolean loadJnetFile()
244   {
245     boolean result = false;
246     String param = app.getParameter("jnetfile");
247     if (param == null)
248     {
249       // jnet became jpred around 2016
250       param = app.getParameter("jpredfile");
251     }
252     if (param != null)
253     {
254       try
255       {
256         ret[0] = param;
257         DataSourceType protocol = resolveFileProtocol(app, ret);
258         JPredFile predictions = new JPredFile(ret[0], protocol);
259         JnetAnnotationMaker.add_annotation(predictions,
260                 app.getViewport().getAlignment(), 0, false);
261         // false == do not add sequence profile from concise output
262         app.getViewport().getAlignment().setupJPredAlignment();
263         app.updateForAnnotations();
264         result = true;
265       } catch (Exception ex)
266       {
267         ex.printStackTrace();
268       }
269     }
270     return result;
271   }
272
273   /**
274    * Load annotations if specified by parameter. Returns true if loaded, else
275    * false.
276    * 
277    * @param alignFrame
278    * @return
279    */
280   protected boolean loadAnnotations()
281   {
282     boolean result = false;
283     String param = app.getParameter("annotations");
284     if (param != null)
285     {
286       ret[0] = param;
287       DataSourceType protocol = resolveFileProtocol(app, ret);
288       param = ret[0];
289       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
290               param, protocol))
291       {
292         app.updateForAnnotations();
293         result = true;
294       }
295       else
296       {
297         System.err
298                 .println("Annotations were not added from annotation file '"
299                         + param + "'");
300       }
301     }
302     return result;
303   }
304
305   /**
306    * Load features file and view settings as specified by parameters. Returns
307    * true if features were loaded, else false.
308    * 
309    * @param alignFrame
310    * @return
311    */
312   protected boolean loadFeatures()
313   {
314     boolean result = false;
315     // ///////////////////////////
316     // modify display of features
317     // we do this before any features have been loaded, ensuring any hidden
318     // groups are hidden when features first displayed
319     //
320     // hide specific groups
321     //
322     String param = app.getParameter("hidefeaturegroups");
323     if (param != null)
324     {
325       app.setFeatureGroupState(separatorListToArray(param, separator),
326               false);
327       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
328     }
329     // show specific groups
330     param = app.getParameter("showfeaturegroups");
331     if (param != null)
332     {
333       app.setFeatureGroupState(separatorListToArray(param, separator),
334               true);
335       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
336     }
337     // and now load features
338     param = app.getParameter("features");
339     if (param != null)
340     {
341       ret[0] = param;
342       DataSourceType protocol = resolveFileProtocol(app, ret);
343
344       result = app.parseFeaturesFile(ret[0], protocol);
345     }
346
347     param = app.getParameter("showFeatureSettings");
348     if (param != null && param.equalsIgnoreCase("true"))
349     {
350       app.newFeatureSettings();
351     }
352     return result;
353   }
354
355   /**
356    * Load a score file if specified by parameter. Returns true if file was
357    * loaded, else false.
358    * 
359    * @param loaderFrame
360    */
361   protected boolean loadScoreFile()
362   {
363     boolean result = false;
364     String sScoreFile = app.getParameter("scoreFile");
365     if (sScoreFile != null && !"".equals(sScoreFile))
366     {
367       try
368       {
369         if (debug)
370         {
371           System.err.println(
372                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
373         }
374         result = app.loadScoreFile(sScoreFile);
375         if (!result)
376         {
377           System.err.println(
378                   "Failed to parse T-COFFEE parameter as a valid score file ('"
379                           + sScoreFile + "')");
380         }
381       } catch (Exception e)
382       {
383         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
384                 sScoreFile, e.getMessage());
385       }
386     }
387     return result;
388   }
389
390   String[] ret = new String[1];
391
392   /**
393    * Load a tree for the alignment if specified by parameter. Returns true if a
394    * tree was loaded, else false.
395    * 
396    * @param loaderFrame
397    * @return
398    */
399   protected boolean loadTree()
400   {
401     boolean result = false;
402     String treeFile = app.getParameter("tree");
403     if (treeFile == null)
404     {
405       treeFile = app.getParameter("treeFile");
406     }
407
408     if (treeFile != null)
409     {
410       try
411       {
412         ret[0] = treeFile;
413         NewickFile fin = new NewickFile(treeFile,
414                 resolveFileProtocol(app, ret));
415         fin.parse();
416
417         if (fin.getTree() != null)
418         {
419           app.loadTree(fin, ret[0]);
420           result = true;
421           if (debug)
422           {
423             System.out.println("Successfully imported tree.");
424           }
425         }
426         else
427         {
428           if (debug)
429           {
430             System.out.println(
431                     "Tree parameter did not resolve to a valid tree.");
432           }
433         }
434       } catch (Exception ex)
435       {
436         ex.printStackTrace();
437       }
438     }
439     return result;
440   }
441
442   /**
443    * form a complete URL given a path to a resource and a reference location on
444    * the same server
445    * 
446    * @param targetPath
447    *          - an absolute path on the same server as localref or a document
448    *          located relative to localref
449    * @param localref
450    *          - a URL on the same server as url
451    * @return a complete URL for the resource located by url
452    */
453   public static String resolveUrlForLocalOrAbsolute(String targetPath,
454           URL localref)
455   {
456     String resolvedPath = "";
457     if (targetPath.startsWith("/"))
458     {
459       String codebase = localref.toString();
460       String localfile = localref.getFile();
461       resolvedPath = codebase.substring(0,
462               codebase.length() - localfile.length()) + targetPath;
463       return resolvedPath;
464     }
465
466     /*
467      * get URL path and strip off any trailing file e.g.
468      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
469      * www.jalview.org/examples/
470      */
471     String urlPath = localref.toString();
472     String directoryPath = urlPath;
473     int lastSeparator = directoryPath.lastIndexOf("/");
474     if (lastSeparator > 0)
475     {
476       directoryPath = directoryPath.substring(0, lastSeparator + 1);
477     }
478
479     if (targetPath.startsWith("/"))
480     {
481       /*
482        * construct absolute URL to a file on the server - this is not allowed?
483        */
484       // String localfile = localref.getFile();
485       // resolvedPath = urlPath.substring(0,
486       // urlPath.length() - localfile.length())
487       // + targetPath;
488       resolvedPath = directoryPath + targetPath.substring(1);
489     }
490     else
491     {
492       resolvedPath = directoryPath + targetPath;
493     }
494     // if (debug)
495     // {
496     // System.err.println(
497     // "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
498     // }
499     return resolvedPath;
500   }
501
502   /**
503    * parse the string into a list
504    * 
505    * @param list
506    * @param separator
507    * @return elements separated by separator
508    */
509   public static String[] separatorListToArray(String list, String separator)
510   {
511     // TODO use StringUtils version (slightly different...)
512     int seplen = separator.length();
513     if (list == null || list.equals("") || list.equals(separator))
514     {
515       return null;
516     }
517     Vector<String> jv = new Vector<>();
518     int cp = 0, pos;
519     while ((pos = list.indexOf(separator, cp)) > cp)
520     {
521       jv.addElement(list.substring(cp, pos));
522       cp = pos + seplen;
523     }
524     if (cp < list.length())
525     {
526       String c = list.substring(cp);
527       if (!c.equals(separator))
528       {
529         jv.addElement(c);
530       }
531     }
532     if (jv.size() > 0)
533     {
534       String[] v = new String[jv.size()];
535       for (int i = 0; i < v.length; i++)
536       {
537         v[i] = jv.elementAt(i);
538       }
539       jv.removeAllElements();
540       // if (debug)
541       // {
542       // System.err.println("Array from '" + separator
543       // + "' separated List:\n" + v.length);
544       // for (int i = 0; i < v.length; i++)
545       // {
546       // System.err.println("item " + i + " '" + v[i] + "'");
547       // }
548       // }
549       return v;
550     }
551     // if (debug)
552     // {
553     // System.err.println(
554     // "Empty Array from '" + separator + "' separated List");
555     // }
556     return null;
557   }
558
559   public static DataSourceType resolveFileProtocol(JalviewApp app,
560           String[] retPath)
561   {
562     String path = retPath[0];
563     /*
564      * is it paste data?
565      */
566     if (path.startsWith("PASTE"))
567     {
568       retPath[0] = path.substring(5);
569       return DataSourceType.PASTE;
570     }
571
572     /*
573      * is it a URL?
574      */
575     if (path.indexOf("://") >= 0)
576     {
577       return DataSourceType.URL;
578     }
579
580     /*
581      * try relative to document root
582      */
583     URL documentBase = app.getDocumentBase();
584     String withDocBase = resolveUrlForLocalOrAbsolute(path, documentBase);
585     if (HttpUtils.isValidUrl(withDocBase))
586     {
587       // if (debug)
588       // {
589       // System.err.println("Prepended document base '" + documentBase
590       // + "' to make: '" + withDocBase + "'");
591       // }
592       retPath[0] = withDocBase;
593       return DataSourceType.URL;
594     }
595
596     /*
597      * try relative to codebase (if different to document base)
598      */
599     URL codeBase = app.getCodeBase();
600     String withCodeBase = resolveUrlForLocalOrAbsolute(path, codeBase);
601     if (!withCodeBase.equals(withDocBase)
602             && HttpUtils.isValidUrl(withCodeBase))
603     {
604       // if (debug)
605       // {
606       // System.err.println("Prepended codebase '" + codeBase
607       // + "' to make: '" + withCodeBase + "'");
608       // }
609       retPath[0] = withCodeBase;
610       return DataSourceType.URL;
611     }
612
613     /*
614      * try locating by classloader; try this last so files in the directory
615      * are resolved using document base
616      */
617     if (inArchive(app.getClass(), path))
618     {
619       return DataSourceType.CLASSLOADER;
620     }
621     return null;
622   }
623
624   /**
625    * Discovers whether the given file is in the Applet Archive
626    * 
627    * @param f
628    *          String
629    * @return boolean
630    */
631   private static boolean inArchive(Class<?> c, String f)
632   {
633     // This might throw a security exception in certain browsers
634     // Netscape Communicator for instance.
635     try
636     {
637       boolean rtn = (c.getResourceAsStream("/" + f) != null);
638       // if (debug)
639       // {
640       // System.err.println("Resource '" + f + "' was "
641       // + (rtn ? "" : "not ") + "located by classloader.");
642       // }
643       return rtn;
644     } catch (Exception ex)
645     {
646       System.out.println("Exception checking resources: " + f + " " + ex);
647       return false;
648     }
649   }
650
651   public void callInitCallback()
652   {
653     String initjscallback = app.getParameter("oninit");
654     if (initjscallback == null)
655     {
656       return;
657     }
658     initjscallback = initjscallback.trim();
659     if (initjscallback.length() > 0)
660     {
661       // TODO
662     }
663   }
664
665   /**
666    * read sequence1...sequenceN as a raw alignment
667    * 
668    * @param jalviewApp
669    * @return
670    */
671   public String getPastedSequence(JalviewApp jalviewApp)
672   {
673     StringBuffer data = new StringBuffer("PASTE");
674     int i = 1;
675     String file = null;
676     while ((file = app.getParameter("sequence" + i)) != null)
677     {
678       data.append(file.toString() + "\n");
679       i++;
680     }
681     if (data.length() > 5)
682     {
683       file = data.toString();
684     }
685     return file;
686   }
687
688   /**
689    * concatenate the list with separator
690    * 
691    * @param list
692    * @param separator
693    * @return concatenated string
694    */
695   public static String arrayToSeparatorList(String[] list, String separator)
696   {
697     // TODO use StringUtils version
698     StringBuffer v = new StringBuffer();
699     if (list != null && list.length > 0)
700     {
701       for (int i = 0, iSize = list.length; i < iSize; i++)
702       {
703         if (list[i] != null)
704         {
705           if (i > 0)
706           {
707             v.append(separator);
708           }
709           v.append(list[i]);
710         }
711       }
712       // if (debug)
713       // {
714       // System.err
715       // .println("Returning '" + separator + "' separated List:\n");
716       // System.err.println(v);
717       // }
718       return v.toString();
719     }
720     // if (debug)
721     // {
722     // System.err.println(
723     // "Returning empty '" + separator + "' separated List\n");
724     // }
725     return "" + separator;
726   }
727
728   public String arrayToSeparatorList(String[] array)
729   {
730     return arrayToSeparatorList(array, separator);
731   }
732
733 }