JAL-1687 ignore case when determining if sequence matches consensus
[jalview.git] / src / jalview / ws / rest / RestServiceDescription.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.ws.rest;
22
23 import jalview.datamodel.SequenceI;
24 import jalview.io.packed.DataProvider.JvDataType;
25 import jalview.ws.rest.params.Alignment;
26 import jalview.ws.rest.params.AnnotationFile;
27 import jalview.ws.rest.params.SeqGroupIndexVector;
28
29 import java.net.URL;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.NoSuchElementException;
36 import java.util.StringTokenizer;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 public class RestServiceDescription
41 {
42   /**
43    * create a new rest service description ready to be configured
44    */
45   public RestServiceDescription()
46   {
47
48   }
49
50   /**
51    * @param details
52    * @param postUrl
53    * @param urlSuffix
54    * @param inputParams
55    * @param hseparable
56    * @param vseparable
57    * @param gapCharacter
58    */
59   public RestServiceDescription(String action, String description,
60           String name, String postUrl, String urlSuffix,
61           Map<String, InputType> inputParams, boolean hseparable,
62           boolean vseparable, char gapCharacter)
63   {
64     super();
65     this.details = new UIinfo();
66     details.Action = action == null ? "" : action;
67     details.description = description == null ? "" : description;
68     details.Name = name == null ? "" : name;
69     this.postUrl = postUrl == null ? "" : postUrl;
70     this.urlSuffix = urlSuffix == null ? "" : urlSuffix;
71     if (inputParams != null)
72     {
73       this.inputParams = inputParams;
74     }
75     this.hseparable = hseparable;
76     this.vseparable = vseparable;
77     this.gapCharacter = gapCharacter;
78   }
79
80   @Override
81   public boolean equals(Object o)
82   {
83     if (o == null || !(o instanceof RestServiceDescription))
84     {
85       return false;
86     }
87     RestServiceDescription other = (RestServiceDescription) o;
88     boolean diff = (gapCharacter != other.gapCharacter);
89     diff |= vseparable != other.vseparable;
90     diff |= hseparable != other.hseparable;
91     diff |= !(urlSuffix == null && other.urlSuffix == null || (urlSuffix != null
92             && other.urlSuffix != null && urlSuffix.equals(other.urlSuffix)));
93     // TODO - robust diff that includes constants and reordering of URL
94     // diff |= !(postUrl.equals(other.postUrl));
95     // diff |= !inputParams.equals(other.inputParams);
96     diff |= !details.Name.equals(other.details.Name);
97     diff |= !details.Action.equals(other.details.Action);
98     diff |= !details.description.equals(other.details.description);
99     return !diff;
100   }
101
102   /**
103    * Service UI Info { Action, Specific Name of Service, Brief Description }
104    */
105
106   public class UIinfo
107   {
108     public String getAction()
109     {
110       return Action;
111     }
112
113     public void setAction(String action)
114     {
115       Action = action;
116     }
117
118     public String getName()
119     {
120       return Name;
121     }
122
123     public void setName(String name)
124     {
125       Name = name;
126     }
127
128     public String getDescription()
129     {
130       return description;
131     }
132
133     public void setDescription(String description)
134     {
135       this.description = description;
136     }
137
138     String Action;
139
140     String Name;
141
142     String description;
143   }
144
145   public UIinfo details = new UIinfo();
146
147   public String getAction()
148   {
149     return details.getAction();
150   }
151
152   public void setAction(String action)
153   {
154     details.setAction(action);
155   }
156
157   public String getName()
158   {
159     return details.getName();
160   }
161
162   public void setName(String name)
163   {
164     details.setName(name);
165   }
166
167   public String getDescription()
168   {
169     return details.getDescription();
170   }
171
172   public void setDescription(String description)
173   {
174     details.setDescription(description);
175   }
176
177   /**
178    * Service base URL
179    */
180   String postUrl;
181
182   public String getPostUrl()
183   {
184     return postUrl;
185   }
186
187   public void setPostUrl(String postUrl)
188   {
189     this.postUrl = postUrl;
190   }
191
192   public String getUrlSuffix()
193   {
194     return urlSuffix;
195   }
196
197   public void setUrlSuffix(String urlSuffix)
198   {
199     this.urlSuffix = urlSuffix;
200   }
201
202   public Map<String, InputType> getInputParams()
203   {
204     return inputParams;
205   }
206
207   public void setInputParams(Map<String, InputType> inputParams)
208   {
209     this.inputParams = inputParams;
210   }
211
212   public void setHseparable(boolean hseparable)
213   {
214     this.hseparable = hseparable;
215   }
216
217   public void setVseparable(boolean vseparable)
218   {
219     this.vseparable = vseparable;
220   }
221
222   public void setGapCharacter(char gapCharacter)
223   {
224     this.gapCharacter = gapCharacter;
225   }
226
227   /**
228    * suffix that should be added to any url used if it does not already end in
229    * the suffix.
230    */
231   String urlSuffix;
232
233   /**
234    * input info given as key/value pairs - mapped to post arguments
235    */
236   Map<String, InputType> inputParams = new HashMap<String, InputType>();
237
238   /**
239    * assigns the given inputType it to its corresponding input parameter token
240    * it.token
241    * 
242    * @param it
243    */
244   public void setInputParam(InputType it)
245   {
246     inputParams.put(it.token, it);
247   }
248
249   /**
250    * remove the given input type it from the set of service input parameters.
251    * 
252    * @param it
253    */
254   public void removeInputParam(InputType it)
255   {
256     inputParams.remove(it.token);
257   }
258
259   /**
260    * service requests alignment data
261    */
262   boolean aligndata;
263
264   /**
265    * service requests alignment and/or seuqence annotationo data
266    */
267   boolean annotdata;
268
269   /**
270    * service requests partitions defined over input (alignment) data
271    */
272   boolean partitiondata;
273
274   /**
275    * process ths input data and set the appropriate shorthand flags describing
276    * the input the service wants
277    */
278   public void setInvolvesFlags()
279   {
280     aligndata = inputInvolves(Alignment.class);
281     annotdata = inputInvolves(AnnotationFile.class);
282     partitiondata = inputInvolves(SeqGroupIndexVector.class);
283   }
284
285   /**
286    * Service return info { alignment, annotation file (loaded back on to
287    * alignment), tree (loaded back on to alignment), sequence annotation -
288    * loaded back on to alignment), text report, pdb structures with sequence
289    * mapping )
290    * 
291    */
292
293   /**
294    * Start with bare minimum: input is alignment + groups on alignment
295    * 
296    * @author JimP
297    * 
298    */
299
300   private String invalidMessage = null;
301
302   /**
303    * parse the given linkString of the form '<label>|<url>|separator
304    * char[|optional sequence separator char]' into parts. url may contain a
305    * string $SEQUENCEIDS<=optional regex=>$ where <=optional regex=> must be of
306    * the form =/<perl style regex>/=$ or $SEQUENCES<=optional regex=>$ or
307    * $SEQUENCES<=optional regex=>$.
308    * 
309    * @param link
310    */
311   public RestServiceDescription(String link)
312   {
313     StringBuffer warnings = new StringBuffer();
314     if (!configureFromEncodedString(link, warnings))
315     {
316       if (warnings.length() > 0)
317       {
318         invalidMessage = warnings.toString();
319       }
320     }
321   }
322
323   public RestServiceDescription(RestServiceDescription toedit)
324   {
325     // Rather then do the above, we cheat and use our human readable
326     // serialization code to clone everything
327     this(toedit.toString());
328     /**
329      * if (toedit == null) { return; } /** urlSuffix = toedit.urlSuffix; postUrl
330      * = toedit.postUrl; hseparable = toedit.hseparable; vseparable =
331      * toedit.vseparable; gapCharacter = toedit.gapCharacter; details = new
332      * RestServiceDescription.UIinfo(); details.Action = toedit.details.Action;
333      * details.description = toedit.details.description; details.Name =
334      * toedit.details.Name; for (InputType itype: toedit.inputParams.values()) {
335      * inputParams.put(itype.token, itype.clone());
336      * 
337      * }
338      */
339     // TODO Implement copy constructor NOW*/
340   }
341
342   /**
343    * @return the invalidMessage
344    */
345   public String getInvalidMessage()
346   {
347     return invalidMessage;
348   }
349
350   /**
351    * Check if URL string was parsed properly.
352    * 
353    * @return boolean - if false then <code>getInvalidMessage</code> returns an
354    *         error message
355    */
356   public boolean isValid()
357   {
358     return invalidMessage == null;
359   }
360
361   private static boolean debug = false;
362
363   /**
364    * parse the string into a list
365    * 
366    * @param list
367    * @param separator
368    * @return elements separated by separator
369    */
370   public static String[] separatorListToArray(String list, String separator)
371   {
372     int seplen = separator.length();
373     if (list == null || list.equals("") || list.equals(separator))
374     {
375       return null;
376     }
377     java.util.ArrayList<String> jv = new ArrayList<String>();
378     int cp = 0, pos, escape;
379     boolean wasescaped = false, wasquoted = false;
380     String lstitem = null;
381     while ((pos = list.indexOf(separator, cp)) >= cp)
382     {
383
384       escape = (pos > 0 && list.charAt(pos - 1) == '\\') ? -1 : 0;
385       if (wasescaped || wasquoted)
386       {
387         // append to previous pos
388         jv.set(jv.size() - 1,
389                 lstitem = lstitem + separator
390                         + list.substring(cp, pos + escape));
391       }
392       else
393       {
394         jv.add(lstitem = list.substring(cp, pos + escape));
395       }
396       cp = pos + seplen;
397       wasescaped = escape == -1;
398       // last separator may be in an unmatched quote
399       wasquoted = (java.util.regex.Pattern.matches(".*='[^']*(?!')",
400               lstitem));
401     }
402     if (cp < list.length())
403     {
404       String c = list.substring(cp);
405       if (wasescaped || wasquoted)
406       {
407         // append final separator
408         jv.set(jv.size() - 1, lstitem + separator + c);
409       }
410       else
411       {
412         if (!c.equals(separator))
413         {
414           jv.add(c);
415         }
416       }
417     }
418     if (jv.size() > 0)
419     {
420       String[] v = jv.toArray(new String[jv.size()]);
421       jv.clear();
422       if (debug)
423       {
424         System.err.println("Array from '" + separator
425                 + "' separated List:\n" + v.length);
426         for (int i = 0; i < v.length; i++)
427         {
428           System.err.println("item " + i + " '" + v[i] + "'");
429         }
430       }
431       return v;
432     }
433     if (debug)
434     {
435       System.err.println("Empty Array from '" + separator
436               + "' separated List");
437     }
438     return null;
439   }
440
441   /**
442    * concatenate the list with separator
443    * 
444    * @param list
445    * @param separator
446    * @return concatenated string
447    */
448   public static String arrayToSeparatorList(String[] list, String separator)
449   {
450     StringBuffer v = new StringBuffer();
451     if (list != null && list.length > 0)
452     {
453       for (int i = 0, iSize = list.length; i < iSize; i++)
454       {
455         if (list[i] != null)
456         {
457           if (v.length() > 0)
458           {
459             v.append(separator);
460           }
461           // TODO - escape any separator values in list[i]
462           v.append(list[i]);
463         }
464       }
465       if (debug)
466       {
467         System.err.println("Returning '" + separator
468                 + "' separated List:\n");
469         System.err.println(v);
470       }
471       return v.toString();
472     }
473     if (debug)
474     {
475       System.err.println("Returning empty '" + separator
476               + "' separated List\n");
477     }
478     return "" + separator;
479   }
480
481   /**
482    * parse a string containing a list of service properties and configure the
483    * service description
484    * 
485    * @param propList
486    *          param warnings a StringBuffer that any warnings about invalid
487    *          content will be appended to.
488    */
489   private boolean configureFromServiceInputProperties(String propList,
490           StringBuffer warnings)
491   {
492     String[] props = separatorListToArray(propList, ",");
493     if (props == null)
494     {
495       return true;
496     }
497     ;
498     boolean valid = true;
499     String val = null;
500     int l = warnings.length();
501     int i;
502     for (String prop : props)
503     {
504       if ((i = prop.indexOf("=")) > -1)
505       {
506         val = prop.substring(i + 1);
507         if (val.startsWith("\'") && val.endsWith("\'"))
508         {
509           val = val.substring(1, val.length() - 1);
510         }
511         prop = prop.substring(0, i);
512       }
513
514       if (prop.equals("hseparable"))
515       {
516         hseparable = true;
517       }
518       if (prop.equals("vseparable"))
519       {
520         vseparable = true;
521       }
522       if (prop.equals("gapCharacter"))
523       {
524         if (val == null || val.length() == 0 || val.length() > 1)
525         {
526           valid = false;
527           warnings.append((warnings.length() > 0 ? "\n" : "")
528                   + ("Invalid service property: gapCharacter=' ' (single character) - was given '"
529                           + val + "'"));
530         }
531         else
532         {
533           gapCharacter = val.charAt(0);
534         }
535       }
536       if (prop.equals("returns"))
537       {
538         _configureOutputFormatFrom(val, warnings);
539       }
540     }
541     // return true if valid is true and warning buffer was not appended to.
542     return valid && (l == warnings.length());
543   }
544
545   private String _genOutputFormatString()
546   {
547     String buff = "";
548     if (resultData == null)
549     {
550       return "";
551     }
552     for (JvDataType type : resultData)
553     {
554       if (buff.length() > 0)
555       {
556         buff += ";";
557       }
558       buff += type.toString();
559     }
560     return buff;
561   }
562
563   private void _configureOutputFormatFrom(String outstring,
564           StringBuffer warnings)
565   {
566     if (outstring.indexOf(";") == -1)
567     {
568       // we add a token, for simplicity
569       outstring = outstring + ";";
570     }
571     StringTokenizer st = new StringTokenizer(outstring, ";");
572     String tok = "";
573     resultData = new ArrayList<JvDataType>();
574     while (st.hasMoreTokens())
575     {
576       try
577       {
578         resultData.add(JvDataType.valueOf(tok = st.nextToken()));
579       } catch (NoSuchElementException x)
580       {
581         warnings.append("Invalid result type: '" + tok
582                 + "' (must be one of: ");
583         String sep = "";
584         for (JvDataType vl : JvDataType.values())
585         {
586           warnings.append(sep);
587           warnings.append(vl.toString());
588           sep = " ,";
589         }
590         warnings.append(" separated by semi-colons)\n");
591       }
592     }
593   }
594
595   private String getServiceIOProperties()
596   {
597     ArrayList<String> vls = new ArrayList<String>();
598     if (isHseparable())
599     {
600       vls.add("hseparable");
601     }
602     ;
603     if (isVseparable())
604     {
605       vls.add("vseparable");
606     }
607     ;
608     vls.add(new String("gapCharacter='" + gapCharacter + "'"));
609     vls.add(new String("returns='" + _genOutputFormatString() + "'"));
610     return arrayToSeparatorList(vls.toArray(new String[0]), ",");
611   }
612
613   public String toString()
614   {
615     StringBuffer result = new StringBuffer();
616     result.append("|");
617     result.append(details.Name);
618     result.append('|');
619     result.append(details.Action);
620     result.append('|');
621     if (details.description != null)
622     {
623       result.append(details.description);
624     }
625     ;
626     // list job input flags
627     result.append('|');
628     result.append(getServiceIOProperties());
629     // list any additional cgi parameters needed for result retrieval
630     if (urlSuffix != null && urlSuffix.length() > 0)
631     {
632       result.append('|');
633       result.append(urlSuffix);
634     }
635     result.append('|');
636     result.append(getInputParamEncodedUrl());
637     return result.toString();
638   }
639
640   /**
641    * processes a service encoded as a string (as generated by
642    * RestServiceDescription.toString()) Note - this will only use the first
643    * service definition encountered in the string to configure the service.
644    * 
645    * @param encoding
646    * @param warnings
647    *          - where warning messages are reported.
648    * @return true if configuration was parsed successfully.
649    */
650   public boolean configureFromEncodedString(String encoding,
651           StringBuffer warnings)
652   {
653     String[] list = separatorListToArray(encoding, "|");
654
655     int nextpos = parseServiceList(list, warnings, 0);
656     if (nextpos > 0)
657     {
658       return true;
659     }
660     return false;
661   }
662
663   /**
664    * processes the given list from position p, attempting to configure the
665    * service from it. Service lists are formed by concatenating individual
666    * stringified services. The first character of a stringified service is '|',
667    * enabling this, and the parser will ignore empty fields in a '|' separated
668    * list when they fall outside a service definition.
669    * 
670    * @param list
671    * @param warnings
672    * @param p
673    * @return
674    */
675   protected int parseServiceList(String[] list, StringBuffer warnings, int p)
676   {
677     boolean invalid = false;
678     // look for the first non-empty position - expect it to be service name
679     while (list[p] != null && list[p].trim().length() == 0)
680     {
681       p++;
682     }
683     details.Name = list[p];
684     details.Action = list[p + 1];
685     details.description = list[p + 2];
686     invalid |= !configureFromServiceInputProperties(list[p + 3], warnings);
687     if (list.length - p > 5 && list[p + 5] != null
688             && list[p + 5].trim().length() > 5)
689     {
690       urlSuffix = list[p + 4];
691       invalid |= !configureFromInputParamEncodedUrl(list[p + 5], warnings);
692       p += 6;
693     }
694     else
695     {
696       if (list.length - p > 4 && list[p + 4] != null
697               && list[p + 4].trim().length() > 5)
698       {
699         urlSuffix = null;
700         invalid |= !configureFromInputParamEncodedUrl(list[p + 4], warnings);
701         p += 5;
702       }
703     }
704     return invalid ? -1 : p;
705   }
706
707   /**
708    * @return string representation of the input parameters, their type and
709    *         constraints, appended to the service's base submission URL
710    */
711   private String getInputParamEncodedUrl()
712   {
713     StringBuffer url = new StringBuffer();
714     if (postUrl == null || postUrl.length() < 5)
715     {
716       return "";
717     }
718
719     url.append(postUrl);
720     char appendChar = (postUrl.indexOf("?") > -1) ? '&' : '?';
721     boolean consts = true;
722     do
723     {
724       for (Map.Entry<String, InputType> param : inputParams.entrySet())
725       {
726         List<String> vals = param.getValue().getURLEncodedParameter();
727         if (param.getValue().isConstant())
728         {
729           if (consts)
730           {
731             url.append(appendChar);
732             appendChar = '&';
733             url.append(param.getValue().token);
734             if (vals.size() == 1)
735             {
736               url.append("=");
737               url.append(vals.get(0));
738             }
739           }
740         }
741         else
742         {
743           if (!consts)
744           {
745             url.append(appendChar);
746             appendChar = '&';
747             url.append(param.getValue().token);
748             url.append("=");
749             // write parameter set as $TOKENPREFIX:csv list of params$ for this
750             // input param
751             url.append("$");
752             url.append(param.getValue().getURLtokenPrefix());
753             url.append(":");
754             url.append(arrayToSeparatorList(vals.toArray(new String[0]),
755                     ","));
756             url.append("$");
757           }
758         }
759
760       }
761       // toggle consts and repeat until !consts is false:
762     } while (!(consts = !consts));
763     return url.toString();
764   }
765
766   /**
767    * parse the service URL and input parameters from the given encoded URL
768    * string and configure the RestServiceDescription from it.
769    * 
770    * @param ipurl
771    * @param warnings
772    *          where any warnings
773    * @return true if URL parsed correctly. false means the configuration failed.
774    */
775   private boolean configureFromInputParamEncodedUrl(String ipurl,
776           StringBuffer warnings)
777   {
778     boolean valid = true;
779     int lastp = 0;
780     String url = new String();
781     Matcher prms = Pattern.compile("([?&])([A-Za-z0-9_]+)=\\$([^$]+)\\$")
782             .matcher(ipurl);
783     Map<String, InputType> iparams = new Hashtable<String, InputType>();
784     InputType jinput;
785     while (prms.find())
786     {
787       if (lastp < prms.start(0))
788       {
789         url += ipurl.substring(lastp, prms.start(0));
790         lastp = prms.end(0) + 1;
791       }
792       String sep = prms.group(1);
793       String tok = prms.group(2);
794       String iprm = prms.group(3);
795       int colon = iprm.indexOf(":");
796       String iprmparams = "";
797       if (colon > -1)
798       {
799         iprmparams = iprm.substring(colon + 1);
800         iprm = iprm.substring(0, colon);
801       }
802       valid = parseTypeString(prms.group(0), tok, iprm, iprmparams,
803               iparams, warnings);
804     }
805     if (valid)
806     {
807       try
808       {
809         URL u = new URL(url);
810         postUrl = url;
811         inputParams = iparams;
812       } catch (Exception e)
813       {
814         warnings.append("Failed to parse '" + url + "' as a URL.\n");
815         valid = false;
816       }
817     }
818     return valid;
819   }
820
821   public static Class[] getInputTypes()
822   {
823     // TODO - find a better way of maintaining this classlist
824     return new Class[]
825     { jalview.ws.rest.params.Alignment.class,
826         jalview.ws.rest.params.AnnotationFile.class,
827         SeqGroupIndexVector.class,
828         jalview.ws.rest.params.SeqIdVector.class,
829         jalview.ws.rest.params.SeqVector.class,
830         jalview.ws.rest.params.Tree.class };
831   }
832
833   public static boolean parseTypeString(String fullstring, String tok,
834           String iprm, String iprmparams, Map<String, InputType> iparams,
835           StringBuffer warnings)
836   {
837     boolean valid = true;
838     InputType jinput;
839     for (Class type : getInputTypes())
840     {
841       try
842       {
843         jinput = (InputType) (type.getConstructor().newInstance(null));
844         if (iprm.equalsIgnoreCase(jinput.getURLtokenPrefix()))
845         {
846           ArrayList<String> al = new ArrayList<String>();
847           for (String prprm : separatorListToArray(iprmparams, ","))
848           {
849             // hack to ensure that strings like "sep=','" containing unescaped
850             // commas as values are concatenated
851             al.add(prprm.trim());
852           }
853           if (!jinput.configureFromURLtokenString(al, warnings))
854           {
855             valid = false;
856             warnings.append("Failed to parse '" + fullstring + "' as a "
857                     + jinput.getURLtokenPrefix() + " input tag.\n");
858           }
859           else
860           {
861             jinput.token = tok;
862             iparams.put(tok, jinput);
863             valid = true;
864           }
865           break;
866         }
867
868       } catch (Throwable thr)
869       {
870       }
871       ;
872     }
873     return valid;
874   }
875
876   /**
877    * covenience method to generate the id and sequence string vector from a set
878    * of seuqences using each sequence's getName() and getSequenceAsString()
879    * method
880    * 
881    * @param seqs
882    * @return String[][] {{sequence ids},{sequence strings}}
883    */
884   public static String[][] formStrings(SequenceI[] seqs)
885   {
886     String[][] idset = new String[2][seqs.length];
887     for (int i = 0; i < seqs.length; i++)
888     {
889       idset[0][i] = seqs[i].getName();
890       idset[1][i] = seqs[i].getSequenceAsString();
891     }
892     return idset;
893   }
894
895   /**
896    * can this service be run on the visible portion of an alignment regardless
897    * of hidden boundaries ?
898    */
899   boolean hseparable = false;
900
901   boolean vseparable = false;
902
903   public boolean isHseparable()
904   {
905     return hseparable;
906   }
907
908   /**
909    * 
910    * @return
911    */
912   public boolean isVseparable()
913   {
914     return vseparable;
915   }
916
917   /**
918    * search the input types for an instance of the given class
919    * 
920    * @param <validInput.inputType> class1
921    * @return
922    */
923   public boolean inputInvolves(Class<?> class1)
924   {
925     assert (InputType.class.isAssignableFrom(class1));
926     for (InputType val : inputParams.values())
927     {
928       if (class1.isAssignableFrom(val.getClass()))
929       {
930         return true;
931       }
932     }
933     return false;
934   }
935
936   char gapCharacter = '-';
937
938   /**
939    * 
940    * @return the preferred gap character for alignments input/output by this
941    *         service
942    */
943   public char getGapCharacter()
944   {
945     return gapCharacter;
946   }
947
948   public String getDecoratedResultUrl(String jobId)
949   {
950     // TODO: correctly write ?/& appropriate to result URL format.
951     return jobId + urlSuffix;
952   }
953
954   private List<JvDataType> resultData = new ArrayList<JvDataType>();
955
956   /**
957    * 
958    * 
959    * TODO: Extend to optionally specify relative/absolute url where data of this
960    * type can be retrieved from
961    * 
962    * @param dt
963    */
964   public void addResultDatatype(JvDataType dt)
965   {
966     if (resultData == null)
967     {
968       resultData = new ArrayList<JvDataType>();
969     }
970     resultData.add(dt);
971   }
972
973   public boolean removeRsultDatatype(JvDataType dt)
974   {
975     if (resultData != null)
976     {
977       return resultData.remove(dt);
978     }
979     return false;
980   }
981
982   public List<JvDataType> getResultDataTypes()
983   {
984     return resultData;
985   }
986
987   /**
988    * parse a concatenated list of rest service descriptions into an array
989    * 
990    * @param services
991    * @return zero or more services.
992    * @throws exceptions
993    *           if the services are improperly encoded.
994    */
995   public static List<RestServiceDescription> parseDescriptions(
996           String services) throws Exception
997   {
998     String[] list = separatorListToArray(services, "|");
999     List<RestServiceDescription> svcparsed = new ArrayList<RestServiceDescription>();
1000     int p = 0, lastp = 0;
1001     StringBuffer warnings = new StringBuffer();
1002     do
1003     {
1004       RestServiceDescription rsd = new RestServiceDescription();
1005       p = rsd.parseServiceList(list, warnings, lastp = p);
1006       if (p > lastp && rsd.isValid())
1007       {
1008         svcparsed.add(rsd);
1009       }
1010       else
1011       {
1012         throw new Exception(
1013                 "Failed to parse user defined RSBS services from :"
1014                         + services
1015                         + "\nFirst error was encountered at token " + lastp
1016                         + " starting " + list[lastp] + ":\n"
1017                         + rsd.getInvalidMessage());
1018       }
1019     } while (p < lastp && p < list.length - 1);
1020     return svcparsed;
1021   }
1022
1023 }