JalviewAppLoader for moving code from JalviewLite relating to applet
[jalview.git] / src / jalview / bin / JalviewAppLoader.java
diff --git a/src/jalview/bin/JalviewAppLoader.java b/src/jalview/bin/JalviewAppLoader.java
new file mode 100644 (file)
index 0000000..5d60cf9
--- /dev/null
@@ -0,0 +1,653 @@
+package jalview.bin;
+
+import jalview.api.JalviewApp;
+import jalview.api.StructureSelectionManagerProvider;
+import jalview.datamodel.PDBEntry;
+import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceI;
+import jalview.io.AnnotationFile;
+import jalview.io.DataSourceType;
+import jalview.io.JPredFile;
+import jalview.io.JnetAnnotationMaker;
+import jalview.io.NewickFile;
+import jalview.structure.StructureSelectionManager;
+import jalview.util.HttpUtils;
+import jalview.util.MessageManager;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+/**
+ * A class to load parameters for either JalviewLite or Jalview
+ * 
+ * @author hansonr
+ *
+ */
+public class JalviewAppLoader
+{
+
+  private JalviewApp app;
+
+  private boolean debug;
+
+  private String separator;
+
+  public JalviewAppLoader(boolean debug)
+  {
+    this.debug = debug;
+  }
+
+  public void load(JalviewApp app)
+  {
+
+    this.app = app;
+
+    String sep = app.getParameter("separator");
+    if (sep != null)
+    {
+      if (sep.length() > 0)
+      {
+        separator = sep;
+      }
+      else
+      {
+        throw new Error(MessageManager
+                .getString("error.invalid_separator_parameter"));
+      }
+    }
+
+    loadTree();
+    loadScoreFile();
+    loadFeatures();
+    loadAnnotations();
+    loadJnetFile();
+    loadPdbFiles();
+  }
+
+  /**
+   * Load PDBFiles if any specified by parameter(s). Returns true if loaded,
+   * else false.
+   * 
+   * @param loaderFrame
+   * @return
+   */
+  protected boolean loadPdbFiles()
+  {
+    boolean result = false;
+    /*
+     * <param name="alignpdbfiles" value="false/true"/> Undocumented for 2.6 -
+     * related to JAL-434
+     */
+
+    boolean doAlign = app.getDefaultParameter("alignpdbfiles", false);
+    app.setAlignPdbStructures(doAlign);
+    /*
+     * <param name="PDBfile" value="1gaq.txt PDB|1GAQ|1GAQ|A PDB|1GAQ|1GAQ|B
+     * PDB|1GAQ|1GAQ|C">
+     * 
+     * <param name="PDBfile2" value="1gaq.txt A=SEQA B=SEQB C=SEQB">
+     * 
+     * <param name="PDBfile3" value="1q0o Q45135_9MICO">
+     */
+
+    int pdbFileCount = 0;
+    // Accumulate pdbs here if they are heading for the same view (if
+    // alignPdbStructures is true)
+    Vector<Object[]> pdbs = new Vector<>();
+    // create a lazy matcher if we're asked to
+    jalview.analysis.SequenceIdMatcher matcher = (app
+            .getDefaultParameter("relaxedidmatch", false))
+                    ? new jalview.analysis.SequenceIdMatcher(
+                            app.getViewport().getAlignment()
+                                    .getSequencesArray())
+                    : null;
+
+    String param;
+    do
+    {
+      if (pdbFileCount > 0)
+      {
+        param = app.getParameter("PDBFILE" + pdbFileCount);
+      }
+      else
+      {
+        param = app.getParameter("PDBFILE");
+      }
+
+      if (param != null)
+      {
+        PDBEntry pdb = new PDBEntry();
+
+        String seqstring;
+        SequenceI[] seqs = null;
+        String[] chains = null;
+
+        StringTokenizer st = new StringTokenizer(param, " ");
+
+        if (st.countTokens() < 2)
+        {
+          String sequence = app.getParameter("PDBSEQ");
+          if (sequence != null)
+          {
+            seqs = new SequenceI[] { matcher == null
+                    ? (Sequence) app.getViewport().getAlignment()
+                            .findName(sequence)
+                    : matcher.findIdMatch(sequence) };
+          }
+
+        }
+        else
+        {
+          param = st.nextToken();
+          List<SequenceI> tmp = new ArrayList<>();
+          List<String> tmp2 = new ArrayList<>();
+
+          while (st.hasMoreTokens())
+          {
+            seqstring = st.nextToken();
+            StringTokenizer st2 = new StringTokenizer(seqstring, "=");
+            if (st2.countTokens() > 1)
+            {
+              // This is the chain
+              tmp2.add(st2.nextToken());
+              seqstring = st2.nextToken();
+            }
+            tmp.add(matcher == null
+                    ? (Sequence) app.getViewport().getAlignment()
+                            .findName(seqstring)
+                    : matcher.findIdMatch(seqstring));
+          }
+
+          seqs = tmp.toArray(new SequenceI[tmp.size()]);
+          if (tmp2.size() == tmp.size())
+          {
+            chains = tmp2.toArray(new String[tmp2.size()]);
+          }
+        }
+        ret[0] = param;
+        DataSourceType protocol = resolveFileProtocol(app,
+                ret);
+        // TODO check JAL-357 for files in a jar (CLASSLOADER)
+        pdb.setFile(ret[0]);
+
+        if (seqs != null)
+        {
+          for (int i = 0; i < seqs.length; i++)
+          {
+            if (seqs[i] != null)
+            {
+              ((Sequence) seqs[i]).addPDBId(pdb);
+              StructureSelectionManager
+                      .getStructureSelectionManager(
+                              (StructureSelectionManagerProvider) app)
+                      .registerPDBEntry(pdb);
+            }
+            else
+            {
+              if (debug)
+              {
+                // this may not really be a problem but we give a warning
+                // anyway
+                System.err.println(
+                        "Warning: Possible input parsing error: Null sequence for attachment of PDB (sequence "
+                                + i + ")");
+              }
+            }
+          }
+
+          if (doAlign)
+          {
+            pdbs.addElement(new Object[] { pdb, seqs, chains, protocol });
+          }
+          else
+          {
+            app.newStructureView(pdb, seqs, chains, protocol);
+          }
+        }
+      }
+
+      pdbFileCount++;
+    } while (param != null || pdbFileCount < 10);
+    if (pdbs.size() > 0)
+    {
+      SequenceI[][] seqs = new SequenceI[pdbs.size()][];
+      PDBEntry[] pdb = new PDBEntry[pdbs.size()];
+      String[][] chains = new String[pdbs.size()][];
+      String[] protocols = new String[pdbs.size()];
+      for (int pdbsi = 0, pdbsiSize = pdbs
+              .size(); pdbsi < pdbsiSize; pdbsi++)
+      {
+        Object[] o = pdbs.elementAt(pdbsi);
+        pdb[pdbsi] = (PDBEntry) o[0];
+        seqs[pdbsi] = (SequenceI[]) o[1];
+        chains[pdbsi] = (String[]) o[2];
+        protocols[pdbsi] = (String) o[3];
+      }
+      app.alignedStructureView(pdb, seqs, chains, protocols);
+      result = true;
+    }
+    return result;
+  }
+
+  /**
+   * Load in a Jnetfile if specified by parameter. Returns true if loaded, else
+   * false.
+   * 
+   * @param alignFrame
+   * @return
+   */
+  protected boolean loadJnetFile()
+  {
+    boolean result = false;
+    String param = app.getParameter("jnetfile");
+    if (param == null)
+    {
+      // jnet became jpred around 2016
+      param = app.getParameter("jpredfile");
+    }
+    if (param != null)
+    {
+      try
+      {
+        ret[0] = param;
+        DataSourceType protocol = resolveFileProtocol(app,
+                ret);
+        JPredFile predictions = new JPredFile(ret[0], protocol);
+        JnetAnnotationMaker.add_annotation(predictions,
+                app.getViewport().getAlignment(), 0, false);
+        // false == do not add sequence profile from concise output
+        app.getViewport().getAlignment().setupJPredAlignment();
+        app.updateForLoader();
+        result = true;
+      } catch (Exception ex)
+      {
+        ex.printStackTrace();
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Load annotations if specified by parameter. Returns true if loaded, else
+   * false.
+   * 
+   * @param alignFrame
+   * @return
+   */
+  protected boolean loadAnnotations()
+  {
+    boolean result = false;
+    String param = app.getParameter("annotations");
+    if (param != null)
+    {
+      ret[0] = param;
+      DataSourceType protocol = resolveFileProtocol(app,
+              ret);
+      param = ret[0];
+      if (new AnnotationFile().annotateAlignmentView(app.getViewport(),
+              param, protocol))
+      {
+        app.updateForLoader();
+        result = true;
+      }
+      else
+      {
+        System.err
+                .println("Annotations were not added from annotation file '"
+                        + param + "'");
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Load features file and view settings as specified by parameters. Returns
+   * true if features were loaded, else false.
+   * 
+   * @param alignFrame
+   * @return
+   */
+  protected boolean loadFeatures()
+  {
+    boolean result = false;
+    // ///////////////////////////
+    // modify display of features
+    // we do this before any features have been loaded, ensuring any hidden
+    // groups are hidden when features first displayed
+    //
+    // hide specific groups
+    //
+    String param = app.getParameter("hidefeaturegroups");
+    if (param != null)
+    {
+      app.setFeatureGroupState(
+              separatorListToArray(param, separator), false);
+      // app.setFeatureGroupStateOn(newAlignFrame, param, false);
+    }
+    // show specific groups
+    param = app.getParameter("showfeaturegroups");
+    if (param != null)
+    {
+      app.setFeatureGroupState(separatorListToArray(param, separator),
+              true);
+      // app.setFeatureGroupStateOn(newAlignFrame, param, true);
+    }
+    // and now load features
+    param = app.getParameter("features");
+    if (param != null)
+    {
+      ret[0] = param;
+      DataSourceType protocol = resolveFileProtocol(app,
+              ret);
+
+      result = app.parseFeaturesFile(param, protocol);
+    }
+
+    param = app.getParameter("showFeatureSettings");
+    if (param != null && param.equalsIgnoreCase("true"))
+    {
+      app.newFeatureSettings();
+    }
+    return result;
+  }
+
+  /**
+   * Load a score file if specified by parameter. Returns true if file was
+   * loaded, else false.
+   * 
+   * @param loaderFrame
+   */
+  protected boolean loadScoreFile()
+  {
+    boolean result = false;
+    String sScoreFile = app.getParameter("scoreFile");
+    if (sScoreFile != null && !"".equals(sScoreFile))
+    {
+      try
+      {
+        if (debug)
+        {
+          System.err.println(
+                  "Attempting to load T-COFFEE score file from the scoreFile parameter");
+        }
+        result = app.loadScoreFile(sScoreFile);
+        if (!result)
+        {
+          System.err.println(
+                  "Failed to parse T-COFFEE parameter as a valid score file ('"
+                          + sScoreFile + "')");
+        }
+      } catch (Exception e)
+      {
+        System.err.printf("Cannot read score file: '%s'. Cause: %s \n",
+                sScoreFile, e.getMessage());
+      }
+    }
+    return result;
+  }
+
+  String[] ret = new String[1];
+  /**
+   * Load a tree for the alignment if specified by parameter. Returns true if a
+   * tree was loaded, else false.
+   * 
+   * @param loaderFrame
+   * @return
+   */
+  protected boolean loadTree()
+  {
+    boolean result = false;
+    String treeFile = app.getParameter("tree");
+    if (treeFile == null)
+    {
+      treeFile = app.getParameter("treeFile");
+    }
+
+    if (treeFile != null)
+    {
+      try
+      {
+        ret[0] = treeFile;
+        NewickFile fin = new NewickFile(treeFile,
+                resolveFileProtocol(app, ret));
+        fin.parse();
+
+        if (fin.getTree() != null)
+        {
+          app.loadTree(fin, ret[0]);
+          result = true;
+          if (debug)
+          {
+            System.out.println("Successfully imported tree.");
+          }
+        }
+        else
+        {
+          if (debug)
+          {
+            System.out.println("Tree parameter did not resolve to a valid tree.");
+          }
+        }
+      } catch (Exception ex)
+      {
+        ex.printStackTrace();
+      }
+    }
+    return result;
+  }
+
+  /**
+   * form a complete URL given a path to a resource and a reference location on
+   * the same server
+   * 
+   * @param targetPath
+   *          - an absolute path on the same server as localref or a document
+   *          located relative to localref
+   * @param localref
+   *          - a URL on the same server as url
+   * @return a complete URL for the resource located by url
+   */
+  public static String resolveUrlForLocalOrAbsolute(String targetPath,
+          URL localref)
+  {
+    String resolvedPath = "";
+    if (targetPath.startsWith("/"))
+    {
+      String codebase = localref.toString();
+      String localfile = localref.getFile();
+      resolvedPath = codebase.substring(0,
+              codebase.length() - localfile.length()) + targetPath;
+      return resolvedPath;
+    }
+  
+    /*
+     * get URL path and strip off any trailing file e.g.
+     * www.jalview.org/examples/index.html#applets?a=b is trimmed to
+     * www.jalview.org/examples/
+     */
+    String urlPath = localref.toString();
+    String directoryPath = urlPath;
+    int lastSeparator = directoryPath.lastIndexOf("/");
+    if (lastSeparator > 0)
+    {
+      directoryPath = directoryPath.substring(0, lastSeparator + 1);
+    }
+  
+    if (targetPath.startsWith("/"))
+    {
+      /*
+       * construct absolute URL to a file on the server - this is not allowed?
+       */
+      // String localfile = localref.getFile();
+      // resolvedPath = urlPath.substring(0,
+      // urlPath.length() - localfile.length())
+      // + targetPath;
+      resolvedPath = directoryPath + targetPath.substring(1);
+    }
+    else
+    {
+      resolvedPath = directoryPath + targetPath;
+    }
+    if (JalviewLite.debug)
+    {
+      System.err.println(
+              "resolveUrlForLocalOrAbsolute returning " + resolvedPath);
+    }
+    return resolvedPath;
+  }
+
+  /**
+   * parse the string into a list
+   * 
+   * @param list
+   * @param separator
+   * @return elements separated by separator
+   */
+  public static String[] separatorListToArray(String list, String separator)
+  {
+    // TODO use StringUtils version (slightly different...)
+    int seplen = separator.length();
+    if (list == null || list.equals("") || list.equals(separator))
+    {
+      return null;
+    }
+    Vector<String> jv = new Vector<>();
+    int cp = 0, pos;
+    while ((pos = list.indexOf(separator, cp)) > cp)
+    {
+      jv.addElement(list.substring(cp, pos));
+      cp = pos + seplen;
+    }
+    if (cp < list.length())
+    {
+      String c = list.substring(cp);
+      if (!c.equals(separator))
+      {
+        jv.addElement(c);
+      }
+    }
+    if (jv.size() > 0)
+    {
+      String[] v = new String[jv.size()];
+      for (int i = 0; i < v.length; i++)
+      {
+        v[i] = jv.elementAt(i);
+      }
+      jv.removeAllElements();
+      // if (debug)
+      // {
+      // System.err.println("Array from '" + separator
+      // + "' separated List:\n" + v.length);
+      // for (int i = 0; i < v.length; i++)
+      // {
+      // System.err.println("item " + i + " '" + v[i] + "'");
+      // }
+      // }
+      return v;
+    }
+    // if (debug)
+    // {
+    // System.err.println(
+    // "Empty Array from '" + separator + "' separated List");
+    // }
+    return null;
+  }
+
+  public static DataSourceType resolveFileProtocol(JalviewApp app,
+          String[] retPath)
+  {
+    String path = retPath[0];
+    /*
+     * is it paste data?
+     */
+    if (path.startsWith("PASTE"))
+    {
+      retPath[0] = path.substring(5);
+      return DataSourceType.PASTE;
+    }
+  
+    /*
+     * is it a URL?
+     */
+    if (path.indexOf("://") >= 0)
+    {
+      return DataSourceType.URL;
+    }
+  
+    /*
+     * try relative to document root
+     */
+    URL documentBase = app.getDocumentBase();
+    String withDocBase = resolveUrlForLocalOrAbsolute(path,
+            documentBase);
+    if (HttpUtils.isValidUrl(withDocBase))
+    {
+      // if (debug)
+      // {
+      // System.err.println("Prepended document base '" + documentBase
+      // + "' to make: '" + withDocBase + "'");
+      // }
+      retPath[0] = withDocBase;
+      return DataSourceType.URL;
+    }
+  
+    /*
+     * try relative to codebase (if different to document base)
+     */
+    URL codeBase = app.getCodeBase();
+    String withCodeBase = resolveUrlForLocalOrAbsolute(path,
+            codeBase);
+    if (!withCodeBase.equals(withDocBase)
+            && HttpUtils.isValidUrl(withCodeBase))
+    {
+      // if (debug)
+      // {
+      // System.err.println("Prepended codebase '" + codeBase
+      // + "' to make: '" + withCodeBase + "'");
+      // }
+      retPath[0] = withCodeBase;
+      return DataSourceType.URL;
+    }
+
+    /*
+     * try locating by classloader; try this last so files in the directory
+     * are resolved using document base
+     */
+    if (inArchive(app.getClass(), path))
+    {
+      return DataSourceType.CLASSLOADER;
+    }
+    return null;
+  }
+
+  /**
+   * Discovers whether the given file is in the Applet Archive
+   * 
+   * @param f
+   *          String
+   * @return boolean
+   */
+  private static boolean inArchive(Class<?> c, String f)
+  {
+    // This might throw a security exception in certain browsers
+    // Netscape Communicator for instance.
+    try
+    {
+      boolean rtn = (c.getResourceAsStream("/" + f) != null);
+      // if (debug)
+      // {
+      // System.err.println("Resource '" + f + "' was "
+      // + (rtn ? "" : "not ") + "located by classloader.");
+      // }
+      return rtn;
+    } catch (Exception ex)
+    {
+      System.out.println("Exception checking resources: " + f + " " + ex);
+      return false;
+    }
+  }
+
+}