JalviewAppLoader for moving code from JalviewLite relating to applet
[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;
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   }
69
70   /**
71    * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
72    * else false.
73    * 
74    * @param loaderFrame
75    * @return
76    */
77   protected boolean loadPdbFiles()
78   {
79     boolean result = false;
80     /*
81      * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
82      * related to JAL-434
83      */
84
85     boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
86     app.setAlignPdbStructures(doAlign);
87     /*
88      * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
89      * PDB|1GAQ|1GAQ|C">
90      * 
91      * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
92      * 
93      * <param name="PDBfile3" value="1q0o Q45135_9MICO">
94      */
95
96     int pdbFileCount = 0;
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     String param;
109     do
110     {
111       if (pdbFileCount > 0)
112       {
113         param = app.getParameter("PDBFILE" + pdbFileCount);
114       }
115       else
116       {
117         param = app.getParameter("PDBFILE");
118       }
119
120       if (param != null)
121       {
122         PDBEntry pdb = new PDBEntry();
123
124         String seqstring;
125         SequenceI[] seqs = null;
126         String[] chains = null;
127
128         StringTokenizer st = new StringTokenizer(param, " ");
129
130         if (st.countTokens() < 2)
131         {
132           String sequence = app.getParameter("PDBSEQ");
133           if (sequence != null)
134           {
135             seqs = new SequenceI[] { matcher == null
136                     ? (Sequence) app.getViewport().getAlignment()
137                             .findName(sequence)
138                     : matcher.findIdMatch(sequence) };
139           }
140
141         }
142         else
143         {
144           param = st.nextToken();
145           List<SequenceI> tmp = new ArrayList<>();
146           List<String> tmp2 = new ArrayList<>();
147
148           while (st.hasMoreTokens())
149           {
150             seqstring = st.nextToken();
151             StringTokenizer st2 = new StringTokenizer(seqstring, "=");
152             if (st2.countTokens() > 1)
153             {
154               // This is the chain
155               tmp2.add(st2.nextToken());
156               seqstring = st2.nextToken();
157             }
158             tmp.add(matcher == null
159                     ? (Sequence) app.getViewport().getAlignment()
160                             .findName(seqstring)
161                     : matcher.findIdMatch(seqstring));
162           }
163
164           seqs = tmp.toArray(new SequenceI[tmp.size()]);
165           if (tmp2.size() == tmp.size())
166           {
167             chains = tmp2.toArray(new String[tmp2.size()]);
168           }
169         }
170         ret[0] = param;
171         DataSourceType protocol = resolveFileProtocol(app,
172                 ret);
173         // TODO check JAL-357 for files in a jar (CLASSLOADER)
174         pdb.setFile(ret[0]);
175
176         if (seqs != null)
177         {
178           for (int i = 0; i < seqs.length; i++)
179           {
180             if (seqs[i] != null)
181             {
182               ((Sequence) seqs[i]).addPDBId(pdb);
183               StructureSelectionManager
184                       .getStructureSelectionManager(
185                               (StructureSelectionManagerProvider) app)
186                       .registerPDBEntry(pdb);
187             }
188             else
189             {
190               if (debug)
191               {
192                 // this may not really be a problem but we give a warning
193                 // anyway
194                 System.err.println(
195                         "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
196                                 + i + ")");
197               }
198             }
199           }
200
201           if (doAlign)
202           {
203             pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
204           }
205           else
206           {
207             app.newStructureView(pdb, seqs, chains, protocol);
208           }
209         }
210       }
211
212       pdbFileCount++;
213     } while (param != null || pdbFileCount < 10);
214     if (pdbs.size() > 0)
215     {
216       SequenceI[][] seqs = new SequenceI[pdbs.size()][];
217       PDBEntry[] pdb = new PDBEntry[pdbs.size()];
218       String[][] chains = new String[pdbs.size()][];
219       String[] protocols = new String[pdbs.size()];
220       for (int pdbsi = 0, pdbsiSize = pdbs
221               .size(); pdbsi < pdbsiSize; pdbsi++)
222       {
223         Object[] o = pdbs.elementAt(pdbsi);
224         pdb[pdbsi] = (PDBEntry) o[0];
225         seqs[pdbsi] = (SequenceI[]) o[1];
226         chains[pdbsi] = (String[]) o[2];
227         protocols[pdbsi] = (String) o[3];
228       }
229       app.alignedStructureView(pdb, seqs, chains, protocols);
230       result = true;
231     }
232     return result;
233   }
234
235   /**
236    * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
237    * false.
238    * 
239    * @param alignFrame
240    * @return
241    */
242   protected boolean loadJnetFile()
243   {
244     boolean result = false;
245     String param = app.getParameter("jnetfile");
246     if (param == null)
247     {
248       // jnet became jpred around 2016
249       param = app.getParameter("jpredfile");
250     }
251     if (param != null)
252     {
253       try
254       {
255         ret[0] = param;
256         DataSourceType protocol = resolveFileProtocol(app,
257                 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.updateForLoader();
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,
288               ret);
289       param = ret[0];
290       if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
291               param, protocol))
292       {
293         app.updateForLoader();
294         result = true;
295       }
296       else
297       {
298         System.err
299                 .println("Annotations were not added from annotation file '"
300                         + param + "'");
301       }
302     }
303     return result;
304   }
305
306   /**
307    * Load features file and view settings as specified by parameters. Returns
308    * true if features were loaded, else false.
309    * 
310    * @param alignFrame
311    * @return
312    */
313   protected boolean loadFeatures()
314   {
315     boolean result = false;
316     // ///////////////////////////
317     // modify display of features
318     // we do this before any features have been loaded, ensuring any hidden
319     // groups are hidden when features first displayed
320     //
321     // hide specific groups
322     //
323     String param = app.getParameter("hidefeaturegroups");
324     if (param != null)
325     {
326       app.setFeatureGroupState(
327               separatorListToArray(param, separator), false);
328       // app.setFeatureGroupStateOn(newAlignFrame, param, false);
329     }
330     // show specific groups
331     param = app.getParameter("showfeaturegroups");
332     if (param != null)
333     {
334       app.setFeatureGroupState(separatorListToArray(param, separator),
335               true);
336       // app.setFeatureGroupStateOn(newAlignFrame, param, true);
337     }
338     // and now load features
339     param = app.getParameter("features");
340     if (param != null)
341     {
342       ret[0] = param;
343       DataSourceType protocol = resolveFileProtocol(app,
344               ret);
345
346       result = app.parseFeaturesFile(param, protocol);
347     }
348
349     param = app.getParameter("showFeatureSettings");
350     if (param != null && param.equalsIgnoreCase("true"))
351     {
352       app.newFeatureSettings();
353     }
354     return result;
355   }
356
357   /**
358    * Load a score file if specified by parameter. Returns true if file was
359    * loaded, else false.
360    * 
361    * @param loaderFrame
362    */
363   protected boolean loadScoreFile()
364   {
365     boolean result = false;
366     String sScoreFile = app.getParameter("scoreFile");
367     if (sScoreFile != null && !"".equals(sScoreFile))
368     {
369       try
370       {
371         if (debug)
372         {
373           System.err.println(
374                   "Attempting to load T-COFFEE score file from the scoreFile parameter");
375         }
376         result = app.loadScoreFile(sScoreFile);
377         if (!result)
378         {
379           System.err.println(
380                   "Failed to parse T-COFFEE parameter as a valid score file ('"
381                           + sScoreFile + "')");
382         }
383       } catch (Exception e)
384       {
385         System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
386                 sScoreFile, e.getMessage());
387       }
388     }
389     return result;
390   }
391
392   String[] ret = new String[1];
393   /**
394    * Load a tree for the alignment if specified by parameter. Returns true if a
395    * tree was loaded, else false.
396    * 
397    * @param loaderFrame
398    * @return
399    */
400   protected boolean loadTree()
401   {
402     boolean result = false;
403     String treeFile = app.getParameter("tree");
404     if (treeFile == null)
405     {
406       treeFile = app.getParameter("treeFile");
407     }
408
409     if (treeFile != null)
410     {
411       try
412       {
413         ret[0] = treeFile;
414         NewickFile fin = new NewickFile(treeFile,
415                 resolveFileProtocol(app, ret));
416         fin.parse();
417
418         if (fin.getTree() != null)
419         {
420           app.loadTree(fin, ret[0]);
421           result = true;
422           if (debug)
423           {
424             System.out.println("Successfully imported tree.");
425           }
426         }
427         else
428         {
429           if (debug)
430           {
431             System.out.println("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 (JalviewLite.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,
585             documentBase);
586     if (HttpUtils.isValidUrl(withDocBase))
587     {
588       // if (debug)
589       // {
590       // System.err.println("Prepended document base '" + documentBase
591       // + "' to make: '" + withDocBase + "'");
592       // }
593       retPath[0] = withDocBase;
594       return DataSourceType.URL;
595     }
596   
597     /*
598      * try relative to codebase (if different to document base)
599      */
600     URL codeBase = app.getCodeBase();
601     String withCodeBase = resolveUrlForLocalOrAbsolute(path,
602             codeBase);
603     if (!withCodeBase.equals(withDocBase)
604             && HttpUtils.isValidUrl(withCodeBase))
605     {
606       // if (debug)
607       // {
608       // System.err.println("Prepended codebase '" + codeBase
609       // + "' to make: '" + withCodeBase + "'");
610       // }
611       retPath[0] = withCodeBase;
612       return DataSourceType.URL;
613     }
614
615     /*
616      * try locating by classloader; try this last so files in the directory
617      * are resolved using document base
618      */
619     if (inArchive(app.getClass(), path))
620     {
621       return DataSourceType.CLASSLOADER;
622     }
623     return null;
624   }
625
626   /**
627    * Discovers whether the given file is in the Applet Archive
628    * 
629    * @param f
630    *          String
631    * @return boolean
632    */
633   private static boolean inArchive(Class<?> c, String f)
634   {
635     // This might throw a security exception in certain browsers
636     // Netscape Communicator for instance.
637     try
638     {
639       boolean rtn = (c.getResourceAsStream("/" + f) != null);
640       // if (debug)
641       // {
642       // System.err.println("Resource '" + f + "' was "
643       // + (rtn ? "" : "not ") + "located by classloader.");
644       // }
645       return rtn;
646     } catch (Exception ex)
647     {
648       System.out.println("Exception checking resources: " + f + " " + ex);
649       return false;
650     }
651   }
652
653 }