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
49 private static final String SEQUENCEID_PLACEHOLDER = DELIM + SEQUENCE_ID
52 private static final String ACCESSION_PLACEHOLDER = DELIM + DB_ACCESSION
56 * A comparator that puts SEQUENCE_ID template links before DB_ACCESSION
57 * links, and otherwise orders by link name + url (not case sensitive). It
58 * expects to compare strings formatted as "Name|URLTemplate" where the
59 * template may include $SEQUENCE_ID$ or $DB_ACCESSION$ or neither.
61 public static final Comparator<String> LINK_COMPARATOR = new Comparator<String>()
64 public int compare(String link1, String link2)
66 if (link1 == null || link2 == null)
68 return 0; // for failsafe only
70 if (link1.contains(SEQUENCEID_PLACEHOLDER)
71 && link2.contains(ACCESSION_PLACEHOLDER))
75 if (link2.contains(SEQUENCEID_PLACEHOLDER)
76 && link1.contains(ACCESSION_PLACEHOLDER))
80 return String.CASE_INSENSITIVE_ORDER.compare(link1, link2);
84 private static final String EQUALS = "=";
86 private static final String SPACE = " ";
88 private String urlSuffix;
90 private String urlPrefix;
92 private String target;
96 private String dbname;
98 private String regexReplace;
100 private boolean dynamic = false;
102 private boolean usesDBaccession = false;
104 private String invalidMessage = null;
107 * parse the given linkString of the form '<label>SEP<url>' into parts url may
108 * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
109 * must be of the form =/<perl style regex>/=$
113 public UrlLink(String link)
115 int sep = link.indexOf(SEP);
116 int psqid = link.indexOf(DELIM + DB_ACCESSION);
117 int nsqid = link.indexOf(DELIM + SEQUENCE_ID);
121 usesDBaccession = true;
123 sep = parseLabel(sep, psqid, link);
125 int endOfRegex = parseUrl(link, DB_ACCESSION, psqid, sep);
126 parseTarget(link, sep, endOfRegex);
131 sep = parseLabel(sep, nsqid, link);
133 int endOfRegex = parseUrl(link, SEQUENCE_ID, nsqid, sep);
135 parseTarget(link, sep, endOfRegex);
139 label = link.substring(0, sep).trim();
141 // if there's a third element in the url link string
142 // it is the target name, otherwise target=label
143 int lastsep = link.lastIndexOf(SEP);
146 urlPrefix = link.substring(sep + 1, lastsep).trim();
147 target = link.substring(lastsep + 1).trim();
151 urlPrefix = link.substring(sep + 1).trim();
155 regexReplace = null; // implies we trim any prefix if necessary //
159 label = label.trim();
160 target = target.trim();
164 * Alternative constructor for separate name, link and description
167 * The string used to match the link to a DB reference id
171 * The description of the associated target DB
173 public UrlLink(String name, String url, String desc)
175 this(name + SEP + url + SEP + desc);
179 * @return the url_suffix
181 public String getUrlSuffix()
187 * @return the url_prefix
189 public String getUrlPrefix()
197 public String getTarget()
205 public String getLabel()
210 public String getUrlWithToken()
212 String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
217 + ((regexReplace != null)
218 ? EQUALS + regexReplace + EQUALS + DELIM
221 + ((urlSuffix == null) ? "" : urlSuffix);
225 * @return the regexReplace
227 public String getRegexReplace()
233 * @return the invalidMessage
235 public String getInvalidMessage()
237 return invalidMessage;
241 * Check if URL string was parsed properly.
243 * @return boolean - if false then <code>getInvalidMessage</code> returns an
246 public boolean isValid()
248 return invalidMessage == null;
253 * @return whether link is dynamic
255 public boolean isDynamic()
262 * @return whether link uses DB Accession id
264 public boolean usesDBAccession()
266 return usesDBaccession;
274 public void setLabel(String newlabel)
276 this.label = newlabel;
284 public void setTarget(String desc)
290 * return one or more URL strings by applying regex to the given idstring
293 * @param onlyIfMatches
294 * - when true url strings are only made if regex is defined and
296 * @return String[] { part of idstring substituted, full substituted url , ..
297 * next part, next url..}
299 public String[] makeUrls(String idstring, boolean onlyIfMatches)
303 if (regexReplace != null)
305 com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
306 .perlCode("/" + regexReplace + "/");
307 if (rg.search(idstring))
309 int ns = rg.numSubs();
313 return new String[] { rg.stringMatched(),
314 urlPrefix + rg.stringMatched() + urlSuffix };
316 * else if (ns==1) { // take only subgroup match return new String[]
317 * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
323 for (int s = 0; s <= rg.numSubs(); s++)
325 System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
326 + " : " + rg.matchedTo(s) + " : '"
327 + rg.stringMatched(s) + "'");
329 // try to collate subgroup matches
330 Vector<String> subs = new Vector<>();
331 // have to loop through submatches, collating them at top level
336 if (s + 1 <= ns && rg.matchedTo(s) > -1
337 && rg.matchedTo(s + 1) > -1
338 && rg.matchedTo(s + 1) < rg.matchedTo(s))
340 // s is top level submatch. search for submatches enclosed by
344 while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
346 if (rg.matchedFrom(r) > -1)
348 mtch += rg.stringMatched(r);
352 if (mtch.length() > 0)
354 subs.addElement(mtch);
355 subs.addElement(urlPrefix + mtch + urlSuffix);
361 if (rg.matchedFrom(s) > -1)
363 subs.addElement(rg.stringMatched(s));
365 urlPrefix + rg.stringMatched(s) + urlSuffix);
371 String[] res = new String[subs.size()];
372 for (int r = 0, rs = subs.size(); r < rs; r++)
374 res[r] = subs.elementAt(r);
376 subs.removeAllElements();
385 /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
386 if (idstring.indexOf(SEP) > -1)
388 idstring = idstring.substring(idstring.lastIndexOf(SEP) + 1);
391 // just return simple url substitution.
392 return new String[] { idstring, urlPrefix + idstring + urlSuffix };
396 return new String[] { "", urlPrefix };
401 public String toString()
403 return label + SEP + getUrlWithToken();
407 * @return delimited string containing label, url and target
409 public String toStringWithTarget()
411 return label + SEP + getUrlWithToken() + SEP + target;
415 * Parse the label from the link string
418 * Location of first occurrence of separator in link string
420 * Position of sequence id or name in link string
422 * Link string containing database name and url
423 * @return Position of last separator symbol prior to any regex symbols
425 protected int parseLabel(int firstSep, int psqid, String link)
432 p = link.indexOf(SEP, sep + 1);
433 } while (p > sep && p < psqid);
434 // Assuming that the URL itself does not contain any SEP symbols
435 // sep now contains last pipe symbol position prior to any regex symbols
436 label = link.substring(0, sep);
442 * Parse the target from the link string
445 * Link string containing database name and url
447 * Location of first separator symbol
449 * Location of end of any regular expression in link string
451 protected void parseTarget(String link, int sep, int endOfRegex)
453 int lastsep = link.lastIndexOf(SEP);
455 if ((lastsep != sep) && (lastsep > endOfRegex))
457 // final element in link string is the target
458 target = link.substring(lastsep + 1).trim();
465 if (target.indexOf(SEP) > -1)
467 // SEP terminated database name / www target at start of Label
468 target = target.substring(0, target.indexOf(SEP));
470 else if (target.indexOf(SPACE) > 2)
472 // space separated label - first word matches database name
473 target = target.substring(0, target.indexOf(SPACE));
478 * Parse the URL part of the link string
481 * Link string containing database name and url
483 * Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
485 * Position of id or name in link string
487 * Position of separator in link string
488 * @return Location of end of any regex in link string
490 protected int parseUrl(String link, String varName, int sqidPos, int sep)
492 urlPrefix = link.substring(sep + 1, sqidPos).trim();
494 // delimiter at start of regex: e.g. $SEQUENCE_ID=/
495 String startDelimiter = DELIM + varName + "=/";
497 // delimiter at end of regex: /=$
498 String endDelimiter = "/=" + DELIM;
500 int startLength = startDelimiter.length();
502 // Parse URL : Whole URL string first
503 int p = link.indexOf(endDelimiter, sqidPos + startLength);
505 if (link.indexOf(startDelimiter) == sqidPos
506 && (p > sqidPos + startLength))
508 // Extract Regex and suffix
509 urlSuffix = link.substring(p + endDelimiter.length());
510 regexReplace = link.substring(sqidPos + startLength, p);
513 com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex
514 .perlCode("/" + regexReplace + "/");
517 invalidMessage = "Invalid Regular Expression : '" + regexReplace
520 } catch (Exception e)
522 invalidMessage = "Invalid Regular Expression : '" + regexReplace
530 // verify format is really correct.
531 if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
533 int lastsep = link.lastIndexOf(SEP);
534 if (lastsep < sqidPos + startLength - 1)
536 // the last SEP character was before the regex, ignore
537 lastsep = link.length();
539 urlSuffix = link.substring(sqidPos + startLength - 1, lastsep)
545 invalidMessage = "Warning: invalid regex structure for URL link : "
554 * Create a set of URL links for a sequence
557 * The sequence to create links for
559 * Map of links: key = id + SEP + link, value = [target, label, id,
562 public void createLinksFromSeq(final SequenceI seq,
563 Map<String, List<String>> linkset)
565 if (seq != null && dynamic)
567 createDynamicLinks(seq, linkset);
571 createStaticLink(linkset);
576 * Create a static URL link
579 * Map of links: key = id + SEP + link, value = [target, label, id,
582 protected void createStaticLink(Map<String, List<String>> linkset)
584 if (!linkset.containsKey(label + SEP + getUrlPrefix()))
586 // Add a non-dynamic link
587 linkset.put(label + SEP + getUrlPrefix(),
588 Arrays.asList(target, label, null, getUrlPrefix()));
593 * Create dynamic URL links
596 * The sequence to create links for
598 * Map of links: key = id + SEP + link, value = [target, label, id,
601 protected void createDynamicLinks(final SequenceI seq,
602 Map<String, List<String>> linkset)
604 // collect id string too
605 String id = seq.getName();
606 String descr = seq.getDescription();
607 if (descr != null && descr.length() < 1)
612 if (usesDBAccession()) // link is ID
614 // collect matching db-refs
615 List<DBRefEntry> dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
619 // if there are any dbrefs which match up with the link
622 for (int r = 0, nd = dbr.size(); r < nd; r++)
624 // create Bare ID link for this URL
625 createBareURLLink(dbr.get(r).getAccessionId(), true, linkset);
629 else if (!usesDBAccession() && id != null) // link is name
631 // create Bare ID link for this URL
632 createBareURLLink(id, false, linkset);
635 // Create urls from description but only for URL links which are regex
637 if (descr != null && getRegexReplace() != null)
639 // create link for this URL from description where regex matches
640 createBareURLLink(descr, false, linkset);
645 * Create a bare URL Link
646 * Returns map where key = id + SEP + link, and value = [target, label, id, link]
648 protected void createBareURLLink(String id, Boolean combineLabel,
649 Map<String, List<String>> linkset)
651 String[] urls = makeUrls(id, true);
654 for (int u = 0; u < urls.length; u += 2)
656 if (!linkset.containsKey(urls[u] + SEP + urls[u + 1]))
658 String thisLabel = label;
661 // incorporate label with idstring
662 thisLabel = label + SEP + urls[u];
665 linkset.put(urls[u] + SEP + urls[u + 1],
666 Arrays.asList(target, thisLabel, urls[u], urls[u + 1]));
673 * Sorts links (formatted as LinkName|LinkPattern) suitable for display in a
676 * <li>SEQUENCE_ID links precede DB_ACCESSION links (i.e. canonical lookup
677 * before cross-references)</li>
678 * <li>otherwise by Link name (case insensitive)</li>
683 public static void sort(List<String> nlinks)
685 Collections.sort(nlinks, LINK_COMPARATOR);