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