2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
23 import static jalview.util.UrlConstants.DB_ACCESSION;
24 import static jalview.util.UrlConstants.DELIM;
25 import static jalview.util.UrlConstants.SEP;
26 import static jalview.util.UrlConstants.SEQUENCE_ID;
28 import jalview.datamodel.DBRefEntry;
29 import jalview.datamodel.SequenceI;
31 import java.util.Arrays;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.List;
36 import java.util.Vector;
39 * A helper class to parse URL Link strings taken from applet parameters or
40 * jalview properties file using the com.stevesoft.pat.Regex implementation.
41 * Jalview 2.4 extension allows regular expressions to be used to parse ID
42 * strings and replace the result in the URL. Regex's operate on the whole ID
43 * string given to the matchURL method, if no regex is supplied, then only text
44 * following the first pipe symbol will be substituted. Usage documentation
50 * A comparator that puts SEQUENCE_ID template links before DB_ACCESSION
51 * links, and otherwise orders by link name (not case sensitive). It expects
52 * to compare strings formatted as "Name|URLTemplate" where the template may
53 * include $SEQUENCE_ID$ or $DB_ACCESSION$ or neither.
55 public static final Comparator<String> LINK_COMPARATOR = new Comparator<String>()
58 public int compare(String link1, String link2)
60 if (link1 == null || link2 == null)
62 return 0; // for failsafe only
64 String[] tokens1 = link1.split("\\|");
65 String[] tokens2 = link2.split("\\|");
66 if (tokens1.length < 2 || tokens2.length < 2)
69 return String.CASE_INSENSITIVE_ORDER.compare(link1, link2);
71 String name1 = tokens1[0];
72 String name2 = tokens2[0];
73 String pattern1 = tokens1[1];
74 String pattern2 = tokens2[1];
75 if (pattern1.contains(UrlConstants.SEQUENCE_ID)
76 && pattern2.contains(UrlConstants.DB_ACCESSION))
80 if (pattern2.contains(UrlConstants.SEQUENCE_ID)
81 && pattern1.contains(UrlConstants.DB_ACCESSION))
85 return String.CASE_INSENSITIVE_ORDER.compare(name1, name2);
90 private static final String EQUALS = "=";
92 private static final String SPACE = " ";
94 private String urlSuffix;
96 private String urlPrefix;
98 private String target;
100 private String label;
102 private String dbname;
104 private String regexReplace;
106 private boolean dynamic = false;
108 private boolean usesDBaccession = false;
110 private String invalidMessage = null;
113 * parse the given linkString of the form '<label>SEP<url>' into parts url may
114 * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
115 * must be of the form =/<perl style regex>/=$
119 public UrlLink(String link)
121 int sep = link.indexOf(SEP);
122 int psqid = link.indexOf(DELIM + DB_ACCESSION);
123 int nsqid = link.indexOf(DELIM + SEQUENCE_ID);
127 usesDBaccession = true;
129 sep = parseLabel(sep, psqid, link);
131 int endOfRegex = parseUrl(link, DB_ACCESSION, psqid, sep);
132 parseTarget(link, sep, endOfRegex);
137 sep = parseLabel(sep, nsqid, link);
139 int endOfRegex = parseUrl(link, SEQUENCE_ID, nsqid, sep);
141 parseTarget(link, sep, endOfRegex);
145 label = link.substring(0, sep).trim();
147 // if there's a third element in the url link string
148 // it is the target name, otherwise target=label
149 int lastsep = link.lastIndexOf(SEP);
152 urlPrefix = link.substring(sep + 1, lastsep).trim();
153 target = link.substring(lastsep + 1).trim();
157 urlPrefix = link.substring(sep + 1).trim();
161 regexReplace = null; // implies we trim any prefix if necessary //
165 label = label.trim();
166 target = target.trim();
170 * Alternative constructor for separate name, link and description
173 * The string used to match the link to a DB reference id
177 * The description of the associated target DB
179 public UrlLink(String name, String url, String desc)
181 this(name + SEP + url + SEP + desc);
185 * @return the url_suffix
187 public String getUrlSuffix()
193 * @return the url_prefix
195 public String getUrlPrefix()
203 public String getTarget()
211 public String getLabel()
216 public String getUrlWithToken()
218 String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
223 + ((regexReplace != null)
224 ? EQUALS + regexReplace + EQUALS + DELIM
227 + ((urlSuffix == null) ? "" : urlSuffix);
231 * @return the regexReplace
233 public String getRegexReplace()
239 * @return the invalidMessage
241 public String getInvalidMessage()
243 return invalidMessage;
247 * Check if URL string was parsed properly.
249 * @return boolean - if false then <code>getInvalidMessage</code> returns an
252 public boolean isValid()
254 return invalidMessage == null;
259 * @return whether link is dynamic
261 public boolean isDynamic()
268 * @return whether link uses DB Accession id
270 public boolean usesDBAccession()
272 return usesDBaccession;
280 public void setLabel(String newlabel)
282 this.label = newlabel;
290 public void setTarget(String desc)
296 * return one or more URL strings by applying regex to the given idstring
299 * @param onlyIfMatches
300 * - when true url strings are only made if regex is defined and
302 * @return String[] { part of idstring substituted, full substituted url , ..
303 * next part, next url..}
305 public String[] makeUrls(String idstring, boolean onlyIfMatches)
309 if (regexReplace != null)
311 com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
312 .perlCode("/" + regexReplace + "/");
313 if (rg.search(idstring))
315 int ns = rg.numSubs();
319 return new String[] { rg.stringMatched(),
320 urlPrefix + rg.stringMatched() + urlSuffix };
322 * else if (ns==1) { // take only subgroup match return new String[]
323 * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
329 for (int s = 0; s <= rg.numSubs(); s++)
331 System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
332 + " : " + rg.matchedTo(s) + " : '"
333 + rg.stringMatched(s) + "'");
335 // try to collate subgroup matches
336 Vector<String> subs = new Vector<>();
337 // have to loop through submatches, collating them at top level
342 if (s + 1 <= ns && rg.matchedTo(s) > -1
343 && rg.matchedTo(s + 1) > -1
344 && rg.matchedTo(s + 1) < rg.matchedTo(s))
346 // s is top level submatch. search for submatches enclosed by
350 while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
352 if (rg.matchedFrom(r) > -1)
354 mtch += rg.stringMatched(r);
358 if (mtch.length() > 0)
360 subs.addElement(mtch);
361 subs.addElement(urlPrefix + mtch + urlSuffix);
367 if (rg.matchedFrom(s) > -1)
369 subs.addElement(rg.stringMatched(s));
371 urlPrefix + rg.stringMatched(s) + urlSuffix);
377 String[] res = new String[subs.size()];
378 for (int r = 0, rs = subs.size(); r < rs; r++)
380 res[r] = subs.elementAt(r);
382 subs.removeAllElements();
391 /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
392 if (idstring.indexOf(SEP) > -1)
394 idstring = idstring.substring(idstring.lastIndexOf(SEP) + 1);
397 // just return simple url substitution.
398 return new String[] { idstring, urlPrefix + idstring + urlSuffix };
402 return new String[] { "", urlPrefix };
407 public String toString()
409 return label + SEP + getUrlWithToken();
413 * @return delimited string containing label, url and target
415 public String toStringWithTarget()
417 return label + SEP + getUrlWithToken() + SEP + target;
421 * Parse the label from the link string
424 * Location of first occurrence of separator in link string
426 * Position of sequence id or name in link string
428 * Link string containing database name and url
429 * @return Position of last separator symbol prior to any regex symbols
431 protected int parseLabel(int firstSep, int psqid, String link)
438 p = link.indexOf(SEP, sep + 1);
439 } while (p > sep && p < psqid);
440 // Assuming that the URL itself does not contain any SEP symbols
441 // sep now contains last pipe symbol position prior to any regex symbols
442 label = link.substring(0, sep);
448 * Parse the target from the link string
451 * Link string containing database name and url
453 * Location of first separator symbol
455 * Location of end of any regular expression in link string
457 protected void parseTarget(String link, int sep, int endOfRegex)
459 int lastsep = link.lastIndexOf(SEP);
461 if ((lastsep != sep) && (lastsep > endOfRegex))
463 // final element in link string is the target
464 target = link.substring(lastsep + 1).trim();
471 if (target.indexOf(SEP) > -1)
473 // SEP terminated database name / www target at start of Label
474 target = target.substring(0, target.indexOf(SEP));
476 else if (target.indexOf(SPACE) > 2)
478 // space separated label - first word matches database name
479 target = target.substring(0, target.indexOf(SPACE));
484 * Parse the URL part of the link string
487 * Link string containing database name and url
489 * Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
491 * Position of id or name in link string
493 * Position of separator in link string
494 * @return Location of end of any regex in link string
496 protected int parseUrl(String link, String varName, int sqidPos, int sep)
498 urlPrefix = link.substring(sep + 1, sqidPos).trim();
500 // delimiter at start of regex: e.g. $SEQUENCE_ID=/
501 String startDelimiter = DELIM + varName + "=/";
503 // delimiter at end of regex: /=$
504 String endDelimiter = "/=" + DELIM;
506 int startLength = startDelimiter.length();
508 // Parse URL : Whole URL string first
509 int p = link.indexOf(endDelimiter, sqidPos + startLength);
511 if (link.indexOf(startDelimiter) == sqidPos
512 && (p > sqidPos + startLength))
514 // Extract Regex and suffix
515 urlSuffix = link.substring(p + endDelimiter.length());
516 regexReplace = link.substring(sqidPos + startLength, p);
519 com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
520 .perlCode("/" + regexReplace + "/");
523 invalidMessage = "Invalid Regular Expression : '" + regexReplace
526 } catch (Exception e)
528 invalidMessage = "Invalid Regular Expression : '" + regexReplace
536 // verify format is really correct.
537 if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
539 int lastsep = link.lastIndexOf(SEP);
540 if (lastsep < sqidPos + startLength - 1)
542 // the last SEP character was before the regex, ignore
543 lastsep = link.length();
545 urlSuffix = link.substring(sqidPos + startLength - 1, lastsep)
551 invalidMessage = "Warning: invalid regex structure for URL link : "
560 * Create a set of URL links for a sequence
563 * The sequence to create links for
565 * Map of links: key = id + SEP + link, value = [target, label, id,
568 public void createLinksFromSeq(final SequenceI seq,
569 Map<String, List<String>> linkset)
571 if (seq != null && dynamic)
573 createDynamicLinks(seq, linkset);
577 createStaticLink(linkset);
582 * Create a static URL link
585 * Map of links: key = id + SEP + link, value = [target, label, id,
588 protected void createStaticLink(Map<String, List<String>> linkset)
590 if (!linkset.containsKey(label + SEP + getUrlPrefix()))
592 // Add a non-dynamic link
593 linkset.put(label + SEP + getUrlPrefix(),
594 Arrays.asList(target, label, null, getUrlPrefix()));
599 * Create dynamic URL links
602 * The sequence to create links for
604 * Map of links: key = id + SEP + link, value = [target, label, id,
607 protected void createDynamicLinks(final SequenceI seq,
608 Map<String, List<String>> linkset)
610 // collect id string too
611 String id = seq.getName();
612 String descr = seq.getDescription();
613 if (descr != null && descr.length() < 1)
618 if (usesDBAccession()) // link is ID
620 // collect matching db-refs
621 DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
625 // if there are any dbrefs which match up with the link
628 for (int r = 0; r < dbr.length; r++)
630 // create Bare ID link for this URL
631 createBareURLLink(dbr[r].getAccessionId(), true, linkset);
635 else if (!usesDBAccession() && id != null) // link is name
637 // create Bare ID link for this URL
638 createBareURLLink(id, false, linkset);
641 // Create urls from description but only for URL links which are regex
643 if (descr != null && getRegexReplace() != null)
645 // create link for this URL from description where regex matches
646 createBareURLLink(descr, false, linkset);
651 * Create a bare URL Link
652 * Returns map where key = id + SEP + link, and value = [target, label, id, link]
654 protected void createBareURLLink(String id, Boolean combineLabel,
655 Map<String, List<String>> linkset)
657 String[] urls = makeUrls(id, true);
660 for (int u = 0; u < urls.length; u += 2)
662 if (!linkset.containsKey(urls[u] + SEP + urls[u + 1]))
664 String thisLabel = label;
667 // incorporate label with idstring
668 thisLabel = label + SEP + urls[u];
671 linkset.put(urls[u] + SEP + urls[u + 1],
672 Arrays.asList(target, thisLabel, urls[u], urls[u + 1]));
679 * Sorts links (formatted as LinkName|LinkPattern) suitable for display in a
682 * <li>SEQUENCE_ID links precede DB_ACCESSION links (i.e. canonical lookup
683 * before cross-references)</li>
684 * <li>otherwise by Link name (case insensitive)</li>
689 public static void sort(List<String> nlinks)
691 Collections.sort(nlinks, LINK_COMPARATOR);