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;
38 import com.stevesoft.pat.Regex;
41 * A helper class to parse URL Link strings taken from applet parameters or
42 * jalview properties file using the com.stevesoft.pat.Regex implementation.
43 * Jalview 2.4 extension allows regular expressions to be used to parse ID
44 * strings and replace the result in the URL. Regex's operate on the whole ID
45 * string given to the matchURL method, if no regex is supplied, then only text
46 * following the first pipe symbol will be substituted. Usage documentation
51 private static final String SEQUENCEID_PLACEHOLDER = DELIM + SEQUENCE_ID
54 private static final String ACCESSION_PLACEHOLDER = DELIM + DB_ACCESSION
58 * A comparator that puts SEQUENCE_ID template links before DB_ACCESSION
59 * links, and otherwise orders by link name + url (not case sensitive). It
60 * expects to compare strings formatted as "Name|URLTemplate" where the
61 * template may include $SEQUENCE_ID$ or $DB_ACCESSION$ or neither.
63 public static final Comparator<String> LINK_COMPARATOR = new Comparator<String>()
66 public int compare(String link1, String link2)
68 if (link1 == null || link2 == null)
70 return 0; // for failsafe only
72 if (link1.contains(SEQUENCEID_PLACEHOLDER)
73 && link2.contains(ACCESSION_PLACEHOLDER))
77 if (link2.contains(SEQUENCEID_PLACEHOLDER)
78 && link1.contains(ACCESSION_PLACEHOLDER))
82 return String.CASE_INSENSITIVE_ORDER.compare(link1, link2);
86 private static final String EQUALS = "=";
88 private static final String SPACE = " ";
90 private String urlSuffix;
92 private String urlPrefix;
94 private String target;
98 private String dbname;
100 private String regexReplace;
102 private boolean dynamic = false;
104 private boolean usesDBaccession = false;
106 private String invalidMessage = null;
109 * parse the given linkString of the form '<label>SEP<url>' into parts url may
110 * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
111 * must be of the form =/<perl style regex>/=$
115 public UrlLink(String link)
117 int sep = link.indexOf(SEP);
118 int psqid = link.indexOf(DELIM + DB_ACCESSION);
119 int nsqid = link.indexOf(DELIM + SEQUENCE_ID);
123 usesDBaccession = true;
125 sep = parseLabel(sep, psqid, link);
127 int endOfRegex = parseUrl(link, DB_ACCESSION, psqid, sep);
128 parseTarget(link, sep, endOfRegex);
133 sep = parseLabel(sep, nsqid, link);
135 int endOfRegex = parseUrl(link, SEQUENCE_ID, nsqid, sep);
137 parseTarget(link, sep, endOfRegex);
141 label = link.substring(0, sep).trim();
143 // if there's a third element in the url link string
144 // it is the target name, otherwise target=label
145 int lastsep = link.lastIndexOf(SEP);
148 urlPrefix = link.substring(sep + 1, lastsep).trim();
149 target = link.substring(lastsep + 1).trim();
153 urlPrefix = link.substring(sep + 1).trim();
157 regexReplace = null; // implies we trim any prefix if necessary //
161 label = label.trim();
162 target = target.trim();
166 * Alternative constructor for separate name, link and description
169 * The string used to match the link to a DB reference id
173 * The description of the associated target DB
175 public UrlLink(String name, String url, String desc)
177 this(name + SEP + url + SEP + desc);
181 * @return the url_suffix
183 public String getUrlSuffix()
189 * @return the url_prefix
191 public String getUrlPrefix()
199 public String getTarget()
207 public String getLabel()
212 public String getUrlWithToken()
214 String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
219 + ((regexReplace != null)
220 ? EQUALS + regexReplace + EQUALS + DELIM
223 + ((urlSuffix == null) ? "" : urlSuffix);
227 * @return the regexReplace
229 public String getRegexReplace()
235 * @return the invalidMessage
237 public String getInvalidMessage()
239 return invalidMessage;
243 * Check if URL string was parsed properly.
245 * @return boolean - if false then <code>getInvalidMessage</code> returns an
248 public boolean isValid()
250 return invalidMessage == null;
255 * @return whether link is dynamic
257 public boolean isDynamic()
264 * @return whether link uses DB Accession id
266 public boolean usesDBAccession()
268 return usesDBaccession;
276 public void setLabel(String newlabel)
278 this.label = newlabel;
286 public void setTarget(String desc)
292 * return one or more URL strings by applying regex to the given idstring
295 * @param onlyIfMatches
296 * - when true url strings are only made if regex is defined and
298 * @return String[] { part of idstring substituted, full substituted url , ..
299 * next part, next url..}
301 public String[] makeUrls(String idstring, boolean onlyIfMatches)
305 if (regexReplace != null)
307 Regex rg = Platform.newRegexPerl("/" + regexReplace + "/");
308 if (rg.search(idstring))
310 int ns = rg.numSubs();
314 return new String[] { rg.stringMatched(),
315 urlPrefix + rg.stringMatched() + urlSuffix };
317 * else if (ns==1) { // take only subgroup match return new String[]
318 * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
324 for (int s = 0; s <= rg.numSubs(); s++)
326 System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
327 + " : " + rg.matchedTo(s) + " : '"
328 + rg.stringMatched(s) + "'");
330 // try to collate subgroup matches
331 Vector<String> subs = new Vector<>();
332 // have to loop through submatches, collating them at top level
337 if (s + 1 <= ns && rg.matchedTo(s) > -1
338 && rg.matchedTo(s + 1) > -1
339 && rg.matchedTo(s + 1) < rg.matchedTo(s))
341 // s is top level submatch. search for submatches enclosed by
345 while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
347 if (rg.matchedFrom(r) > -1)
349 mtch += rg.stringMatched(r);
353 if (mtch.length() > 0)
355 subs.addElement(mtch);
356 subs.addElement(urlPrefix + mtch + urlSuffix);
362 if (rg.matchedFrom(s) > -1)
364 subs.addElement(rg.stringMatched(s));
366 urlPrefix + rg.stringMatched(s) + urlSuffix);
372 String[] res = new String[subs.size()];
373 for (int r = 0, rs = subs.size(); r < rs; r++)
375 res[r] = subs.elementAt(r);
377 subs.removeAllElements();
386 /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
387 if (idstring.indexOf(SEP) > -1)
389 idstring = idstring.substring(idstring.lastIndexOf(SEP) + 1);
392 // just return simple url substitution.
393 return new String[] { idstring, urlPrefix + idstring + urlSuffix };
397 return new String[] { "", urlPrefix };
402 public String toString()
404 return label + SEP + getUrlWithToken();
408 * @return delimited string containing label, url and target
410 public String toStringWithTarget()
412 return label + SEP + getUrlWithToken() + SEP + target;
416 * Parse the label from the link string
419 * Location of first occurrence of separator in link string
421 * Position of sequence id or name in link string
423 * Link string containing database name and url
424 * @return Position of last separator symbol prior to any regex symbols
426 protected int parseLabel(int firstSep, int psqid, String link)
433 p = link.indexOf(SEP, sep + 1);
434 } while (p > sep && p < psqid);
435 // Assuming that the URL itself does not contain any SEP symbols
436 // sep now contains last pipe symbol position prior to any regex symbols
437 label = link.substring(0, sep);
443 * Parse the target from the link string
446 * Link string containing database name and url
448 * Location of first separator symbol
450 * Location of end of any regular expression in link string
452 protected void parseTarget(String link, int sep, int endOfRegex)
454 int lastsep = link.lastIndexOf(SEP);
456 if ((lastsep != sep) && (lastsep > endOfRegex))
458 // final element in link string is the target
459 target = link.substring(lastsep + 1).trim();
466 if (target.indexOf(SEP) > -1)
468 // SEP terminated database name / www target at start of Label
469 target = target.substring(0, target.indexOf(SEP));
471 else if (target.indexOf(SPACE) > 2)
473 // space separated label - first word matches database name
474 target = target.substring(0, target.indexOf(SPACE));
479 * Parse the URL part of the link string
482 * Link string containing database name and url
484 * Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
486 * Position of id or name in link string
488 * Position of separator in link string
489 * @return Location of end of any regex in link string
491 protected int parseUrl(String link, String varName, int sqidPos, int sep)
493 urlPrefix = link.substring(sep + 1, sqidPos).trim();
495 // delimiter at start of regex: e.g. $SEQUENCE_ID=/
496 String startDelimiter = DELIM + varName + "=/";
498 // delimiter at end of regex: /=$
499 String endDelimiter = "/=" + DELIM;
501 int startLength = startDelimiter.length();
503 // Parse URL : Whole URL string first
504 int p = link.indexOf(endDelimiter, sqidPos + startLength);
506 if (link.indexOf(startDelimiter) == sqidPos
507 && (p > sqidPos + startLength))
509 // Extract Regex and suffix
510 urlSuffix = link.substring(p + endDelimiter.length());
512 regexReplace = link.substring(sqidPos + startLength, p));
518 // verify format is really correct.
519 if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
521 int lastsep = link.lastIndexOf(SEP);
522 if (lastsep < sqidPos + startLength - 1)
524 // the last SEP character was before the regex, ignore
525 lastsep = link.length();
527 urlSuffix = link.substring(sqidPos + startLength - 1, lastsep)
533 invalidMessage = "Warning: invalid regex structure for URL link : "
541 private void testRegexPerl(String r)
546 rg = Platform.newRegexPerl("/" + r + "/");
547 } catch (Exception e)
552 invalidMessage = "Invalid Regular Expression : '" + r + "'\n";
557 * Create a set of URL links for a sequence
560 * The sequence to create links for
562 * Map of links: key = id + SEP + link, value = [target, label, id,
565 public void createLinksFromSeq(final SequenceI seq,
566 Map<String, List<String>> linkset)
568 if (seq != null && dynamic)
570 createDynamicLinks(seq, linkset);
574 createStaticLink(linkset);
579 * Create a static URL link
582 * Map of links: key = id + SEP + link, value = [target, label, id,
585 protected void createStaticLink(Map<String, List<String>> linkset)
587 if (!linkset.containsKey(label + SEP + getUrlPrefix()))
589 // Add a non-dynamic link
590 linkset.put(label + SEP + getUrlPrefix(),
591 Arrays.asList(target, label, null, getUrlPrefix()));
596 * Create dynamic URL links
599 * The sequence to create links for
601 * Map of links: key = id + SEP + link, value = [target, label, id,
604 protected void createDynamicLinks(final SequenceI seq,
605 Map<String, List<String>> linkset)
607 // collect id string too
608 String id = seq.getName();
609 String descr = seq.getDescription();
610 if (descr != null && descr.length() < 1)
615 if (usesDBAccession()) // link is ID
617 // collect matching db-refs
618 List<DBRefEntry> dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
622 // if there are any dbrefs which match up with the link
625 for (int r = 0, nd = dbr.size(); r < nd; r++)
627 // create Bare ID link for this URL
628 createBareURLLink(dbr.get(r).getAccessionId(), true, linkset);
632 else if (!usesDBAccession() && id != null) // link is name
634 // create Bare ID link for this URL
635 createBareURLLink(id, false, linkset);
638 // Create urls from description but only for URL links which are regex
640 if (descr != null && getRegexReplace() != null)
642 // create link for this URL from description where regex matches
643 createBareURLLink(descr, false, linkset);
648 * Create a bare URL Link
649 * Returns map where key = id + SEP + link, and value = [target, label, id, link]
651 protected void createBareURLLink(String id, Boolean combineLabel,
652 Map<String, List<String>> linkset)
654 String[] urls = makeUrls(id, true);
657 for (int u = 0; u < urls.length; u += 2)
659 if (!linkset.containsKey(urls[u] + SEP + urls[u + 1]))
661 String thisLabel = label;
664 // incorporate label with idstring
665 thisLabel = label + SEP + urls[u];
668 linkset.put(urls[u] + SEP + urls[u + 1],
669 Arrays.asList(target, thisLabel, urls[u], urls[u + 1]));
676 * Sorts links (formatted as LinkName|LinkPattern) suitable for display in a
679 * <li>SEQUENCE_ID links precede DB_ACCESSION links (i.e. canonical lookup
680 * before cross-references)</li>
681 * <li>otherwise by Link name (case insensitive)</li>
686 public static void sort(List<String> nlinks)
688 Collections.sort(nlinks, LINK_COMPARATOR);