Code tidy up. Unit test passes.
[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.SEQUENCE_ID;
24 import static jalview.util.UrlConstants.SEQUENCE_NAME;
25
26 import java.util.Vector;
27
28 public class UrlLink
29 {
30   /**
31    * helper class to parse URL Link strings taken from applet parameters or
32    * jalview properties file using the com.stevesoft.pat.Regex implementation.
33    * Jalview 2.4 extension allows regular expressions to be used to parse ID
34    * strings and replace the result in the URL. Regex's operate on the whole ID
35    * string given to the matchURL method, if no regex is supplied, then only
36    * text following the first pipe symbol will be susbstituted. Usage
37    * documentation todo.
38    */
39   private String url_suffix, url_prefix, target, label, regexReplace;
40
41   private boolean dynamic = false;
42
43   private boolean uses_seq_id = false;
44
45   private String invalidMessage = null;
46
47   /**
48    * parse the given linkString of the form '<label>|<url>' into parts url may
49    * contain a string $SEQUENCE_ID<=optional regex=>$ where <=optional regex=>
50    * must be of the form =/<perl style regex>/=$
51    * 
52    * @param link
53    */
54   public UrlLink(String link)
55   {
56     int sep = link.indexOf("|");
57     int psqid = link.indexOf("$" + SEQUENCE_ID);
58     int nsqid = link.indexOf("$" + SEQUENCE_NAME);
59     if (psqid > -1)
60     {
61       dynamic = true;
62       uses_seq_id = true;
63
64       sep = parseTargetAndLabel(sep, psqid, link);
65
66       parseUrl(link, SEQUENCE_ID, psqid, sep);
67     }
68     else if (nsqid > -1)
69     {
70       dynamic = true;
71       sep = parseTargetAndLabel(sep, nsqid, link);
72
73       parseUrl(link, SEQUENCE_NAME, nsqid, sep);
74     }
75     else
76     {
77       target = link.substring(0, sep);
78       sep = link.lastIndexOf("|");
79       label = link.substring(0, sep);
80       url_prefix = link.substring(sep + 1);
81       regexReplace = null; // implies we trim any prefix if necessary //
82       // regexReplace=".*\\|?(.*)";
83       url_suffix = null;
84     }
85
86     label = label.trim();
87     target = target.trim();
88     target = target.toUpperCase(); // DBRefEntry uppercases DB names
89   }
90
91   /**
92    * @return the url_suffix
93    */
94   public String getUrl_suffix()
95   {
96     return url_suffix;
97   }
98
99   /**
100    * @return the url_prefix
101    */
102   public String getUrl_prefix()
103   {
104     return url_prefix;
105   }
106
107   /**
108    * @return the target
109    */
110   public String getTarget()
111   {
112     return target;
113   }
114
115   /**
116    * @return the label
117    */
118   public String getLabel()
119   {
120     return label;
121   }
122
123   /**
124    * @return the regexReplace
125    */
126   public String getRegexReplace()
127   {
128     return regexReplace;
129   }
130
131   /**
132    * @return the invalidMessage
133    */
134   public String getInvalidMessage()
135   {
136     return invalidMessage;
137   }
138
139   /**
140    * Check if URL string was parsed properly.
141    * 
142    * @return boolean - if false then <code>getInvalidMessage</code> returns an
143    *         error message
144    */
145   public boolean isValid()
146   {
147     return invalidMessage == null;
148   }
149
150   /**
151    * return one or more URL strings by applying regex to the given idstring
152    * 
153    * @param idstring
154    * @param onlyIfMatches
155    *          - when true url strings are only made if regex is defined and
156    *          matches
157    * @return String[] { part of idstring substituted, full substituted url , ..
158    *         next part, next url..}
159    */
160   public String[] makeUrls(String idstring, boolean onlyIfMatches)
161   {
162     if (dynamic)
163     {
164       if (regexReplace != null)
165       {
166         com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
167                 + regexReplace + "/");
168         if (rg.search(idstring))
169         {
170           int ns = rg.numSubs();
171           if (ns == 0)
172           {
173             // take whole regex
174             return new String[] { rg.stringMatched(),
175                 url_prefix + rg.stringMatched() + url_suffix };
176           } /*
177              * else if (ns==1) { // take only subgroup match return new String[]
178              * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
179              * }; }
180              */
181           else
182           {
183             // debug
184             for (int s = 0; s <= rg.numSubs(); s++)
185             {
186               System.err.println("Sub " + s + " : " + rg.matchedFrom(s)
187                       + " : " + rg.matchedTo(s) + " : '"
188                       + rg.stringMatched(s) + "'");
189             }
190             // try to collate subgroup matches
191             Vector subs = new Vector();
192             // have to loop through submatches, collating them at top level
193             // match
194             int s = 0; // 1;
195             while (s <= ns)
196             {
197               if (s + 1 <= ns && rg.matchedTo(s) > -1
198                       && rg.matchedTo(s + 1) > -1
199                       && rg.matchedTo(s + 1) < rg.matchedTo(s))
200               {
201                 // s is top level submatch. search for submatches enclosed by
202                 // this one
203                 int r = s + 1;
204                 String mtch = "";
205                 while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
206                 {
207                   if (rg.matchedFrom(r) > -1)
208                   {
209                     mtch += rg.stringMatched(r);
210                   }
211                   r++;
212                 }
213                 if (mtch.length() > 0)
214                 {
215                   subs.addElement(mtch);
216                   subs.addElement(url_prefix + mtch + url_suffix);
217                 }
218                 s = r;
219               }
220               else
221               {
222                 if (rg.matchedFrom(s) > -1)
223                 {
224                   subs.addElement(rg.stringMatched(s));
225                   subs.addElement(url_prefix + rg.stringMatched(s)
226                           + url_suffix);
227                 }
228                 s++;
229               }
230             }
231
232             String[] res = new String[subs.size()];
233             for (int r = 0, rs = subs.size(); r < rs; r++)
234             {
235               res[r] = (String) subs.elementAt(r);
236             }
237             subs.removeAllElements();
238             return res;
239           }
240         }
241         if (onlyIfMatches)
242         {
243           return null;
244         }
245       }
246       /* Otherwise - trim off any 'prefix' - pre 2.4 Jalview behaviour */
247       if (idstring.indexOf("|") > -1)
248       {
249         idstring = idstring.substring(idstring.lastIndexOf("|") + 1);
250       }
251
252       // just return simple url substitution.
253       return new String[] { idstring, url_prefix + idstring + url_suffix };
254     }
255     else
256     {
257       return new String[] { "", url_prefix };
258     }
259   }
260
261   @Override
262   public String toString()
263   {
264     return label
265             + "|"
266             + url_prefix
267             + (dynamic ? ("$" + SEQUENCE_ID + ((regexReplace != null) ? "="
268                     + regexReplace + "=$" : "$")) : "")
269             + ((url_suffix == null) ? "" : url_suffix);
270
271   }
272
273   /**
274    * 
275    * @param firstSep
276    *          Location of first occurrence of separator in link string
277    * @param psqid
278    *          Position of sequence id or name in link string
279    * @param link
280    *          Link string containing database name and url
281    * @return Position of last separator symbol prior to any regex symbols
282    */
283   protected int parseTargetAndLabel(int firstSep, int psqid, String link)
284   {
285     int p = firstSep;
286     int sep = firstSep;
287     do
288     {
289       sep = p;
290       p = link.indexOf("|", sep + 1);
291     } while (p > sep && p < psqid);
292     // Assuming that the URL itself does not contain any '|' symbols
293     // sep now contains last pipe symbol position prior to any regex symbols
294     label = link.substring(0, sep);
295     if (label.indexOf("|") > -1)
296     {
297       // | terminated database name / www target at start of Label
298       target = label.substring(0, label.indexOf("|"));
299     }
300     else if (label.indexOf(" ") > 2)
301     {
302       // space separated Label - matches database name
303       target = label.substring(0, label.indexOf(" "));
304     }
305     else
306     {
307       target = label;
308     }
309     return sep;
310   }
311
312   /**
313    * Parse the URL part of the link string
314    * 
315    * @param link
316    *          Link string containing database name and url
317    * @param varName
318    *          Name of variable in url string (e.g. SEQUENCE_ID, SEQUENCE_NAME)
319    * @param sqidPos
320    *          Position of id or name in link string
321    * @param sep
322    *          Position of separator in link string
323    */
324   protected void parseUrl(String link, String varName, int sqidPos, int sep)
325   {
326     url_prefix = link.substring(sep + 1, sqidPos);
327
328     // delimiter at start of regex: e.g. $SEQUENCE_ID=/
329     String startDelimiter = "$" + varName + "=/";
330
331     // delimiter at end of regex: /=$
332     String endDelimiter = "/=$";
333
334     int startLength = startDelimiter.length();
335
336     // Parse URL : Whole URL string first
337     int p = link.indexOf(endDelimiter, sqidPos + startLength);
338
339     if (link.indexOf(startDelimiter) == sqidPos
340             && (p > sqidPos + startLength))
341     {
342       // Extract Regex and suffix
343       url_suffix = link.substring(p + endDelimiter.length());
344       regexReplace = link.substring(sqidPos + startLength, p);
345       try
346       {
347         com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
348                 + regexReplace + "/");
349         if (rg == null)
350         {
351           invalidMessage = "Invalid Regular Expression : '" + regexReplace
352                   + "'\n";
353         }
354       } catch (Exception e)
355       {
356         invalidMessage = "Invalid Regular Expression : '" + regexReplace
357                 + "'\n";
358       }
359     }
360     else
361     {
362       // no regex
363       regexReplace = null;
364       // verify format is really correct.
365       if (link.indexOf("$" + varName + "$") == sqidPos)
366       {
367         url_suffix = link.substring(sqidPos + startLength - 1);
368         regexReplace = null;
369       }
370       else
371       {
372         invalidMessage = "Warning: invalid regex structure for URL link : "
373                 + link;
374       }
375     }
376   }
377
378   private static void testUrls(UrlLink ul, String idstring, String[] urls)
379   {
380
381     if (urls == null)
382     {
383       System.out.println("Created NO urls.");
384     }
385     else
386     {
387       System.out.println("Created " + (urls.length / 2) + " Urls.");
388       for (int uls = 0; uls < urls.length; uls += 2)
389       {
390         System.out.println("URL Replacement text : " + urls[uls]
391                 + " : URL : " + urls[uls + 1]);
392       }
393     }
394   }
395
396   public static void main(String argv[])
397   {
398     String[] links = new String[] {
399     /*
400      * "AlinkT|Target|http://foo.foo.soo/",
401      * "myUrl1|http://$SEQUENCE_ID=/[0-9]+/=$.someserver.org/foo",
402      * "myUrl2|http://$SEQUENCE_ID=/(([0-9]+).+([A-Za-z]+))/=$.someserver.org/foo"
403      * ,
404      * "myUrl3|http://$SEQUENCE_ID=/([0-9]+).+([A-Za-z]+)/=$.someserver.org/foo"
405      * , "myUrl4|target|http://$SEQUENCE_ID$.someserver.org/foo|too",
406      * "PF1|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(?:PFAM:)?(.+)/=$"
407      * ,
408      * "PF2|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/(PFAM:)?(.+)/=$"
409      * ,
410      * "PF3|http://us.expasy.org/cgi-bin/niceprot.pl?$SEQUENCE_ID=/PFAM:(.+)/=$"
411      * , "NOTFER|http://notfer.org/$SEQUENCE_ID=/(?<!\\s)(.+)/=$",
412      */
413     "NESTED|http://nested/$" + SEQUENCE_ID
414             + "=/^(?:Label:)?(?:(?:gi\\|(\\d+))|([^:]+))/=$/nested" };
415     String[] idstrings = new String[] {
416     /*
417      * //"LGUL_human", //"QWIQW_123123", "uniprot|why_do+_12313_foo",
418      * //"123123312", "123123 ABCDE foo", "PFAM:PF23943",
419      */
420     "Label:gi|9234|pdb|102L|A" };
421     // TODO: test the setLabel method.
422     for (int i = 0; i < links.length; i++)
423     {
424       UrlLink ul = new UrlLink(links[i]);
425       if (ul.isValid())
426       {
427         System.out.println("\n\n\n");
428         System.out.println("Link " + i + " " + links[i] + " : "
429                 + ul.toString());
430         System.out.println(" pref : "
431                 + ul.getUrl_prefix()
432                 + "\n suf : "
433                 + ul.getUrl_suffix()
434                 + "\n : "
435                 + ((ul.getRegexReplace() != null) ? ul.getRegexReplace()
436                         : ""));
437         for (int ids = 0; ids < idstrings.length; ids++)
438         {
439           System.out.println("ID String : " + idstrings[ids]
440                   + "\nWithout onlyIfMatches:");
441           String[] urls = ul.makeUrls(idstrings[ids], false);
442           testUrls(ul, idstrings[ids], urls);
443           System.out.println("With onlyIfMatches set.");
444           urls = ul.makeUrls(idstrings[ids], true);
445           testUrls(ul, idstrings[ids], urls);
446         }
447       }
448       else
449       {
450         System.err.println("Invalid URLLink : " + links[i] + " : "
451                 + ul.getInvalidMessage());
452       }
453     }
454   }
455
456   public boolean isDynamic()
457   {
458     // TODO Auto-generated method stub
459     return dynamic;
460   }
461
462   public boolean usesSeqId()
463   {
464     return uses_seq_id;
465   }
466
467   public void setLabel(String newlabel)
468   {
469     this.label = newlabel;
470   }
471 }