JAL-2316 Refactoring of functionality to provide urls to gui
[jalview.git] / src / jalview / util / UrlLink.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
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.
11  *  
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.
16  * 
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.
20  */
21 package jalview.util;
22
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;
27
28 import jalview.datamodel.DBRefEntry;
29 import jalview.datamodel.SequenceI;
30
31 import java.util.Arrays;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Vector;
35
36 public class UrlLink
37 {
38   /**
39    * 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
44    * text following the first pipe symbol will be substituted. Usage
45    * documentation todo.
46    */
47
48   private String urlSuffix;
49
50   private String urlPrefix;
51
52   private String target;
53
54   private String label;
55
56   private String regexReplace;
57
58   private boolean dynamic = false;
59
60   private boolean usesDBaccession = false;
61
62   private String invalidMessage = null;
63
64   /**
65    * parse the given linkString of the form '<label>SEP<url>' into parts url may
66    * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
67    * must be of the form =/<perl style regex>/=$
68    * 
69    * @param link
70    */
71   public UrlLink(String link)
72   {
73     int sep = link.indexOf(SEP);
74     int psqid = link.indexOf(DELIM + DB_ACCESSION);
75     int nsqid = link.indexOf(DELIM + SEQUENCE_ID);
76     if (psqid > -1)
77     {
78       dynamic = true;
79       usesDBaccession = true;
80
81       sep = parseTargetAndLabel(sep, psqid, link);
82
83       parseUrl(link, DB_ACCESSION, psqid, sep);
84     }
85     else if (nsqid > -1)
86     {
87       dynamic = true;
88       sep = parseTargetAndLabel(sep, nsqid, link);
89
90       parseUrl(link, SEQUENCE_ID, nsqid, sep);
91     }
92     else
93     {
94       target = link.substring(0, sep);
95       sep = link.lastIndexOf(SEP);
96       label = link.substring(0, sep);
97       urlPrefix = link.substring(sep + 1).trim();
98       regexReplace = null; // implies we trim any prefix if necessary //
99       urlSuffix = null;
100     }
101
102     label = label.trim();
103     target = target.trim();
104   }
105
106   /**
107    * @return the url_suffix
108    */
109   public String getUrl_suffix()
110   {
111     return urlSuffix;
112   }
113
114   /**
115    * @return the url_prefix
116    */
117   public String getUrl_prefix()
118   {
119     return urlPrefix;
120   }
121
122   /**
123    * @return the target
124    */
125   public String getTarget()
126   {
127     return target;
128   }
129
130   /**
131    * @return the label
132    */
133   public String getLabel()
134   {
135     return label;
136   }
137
138   /**
139    * @return the regexReplace
140    */
141   public String getRegexReplace()
142   {
143     return regexReplace;
144   }
145
146   /**
147    * @return the invalidMessage
148    */
149   public String getInvalidMessage()
150   {
151     return invalidMessage;
152   }
153
154   /**
155    * Check if URL string was parsed properly.
156    * 
157    * @return boolean - if false then <code>getInvalidMessage</code> returns an
158    *         error message
159    */
160   public boolean isValid()
161   {
162     return invalidMessage == null;
163   }
164
165   /**
166    * 
167    * @return whether link is dynamic
168    */
169   public boolean isDynamic()
170   {
171     return dynamic;
172   }
173
174   /**
175    * 
176    * @return whether link uses DB Accession id
177    */
178   public boolean usesDBAccession()
179   {
180     return usesDBaccession;
181   }
182
183   /**
184    * Set the label
185    * 
186    * @param newlabel
187    */
188   public void setLabel(String newlabel)
189   {
190     this.label = newlabel;
191   }
192
193   /**
194    * return one or more URL strings by applying regex to the given idstring
195    * 
196    * @param idstring
197    * @param onlyIfMatches
198    *          - when true url strings are only made if regex is defined and
199    *          matches
200    * @return String[] { part of idstring substituted, full substituted url , ..
201    *         next part, next url..}
202    */
203   public String[] makeUrls(String idstring, boolean onlyIfMatches)
204   {
205     if (dynamic)
206     {
207       if (regexReplace != null)
208       {
209         com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
210                 + regexReplace + "/");
211         if (rg.search(idstring))
212         {
213           int ns = rg.numSubs();
214           if (ns == 0)
215           {
216             // take whole regex
217             return new String[] { rg.stringMatched(),
218                 urlPrefix + rg.stringMatched() + urlSuffix };
219           } /*
220              * else if (ns==1) { // take only subgroup match return new String[]
221              * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
222              * }; }
223              */
224           else
225           {
226             // debug
227             for (int s = 0; s <= rg.numSubs(); s++)
228             {
229               System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
230                       + " : " + rg.matchedTo(s) + " : '"
231                       + rg.stringMatched(s) + "'");
232             }
233             // try to collate subgroup matches
234             Vector subs = new Vector();
235             // have to loop through submatches, collating them at top level
236             // match
237             int s = 0; // 1;
238             while (s <= ns)
239             {
240               if (s + 1 <= ns && rg.matchedTo(s) > -1
241                       && rg.matchedTo(s + 1) > -1
242                       && rg.matchedTo(s + 1) < rg.matchedTo(s))
243               {
244                 // s is top level submatch. search for submatches enclosed by
245                 // this one
246                 int r = s + 1;
247                 String mtch = "";
248                 while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
249                 {
250                   if (rg.matchedFrom(r) > -1)
251                   {
252                     mtch += rg.stringMatched(r);
253                   }
254                   r++;
255                 }
256                 if (mtch.length() > 0)
257                 {
258                   subs.addElement(mtch);
259                   subs.addElement(urlPrefix + mtch + urlSuffix);
260                 }
261                 s = r;
262               }
263               else
264               {
265                 if (rg.matchedFrom(s) > -1)
266                 {
267                   subs.addElement(rg.stringMatched(s));
268                   subs.addElement(urlPrefix + rg.stringMatched(s)
269                           + urlSuffix);
270                 }
271                 s++;
272               }
273             }
274
275             String[] res = new String[subs.size()];
276             for (int r = 0, rs = subs.size(); r < rs; r++)
277             {
278               res[r] = (String) subs.elementAt(r);
279             }
280             subs.removeAllElements();
281             return res;
282           }
283         }
284         if (onlyIfMatches)
285         {
286           return null;
287         }
288       }
289       /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
290       if (idstring.indexOf(SEP) > -1)
291       {
292         idstring = idstring.substring(idstring.lastIndexOf(SEP) + 1);
293       }
294
295       // just return simple url substitution.
296       return new String[] { idstring, urlPrefix + idstring + urlSuffix };
297     }
298     else
299     {
300       return new String[] { "", urlPrefix };
301     }
302   }
303
304   @Override
305   public String toString()
306   {
307     String var = (usesDBaccession ? DB_ACCESSION : SEQUENCE_ID);
308
309     return label
310             + SEP
311             + urlPrefix
312             + (dynamic ? (DELIM + var + ((regexReplace != null) ? "="
313                     + regexReplace + "=" + DELIM : DELIM)) : "")
314             + ((urlSuffix == null) ? "" : urlSuffix);
315   }
316
317   /**
318    * 
319    * @param firstSep
320    *          Location of first occurrence of separator in link string
321    * @param psqid
322    *          Position of sequence id or name in link string
323    * @param link
324    *          Link string containing database name and url
325    * @return Position of last separator symbol prior to any regex symbols
326    */
327   protected int parseTargetAndLabel(int firstSep, int psqid, String link)
328   {
329     int p = firstSep;
330     int sep = firstSep;
331     do
332     {
333       sep = p;
334       p = link.indexOf(SEP, sep + 1);
335     } while (p > sep && p < psqid);
336     // Assuming that the URL itself does not contain any SEP symbols
337     // sep now contains last pipe symbol position prior to any regex symbols
338     label = link.substring(0, sep);
339     if (label.indexOf(SEP) > -1)
340     {
341       // SEP terminated database name / www target at start of Label
342       target = label.substring(0, label.indexOf(SEP));
343     }
344     else if (label.indexOf(" ") > 2)
345     {
346       // space separated Label - matches database name
347       target = label.substring(0, label.indexOf(" "));
348     }
349     else
350     {
351       target = label;
352     }
353     return sep;
354   }
355
356   /**
357    * Parse the URL part of the link string
358    * 
359    * @param link
360    *          Link string containing database name and url
361    * @param varName
362    *          Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
363    * @param sqidPos
364    *          Position of id or name in link string
365    * @param sep
366    *          Position of separator in link string
367    */
368   protected void parseUrl(String link, String varName, int sqidPos, int sep)
369   {
370     urlPrefix = link.substring(sep + 1, sqidPos).trim();
371
372     // delimiter at start of regex: e.g. $SEQUENCE_ID=/
373     String startDelimiter = DELIM + varName + "=/";
374
375     // delimiter at end of regex: /=$
376     String endDelimiter = "/=" + DELIM;
377
378     int startLength = startDelimiter.length();
379
380     // Parse URL : Whole URL string first
381     int p = link.indexOf(endDelimiter, sqidPos + startLength);
382
383     if (link.indexOf(startDelimiter) == sqidPos
384             && (p > sqidPos + startLength))
385     {
386       // Extract Regex and suffix
387       urlSuffix = link.substring(p + endDelimiter.length());
388       regexReplace = link.substring(sqidPos + startLength, p);
389       try
390       {
391         com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
392                 + regexReplace + "/");
393         if (rg == null)
394         {
395           invalidMessage = "Invalid Regular Expression : '" + regexReplace
396                   + "'\n";
397         }
398       } catch (Exception e)
399       {
400         invalidMessage = "Invalid Regular Expression : '" + regexReplace
401                 + "'\n";
402       }
403     }
404     else
405     {
406       // no regex
407       regexReplace = null;
408       // verify format is really correct.
409       if (link.indexOf(DELIM + varName + DELIM) == sqidPos)
410       {
411         urlSuffix = link.substring(sqidPos + startLength - 1);
412         regexReplace = null;
413       }
414       else
415       {
416         invalidMessage = "Warning: invalid regex structure for URL link : "
417                 + link;
418       }
419     }
420   }
421
422   /**
423    * Create a set of URL links for a sequence
424    * 
425    * @param seq
426    *          The sequence to create links for
427    * @param linkset
428    *          Map of links: key = id + SEP + link, value = [target, label, id,
429    *          link]
430    */
431   public void createLinksFromSeq(final SequenceI seq,
432           Map<String, List<String>> linkset)
433   {
434     if (seq != null && dynamic)
435     {
436       createDynamicLinks(seq, linkset);
437     }
438     else
439     {
440       createStaticLink(linkset);
441     }
442   }
443
444   /**
445    * Create a static URL link
446    * 
447    * @param linkset
448    *          Map of links: key = id + SEP + link, value = [target, label, id,
449    *          link]
450    */
451   protected void createStaticLink(Map<String, List<String>> linkset)
452   {
453     if (!linkset.containsKey(label + SEP + getUrl_prefix()))
454     {
455       // Add a non-dynamic link
456       linkset.put(label + SEP + getUrl_prefix(),
457               Arrays.asList(target, label, null, getUrl_prefix()));
458     }
459   }
460
461   /**
462    * Create dynamic URL links
463    * 
464    * @param seq
465    *          The sequence to create links for
466    * @param linkset
467    *          Map of links: key = id + SEP + link, value = [target, label, id,
468    *          link]
469    */
470   protected void createDynamicLinks(final SequenceI seq,
471           Map<String, List<String>> linkset)
472   {
473     // collect id string too
474     String id = seq.getName();
475     String descr = seq.getDescription();
476     if (descr != null && descr.length() < 1)
477     {
478       descr = null;
479     }
480
481     if (usesDBAccession()) // link is ID
482     {
483       // collect matching db-refs
484       DBRefEntry[] dbr = DBRefUtils.selectRefs(seq.getDBRefs(),
485               new String[] { target });
486
487       // if there are any dbrefs which match up with the link
488       if (dbr != null)
489       {
490         for (int r = 0; r < dbr.length; r++)
491         {
492           // create Bare ID link for this URL
493           createBareURLLink(dbr[r].getAccessionId(), true, linkset);
494         }
495       }
496     }
497     else if (!usesDBAccession() && id != null) // link is name
498     {
499       // create Bare ID link for this URL
500       createBareURLLink(id, false, linkset);
501     }
502
503     // Create urls from description but only for URL links which are regex
504     // links
505     if (descr != null && getRegexReplace() != null)
506     {
507       // create link for this URL from description where regex matches
508       createBareURLLink(descr, false, linkset);
509     }
510   }
511
512   /*
513    * Create a bare URL Link
514    * Returns map where key = id + SEP + link, and value = [target, label, id, link]
515    */
516   protected void createBareURLLink(String id, Boolean combineLabel,
517           Map<String, List<String>> linkset)
518   {
519     String[] urls = makeUrls(id, true);
520     if (urls != null)
521     {
522       for (int u = 0; u < urls.length; u += 2)
523       {
524         if (!linkset.containsKey(urls[u] + SEP + urls[u + 1]))
525         {
526           String thisLabel = label;
527           if (combineLabel)
528           {
529             // incorporate label with idstring
530             thisLabel = label + SEP + urls[u];
531           }
532
533           linkset.put(urls[u] + SEP + urls[u + 1],
534                   Arrays.asList(target, thisLabel, urls[u], urls[u + 1]));
535         }
536       }
537     }
538   }
539
540   private static void testUrls(UrlLink ul, String idstring, String[] urls)
541   {
542
543     if (urls == null)
544     {
545       System.out.println("Created NO urls.");
546     }
547     else
548     {
549       System.out.println("Created " + (urls.length / 2) + " Urls.");
550       for (int uls = 0; uls < urls.length; uls += 2)
551       {
552         System.out.println("URL Replacement text : " + urls[uls]
553                 + " : URL : " + urls[uls + 1]);
554       }
555     }
556   }
557
558   public static void main(String argv[])
559   {
560     String[] links = new String[] {
561     /*
562      * "AlinkT|Target|http://foo.foo.soo/",
563      * "myUrl1|http://$SEQUENCE_ID=/[0-9]+/=$.someserver.org/foo",
564      * "myUrl2|http://$SEQUENCE_ID=/(([0-9]+).+([A-Za-z]+))/=$.someserver.org/foo"
565      * ,
566      * "myUrl3|http://$SEQUENCE_ID=/([0-9]+).+([A-Za-z]+)/=$.someserver.org/foo"
567      * , "myUrl4|target|http://$SEQUENCE_ID$.someserver.org/foo|too",
568      * "PF1|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(?:PFAM:)?(.+)/=$"
569      * ,
570      * "PF2|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(PFAM:)?(.+)/=$"
571      * ,
572      * "PF3|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/PFAM:(.+)/=$"
573      * , "NOTFER|http://notfer.org/$SEQUENCE_ID=/(?<!\\s)(.+)/=$",
574      */
575     "NESTED|http://nested/$" + DB_ACCESSION
576             + "=/^(?:Label:)?(?:(?:gi\\|(\\d+))|([^:]+))/=$/nested" };
577     String[] idstrings = new String[] {
578     /*
579      * //"LGUL_human", //"QWIQW_123123", "uniprot|why_do+_12313_foo",
580      * //"123123312", "123123 ABCDE foo", "PFAM:PF23943",
581      */
582     "Label:gi|9234|pdb|102L|A" };
583     // TODO: test the setLabel method.
584     for (int i = 0; i < links.length; i++)
585     {
586       UrlLink ul = new UrlLink(links[i]);
587       if (ul.isValid())
588       {
589         System.out.println("\n\n\n");
590         System.out.println("Link " + i + " " + links[i] + " : "
591                 + ul.toString());
592         System.out.println(" pref : "
593                 + ul.getUrl_prefix()
594                 + "\n suf : "
595                 + ul.getUrl_suffix()
596                 + "\n : "
597                 + ((ul.getRegexReplace() != null) ? ul.getRegexReplace()
598                         : ""));
599         for (int ids = 0; ids < idstrings.length; ids++)
600         {
601           System.out.println("ID String : " + idstrings[ids]
602                   + "\nWithout onlyIfMatches:");
603           String[] urls = ul.makeUrls(idstrings[ids], false);
604           testUrls(ul, idstrings[ids], urls);
605           System.out.println("With onlyIfMatches set.");
606           urls = ul.makeUrls(idstrings[ids], true);
607           testUrls(ul, idstrings[ids], urls);
608         }
609       }
610       else
611       {
612         System.err.println("Invalid URLLink : " + links[i] + " : "
613                 + ul.getInvalidMessage());
614       }
615     }
616   }
617 }