dbb3611b432fbd995d54fd61a202a0de87b1b63a
[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,
258                 ret);
259         JPredFile predictions = new JPredFile(ret[0], protocol);
260         JnetAnnotationMaker.add_annotation(predictions,
261                 app.getViewport().getAlignment(), 0, false);
262         // false == do not add sequence profile from concise output
263         app.getViewport().getAlignment().setupJPredAlignment();
264         app.updateForAnnotations();
265         result = true;
266       } catch (Exception ex)
267       {
268         ex.printStackTrace();
269       }
270     }
271     return result;
272   }
273
274   /**
275    * Load annotations if specified by parameter. Returns true if loaded, else
276    * false.
277    * 
278    * @param alignFrame
279    * @return
280    */
281   protected boolean loadAnnotations()
282   {
283     boolean result = false;
284     String param = app.getParameter("annotations");
285     if (param != null)
286     {
287       ret[0] = param;
288       DataSourceType protocol = resolveFileProtocol(app,
289               ret);
290       param = ret[0];
291       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
292               param, protocol))
293       {
294         app.updateForAnnotations();
295         result = true;
296       }
297       else
298       {
299         System.err
300                 .println("Annotations were not added from annotation file '"
301                         + param + "'");
302       }
303     }
304     return result;
305   }
306
307   /**
308    * Load features file and view settings as specified by parameters. Returns
309    * true if features were loaded, else false.
310    * 
311    * @param alignFrame
312    * @return
313    */
314   protected boolean loadFeatures()
315   {
316     boolean result = false;
317     // ///////////////////////////
318     // modify display of features
319     // we do this before any features have been loaded, ensuring any hidden
320     // groups are hidden when features first displayed
321     //
322     // hide specific groups
323     //
324     String param = app.getParameter("hidefeaturegroups");
325     if (param != null)
326     {
327       app.setFeatureGroupState(
328               separatorListToArray(param, separator), false);
329       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
330     }
331     // show specific groups
332     param = app.getParameter("showfeaturegroups");
333     if (param != null)
334     {
335       app.setFeatureGroupState(separatorListToArray(param, separator),
336               true);
337       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
338     }
339     // and now load features
340     param = app.getParameter("features");
341     if (param != null)
342     {
343       ret[0] = param;
344       DataSourceType protocol = resolveFileProtocol(app,
345               ret);
346
347       result = app.parseFeaturesFile(ret[0], protocol);
348     }
349
350     param = app.getParameter("showFeatureSettings");
351     if (param != null && param.equalsIgnoreCase("true"))
352     {
353       app.newFeatureSettings();
354     }
355     return result;
356   }
357
358   /**
359    * Load a score file if specified by parameter. Returns true if file was
360    * loaded, else false.
361    * 
362    * @param loaderFrame
363    */
364   protected boolean loadScoreFile()
365   {
366     boolean result = false;
367     String sScoreFile = app.getParameter("scoreFile");
368     if (sScoreFile != null && !"".equals(sScoreFile))
369     {
370       try
371       {
372         if (debug)
373         {
374           System.err.println(
375                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
376         }
377         result = app.loadScoreFile(sScoreFile);
378         if (!result)
379         {
380           System.err.println(
381                   "Failed to parse T-COFFEE parameter as a valid score file ('"
382                           + sScoreFile + "')");
383         }
384       } catch (Exception e)
385       {
386         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
387                 sScoreFile, e.getMessage());
388       }
389     }
390     return result;
391   }
392
393   String[] ret = new String[1];
394   /**
395    * Load a tree for the alignment if specified by parameter. Returns true if a
396    * tree was loaded, else false.
397    * 
398    * @param loaderFrame
399    * @return
400    */
401   protected boolean loadTree()
402   {
403     boolean result = false;
404     String treeFile = app.getParameter("tree");
405     if (treeFile == null)
406     {
407       treeFile = app.getParameter("treeFile");
408     }
409
410     if (treeFile != null)
411     {
412       try
413       {
414         ret[0] = treeFile;
415         NewickFile fin = new NewickFile(treeFile,
416                 resolveFileProtocol(app, ret));
417         fin.parse();
418
419         if (fin.getTree() != null)
420         {
421           app.loadTree(fin, ret[0]);
422           result = true;
423           if (debug)
424           {
425             System.out.println("Successfully imported tree.");
426           }
427         }
428         else
429         {
430           if (debug)
431           {
432             System.out.println("Tree parameter did not resolve to a valid tree.");
433           }
434         }
435       } catch (Exception ex)
436       {
437         ex.printStackTrace();
438       }
439     }
440     return result;
441   }
442
443   /**
444    * form a complete URL given a path to a resource and a reference location on
445    * the same server
446    * 
447    * @param targetPath
448    *          - an absolute path on the same server as localref or a document
449    *          located relative to localref
450    * @param localref
451    *          - a URL on the same server as url
452    * @return a complete URL for the resource located by url
453    */
454   public static String resolveUrlForLocalOrAbsolute(String targetPath,
455           URL localref)
456   {
457     String resolvedPath = "";
458     if (targetPath.startsWith("/"))
459     {
460       String codebase = localref.toString();
461       String localfile = localref.getFile();
462       resolvedPath = codebase.substring(0,
463               codebase.length() - localfile.length()) + targetPath;
464       return resolvedPath;
465     }
466   
467     /*
468      * get URL path and strip off any trailing file e.g.
469      * www.jalview.org/examples/index.html#applets?a=b is trimmed to
470      * www.jalview.org/examples/
471      */
472     String urlPath = localref.toString();
473     String directoryPath = urlPath;
474     int lastSeparator = directoryPath.lastIndexOf("/");
475     if (lastSeparator > 0)
476     {
477       directoryPath = directoryPath.substring(0, lastSeparator + 1);
478     }
479   
480     if (targetPath.startsWith("/"))
481     {
482       /*
483        * construct absolute URL to a file on the server - this is not allowed?
484        */
485       // String localfile = localref.getFile();
486       // resolvedPath = urlPath.substring(0,
487       // urlPath.length() - localfile.length())
488       // + targetPath;
489       resolvedPath = directoryPath + targetPath.substring(1);
490     }
491     else
492     {
493       resolvedPath = directoryPath + targetPath;
494     }
495     if (JalviewLite.debug)
496     {
497       System.err.println(
498               "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
499     }
500     return resolvedPath;
501   }
502
503   /**
504    * parse the string into a list
505    * 
506    * @param list
507    * @param separator
508    * @return elements separated by separator
509    */
510   public static String[] separatorListToArray(String list, String separator)
511   {
512     // TODO use StringUtils version (slightly different...)
513     int seplen = separator.length();
514     if (list == null || list.equals("") || list.equals(separator))
515     {
516       return null;
517     }
518     Vector<String> jv = new Vector<>();
519     int cp = 0, pos;
520     while ((pos = list.indexOf(separator, cp)) > cp)
521     {
522       jv.addElement(list.substring(cp, pos));
523       cp = pos + seplen;
524     }
525     if (cp < list.length())
526     {
527       String c = list.substring(cp);
528       if (!c.equals(separator))
529       {
530         jv.addElement(c);
531       }
532     }
533     if (jv.size() > 0)
534     {
535       String[] v = new String[jv.size()];
536       for (int i = 0; i < v.length; i++)
537       {
538         v[i] = jv.elementAt(i);
539       }
540       jv.removeAllElements();
541       // if (debug)
542       // {
543       // System.err.println("Array from '" + separator
544       // + "' separated List:\n" + v.length);
545       // for (int i = 0; i < v.length; i++)
546       // {
547       // System.err.println("item " + i + " '" + v[i] + "'");
548       // }
549       // }
550       return v;
551     }
552     // if (debug)
553     // {
554     // System.err.println(
555     // "Empty Array from '" + separator + "' separated List");
556     // }
557     return null;
558   }
559
560   public static DataSourceType resolveFileProtocol(JalviewApp app,
561           String[] retPath)
562   {
563     String path = retPath[0];
564     /*
565      * is it paste data?
566      */
567     if (path.startsWith("PASTE"))
568     {
569       retPath[0] = path.substring(5);
570       return DataSourceType.PASTE;
571     }
572   
573     /*
574      * is it a URL?
575      */
576     if (path.indexOf("://") >= 0)
577     {
578       return DataSourceType.URL;
579     }
580   
581     /*
582      * try relative to document root
583      */
584     URL documentBase = app.getDocumentBase();
585     String withDocBase = resolveUrlForLocalOrAbsolute(path,
586             documentBase);
587     if (HttpUtils.isValidUrl(withDocBase))
588     {
589       // if (debug)
590       // {
591       // System.err.println("Prepended document base '" + documentBase
592       // + "' to make: '" + withDocBase + "'");
593       // }
594       retPath[0] = withDocBase;
595       return DataSourceType.URL;
596     }
597   
598     /*
599      * try relative to codebase (if different to document base)
600      */
601     URL codeBase = app.getCodeBase();
602     String withCodeBase = resolveUrlForLocalOrAbsolute(path,
603             codeBase);
604     if (!withCodeBase.equals(withDocBase)
605             && HttpUtils.isValidUrl(withCodeBase))
606     {
607       // if (debug)
608       // {
609       // System.err.println("Prepended codebase '" + codeBase
610       // + "' to make: '" + withCodeBase + "'");
611       // }
612       retPath[0] = withCodeBase;
613       return DataSourceType.URL;
614     }
615
616     /*
617      * try locating by classloader; try this last so files in the directory
618      * are resolved using document base
619      */
620     if (inArchive(app.getClass(), path))
621     {
622       return DataSourceType.CLASSLOADER;
623     }
624     return null;
625   }
626
627   /**
628    * Discovers whether the given file is in the Applet Archive
629    * 
630    * @param f
631    *          String
632    * @return boolean
633    */
634   private static boolean inArchive(Class<?> c, String f)
635   {
636     // This might throw a security exception in certain browsers
637     // Netscape Communicator for instance.
638     try
639     {
640       boolean rtn = (c.getResourceAsStream("/" + f) != null);
641       // if (debug)
642       // {
643       // System.err.println("Resource '" + f + "' was "
644       // + (rtn ? "" : "not ") + "located by classloader.");
645       // }
646       return rtn;
647     } catch (Exception ex)
648     {
649       System.out.println("Exception checking resources: " + f + " " + ex);
650       return false;
651     }
652   }
653
654   public void callInitCallback()
655   {
656     String initjscallback = app.getParameter("oninit");
657     if (initjscallback == null)
658     {
659       return;
660     }
661     initjscallback = initjscallback.trim();
662     if (initjscallback.length() > 0)
663     {
664       // TODO
665     }
666   }
667
668 }