27b81d4583134374618fe1de92a0f5515e3d4ead
[jalview.git] / src / jalview / util / GroupUrlLink.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.5)
3  * Copyright (C) 2010 J Procter, AM Waterhouse, G Barton, M Clamp, S Searle
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 of the License, or (at your option) any later version.
10  * 
11  * Jalview is distributed in the hope that it will be useful, but 
12  * WITHOUT ANY WARRANTY; without even the implied warranty 
13  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
14  * PURPOSE.  See the GNU General Public License for more details.
15  * 
16  * You should have received a copy of the GNU General Public License along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 package jalview.util;
19
20 import jalview.datamodel.Sequence;
21 import jalview.datamodel.SequenceI;
22
23 import java.util.Hashtable;
24 import java.util.Vector;
25
26 public class GroupUrlLink
27 {
28   /**
29    * Helper class based on the UrlLink class which enables URLs to be
30    * constructed from sequences or IDs associated with a group of sequences. URL
31    * definitions consist of a pipe separated string containing a <label>|<url
32    * construct>|<separator character>[|<sequence separator character>]. The url
33    * construct includes regex qualified tokens which are replaced with seuqence
34    * IDs ($SEQUENCE_IDS$) and/or seuqence regions ($SEQUENCES$) that are
35    * extracted from the group. See <code>UrlLink</code> for more information
36    * about the approach, and the original implementation. Documentation to come.
37    * Note - groupUrls can be very big!
38    */
39   private String url_prefix, target, label;
40
41   /**
42    * these are all filled in order of the occurence of each token in the url
43    * string template
44    */
45   private String url_suffix[], separators[], regexReplace[];
46
47   private String invalidMessage = null;
48
49   /**
50    * tokens that can be replaced in the URL.
51    */
52   private static String[] tokens;
53
54   /**
55    * position of each token (which can appear once only) in the url
56    */
57   private int[] segs;
58
59   /**
60    * contains tokens in the order they appear in the URL template.
61    */
62   private String[] mtch;
63   static
64   {
65     if (tokens == null)
66     {
67       tokens = new String[]
68       { "SEQUENCEIDS", "SEQUENCES", "DATASETID" };
69     }
70   }
71
72   // private int idseg = -1, seqseg = -1;
73
74   /**
75    * parse the given linkString of the form '<label>|<url>|separator
76    * char[|optional sequence separator char]' into parts. url may contain a
77    * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
78    * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
79    * $SEQUENCES<=optional regex=>$.
80    * 
81    * @param link
82    */
83   public GroupUrlLink(String link)
84   {
85     int sep = link.indexOf("|");
86     segs = new int[tokens.length];
87     int ntoks = 0;
88     for (int i = 0; i < segs.length; i++)
89     {
90       if ((segs[i] = link.indexOf("$" + tokens[i])) > -1)
91       {
92         ntoks++;
93       }
94     }
95     // expect at least one token
96     if (ntoks == 0)
97     {
98       invalidMessage = "Group URL string must contain at least one of ";
99       for (int i = 0; i < segs.length; i++)
100       {
101         invalidMessage += " '$" + tokens[i] + "[=/regex=/]$'";
102       }
103       return;
104     }
105
106     int[] ptok = new int[ntoks + 1];
107     String[] tmtch = new String[ntoks + 1];
108     mtch = new String[ntoks];
109     for (int i = 0, t = 0; i < segs.length; i++)
110     {
111       if (segs[i] > -1)
112       {
113         ptok[t] = segs[i];
114         tmtch[t++] = tokens[i];
115       }
116     }
117     ptok[ntoks] = link.length();
118     tmtch[ntoks] = "$$$$$$$$$";
119     jalview.util.QuickSort.sort(ptok, tmtch);
120     for (int i = 0; i < ntoks; i++)
121     {
122       mtch[i] = tmtch[i]; // TODO: check order is ascending
123     }
124     /*
125      * replaces the specific code below {}; if (psqids > -1 && pseqs > -1) { if
126      * (psqids > pseqs) { idseg = 1; seqseg = 0;
127      * 
128      * ptok = new int[] { pseqs, psqids, link.length() }; mtch = new String[] {
129      * "$SEQUENCES", "$SEQUENCEIDS" }; } else { idseg = 0; seqseg = 1; ptok =
130      * new int[] { psqids, pseqs, link.length() }; mtch = new String[] {
131      * "$SEQUENCEIDS", "$SEQUENCES" }; } } else { if (psqids != -1) { idseg = 0;
132      * ptok = new int[] { psqids, link.length() }; mtch = new String[] {
133      * "$SEQUENCEIDS" }; } else { seqseg = 0; ptok = new int[] { pseqs,
134      * link.length() }; mtch = new String[] { "$SEQUENCES" }; } }
135      */
136
137     int p = sep;
138     // first get the label and target part before the first |
139     do
140     {
141       sep = p;
142       p = link.indexOf("|", sep + 1);
143     } while (p > sep && p < ptok[0]);
144     // Assuming that the URL itself does not contain any '|' symbols
145     // sep now contains last pipe symbol position prior to any regex symbols
146     label = link.substring(0, sep);
147     if (label.indexOf("|") > -1)
148     {
149       // | terminated database name / www target at start of Label
150       target = label.substring(0, label.indexOf("|"));
151     }
152     else if (label.indexOf(" ") > 2)
153     {
154       // space separated Label - matches database name
155       target = label.substring(0, label.indexOf(" "));
156     }
157     else
158     {
159       target = label;
160     }
161     // Now Parse URL : Whole URL string first
162     url_prefix = link.substring(sep + 1, ptok[0]);
163     url_suffix = new String[mtch.length];
164     regexReplace = new String[mtch.length];
165     // and loop through tokens
166     for (int pass = 0; pass < mtch.length; pass++)
167     {
168       int mlength = 3 + mtch[pass].length();
169       if (link.indexOf("$" + mtch[pass] + "=/") == ptok[pass]
170               && (p = link.indexOf("/=$", ptok[pass] + mlength)) > ptok[pass]
171                       + mlength)
172       {
173         // Extract Regex and suffix
174         if (ptok[pass + 1] < p + 3)
175         {
176           // tokens are not allowed inside other tokens - e.g. inserting a
177           // $sequences$ into the regex match for the sequenceid
178           invalidMessage = "Token regexes cannot contain other regexes (did you terminate the $"
179                   + mtch[pass] + " regex with a '/=$' ?";
180           return;
181         }
182         url_suffix[pass] = link.substring(p + 3, ptok[pass + 1]);
183         regexReplace[pass] = link.substring(ptok[pass] + mlength, p);
184         try
185         {
186           com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
187                   + regexReplace[pass] + "/");
188           if (rg == null)
189           {
190             invalidMessage = "Invalid Regular Expression : '"
191                     + regexReplace[pass] + "'\n";
192           }
193         } catch (Exception e)
194         {
195           invalidMessage = "Invalid Regular Expression : '"
196                   + regexReplace[pass] + "'\n";
197         }
198       }
199       else
200       {
201         regexReplace[pass] = null;
202         // verify format is really correct.
203         if ((p = link.indexOf("$" + mtch[pass] + "$")) == ptok[pass])
204         {
205           url_suffix[pass] = link.substring(p + mtch[pass].length() + 2,
206                   ptok[pass + 1]);
207         }
208         else
209         {
210           invalidMessage = "Warning: invalid regex structure (after '"
211                   + mtch[0] + "') for URL link : " + link;
212         }
213       }
214     }
215     int pass = 0;
216     separators = new String[url_suffix.length];
217     String suffices = url_suffix[url_suffix.length - 1], lastsep = ",";
218     // have a look in the last suffix for any more separators.
219     while ((p = suffices.indexOf('|')) > -1)
220     {
221       separators[pass] = suffices.substring(p + 1);
222       if (pass == 0)
223       {
224         // trim the original suffix string
225         url_suffix[url_suffix.length - 1] = suffices.substring(0, p);
226       }
227       else
228       {
229         lastsep = (separators[pass - 1] = separators[pass - 1].substring(0,
230                 p));
231       }
232       suffices = separators[pass];
233       pass++;
234     }
235     if (pass > 0)
236     {
237       lastsep = separators[pass - 1];
238     }
239     // last separator is always used for all the remaining separators
240     while (pass < separators.length)
241     {
242       separators[pass++] = lastsep;
243     }
244   }
245
246   /**
247    * @return the url_suffix
248    */
249   public String getUrl_suffix()
250   {
251     return url_suffix[url_suffix.length - 1];
252   }
253
254   /**
255    * @return the url_prefix
256    */
257   public String getUrl_prefix()
258   {
259     return url_prefix;
260   }
261
262   /**
263    * @return the target
264    */
265   public String getTarget()
266   {
267     return target;
268   }
269
270   /**
271    * @return the label
272    */
273   public String getLabel()
274   {
275     return label;
276   }
277
278   /**
279    * @return the sequence ID regexReplace
280    */
281   public String getIDRegexReplace()
282   {
283     return _replaceFor(tokens[0]);
284   }
285
286   private String _replaceFor(String token)
287   {
288     for (int i = 0; i < mtch.length; i++)
289       if (segs[i] > -1 && mtch[i].equals(token))
290       {
291         return regexReplace[i];
292       }
293     return null;
294   }
295
296   /**
297    * @return the sequence ID regexReplace
298    */
299   public String getSeqRegexReplace()
300   {
301     return _replaceFor(tokens[1]);
302   }
303
304   /**
305    * @return the invalidMessage
306    */
307   public String getInvalidMessage()
308   {
309     return invalidMessage;
310   }
311
312   /**
313    * Check if URL string was parsed properly.
314    * 
315    * @return boolean - if false then <code>getInvalidMessage</code> returns an
316    *         error message
317    */
318   public boolean isValid()
319   {
320     return invalidMessage == null;
321   }
322
323   /**
324    * return one or more URL strings by applying regex to the given idstring
325    * 
326    * @param idstrings
327    *          array of id strings to pass to service
328    * @param seqstrings
329    *          array of seq strings to pass to service
330    * @param onlyIfMatches
331    *          - when true url strings are only made if regex is defined and
332    *          matches for all qualified tokens in groupURL - TODO: consider if
333    *          onlyIfMatches is really a useful parameter!
334    * @return null or Object[] { int[] { number of seqs substituted},boolean[] {
335    *         which seqs were substituted }, StringBuffer[] { substituted lists
336    *         for each token }, String[] { url } }
337    */
338   public Object[] makeUrls(String[] idstrings, String[] seqstrings,
339           String dsstring, boolean onlyIfMatches)
340   {
341     Hashtable rstrings = replacementArgs(idstrings, seqstrings, dsstring);
342     return makeUrls(rstrings, onlyIfMatches);
343   }
344
345   /**
346    * gathers input into a hashtable
347    * 
348    * @param idstrings
349    * @param seqstrings
350    * @param dsstring
351    * @return
352    */
353   private Hashtable replacementArgs(String[] idstrings,
354           String[] seqstrings, String dsstring)
355   {
356     Hashtable rstrings = new Hashtable();
357     rstrings.put(tokens[0], idstrings);
358     rstrings.put(tokens[1], seqstrings);
359     rstrings.put(tokens[2], new String[]
360     { dsstring });
361     if (idstrings.length != seqstrings.length)
362     {
363       throw new Error(
364               "idstrings and seqstrings contain one string each per sequence.");
365     }
366     return rstrings;
367   }
368
369   public Object[] makeUrls(Hashtable repstrings, boolean onlyIfMatches)
370   {
371     return makeUrlsIf(true, repstrings, onlyIfMatches);
372   }
373
374   /**
375    * 
376    * @param ids
377    * @param seqstr
378    * @param string
379    * @param b
380    * @return URL stub objects ready to pass to constructFrom
381    */
382   public Object[] makeUrlStubs(String[] ids, String[] seqstr,
383           String string, boolean b)
384   {
385     Hashtable rstrings = replacementArgs(ids, seqstr, string);
386     Object[] stubs = makeUrlsIf(false, rstrings, b);
387     if (stubs != null)
388     {
389       return new Object[]
390       { stubs[0], stubs[1], rstrings, new boolean[]
391       { b } };
392     }
393     // TODO Auto-generated method stub
394     return null;
395   }
396
397   /**
398    * generate the URL for the given URL stub object array returned from
399    * makeUrlStubs
400    * 
401    * @param stubs
402    * @return URL string.
403    */
404   public String constructFrom(Object[] stubs)
405   {
406     Object[] results = makeUrlsIf(true, (Hashtable) stubs[2],
407             ((boolean[]) stubs[3])[0]);
408     return ((String[]) results[3])[0];
409   }
410
411   /**
412    * conditionally generate urls or stubs for a given input.
413    * 
414    * @param createFullUrl
415    *          set to false if you only want to test if URLs would be generated.
416    * @param repstrings
417    * @param onlyIfMatches
418    * @return null if no url is generated. Object[] { int[] { number of matches
419    *         seqs }, boolean[] { which matched }, (if createFullUrl also has
420    *         StringBuffer[] { segment generated from inputs that is used in URL
421    *         }, String[] { url })}
422    */
423   protected Object[] makeUrlsIf(boolean createFullUrl,
424           Hashtable repstrings, boolean onlyIfMatches)
425   {
426     int pass = 0;
427
428     // prepare string arrays in correct order to be assembled into URL input
429     String[][] idseq = new String[mtch.length][]; // indexed by pass
430     int mins = 0, maxs = 0; // allowed two values, 1 or n-sequences.
431     for (int i = 0; i < mtch.length; i++)
432     {
433       idseq[i] = (String[]) repstrings.get(mtch[i]);
434       if (idseq[i].length >= 1)
435       {
436         if (mins == 0 && idseq[i].length == 1)
437         {
438           mins = 1;
439         }
440         if (maxs < 2)
441         {
442           maxs = idseq[i].length;
443         }
444         else
445         {
446           if (maxs != idseq[i].length)
447           {
448             throw new Error(
449                     "Cannot have mixed length replacement vectors. Replacement vector for "
450                             + (mtch[i]) + " is " + idseq[i].length
451                             + " strings long, and have already seen a "
452                             + maxs + " length vector.");
453           }
454         }
455       }
456       else
457       {
458         throw new Error(
459                 "Cannot have zero length vector of replacement strings - either 1 value or n values.");
460       }
461     }
462     // iterate through input, collating segments to be inserted into url
463     StringBuffer matched[] = new StringBuffer[idseq.length];
464     // and precompile regexes
465     com.stevesoft.pat.Regex[] rgxs = new com.stevesoft.pat.Regex[matched.length];
466     for (pass = 0; pass < matched.length; pass++)
467     {
468       matched[pass] = new StringBuffer();
469       if (regexReplace[pass] != null)
470       {
471         rgxs[pass] = com.stevesoft.pat.Regex.perlCode("/"
472                 + regexReplace[pass] + "/");
473       }
474       else
475       {
476         rgxs[pass] = null;
477       }
478     }
479     // tot up the invariant lengths for this url
480     int urllength = url_prefix.length();
481     for (pass = 0; pass < matched.length; pass++)
482     {
483       urllength += url_suffix[pass].length();
484     }
485
486     // flags to record which of the input sequences were actually used to
487     // generate the
488     // url
489     boolean[] thismatched = new boolean[maxs];
490     int seqsmatched = 0;
491     for (int sq = 0; sq < maxs; sq++)
492     {
493       // initialise flag for match
494       thismatched[sq] = false;
495       StringBuffer[] thematches = new StringBuffer[rgxs.length];
496       for (pass = 0; pass < rgxs.length; pass++)
497       {
498         thematches[pass] = new StringBuffer(); // initialise - in case there are
499                                                // no more
500         // matches.
501         // if a regex is provided, then it must match for all sequences in all
502         // tokens for it to be considered.
503         if (idseq[pass].length <= sq)
504         {
505           // no more replacement strings to try for this token
506           continue;
507         }
508         if (rgxs[pass] != null)
509         {
510           com.stevesoft.pat.Regex rg = rgxs[pass];
511           int rematchat = 0;
512           // concatenate all matches of re in the given string!
513           while (rg.searchFrom(idseq[pass][sq], rematchat))
514           {
515             rematchat = rg.matchedTo();
516             thismatched[sq] |= true;
517             urllength += rg.charsMatched(); // count length
518             if (!createFullUrl)
519             {
520               continue; // don't bother making the URL replacement text.
521             }
522             // do we take the cartesian products of the substituents ?
523             int ns = rg.numSubs();
524             if (ns == 0)
525             {
526               thematches[pass].append(rg.stringMatched());// take whole regex
527             }
528             /*
529              * else if (ns==1) { // take only subgroup match return new String[]
530              * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
531              * }; }
532              */
533             // deal with multiple submatch case - for moment we do the simplest
534             // - concatenate the matched regions, instead of creating a complete
535             // list for each alternate match over all sequences.
536             // TODO: specify a 'replace pattern' - next refinement
537             else
538             {
539               // debug
540               /*
541                * for (int s = 0; s <= rg.numSubs(); s++) {
542                * System.err.println("Sub " + s + " : " + rg.matchedFrom(s) +
543                * " : " + rg.matchedTo(s) + " : '" + rg.stringMatched(s) + "'");
544                * }
545                */
546               // try to collate subgroup matches
547               StringBuffer subs = new StringBuffer();
548               // have to loop through submatches, collating them at top level
549               // match
550               int s = 0; // 1;
551               while (s <= ns)
552               {
553                 if (s + 1 <= ns && rg.matchedTo(s) > -1
554                         && rg.matchedTo(s + 1) > -1
555                         && rg.matchedTo(s + 1) < rg.matchedTo(s))
556                 {
557                   // s is top level submatch. search for submatches enclosed by
558                   // this one
559                   int r = s + 1;
560                   StringBuffer rmtch = new StringBuffer();
561                   while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
562                   {
563                     if (rg.matchedFrom(r) > -1)
564                     {
565                       rmtch.append(rg.stringMatched(r));
566                     }
567                     r++;
568                   }
569                   if (rmtch.length() > 0)
570                   {
571                     subs.append(rmtch); // simply concatenate
572                   }
573                   s = r;
574                 }
575                 else
576                 {
577                   if (rg.matchedFrom(s) > -1)
578                   {
579                     subs.append(rg.stringMatched(s)); // concatenate
580                   }
581                   s++;
582                 }
583               }
584               thematches[pass].append(subs);
585             }
586           }
587         }
588         else
589         {
590           // are we only supposed to take regex matches ?
591           if (!onlyIfMatches)
592           {
593             thismatched[sq] |= true;
594             urllength += idseq[pass][sq].length(); // tot up length
595             if (createFullUrl)
596             {
597               thematches[pass] = new StringBuffer(idseq[pass][sq]); // take
598                                                                     // whole
599                                                                     // string -
600               // regardless - probably not a
601               // good idea!
602               /*
603                * TODO: do some boilerplate trimming of the fields to make them
604                * sensible e.g. trim off any 'prefix' in the id string (see
605                * UrlLink for the below) - pre 2.4 Jalview behaviour if
606                * (idstring.indexOf("|") > -1) { idstring =
607                * idstring.substring(idstring.lastIndexOf("|") + 1); }
608                */
609             }
610
611           }
612         }
613       }
614
615       // check if we are going to add this sequence's results ? all token
616       // replacements must be valid for this to happen!
617       // (including single value replacements - eg. dataset name)
618       if (thismatched[sq])
619       {
620         if (createFullUrl)
621         {
622           for (pass = 0; pass < matched.length; pass++)
623           {
624             if (idseq[pass].length > 1 && matched[pass].length() > 0)
625             {
626               matched[pass].append(separators[pass]);
627             }
628             matched[pass].append(thematches[pass]);
629           }
630         }
631         seqsmatched++;
632       }
633     }
634     // finally, if any sequences matched, then form the URL and return
635     if (seqsmatched == 0 || (createFullUrl && matched[0].length() == 0))
636     {
637       // no matches - no url generated
638       return null;
639     }
640     // check if we are beyond the feasible command line string limit for this
641     // platform
642     if ((urllength + 32) > Platform.getMaxCommandLineLength())
643     {
644       System.err.println("URL estimated to be too long " + urllength);
645       return null;
646     }
647     if (!createFullUrl)
648     {
649       // just return the essential info about what the URL would be generated
650       // from
651       return new Object[]
652       { new int[]
653       { seqsmatched }, thismatched };
654     }
655     // otherwise, create the URL completely.
656
657     StringBuffer submiturl = new StringBuffer();
658     submiturl.append(url_prefix);
659     for (pass = 0; pass < matched.length; pass++)
660     {
661       submiturl.append(matched[pass]);
662       if (url_suffix[pass] != null)
663       {
664         submiturl.append(url_suffix[pass]);
665       }
666     }
667
668     return new Object[]
669     { new int[]
670     { seqsmatched }, thismatched, matched, new String[]
671     { submiturl.toString() } };
672   }
673
674   /**
675    * 
676    * @param urlstub
677    * @return number of distinct sequence (id or seuqence) replacements predicted
678    *         for this stub
679    */
680   public int getNumberInvolved(Object[] urlstub)
681   {
682     return ((int[]) urlstub[0])[0]; // returns seqsmatched from
683                                     // makeUrlsIf(false,...)
684   }
685
686   /**
687    * get token types present in this url as a bitfield indicating presence of
688    * each token from tokens (LSB->MSB).
689    * 
690    * @return groupURL class as integer
691    */
692   public int getGroupURLType()
693   {
694     int r = 0;
695     for (int pass = 0; pass < tokens.length; pass++)
696     {
697       for (int i = 0; i < mtch.length; i++)
698       {
699         if (mtch[i].equals(tokens[pass]))
700         {
701           r += 1 << pass;
702         }
703       }
704     }
705     return r;
706   }
707
708   public String toString()
709   {
710     StringBuffer result = new StringBuffer();
711     result.append(label + "|" + url_prefix);
712     int r;
713     for (r = 0; r < url_suffix.length; r++)
714     {
715       result.append("$");
716       result.append(mtch[r]);
717       if (regexReplace[r] != null)
718       {
719         result.append("=/");
720         result.append(regexReplace[r]);
721         result.append("/=");
722       }
723       result.append("$");
724       result.append(url_suffix[r]);
725     }
726     for (r = 0; r < separators.length; r++)
727     {
728       result.append("|");
729       result.append(separators[r]);
730     }
731     return result.toString();
732   }
733
734   /**
735    * report stats about the generated url string given an input set
736    * 
737    * @param ul
738    * @param idstring
739    * @param url
740    */
741   private static void testUrls(GroupUrlLink ul, String[][] idstring,
742           Object[] url)
743   {
744
745     if (url == null)
746     {
747       System.out.println("Created NO urls.");
748     }
749     else
750     {
751       System.out.println("Created a url from " + ((int[]) url[0])[0]
752               + "out of " + idstring[0].length + " sequences.");
753       System.out.println("Sequences that did not match:");
754       for (int sq = 0; sq < idstring[0].length; sq++)
755       {
756         if (!((boolean[]) url[1])[sq])
757         {
758           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
759                   + idstring[1][sq]);
760         }
761       }
762       System.out.println("Sequences that DID match:");
763       for (int sq = 0; sq < idstring[0].length; sq++)
764       {
765         if (((boolean[]) url[1])[sq])
766         {
767           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
768                   + idstring[1][sq]);
769         }
770       }
771       System.out.println("The generated URL:");
772       System.out.println(((String[]) url[3])[0]);
773     }
774   }
775
776   public static void main(String argv[])
777   {
778     String[] links = new String[]
779     {
780         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=linkInDatasetFromJalview&input=$SEQUENCEIDS$&inputType=0|,",
781         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=linkInDatasetFromJalview&input=$SEQUENCES$&inputType=1|,",
782         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=$DATASETID$&input=$SEQUENCEIDS$&inputType=0|,",
783         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Enfin%20Default%20Workflow&datasetName=$DATASETID$&input=$SEQUENCES$&inputType=1|,",
784         "EnVision2|IDS|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=$SEQUENCEIDS$&datasetName=linkInDatasetFromJalview&input=$SEQUENCEIDS$&inputType=0|,",
785         "EnVision2|Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=$SEQUENCEIDS$&datasetName=$DATASETID$&input=$SEQUENCES$&inputType=1|,",
786         "EnVision2 Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Default&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/([a-zA-Z]+)/=$&inputType=1|,",
787         "EnVision2 Seqs|http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?workflow=Default&datasetName=JalviewSeqs$DATASETID$&input=$SEQUENCES=/[A-Za-z]+/=$&inputType=1|,"
788     /*
789      * http://www.ebi.ac.uk/enfin-srv/envision2/pages/linkin.jsf?input=P38389,P38398
790      * &inputType=0&workflow=Enfin%20Default%20Workflow&datasetName=
791      * linkInDatasetFromPRIDE
792      */
793     };
794
795     SequenceI[] seqs = new SequenceI[]
796     { new Sequence("StupidLabel:gi|9234|pdb|102L|A",
797             "asdiasdpasdpadpwpadasdpaspdw"), };
798     String[][] seqsandids = formStrings(seqs);
799     for (int i = 0; i < links.length; i++)
800     {
801       GroupUrlLink ul = new GroupUrlLink(links[i]);
802       if (ul.isValid())
803       {
804         System.out.println("\n\n\n");
805         System.out.println("Link " + i + " " + links[i] + " : "
806                 + ul.toString());
807         System.out.println(" pref : " + ul.getUrl_prefix());
808         System.out.println(" IdReplace : " + ul.getIDRegexReplace());
809         System.out.println(" SeqReplace : " + ul.getSeqRegexReplace());
810         System.out.println(" Suffixes : " + ul.getUrl_suffix());
811
812         System.out
813                 .println("<insert input id and sequence strings here> Without onlyIfMatches:");
814         Object[] urls = ul.makeUrls(seqsandids[0], seqsandids[1],
815                 "mydataset", false);
816         testUrls(ul, seqsandids, urls);
817         System.out
818                 .println("<insert input id and sequence strings here> With onlyIfMatches set:");
819         urls = ul.makeUrls(seqsandids[0], seqsandids[1], "mydataset", true);
820         testUrls(ul, seqsandids, urls);
821       }
822       else
823       {
824         System.err.println("Invalid URLLink : " + links[i] + " : "
825                 + ul.getInvalidMessage());
826       }
827     }
828   }
829
830   /**
831    * covenience method to generate the id and sequence string vector from a set
832    * of seuqences using each sequence's getName() and getSequenceAsString()
833    * method
834    * 
835    * @param seqs
836    * @return String[][] {{sequence ids},{sequence strings}}
837    */
838   public static String[][] formStrings(SequenceI[] seqs)
839   {
840     String[][] idset = new String[2][seqs.length];
841     for (int i = 0; i < seqs.length; i++)
842     {
843       idset[0][i] = seqs[i].getName();
844       idset[1][i] = seqs[i].getSequenceAsString();
845     }
846     return idset;
847   }
848
849   public void setLabel(String newlabel)
850   {
851     this.label = newlabel;
852   }
853
854 }