JAL-2418 source formatting
[jalview.git] / src / jalview / util / UrlLink.java
index 84a327a..007da86 100644 (file)
-/*\r
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.7)\r
- * Copyright (C) 2011 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle\r
- * \r
- * This file is part of Jalview.\r
- * \r
- * Jalview is free software: you can redistribute it and/or\r
- * modify it under the terms of the GNU General Public License \r
- * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.\r
- * \r
- * Jalview is distributed in the hope that it will be useful, but \r
- * WITHOUT ANY WARRANTY; without even the implied warranty \r
- * of MERCHANTABILITY or FITNESS FOR A PARTICULAR \r
- * PURPOSE.  See the GNU General Public License for more details.\r
- * \r
- * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.\r
- */\r
-package jalview.util;\r
-\r
-import java.util.Vector;\r
-\r
-public class UrlLink\r
-{\r
-  /**\r
-   * helper class to parse URL Link strings taken from applet parameters or\r
-   * jalview properties file using the com.stevesoft.pat.Regex implementation.\r
-   * Jalview 2.4 extension allows regular expressions to be used to parse ID\r
-   * strings and replace the result in the URL. Regex's operate on the whole ID\r
-   * string given to the matchURL method, if no regex is supplied, then only\r
-   * text following the first pipe symbol will be susbstituted. Usage\r
-   * documentation todo.\r
-   */\r
-  private String url_suffix, url_prefix, target, label, regexReplace;\r
-\r
-  private boolean dynamic = false;\r
-\r
-  private String invalidMessage = null;\r
-\r
-  /**\r
-   * parse the given linkString of the form '<label>|<url>' into parts url may\r
-   * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>\r
-   * must be of the form =/<perl style regex>/=$\r
-   * \r
-   * @param link\r
-   */\r
-  public UrlLink(String link)\r
-  {\r
-    int sep = link.indexOf("|"), psqid = link.indexOf("$SEQUENCE_ID");\r
-    if (psqid > -1)\r
-    {\r
-      dynamic = true;\r
-      int p = sep;\r
-      do\r
-      {\r
-        sep = p;\r
-        p = link.indexOf("|", sep + 1);\r
-      } while (p > sep && p < psqid);\r
-      // Assuming that the URL itself does not contain any '|' symbols\r
-      // sep now contains last pipe symbol position prior to any regex symbols\r
-      label = link.substring(0, sep);\r
-      if (label.indexOf("|") > -1)\r
-      {\r
-        // | terminated database name / www target at start of Label\r
-        target = label.substring(0, label.indexOf("|"));\r
-      }\r
-      else if (label.indexOf(" ") > 2)\r
-      {\r
-        // space separated Label - matches database name\r
-        target = label.substring(0, label.indexOf(" "));\r
-      }\r
-      else\r
-      {\r
-        target = label;\r
-      }\r
-      // Parse URL : Whole URL string first\r
-      url_prefix = link.substring(sep + 1, psqid);\r
-      if (link.indexOf("$SEQUENCE_ID=/") == psqid\r
-              && (p = link.indexOf("/=$", psqid + 14)) > psqid + 14)\r
-      {\r
-        // Extract Regex and suffix\r
-        url_suffix = link.substring(p + 3);\r
-        regexReplace = link.substring(psqid + 14, p);\r
-        try\r
-        {\r
-          com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"\r
-                  + regexReplace + "/");\r
-          if (rg == null)\r
-          {\r
-            invalidMessage = "Invalid Regular Expression : '"\r
-                    + regexReplace + "'\n";\r
-          }\r
-        } catch (Exception e)\r
-        {\r
-          invalidMessage = "Invalid Regular Expression : '" + regexReplace\r
-                  + "'\n";\r
-        }\r
-      }\r
-      else\r
-      {\r
-        regexReplace = null;\r
-        // verify format is really correct.\r
-        if (link.indexOf("$SEQUENCE_ID$") == psqid)\r
-        {\r
-          url_suffix = link.substring(psqid + 13);\r
-          regexReplace = null;\r
-        }\r
-        else\r
-        {\r
-          invalidMessage = "Warning: invalid regex structure for URL link : "\r
-                  + link;\r
-        }\r
-      }\r
-    }\r
-    else\r
-    {\r
-      target = link.substring(0, sep);\r
-      label = link.substring(0, sep = link.lastIndexOf("|"));\r
-      url_prefix = link.substring(sep + 1);\r
-      regexReplace = null; // implies we trim any prefix if necessary //\r
-      // regexReplace=".*\\|?(.*)";\r
-      url_suffix = null;\r
-    }\r
-  }\r
-\r
-  /**\r
-   * @return the url_suffix\r
-   */\r
-  public String getUrl_suffix()\r
-  {\r
-    return url_suffix;\r
-  }\r
-\r
-  /**\r
-   * @return the url_prefix\r
-   */\r
-  public String getUrl_prefix()\r
-  {\r
-    return url_prefix;\r
-  }\r
-\r
-  /**\r
-   * @return the target\r
-   */\r
-  public String getTarget()\r
-  {\r
-    return target;\r
-  }\r
-\r
-  /**\r
-   * @return the label\r
-   */\r
-  public String getLabel()\r
-  {\r
-    return label;\r
-  }\r
-\r
-  /**\r
-   * @return the regexReplace\r
-   */\r
-  public String getRegexReplace()\r
-  {\r
-    return regexReplace;\r
-  }\r
-\r
-  /**\r
-   * @return the invalidMessage\r
-   */\r
-  public String getInvalidMessage()\r
-  {\r
-    return invalidMessage;\r
-  }\r
-\r
-  /**\r
-   * Check if URL string was parsed properly.\r
-   * \r
-   * @return boolean - if false then <code>getInvalidMessage</code> returns an\r
-   *         error message\r
-   */\r
-  public boolean isValid()\r
-  {\r
-    return invalidMessage == null;\r
-  }\r
-\r
-  /**\r
-   * return one or more URL strings by applying regex to the given idstring\r
-   * \r
-   * @param idstring\r
-   * @param onlyIfMatches\r
-   *          - when true url strings are only made if regex is defined and\r
-   *          matches\r
-   * @return String[] { part of idstring substituted, full substituted url , ..\r
-   *         next part, next url..}\r
-   */\r
-  public String[] makeUrls(String idstring, boolean onlyIfMatches)\r
-  {\r
-    if (dynamic)\r
-    {\r
-      if (regexReplace != null)\r
-      {\r
-        com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"\r
-                + regexReplace + "/");\r
-        if (rg.search(idstring))\r
-        {\r
-          int ns = rg.numSubs();\r
-          if (ns == 0)\r
-          {\r
-            // take whole regex\r
-            return new String[]\r
-            { rg.stringMatched(),\r
-                url_prefix + rg.stringMatched() + url_suffix };\r
-          } /*\r
-             * else if (ns==1) { // take only subgroup match return new String[]\r
-             * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix\r
-             * }; }\r
-             */\r
-          else\r
-          {\r
-            // debug\r
-            for (int s = 0; s <= rg.numSubs(); s++)\r
-            {\r
-              System.err.println("Sub " + s + " : " + rg.matchedFrom(s)\r
-                      + " : " + rg.matchedTo(s) + " : '"\r
-                      + rg.stringMatched(s) + "'");\r
-            }\r
-            // try to collate subgroup matches\r
-            Vector subs = new Vector();\r
-            // have to loop through submatches, collating them at top level\r
-            // match\r
-            int s = 0; // 1;\r
-            while (s <= ns)\r
-            {\r
-              if (s + 1 <= ns && rg.matchedTo(s) > -1\r
-                      && rg.matchedTo(s + 1) > -1\r
-                      && rg.matchedTo(s + 1) < rg.matchedTo(s))\r
-              {\r
-                // s is top level submatch. search for submatches enclosed by\r
-                // this one\r
-                int r = s + 1;\r
-                String mtch = "";\r
-                while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))\r
-                {\r
-                  if (rg.matchedFrom(r) > -1)\r
-                  {\r
-                    mtch += rg.stringMatched(r);\r
-                  }\r
-                  r++;\r
-                }\r
-                if (mtch.length() > 0)\r
-                {\r
-                  subs.addElement(mtch);\r
-                  subs.addElement(url_prefix + mtch + url_suffix);\r
-                }\r
-                s = r;\r
-              }\r
-              else\r
-              {\r
-                if (rg.matchedFrom(s) > -1)\r
-                {\r
-                  subs.addElement(rg.stringMatched(s));\r
-                  subs.addElement(url_prefix + rg.stringMatched(s)\r
-                          + url_suffix);\r
-                }\r
-                s++;\r
-              }\r
-            }\r
-\r
-            String[] res = new String[subs.size()];\r
-            for (int r = 0, rs = subs.size(); r < rs; r++)\r
-            {\r
-              res[r] = (String) subs.elementAt(r);\r
-            }\r
-            subs.removeAllElements();\r
-            return res;\r
-          }\r
-        }\r
-        if (onlyIfMatches)\r
-        {\r
-          return null;\r
-        }\r
-      }\r
-      /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */\r
-      if (idstring.indexOf("|") > -1)\r
-      {\r
-        idstring = idstring.substring(idstring.lastIndexOf("|") + 1);\r
-      }\r
-\r
-      // just return simple url substitution.\r
-      return new String[]\r
-      { idstring, url_prefix + idstring + url_suffix };\r
-    }\r
-    else\r
-    {\r
-      return new String[]\r
-      { "", url_prefix };\r
-    }\r
-  }\r
-\r
-  public String toString()\r
-  {\r
-    return label\r
-            + "|"\r
-            + url_prefix\r
-            + (dynamic ? ("$SEQUENCE_ID" + ((regexReplace != null) ? "="\r
-                    + regexReplace + "=$" : "$")) : "")\r
-            + ((url_suffix == null) ? "" : url_suffix);\r
-\r
-  }\r
-\r
-  private static void testUrls(UrlLink ul, String idstring, String[] urls)\r
-  {\r
-\r
-    if (urls == null)\r
-    {\r
-      System.out.println("Created NO urls.");\r
-    }\r
-    else\r
-    {\r
-      System.out.println("Created " + (urls.length / 2) + " Urls.");\r
-      for (int uls = 0; uls < urls.length; uls += 2)\r
-      {\r
-        System.out.println("URL Replacement text : " + urls[uls]\r
-                + " : URL : " + urls[uls + 1]);\r
-      }\r
-    }\r
-  }\r
-\r
-  public static void main(String argv[])\r
-  {\r
-    String[] links = new String[]\r
-    {\r
-    /*\r
-     * "AlinkT|Target|http://foo.foo.soo/",\r
-     * "myUrl1|http://$SEQUENCE_ID=/[0-9]+/=$.someserver.org/foo",\r
-     * "myUrl2|http://$SEQUENCE_ID=/(([0-9]+).+([A-Za-z]+))/=$.someserver.org/foo"\r
-     * ,\r
-     * "myUrl3|http://$SEQUENCE_ID=/([0-9]+).+([A-Za-z]+)/=$.someserver.org/foo"\r
-     * , "myUrl4|target|http://$SEQUENCE_ID$.someserver.org/foo|too",\r
-     * "PF1|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(?:PFAM:)?(.+)/=$"\r
-     * ,\r
-     * "PF2|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(PFAM:)?(.+)/=$"\r
-     * ,\r
-     * "PF3|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/PFAM:(.+)/=$"\r
-     * , "NOTFER|http://notfer.org/$SEQUENCE_ID=/(?<!\\s)(.+)/=$",\r
-     */\r
-    "NESTED|http://nested/$SEQUENCE_ID=/^(?:Label:)?(?:(?:gi\\|(\\d+))|([^:]+))/=$/nested" };\r
-    String[] idstrings = new String[]\r
-    {\r
-    /*\r
-     * //"LGUL_human", //"QWIQW_123123", "uniprot|why_do+_12313_foo",\r
-     * //"123123312", "123123 ABCDE foo", "PFAM:PF23943",\r
-     */\r
-    "Label:gi|9234|pdb|102L|A" };\r
-    // TODO: test the setLabel method.\r
-    for (int i = 0; i < links.length; i++)\r
-    {\r
-      UrlLink ul = new UrlLink(links[i]);\r
-      if (ul.isValid())\r
-      {\r
-        System.out.println("\n\n\n");\r
-        System.out.println("Link " + i + " " + links[i] + " : "\r
-                + ul.toString());\r
-        System.out.println(" pref : "\r
-                + ul.getUrl_prefix()\r
-                + "\n suf : "\r
-                + ul.getUrl_suffix()\r
-                + "\n : "\r
-                + ((ul.getRegexReplace() != null) ? ul.getRegexReplace()\r
-                        : ""));\r
-        for (int ids = 0; ids < idstrings.length; ids++)\r
-        {\r
-          System.out.println("ID String : " + idstrings[ids]\r
-                  + "\nWithout onlyIfMatches:");\r
-          String[] urls = ul.makeUrls(idstrings[ids], false);\r
-          testUrls(ul, idstrings[ids], urls);\r
-          System.out.println("With onlyIfMatches set.");\r
-          urls = ul.makeUrls(idstrings[ids], true);\r
-          testUrls(ul, idstrings[ids], urls);\r
-        }\r
-      }\r
-      else\r
-      {\r
-        System.err.println("Invalid URLLink : " + links[i] + " : "\r
-                + ul.getInvalidMessage());\r
-      }\r
-    }\r
-  }\r
-\r
-  public boolean isDynamic()\r
-  {\r
-    // TODO Auto-generated method stub\r
-    return dynamic;\r
-  }\r
-\r
-  public void setLabel(String newlabel)\r
-  {\r
-    this.label = newlabel;\r
-  }\r
-}\r
+/*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ * 
+ * This file is part of Jalview.
+ * 
+ * Jalview is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License 
+ * as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *  
+ * Jalview is distributed in the hope that it will be useful, but 
+ * WITHOUT ANY WARRANTY; without even the implied warranty 
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+ * PURPOSE.  See the GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+package jalview.util;
+
+import static jalview.util.UrlConstants.DB_ACCESSION;
+import static jalview.util.UrlConstants.DELIM;
+import static jalview.util.UrlConstants.SEP;
+import static jalview.util.UrlConstants.SEQUENCE_ID;
+
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.SequenceI;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+
+public class UrlLink
+{
+  /**
+   * helper class to parse URL Link strings taken from applet parameters or
+   * jalview properties file using the com.stevesoft.pat.Regex implementation.
+   * Jalview 2.4 extension allows regular expressions to be used to parse ID
+   * strings and replace the result in the URL. Regex's operate on the whole ID
+   * string given to the matchURL method, if no regex is supplied, then only
+   * text following the first pipe symbol will be substituted. Usage
+   * documentation todo.
+   */
+
+  private static final String EQUALS = "=";
+
+  private static final String SPACE = " ";
+
+  private String urlSuffix;
+
+  private String urlPrefix;
+
+  private String target;
+
+  private String label;
+
+  private String dbname;
+
+  private String regexReplace;
+
+  private boolean dynamic = false;
+
+  private boolean usesDBaccession = false;
+
+  private String invalidMessage = null;
+
+  /**
+   * parse the given linkString of the form '<label>SEP<url>' into parts url may
+   * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
+   * must be of the form =/<perl style regex>/=$
+   * 
+   * @param link
+   */
+  public UrlLink(String link)
+  {
+    int sep = link.indexOf(SEP);
+    int psqid = link.indexOf(DELIM + DB_ACCESSION);
+    int nsqid = link.indexOf(DELIM + SEQUENCE_ID);
+    if (psqid > -1)
+    {
+      dynamic = true;
+      usesDBaccession = true;
+
+      sep = parseLabel(sep, psqid, link);
+
+      int endOfRegex = parseUrl(link, DB_ACCESSION, psqid, sep);
+      parseTarget(link, sep, endOfRegex);
+    }
+    else if (nsqid > -1)
+    {
+      dynamic = true;
+      sep = parseLabel(sep, nsqid, link);
+
+      int endOfRegex = parseUrl(link, SEQUENCE_ID, nsqid, sep);
+
+      parseTarget(link, sep, endOfRegex);
+    }
+    else
+    {
+      label = link.substring(0, sep).trim();
+
+      // if there's a third element in the url link string
+      // it is the target name, otherwise target=label
+      int lastsep = link.lastIndexOf(SEP);
+      if (lastsep != sep)
+      {
+        urlPrefix = link.substring(sep + 1, lastsep).trim();
+        target = link.substring(lastsep + 1).trim();
+      }
+      else
+      {
+        urlPrefix = link.substring(sep + 1).trim();
+        target = label;
+      }
+
+      regexReplace = null; // implies we trim any prefix if necessary //
+      urlSuffix = null;
+    }
+
+    label = label.trim();
+    target = target.trim();
+  }
+
+  /**
+   * Alternative constructor for separate name, link and description
+   * 
+   * @param name
+   *          The string used to match the link to a DB reference id
+   * @param url
+   *          The url to link to
+   * @param desc
+   *          The description of the associated target DB
+   */
+  public UrlLink(String name, String url, String desc)
+  {
+    this(name + SEP + url + SEP + desc);
+  }
+
+  /**
+   * @return the url_suffix
+   */
+  public String getUrlSuffix()
+  {
+    return urlSuffix;
+  }
+
+  /**
+   * @return the url_prefix
+   */
+  public String getUrlPrefix()
+  {
+    return urlPrefix;
+  }
+
+  /**
+   * @return the target
+   */
+  public String getTarget()
+  {
+    return target;
+  }
+
+  /**
+   * @return the label
+   */
+  public String getLabel()
+  {
+    return label;
+  }
+
+  public String getUrlWithToken()
+  {
+    String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
+
+    return urlPrefix
+            + (dynamic
+                    ? (DELIM + var
+                            + ((regexReplace != null)
+                                    ? EQUALS + regexReplace + EQUALS + DELIM
+                                    : DELIM))
+                    : "")
+            + ((urlSuffix == null) ? "" : urlSuffix);
+  }
+
+  /**
+   * @return the regexReplace
+   */
+  public String getRegexReplace()
+  {
+    return regexReplace;
+  }
+
+  /**
+   * @return the invalidMessage
+   */
+  public String getInvalidMessage()
+  {
+    return invalidMessage;
+  }
+
+  /**
+   * Check if URL string was parsed properly.
+   * 
+   * @return boolean - if false then <code>getInvalidMessage</code> returns an
+   *         error message
+   */
+  public boolean isValid()
+  {
+    return invalidMessage == null;
+  }
+
+  /**
+   * 
+   * @return whether link is dynamic
+   */
+  public boolean isDynamic()
+  {
+    return dynamic;
+  }
+
+  /**
+   * 
+   * @return whether link uses DB Accession id
+   */
+  public boolean usesDBAccession()
+  {
+    return usesDBaccession;
+  }
+
+  /**
+   * Set the label
+   * 
+   * @param newlabel
+   */
+  public void setLabel(String newlabel)
+  {
+    this.label = newlabel;
+  }
+
+  /**
+   * Set the target
+   * 
+   * @param desc
+   */
+  public void setTarget(String desc)
+  {
+    target = desc;
+  }
+
+  /**
+   * return one or more URL strings by applying regex to the given idstring
+   * 
+   * @param idstring
+   * @param onlyIfMatches
+   *          - when true url strings are only made if regex is defined and
+   *          matches
+   * @return String[] { part of idstring substituted, full substituted url , ..
+   *         next part, next url..}
+   */
+  public String[] makeUrls(String idstring, boolean onlyIfMatches)
+  {
+    if (dynamic)
+    {
+      if (regexReplace != null)
+      {
+        com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
+                .perlCode("/" + regexReplace + "/");
+        if (rg.search(idstring))
+        {
+          int ns = rg.numSubs();
+          if (ns == 0)
+          {
+            // take whole regex
+            return new String[] { rg.stringMatched(),
+                urlPrefix + rg.stringMatched() + urlSuffix };
+          } /*
+             * else if (ns==1) { // take only subgroup match return new String[]
+             * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
+             * }; }
+             */
+          else
+          {
+            // debug
+            for (int s = 0; s <= rg.numSubs(); s++)
+            {
+              System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
+                      + " : " + rg.matchedTo(s) + " : '"
+                      + rg.stringMatched(s) + "'");
+            }
+            // try to collate subgroup matches
+            Vector<String> subs = new Vector<String>();
+            // have to loop through submatches, collating them at top level
+            // match
+            int s = 0; // 1;
+            while (s <= ns)
+            {
+              if (s + 1 <= ns && rg.matchedTo(s) > -1
+                      && rg.matchedTo(s + 1) > -1
+                      && rg.matchedTo(s + 1) < rg.matchedTo(s))
+              {
+                // s is top level submatch. search for submatches enclosed by
+                // this one
+                int r = s + 1;
+                String mtch = "";
+                while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
+                {
+                  if (rg.matchedFrom(r) > -1)
+                  {
+                    mtch += rg.stringMatched(r);
+                  }
+                  r++;
+                }
+                if (mtch.length() > 0)
+                {
+                  subs.addElement(mtch);
+                  subs.addElement(urlPrefix + mtch + urlSuffix);
+                }
+                s = r;
+              }
+              else
+              {
+                if (rg.matchedFrom(s) > -1)
+                {
+                  subs.addElement(rg.stringMatched(s));
+                  subs.addElement(
+                          urlPrefix + rg.stringMatched(s) + urlSuffix);
+                }
+                s++;
+              }
+            }
+
+            String[] res = new String[subs.size()];
+            for (int r = 0, rs = subs.size(); r < rs; r++)
+            {
+              res[r] = subs.elementAt(r);
+            }
+            subs.removeAllElements();
+            return res;
+          }
+        }
+        if (onlyIfMatches)
+        {
+          return null;
+        }
+      }
+      /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
+      if (idstring.indexOf(SEP) > -1)
+      {
+        idstring = idstring.substring(idstring.lastIndexOf(SEP) + 1);
+      }
+
+      // just return simple url substitution.
+      return new String[] { idstring, urlPrefix + idstring + urlSuffix };
+    }
+    else
+    {
+      return new String[] { "", urlPrefix };
+    }
+  }
+
+  @Override
+  public String toString()
+  {
+    return label + SEP + getUrlWithToken();
+  }
+
+  /**
+   * @return delimited string containing label, url and target
+   */
+  public String toStringWithTarget()
+  {
+    return label + SEP + getUrlWithToken() + SEP + target;
+  }
+
+  /**
+   * Parse the label from the link string
+   * 
+   * @param firstSep
+   *          Location of first occurrence of separator in link string
+   * @param psqid
+   *          Position of sequence id or name in link string
+   * @param link
+   *          Link string containing database name and url
+   * @return Position of last separator symbol prior to any regex symbols
+   */
+  protected int parseLabel(int firstSep, int psqid, String link)
+  {
+    int p = firstSep;
+    int sep = firstSep;
+    do
+    {
+      sep = p;
+      p = link.indexOf(SEP, sep + 1);
+    } while (p > sep && p < psqid);
+    // Assuming that the URL itself does not contain any SEP symbols
+    // sep now contains last pipe symbol position prior to any regex symbols
+    label = link.substring(0, sep);
+
+    return sep;
+  }
+
+  /**
+   * Parse the target from the link string
+   * 
+   * @param link
+   *          Link string containing database name and url
+   * @param sep
+   *          Location of first separator symbol
+   * @param endOfRegex
+   *          Location of end of any regular expression in link string
+   */
+  protected void parseTarget(String link, int sep, int endOfRegex)
+  {
+    int lastsep = link.lastIndexOf(SEP);
+
+    if ((lastsep != sep) && (lastsep > endOfRegex))
+    {
+      // final element in link string is the target
+      target = link.substring(lastsep + 1).trim();
+    }
+    else
+    {
+      target = label;
+    }
+
+    if (target.indexOf(SEP) > -1)
+    {
+      // SEP terminated database name / www target at start of Label
+      target = target.substring(0, target.indexOf(SEP));
+    }
+    else if (target.indexOf(SPACE) > 2)
+    {
+      // space separated label - first word matches database name
+      target = target.substring(0, target.indexOf(SPACE));
+    }
+  }
+
+  /**
+   * Parse the URL part of the link string
+   * 
+   * @param link
+   *          Link string containing database name and url
+   * @param varName
+   *          Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
+   * @param sqidPos
+   *          Position of id or name in link string
+   * @param sep
+   *          Position of separator in link string
+   * @return Location of end of any regex in link string
+   */
+  protected int parseUrl(String link, String varName, int sqidPos, int sep)
+  {
+    urlPrefix = link.substring(sep + 1, sqidPos).trim();
+
+    // delimiter at start of regex: e.g. $SEQUENCE_ID=/
+    String startDelimiter = DELIM + varName + "=/";
+
+    // delimiter at end of regex: /=$
+    String endDelimiter = "/=" + DELIM;
+
+    int startLength = startDelimiter.length();
+
+    // Parse URL : Whole URL string first
+    int p = link.indexOf(endDelimiter, sqidPos + startLength);
+
+    if (link.indexOf(startDelimiter) == sqidPos
+            && (p > sqidPos + startLength))
+    {
+      // Extract Regex and suffix
+      urlSuffix = link.substring(p + endDelimiter.length());
+      regexReplace = link.substring(sqidPos + startLength, p);
+      try
+      {
+        com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
+                .perlCode("/" + regexReplace + "/");
+        if (rg == null)
+        {
+          invalidMessage = "Invalid Regular Expression : '" + regexReplace
+                  + "'\n";
+        }
+      } catch (Exception e)
+      {
+        invalidMessage = "Invalid Regular Expression : '" + regexReplace
+                + "'\n";
+      }
+    }
+    else
+    {
+      // no regex
+      regexReplace = null;
+      // verify format is really correct.
+      if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
+      {
+        int lastsep = link.lastIndexOf(SEP);
+        if (lastsep < sqidPos + startLength - 1)
+        {
+          // the last SEP character was before the regex, ignore
+          lastsep = link.length();
+        }
+        urlSuffix = link.substring(sqidPos + startLength - 1, lastsep)
+                .trim();
+        regexReplace = null;
+      }
+      else
+      {
+        invalidMessage = "Warning: invalid regex structure for URL link : "
+                + link;
+      }
+    }
+
+    return p;
+  }
+
+  /**
+   * Create a set of URL links for a sequence
+   * 
+   * @param seq
+   *          The sequence to create links for
+   * @param linkset
+   *          Map of links: key = id + SEP + link, value = [target, label, id,
+   *          link]
+   */
+  public void createLinksFromSeq(final SequenceI seq,
+          Map<String, List<String>> linkset)
+  {
+    if (seq != null && dynamic)
+    {
+      createDynamicLinks(seq, linkset);
+    }
+    else
+    {
+      createStaticLink(linkset);
+    }
+  }
+
+  /**
+   * Create a static URL link
+   * 
+   * @param linkset
+   *          Map of links: key = id + SEP + link, value = [target, label, id,
+   *          link]
+   */
+  protected void createStaticLink(Map<String, List<String>> linkset)
+  {
+    if (!linkset.containsKey(label + SEP + getUrlPrefix()))
+    {
+      // Add a non-dynamic link
+      linkset.put(label + SEP + getUrlPrefix(),
+              Arrays.asList(target, label, null, getUrlPrefix()));
+    }
+  }
+
+  /**
+   * Create dynamic URL links
+   * 
+   * @param seq
+   *          The sequence to create links for
+   * @param linkset
+   *          Map of links: key = id + SEP + link, value = [target, label, id,
+   *          link]
+   */
+  protected void createDynamicLinks(final SequenceI seq,
+          Map<String, List<String>> linkset)
+  {
+    // collect id string too
+    String id = seq.getName();
+    String descr = seq.getDescription();
+    if (descr != null && descr.length() < 1)
+    {
+      descr = null;
+    }
+
+    if (usesDBAccession()) // link is ID
+    {
+      // collect matching db-refs
+      DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
+              new String[]
+              { target });
+
+      // if there are any dbrefs which match up with the link
+      if (dbr != null)
+      {
+        for (int r = 0; r < dbr.length; r++)
+        {
+          // create Bare ID link for this URL
+          createBareURLLink(dbr[r].getAccessionId(), true, linkset);
+        }
+      }
+    }
+    else if (!usesDBAccession() && id != null) // link is name
+    {
+      // create Bare ID link for this URL
+      createBareURLLink(id, false, linkset);
+    }
+
+    // Create urls from description but only for URL links which are regex
+    // links
+    if (descr != null && getRegexReplace() != null)
+    {
+      // create link for this URL from description where regex matches
+      createBareURLLink(descr, false, linkset);
+    }
+  }
+
+  /*
+   * Create a bare URL Link
+   * Returns map where key = id + SEP + link, and value = [target, label, id, link]
+   */
+  protected void createBareURLLink(String id, Boolean combineLabel,
+          Map<String, List<String>> linkset)
+  {
+    String[] urls = makeUrls(id, true);
+    if (urls != null)
+    {
+      for (int u = 0; u < urls.length; u += 2)
+      {
+        if (!linkset.containsKey(urls[u] + SEP + urls[u + 1]))
+        {
+          String thisLabel = label;
+          if (combineLabel)
+          {
+            // incorporate label with idstring
+            thisLabel = label + SEP + urls[u];
+          }
+
+          linkset.put(urls[u] + SEP + urls[u + 1],
+                  Arrays.asList(target, thisLabel, urls[u], urls[u + 1]));
+        }
+      }
+    }
+  }
+}