17d7a6eda1f3d2cf87b695afd6220b17f3cc91cf
[jalview.git] / src / jalview / ws / rest / RestServiceDescription.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer (Version 2.6)
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.ws.rest;
19
20
21 import jalview.datamodel.SequenceI;
22 import jalview.io.packed.DataProvider;
23 import jalview.io.packed.SimpleDataProvider;
24 import jalview.io.packed.DataProvider.JvDataType;
25 import jalview.util.GroupUrlLink.UrlStringTooLongException;
26 import jalview.util.Platform;
27 import jalview.ws.rest.params.Alignment;
28 import jalview.ws.rest.params.AnnotationFile;
29 import jalview.ws.rest.params.SeqGroupIndexVector;
30
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.Map;
36
37
38 public class RestServiceDescription
39 {
40   /**
41    * @param details
42    * @param postUrl
43    * @param urlSuffix
44    * @param inputParams
45    * @param hseparable
46    * @param vseparable
47    * @param gapCharacter
48    */
49   public RestServiceDescription(String action,String description,String name, String postUrl,
50           String urlSuffix, Map<String, InputType> inputParams,
51           boolean hseparable, boolean vseparable, char gapCharacter)
52   {
53     super();
54     this.details = new UIinfo();
55     details.Action= action;
56     details.description = description;
57     details.Name = name;
58     this.postUrl = postUrl;
59     this.urlSuffix = urlSuffix;
60     this.inputParams = inputParams;
61     this.hseparable = hseparable;
62     this.vseparable = vseparable;
63     this.gapCharacter = gapCharacter;
64   }
65   /**
66    * Service UI Info { Action, Specific Name of Service, Brief Description }
67    */
68    
69   public class UIinfo {
70     String Action;
71     String Name;
72     String description;
73   }
74   public UIinfo details = new UIinfo();
75   
76   /** Service base URL
77    */
78   String postUrl;
79   /**
80    * suffix that should be added to any url used if it does not already end in the suffix.
81    */
82   String urlSuffix;
83   
84   /** input info given as key/value pairs - mapped to post arguments 
85    */ 
86   Map<String,InputType> inputParams=new HashMap();
87   /**
88    * assigns the given inputType it to its corresponding input parameter token it.token  
89    * @param it
90    */
91   public void setInputParam(InputType it)
92   {
93     inputParams.put(it.token, it);
94   }
95   /**
96    * remove the given input type it from the set of service input parameters.
97    * @param it
98    */
99   public void removeInputParam(InputType it)
100   {
101     inputParams.remove(it.token);
102   }
103   /**
104    * service requests alignment data
105    */
106   boolean aligndata;
107   /**
108    * service requests alignment and/or seuqence annotationo data 
109    */
110   boolean annotdata;
111   /**
112    * service requests partitions defined over input (alignment) data
113    */
114   boolean partitiondata;
115   
116   /**
117    * process ths input data and set the appropriate shorthand flags describing the input the service wants
118    */
119   public void setInvolvesFlags() {
120     aligndata = inputInvolves(Alignment.class);
121     annotdata = inputInvolves(AnnotationFile.class);
122     partitiondata = inputInvolves(SeqGroupIndexVector.class);
123   }
124
125   /** Service return info { alignment, annotation file (loaded back on to alignment), tree (loaded back on to alignment), sequence annotation - loaded back on to alignment), text report, pdb structures with sequence mapping )
126    * 
127    */ 
128   
129   /** Start with bare minimum: input is alignment + groups on alignment
130    *    
131    * @author JimP
132    *
133    */
134
135   /**
136    * Helper class based on the UrlLink class which enables URLs to be
137    * constructed from sequences or IDs associated with a group of sequences. URL
138    * definitions consist of a pipe separated string containing a <label>|<url
139    * construct>|<separator character>[|<sequence separator character>]. The url
140    * construct includes regex qualified tokens which are replaced with seuqence
141    * IDs ($SEQUENCE_IDS$) and/or seuqence regions ($SEQUENCES$) that are
142    * extracted from the group. See <code>UrlLink</code> for more information
143    * about the approach, and the original implementation. Documentation to come.
144    * Note - groupUrls can be very big!
145    */
146   private String url_prefix, target, label;
147
148   /**
149    * these are all filled in order of the occurence of each token in the url
150    * string template
151    */
152   private String url_suffix[], separators[], regexReplace[];
153
154   private String invalidMessage = null;
155
156   /**
157    * tokens that can be replaced in the URL.
158    */
159   private static String[] tokens;
160
161   /**
162    * position of each token (which can appear once only) in the url
163    */
164   private int[] segs;
165
166   /**
167    * contains tokens in the order they appear in the URL template.
168    */
169   private String[] mtch;
170   static
171   {
172     if (tokens == null)
173     {
174       tokens = new String[]
175       { "SEQUENCEIDS", "SEQUENCES", "DATASETID" };
176     }
177   }
178
179   /**
180    * test for GroupURLType bitfield (with default tokens)
181    */
182   public static final int SEQUENCEIDS = 1;
183
184   /**
185    * test for GroupURLType bitfield (with default tokens)
186    */
187   public static final int SEQUENCES = 2;
188
189   /**
190    * test for GroupURLType bitfield (with default tokens)
191    */
192   public static final int DATASETID = 4;
193
194   // private int idseg = -1, seqseg = -1;
195
196   /**
197    * parse the given linkString of the form '<label>|<url>|separator
198    * char[|optional sequence separator char]' into parts. url may contain a
199    * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
200    * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
201    * $SEQUENCES<=optional regex=>$.
202    * 
203    * @param link
204    */
205   public RestServiceDescription(String link)
206   {
207     int sep = link.indexOf("|");
208     segs = new int[tokens.length];
209     int ntoks = 0;
210     for (int i = 0; i < segs.length; i++)
211     {
212       if ((segs[i] = link.indexOf("$" + tokens[i])) > -1)
213       {
214         ntoks++;
215       }
216     }
217     // expect at least one token
218     if (ntoks == 0)
219     {
220       invalidMessage = "Group URL string must contain at least one of ";
221       for (int i = 0; i < segs.length; i++)
222       {
223         invalidMessage += " '$" + tokens[i] + "[=/regex=/]$'";
224       }
225       return;
226     }
227
228     int[] ptok = new int[ntoks + 1];
229     String[] tmtch = new String[ntoks + 1];
230     mtch = new String[ntoks];
231     for (int i = 0, t = 0; i < segs.length; i++)
232     {
233       if (segs[i] > -1)
234       {
235         ptok[t] = segs[i];
236         tmtch[t++] = tokens[i];
237       }
238     }
239     ptok[ntoks] = link.length();
240     tmtch[ntoks] = "$$$$$$$$$";
241     jalview.util.QuickSort.sort(ptok, tmtch);
242     for (int i = 0; i < ntoks; i++)
243     {
244       mtch[i] = tmtch[i]; // TODO: check order is ascending
245     }
246     /*
247      * replaces the specific code below {}; if (psqids > -1 && pseqs > -1) { if
248      * (psqids > pseqs) { idseg = 1; seqseg = 0;
249      * 
250      * ptok = new int[] { pseqs, psqids, link.length() }; mtch = new String[] {
251      * "$SEQUENCES", "$SEQUENCEIDS" }; } else { idseg = 0; seqseg = 1; ptok =
252      * new int[] { psqids, pseqs, link.length() }; mtch = new String[] {
253      * "$SEQUENCEIDS", "$SEQUENCES" }; } } else { if (psqids != -1) { idseg = 0;
254      * ptok = new int[] { psqids, link.length() }; mtch = new String[] {
255      * "$SEQUENCEIDS" }; } else { seqseg = 0; ptok = new int[] { pseqs,
256      * link.length() }; mtch = new String[] { "$SEQUENCES" }; } }
257      */
258
259     int p = sep;
260     // first get the label and target part before the first |
261     do
262     {
263       sep = p;
264       p = link.indexOf("|", sep + 1);
265     } while (p > sep && p < ptok[0]);
266     // Assuming that the URL itself does not contain any '|' symbols
267     // sep now contains last pipe symbol position prior to any regex symbols
268     label = link.substring(0, sep);
269     if (label.indexOf("|") > -1)
270     {
271       // | terminated database name / www target at start of Label
272       target = label.substring(0, label.indexOf("|"));
273     }
274     else if (label.indexOf(" ") > 2)
275     {
276       // space separated Label - matches database name
277       target = label.substring(0, label.indexOf(" "));
278     }
279     else
280     {
281       target = label;
282     }
283     // Now Parse URL : Whole URL string first
284     url_prefix = link.substring(sep + 1, ptok[0]);
285     url_suffix = new String[mtch.length];
286     regexReplace = new String[mtch.length];
287     // and loop through tokens
288     for (int pass = 0; pass < mtch.length; pass++)
289     {
290       int mlength = 3 + mtch[pass].length();
291       if (link.indexOf("$" + mtch[pass] + "=/") == ptok[pass]
292               && (p = link.indexOf("/=$", ptok[pass] + mlength)) > ptok[pass]
293                       + mlength)
294       {
295         // Extract Regex and suffix
296         if (ptok[pass + 1] < p + 3)
297         {
298           // tokens are not allowed inside other tokens - e.g. inserting a
299           // $sequences$ into the regex match for the sequenceid
300           invalidMessage = "Token regexes cannot contain other regexes (did you terminate the $"
301                   + mtch[pass] + " regex with a '/=$' ?";
302           return;
303         }
304         url_suffix[pass] = link.substring(p + 3, ptok[pass + 1]);
305         regexReplace[pass] = link.substring(ptok[pass] + mlength, p);
306         try
307         {
308           com.stevesoft.pat.Regex rg = com.stevesoft.pat.Regex.perlCode("/"
309                   + regexReplace[pass] + "/");
310           if (rg == null)
311           {
312             invalidMessage = "Invalid Regular Expression : '"
313                     + regexReplace[pass] + "'\n";
314           }
315         } catch (Exception e)
316         {
317           invalidMessage = "Invalid Regular Expression : '"
318                   + regexReplace[pass] + "'\n";
319         }
320       }
321       else
322       {
323         regexReplace[pass] = null;
324         // verify format is really correct.
325         if ((p = link.indexOf("$" + mtch[pass] + "$")) == ptok[pass])
326         {
327           url_suffix[pass] = link.substring(p + mtch[pass].length() + 2,
328                   ptok[pass + 1]);
329         }
330         else
331         {
332           invalidMessage = "Warning: invalid regex structure (after '"
333                   + mtch[0] + "') for URL link : " + link;
334         }
335       }
336     }
337     int pass = 0;
338     separators = new String[url_suffix.length];
339     String suffices = url_suffix[url_suffix.length - 1], lastsep = ",";
340     // have a look in the last suffix for any more separators.
341     while ((p = suffices.indexOf('|')) > -1)
342     {
343       separators[pass] = suffices.substring(p + 1);
344       if (pass == 0)
345       {
346         // trim the original suffix string
347         url_suffix[url_suffix.length - 1] = suffices.substring(0, p);
348       }
349       else
350       {
351         lastsep = (separators[pass - 1] = separators[pass - 1].substring(0,
352                 p));
353       }
354       suffices = separators[pass];
355       pass++;
356     }
357     if (pass > 0)
358     {
359       lastsep = separators[pass - 1];
360     }
361     // last separator is always used for all the remaining separators
362     while (pass < separators.length)
363     {
364       separators[pass++] = lastsep;
365     }
366   }
367
368   /**
369    * @return the url_suffix
370    */
371   public String getUrl_suffix()
372   {
373     return url_suffix[url_suffix.length - 1];
374   }
375
376   /**
377    * @return the url_prefix
378    */
379   public String getUrl_prefix()
380   {
381     return url_prefix;
382   }
383
384   /**
385    * @return the target
386    */
387   public String getTarget()
388   {
389     return target;
390   }
391
392   /**
393    * @return the label
394    */
395   public String getLabel()
396   {
397     return label;
398   }
399
400   /**
401    * @return the sequence ID regexReplace
402    */
403   public String getIDRegexReplace()
404   {
405     return _replaceFor(tokens[0]);
406   }
407
408   private String _replaceFor(String token)
409   {
410     for (int i = 0; i < mtch.length; i++)
411       if (segs[i] > -1 && mtch[i].equals(token))
412       {
413         return regexReplace[i];
414       }
415     return null;
416   }
417
418   /**
419    * @return the sequence ID regexReplace
420    */
421   public String getSeqRegexReplace()
422   {
423     return _replaceFor(tokens[1]);
424   }
425
426   /**
427    * @return the invalidMessage
428    */
429   public String getInvalidMessage()
430   {
431     return invalidMessage;
432   }
433
434   /**
435    * Check if URL string was parsed properly.
436    * 
437    * @return boolean - if false then <code>getInvalidMessage</code> returns an
438    *         error message
439    */
440   public boolean isValid()
441   {
442     return invalidMessage == null;
443   }
444
445
446   /**
447    * gathers input into a hashtable
448    * 
449    * @param idstrings
450    * @param seqstrings
451    * @param dsstring
452    * @return
453    */
454   private Hashtable replacementArgs(String[] idstrings,
455           String[] seqstrings, String dsstring)
456   {
457     Hashtable rstrings = new Hashtable();
458     rstrings.put(tokens[0], idstrings);
459     rstrings.put(tokens[1], seqstrings);
460     rstrings.put(tokens[2], new String[]
461     { dsstring });
462     if (idstrings.length != seqstrings.length)
463     {
464       throw new Error(
465               "idstrings and seqstrings contain one string each per sequence.");
466     }
467     return rstrings;
468   }
469
470
471
472
473   /**
474    * conditionally generate urls or stubs for a given input.
475    * 
476    * @param createFullUrl
477    *          set to false if you only want to test if URLs would be generated.
478    * @param repstrings
479    * @param onlyIfMatches
480    * @return null if no url is generated. Object[] { int[] { number of matches
481    *         seqs }, boolean[] { which matched }, (if createFullUrl also has
482    *         StringBuffer[] { segment generated from inputs that is used in URL
483    *         }, String[] { url })}
484    * @throws Exception 
485    * @throws UrlStringTooLongException
486    */
487   protected Object[] makeUrlsIf(boolean createFullUrl,
488           Hashtable repstrings, boolean onlyIfMatches) throws Exception
489   {
490     int pass = 0;
491
492     // prepare string arrays in correct order to be assembled into URL input
493     String[][] idseq = new String[mtch.length][]; // indexed by pass
494     int mins = 0, maxs = 0; // allowed two values, 1 or n-sequences.
495     for (int i = 0; i < mtch.length; i++)
496     {
497       idseq[i] = (String[]) repstrings.get(mtch[i]);
498       if (idseq[i].length >= 1)
499       {
500         if (mins == 0 && idseq[i].length == 1)
501         {
502           mins = 1;
503         }
504         if (maxs < 2)
505         {
506           maxs = idseq[i].length;
507         }
508         else
509         {
510           if (maxs != idseq[i].length)
511           {
512             throw new Error(
513                     "Cannot have mixed length replacement vectors. Replacement vector for "
514                             + (mtch[i]) + " is " + idseq[i].length
515                             + " strings long, and have already seen a "
516                             + maxs + " length vector.");
517           }
518         }
519       }
520       else
521       {
522         throw new Error(
523                 "Cannot have zero length vector of replacement strings - either 1 value or n values.");
524       }
525     }
526     // iterate through input, collating segments to be inserted into url
527     StringBuffer matched[] = new StringBuffer[idseq.length];
528     // and precompile regexes
529     com.stevesoft.pat.Regex[] rgxs = new com.stevesoft.pat.Regex[matched.length];
530     for (pass = 0; pass < matched.length; pass++)
531     {
532       matched[pass] = new StringBuffer();
533       if (regexReplace[pass] != null)
534       {
535         rgxs[pass] = com.stevesoft.pat.Regex.perlCode("/"
536                 + regexReplace[pass] + "/");
537       }
538       else
539       {
540         rgxs[pass] = null;
541       }
542     }
543     // tot up the invariant lengths for this url
544     int urllength = url_prefix.length();
545     for (pass = 0; pass < matched.length; pass++)
546     {
547       urllength += url_suffix[pass].length();
548     }
549
550     // flags to record which of the input sequences were actually used to
551     // generate the
552     // url
553     boolean[] thismatched = new boolean[maxs];
554     int seqsmatched = 0;
555     for (int sq = 0; sq < maxs; sq++)
556     {
557       // initialise flag for match
558       thismatched[sq] = false;
559       StringBuffer[] thematches = new StringBuffer[rgxs.length];
560       for (pass = 0; pass < rgxs.length; pass++)
561       {
562         thematches[pass] = new StringBuffer(); // initialise - in case there are
563                                                // no more
564         // matches.
565         // if a regex is provided, then it must match for all sequences in all
566         // tokens for it to be considered.
567         if (idseq[pass].length <= sq)
568         {
569           // no more replacement strings to try for this token
570           continue;
571         }
572         if (rgxs[pass] != null)
573         {
574           com.stevesoft.pat.Regex rg = rgxs[pass];
575           int rematchat = 0;
576           // concatenate all matches of re in the given string!
577           while (rg.searchFrom(idseq[pass][sq], rematchat))
578           {
579             rematchat = rg.matchedTo();
580             thismatched[sq] |= true;
581             urllength += rg.charsMatched(); // count length
582             if ((urllength + 32) > Platform.getMaxCommandLineLength())
583             {
584               throw new Exception("urllength");
585             }
586
587             if (!createFullUrl)
588             {
589               continue; // don't bother making the URL replacement text.
590             }
591             // do we take the cartesian products of the substituents ?
592             int ns = rg.numSubs();
593             if (ns == 0)
594             {
595               thematches[pass].append(rg.stringMatched());// take whole regex
596             }
597             /*
598              * else if (ns==1) { // take only subgroup match return new String[]
599              * { rg.stringMatched(1), url_prefix+rg.stringMatched(1)+url_suffix
600              * }; }
601              */
602             // deal with multiple submatch case - for moment we do the simplest
603             // - concatenate the matched regions, instead of creating a complete
604             // list for each alternate match over all sequences.
605             // TODO: specify a 'replace pattern' - next refinement
606             else
607             {
608               // debug
609               /*
610                * for (int s = 0; s <= rg.numSubs(); s++) {
611                * System.err.println("Sub " + s + " : " + rg.matchedFrom(s) +
612                * " : " + rg.matchedTo(s) + " : '" + rg.stringMatched(s) + "'");
613                * }
614                */
615               // try to collate subgroup matches
616               StringBuffer subs = new StringBuffer();
617               // have to loop through submatches, collating them at top level
618               // match
619               int s = 0; // 1;
620               while (s <= ns)
621               {
622                 if (s + 1 <= ns && rg.matchedTo(s) > -1
623                         && rg.matchedTo(s + 1) > -1
624                         && rg.matchedTo(s + 1) < rg.matchedTo(s))
625                 {
626                   // s is top level submatch. search for submatches enclosed by
627                   // this one
628                   int r = s + 1;
629                   StringBuffer rmtch = new StringBuffer();
630                   while (r <= ns && rg.matchedTo(r) <= rg.matchedTo(s))
631                   {
632                     if (rg.matchedFrom(r) > -1)
633                     {
634                       rmtch.append(rg.stringMatched(r));
635                     }
636                     r++;
637                   }
638                   if (rmtch.length() > 0)
639                   {
640                     subs.append(rmtch); // simply concatenate
641                   }
642                   s = r;
643                 }
644                 else
645                 {
646                   if (rg.matchedFrom(s) > -1)
647                   {
648                     subs.append(rg.stringMatched(s)); // concatenate
649                   }
650                   s++;
651                 }
652               }
653               thematches[pass].append(subs);
654             }
655           }
656         }
657         else
658         {
659           // are we only supposed to take regex matches ?
660           if (!onlyIfMatches)
661           {
662             thismatched[sq] |= true;
663             urllength += idseq[pass][sq].length(); // tot up length
664             if (createFullUrl)
665             {
666               thematches[pass] = new StringBuffer(idseq[pass][sq]); // take
667                                                                     // whole
668                                                                     // string -
669               // regardless - probably not a
670               // good idea!
671               /*
672                * TODO: do some boilerplate trimming of the fields to make them
673                * sensible e.g. trim off any 'prefix' in the id string (see
674                * UrlLink for the below) - pre 2.4 Jalview behaviour if
675                * (idstring.indexOf("|") > -1) { idstring =
676                * idstring.substring(idstring.lastIndexOf("|") + 1); }
677                */
678             }
679
680           }
681         }
682       }
683
684       // check if we are going to add this sequence's results ? all token
685       // replacements must be valid for this to happen!
686       // (including single value replacements - eg. dataset name)
687       if (thismatched[sq])
688       {
689         if (createFullUrl)
690         {
691           for (pass = 0; pass < matched.length; pass++)
692           {
693             if (idseq[pass].length > 1 && matched[pass].length() > 0)
694             {
695               matched[pass].append(separators[pass]);
696             }
697             matched[pass].append(thematches[pass]);
698           }
699         }
700         seqsmatched++;
701       }
702     }
703     // finally, if any sequences matched, then form the URL and return
704     if (seqsmatched == 0 || (createFullUrl && matched[0].length() == 0))
705     {
706       // no matches - no url generated
707       return null;
708     }
709     // check if we are beyond the feasible command line string limit for this
710     // platform
711     if ((urllength + 32) > Platform.getMaxCommandLineLength())
712     {
713       throw new Exception("urllength");
714     }
715     if (!createFullUrl)
716     {
717       // just return the essential info about what the URL would be generated
718       // from
719       return new Object[]
720       { new int[]
721       { seqsmatched }, thismatched };
722     }
723     // otherwise, create the URL completely.
724
725     StringBuffer submiturl = new StringBuffer();
726     submiturl.append(url_prefix);
727     for (pass = 0; pass < matched.length; pass++)
728     {
729       submiturl.append(matched[pass]);
730       if (url_suffix[pass] != null)
731       {
732         submiturl.append(url_suffix[pass]);
733       }
734     }
735
736     return new Object[]
737     { new int[]
738     { seqsmatched }, thismatched, matched, new String[]
739     { submiturl.toString() } };
740   }
741
742   /**
743    * 
744    * @param urlstub
745    * @return number of distinct sequence (id or seuqence) replacements predicted
746    *         for this stub
747    */
748   public int getNumberInvolved(Object[] urlstub)
749   {
750     return ((int[]) urlstub[0])[0]; // returns seqsmatched from
751                                     // makeUrlsIf(false,...)
752   }
753
754   /**
755    * get token types present in this url as a bitfield indicating presence of
756    * each token from tokens (LSB->MSB).
757    * 
758    * @return groupURL class as integer
759    */
760   public int getGroupURLType()
761   {
762     int r = 0;
763     for (int pass = 0; pass < tokens.length; pass++)
764     {
765       for (int i = 0; i < mtch.length; i++)
766       {
767         if (mtch[i].equals(tokens[pass]))
768         {
769           r += 1 << pass;
770         }
771       }
772     }
773     return r;
774   }
775
776   public String toString()
777   {
778     StringBuffer result = new StringBuffer();
779     result.append(label + "|" + url_prefix);
780     int r;
781     for (r = 0; r < url_suffix.length; r++)
782     {
783       result.append("$");
784       result.append(mtch[r]);
785       if (regexReplace[r] != null)
786       {
787         result.append("=/");
788         result.append(regexReplace[r]);
789         result.append("/=");
790       }
791       result.append("$");
792       result.append(url_suffix[r]);
793     }
794     for (r = 0; r < separators.length; r++)
795     {
796       result.append("|");
797       result.append(separators[r]);
798     }
799     return result.toString();
800   }
801
802   /**
803    * report stats about the generated url string given an input set
804    * 
805    * @param ul
806    * @param idstring
807    * @param url
808    */
809   private static void testUrls(RestServiceDescription
810           ul, String[][] idstring,
811           Object[] url)
812   {
813
814     if (url == null)
815     {
816       System.out.println("Created NO urls.");
817     }
818     else
819     {
820       System.out.println("Created a url from " + ((int[]) url[0])[0]
821               + "out of " + idstring[0].length + " sequences.");
822       System.out.println("Sequences that did not match:");
823       for (int sq = 0; sq < idstring[0].length; sq++)
824       {
825         if (!((boolean[]) url[1])[sq])
826         {
827           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
828                   + idstring[1][sq]);
829         }
830       }
831       System.out.println("Sequences that DID match:");
832       for (int sq = 0; sq < idstring[0].length; sq++)
833       {
834         if (((boolean[]) url[1])[sq])
835         {
836           System.out.println("Seq " + sq + ": " + idstring[0][sq] + "\t: "
837                   + idstring[1][sq]);
838         }
839       }
840       System.out.println("The generated URL:");
841       System.out.println(((String[]) url[3])[0]);
842     }
843   }
844
845   public static void main(String argv[])
846   {
847   }
848
849   /**
850    * covenience method to generate the id and sequence string vector from a set
851    * of seuqences using each sequence's getName() and getSequenceAsString()
852    * method
853    * 
854    * @param seqs
855    * @return String[][] {{sequence ids},{sequence strings}}
856    */
857   public static String[][] formStrings(SequenceI[] seqs)
858   {
859     String[][] idset = new String[2][seqs.length];
860     for (int i = 0; i < seqs.length; i++)
861     {
862       idset[0][i] = seqs[i].getName();
863       idset[1][i] = seqs[i].getSequenceAsString();
864     }
865     return idset;
866   }
867
868   public void setLabel(String newlabel)
869   {
870     this.label = newlabel;
871   }
872
873   /**
874    * can this service be run on the visible portion of an alignment regardless of hidden boundaries ?
875    */
876   boolean hseparable=false;
877   boolean vseparable=false;
878   
879   public boolean isHseparable()
880   {
881     // TODO Auto-generated method stub
882     return hseparable;
883   }
884   /**
885    * 
886    * @return
887    */
888   public boolean isVseparable()
889   {
890     // TODO Auto-generated method stub
891     return hseparable;
892   }
893
894   /**
895    * search the input types for an instance of the given class
896    * @param <validInput.inputType> class1
897    * @return
898    */
899   public boolean inputInvolves(Class<?> class1)
900   {
901     assert(InputType.class.isAssignableFrom(class1));
902     for (InputType val:inputParams.values())
903     {
904       if (class1.isAssignableFrom(val.getClass()))
905       {
906         return true;
907       }
908     }
909     return false;
910   }
911   char gapCharacter = '-';
912   /**
913    * 
914    * @return the preferred gap character for alignments input/output by this service 
915    */
916   public char getGapCharacter()
917   {
918     return gapCharacter;
919   }
920
921   public String getDecoratedResultUrl(String jobId)
922   {
923     // TODO: correctly write ?/& appropriate to result URL format.
924     return jobId+urlSuffix;
925   }
926   private List<JvDataType> resultData;
927   /**
928    * 
929    * 
930    * TODO: Extend to optionally specify relative/absolute url where data of this type can be retrieved from
931    * @param dt
932    */
933   public void addResultDatatype(JvDataType dt)
934   {
935     if (resultData==null)
936     {
937       resultData = new ArrayList<JvDataType>();
938     }
939     resultData.add(dt);
940   }
941   public boolean removeRsultDatatype(JvDataType dt)
942   {
943     if (resultData!=null)
944     {
945       return resultData.remove(dt);
946     }
947     return false;
948   }
949   public List<JvDataType> getResultDataTypes()
950   {
951     return resultData;
952   }
953 }