Parameter.java
001 /*
002  *  Parameter.java
003  *
004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Hamish Cunningham, 15/Oct/2000
013  *
014  *  $Id: Parameter.java 17924 2014-05-07 09:45:23Z markagreenwood $
015  */
016 
017 package gate.creole;
018 
019 import gate.Factory;
020 import gate.FeatureMap;
021 import gate.Gate;
022 import gate.Resource;
023 import gate.util.GateRuntimeException;
024 import gate.util.Strings;
025 
026 import java.io.Serializable;
027 import java.net.MalformedURLException;
028 import java.net.URL;
029 import java.util.ArrayList;
030 import java.util.Collection;
031 import java.util.HashMap;
032 import java.util.HashSet;
033 import java.util.LinkedList;
034 import java.util.List;
035 import java.util.Map;
036 import java.util.Queue;
037 import java.util.Set;
038 import java.util.SortedSet;
039 import java.util.StringTokenizer;
040 import java.util.TreeSet;
041 
042 
043 /** Models a resource parameter.
044   */
045 public class Parameter implements Serializable
046 {
047   private static final long serialVersionUID = 6611664706992065985L;
048 
049   /**
050    * Constructor
051    @param baseUrl the URL to the creole.xml file that defines the resource 
052    * this parameter belongs to. This will be used a context when deriving 
053    * default values for the parameters of type URL.
054    */
055   public Parameter(URL baseUrl){
056     this.baseURL = baseUrl;
057   }
058   
059   /** The type name of the parameter */
060   String typeName;
061 
062   /** Set the type name for this parameter */
063   public void setTypeName(String typeName) { this.typeName = typeName; }
064 
065   /** Get the type name for this parameter */
066   public String getTypeName() { return typeName; }
067 
068   /** Is the parameter optional? */
069   boolean optional = false;
070 
071   /** Set optionality of this parameter */
072   public void setOptional(boolean optional) { this.optional = optional; }
073 
074   /** Is the parameter optional? */
075   public boolean isOptional() { return optional; }
076 
077   /** The name of the item's class. If the parameter is a collection then
078     * we need  to know the class of its items in order to create them the
079     * way we want.
080     */
081   String itemClassName = null;
082 
083   /** A set of strings representing suffixes for URL params*/
084   Set<String> suffixes = null;
085   
086   /**
087    * Map giving concrete classes that should be used for a parameter
088    * whose declared type is an interface.  This substitution allows a
089    * resource to specify a parameter of type, e.g. java.util.List,
090    * and give it a default value in creole.xml.  The runtime class of
091    * the default value will be taken from this map.
092    
093    * Note that for this to work, it must be the case that for every
094    * key <i>k</i> in this map,
095    
096    <code>k.isAssignableFrom(substituteClasses.get(k))</code>
097    */
098    static Map<Class<?>, Class<?>> substituteClasses = 
099     new HashMap<Class<?>, Class<?>>();
100   
101   static {
102     substituteClasses.put(Collection.class, ArrayList.class);
103     substituteClasses.put(List.class, ArrayList.class);
104     substituteClasses.put(Set.class, HashSet.class);
105     substituteClasses.put(SortedSet.class, TreeSet.class);
106     substituteClasses.put(Queue.class, LinkedList.class);
107   }
108 
109   /** Calculate and return the default value for this parameter */
110   public Object calculateDefaultValue() throws ParameterException {
111     // if there's no default string and this is a builtin type, return null
112     if(
113       defaultValueString == null && typeName != null &&
114       typeName.startsWith("java.")
115     )
116       return null;
117 
118     return calculateValueFromString(defaultValueString);
119   // calculateDefaultValue()
120 
121   /** Calculate and return the value for this parameter starting from a String
122    */
123   @SuppressWarnings("unchecked")
124   public Object calculateValueFromString(String stringValue)
125   throws ParameterException {
126     //if we have no string we can't construct a value
127     Object value = null;
128 
129     // get the Class for the parameter via Class.forName or CREOLE register
130     Class<?> paramClass = getParameterClass();
131     if(substituteClasses.containsKey(paramClass)) {
132       paramClass = substituteClasses.get(paramClass);
133     }
134 
135 
136     // Test if the paramClass is a collection and if it is, try to
137     // construct the param as a collection of items specified in the
138     // default string value.  If paramClass is an interface type we
139     // look up its substitute concrete type in the map
140     // collectionSubstituteClasses and create a value of that type.
141     if (Collection.class.isAssignableFrom(paramClass&&
142             !paramClass.isInterface()){
143       // Create an collection object belonging to paramClass
144       Collection<?> colection = null;
145       try{
146         colection = paramClass.asSubclass(Collection.class).getConstructor(new Class<?>[]{}).
147                                   newInstance(new Object[]{});
148       catch(Exception ex){
149           throw new ParameterException("Could not construct an object of type "
150             + typeName + " for param " + name +
151             "\nProblem was: " + ex.toString());
152       }// End try
153       // If an itemClassName was specified then try to create objects belonging
154       // to this class and add them to the collection. Otherwise add the
155       // string tokens to the collection.
156       if(itemClassName == null){
157         // Read the tokens from the default value and try to create items
158         // belonging to the itemClassName
159         StringTokenizer strTokenizer = new StringTokenizer(
160                                                       stringValue,";");
161         while(strTokenizer.hasMoreTokens()){
162           String itemStringValue = strTokenizer.nextToken();
163           ((Collection<String>)colection).add(itemStringValue);
164         }// End while
165       }else{
166         Class<?> itemClass = null;
167         try{
168           itemClass = Gate.getClassLoader().loadClass(itemClassName);
169         }catch(ClassNotFoundException e){
170           throw new ParameterException("Could not construct a class object for "
171             + itemClassName + " for param "+ name +
172             ", with type name="+ typeName);
173         }// End try
174         // Read the tokens from the default value and try to create items
175         // belonging to the itemClassName
176         StringTokenizer strTokenizer = new StringTokenizer(
177                                                       stringValue,";");
178         while(strTokenizer.hasMoreTokens()){
179           // Read a string item and construct an object belonging to
180           // itemClassName
181           String itemStringValue = strTokenizer.nextToken();
182           Object itemValue = null;
183           try{
184             itemValue = itemClass.getConstructor(new Class<?>[]{String.class}).
185                                   newInstance(new Object[]{itemStringValue});
186           }catch(Exception e){
187             throw new ParameterException("Could not create an object of " +
188             itemClassName + " for param name "+ name + ", with type name ="+
189             typeName);
190           }// End try
191           // Add the item value object to the collection
192           ((Collection<Object>)colection).add(itemValue);
193         }// End while
194       }// End if(itemClassName == null)
195       return colection;
196     }// End if (Collection.class.isAssignableFrom(paramClass))
197     
198     if(FeatureMap.class.isAssignableFrom(paramClass)) {
199       // a null string value means a null FeatureMap
200       if(stringValue == nullreturn null;
201       FeatureMap fm = null;
202       // if the type is an interface type (not a concrete implementation)
203       // then just create a normal feature map using the factory
204       if(paramClass.isInterface()) {
205         fm = Factory.newFeatureMap();
206       }
207       else {
208         try{
209           fm = paramClass.asSubclass(FeatureMap.class).getConstructor(new Class<?>[]{}).
210                                     newInstance(new Object[]{});
211         catch(Exception ex){
212             throw new ParameterException("Could not construct an object of type "
213               + typeName + " for param " + name +
214               "\nProblem was: " + ex.toString());
215         }
216       }
217       
218       // Read the tokens from the default value and try to create items
219       // belonging to the itemClassName
220       StringTokenizer strTokenizer = new StringTokenizer(
221                                                     stringValue,";");
222       while(strTokenizer.hasMoreTokens()) {
223         String keyAndValue = strTokenizer.nextToken();
224         int indexOfEquals = keyAndValue.indexOf('=');
225         if(indexOfEquals == -1) {
226           throw new ParameterException("Error parsing string \""
227                   + stringValue + "\" for parameter " + name + " of type "
228                   + typeName + ". Value string must be of the form "
229                   "name1=value1;name2=value2;...");
230         }
231         String featName = keyAndValue.substring(0, indexOfEquals);
232         String featValue = keyAndValue.substring(indexOfEquals + 1);
233         fm.put(featName, featValue);
234       }
235       
236       return fm;
237     }
238     
239     // Java 5.0 enum types
240     if(paramClass.isEnum()) {
241       if(stringValue == null) {
242         value = null;
243       }
244       else {
245         try {
246           value = Enum.valueOf(paramClass.asSubclass(Enum.class), stringValue);
247         }
248         catch(IllegalArgumentException e) {
249           throw new ParameterException("Invalid enum constant name "
250               + stringValue + " for type " + typeName);
251         }
252       }
253     }
254     // java builtin types - for numeric types, we don't attempt to parse an
255     // empty string value, but just leave value as null
256     else if(typeName.startsWith("java.")) {
257       if(typeName.equals("java.lang.Boolean"))
258         value = Boolean.valueOf(stringValue);
259       else if(typeName.equals("java.lang.Long")) {
260         if(stringValue != null && !stringValue.equals("")) {
261           value = Long.valueOf(stringValue);
262         }
263       }
264       else if(typeName.equals("java.lang.Integer")) {
265         if(stringValue != null && !stringValue.equals("")) {
266           value = Integer.valueOf(stringValue);
267         }
268       }
269       else if(typeName.equals("java.lang.String"))
270         value = stringValue;
271       else if(typeName.equals("java.lang.Double")) {
272         if(stringValue != null && !stringValue.equals("")) {
273           value = Double.valueOf(stringValue);
274         }
275       }
276       else if(typeName.equals("java.lang.Float")) {
277         if(stringValue != null && !stringValue.equals("")) {
278           value = Float.valueOf(stringValue);
279         }
280       }
281       else if(typeName.equals("java.net.URL"))
282         try{
283           if(stringValue != null && !stringValue.equals("")) {
284             value = new URL(baseURL, stringValue);
285           }
286         }catch(MalformedURLException mue){
287           //value = null;
288           throw new ParameterException("Malformed URL parameter value: "+stringValue,mue);
289         }
290       else{
291         //try to construct a new value from the string using a constructor
292         // e.g. for URLs
293         try{
294           if(!paramClass.isAssignableFrom(String.class)){
295             value = paramClass.getConstructor(new Class<?>[]{String.class}).
296                          newInstance(new Object[]{stringValue});
297           }
298         }catch(Exception e){
299           throw new ParameterException("Unsupported parameter type " + typeName);
300         }
301       }
302     else {
303       // non java types
304       // null string value means null target value
305       if(stringValue != null) {
306         // otherwise, if it's a GATE resource type pick the first registered instance 
307         if(resData == null)
308           resData = Gate.getCreoleRegister().get(typeName);
309         if(resData == null){
310           //unknown type
311           return null;
312         }
313   
314         List<Resource> instantiations = resData.getInstantiations();
315         if(! instantiations.isEmpty()) value = instantiations.get(0);
316       }
317     }
318 
319     return value;
320   // calculateValueFromString()
321 
322 
323   /** The resource data that this parameter is part of. */
324   protected ResourceData resData;
325 
326   /** Get the default value for this parameter. If the value is
327     * currently null it will try and calculate a value.
328     @see #calculateDefaultValue()
329     */
330   public Object getDefaultValue() throws ParameterException {
331     return calculateDefaultValue();
332   // getDefaultValue
333 
334   /** Default value string (unprocessed, from the metadata)
335     * for the parameter
336     */
337   String defaultValueString;
338 
339   /** Set the default value string (from the metadata)
340     * for the parameter
341     */
342   public void setDefaultValueString(String defaultValueString) {
343     this.defaultValueString = defaultValueString;
344   // setDefaultValueString
345 
346   /** Get the default value string (unprocessed, from the metadata)
347     * for the parameter
348     */
349   public String getDefaultValueString() { return defaultValueString; }
350 
351   /** Comment for the parameter */
352   String comment;
353 
354   /** Set the comment for this parameter */
355   public void setComment(String comment) { this.comment = comment; }
356 
357   /** Get the comment for this parameter */
358   public String getComment() { return comment; }
359 
360   /** helpURL for the parameter */
361   String helpURL;
362   
363   /** Set the helpURL for this parameter */
364   public void sethelpURL(String helpURL) { this.helpURL = helpURL; }
365   
366   /** Get the helpURL for this parameter */
367   public String gethelpURL() { return helpURL; }
368   
369   /** Name for the parameter */
370   String name;
371 
372   /** Set the name for this parameter */
373   public void setName(String name) { this.name = name; }
374 
375   /** Get the name for this parameter */
376   public String getName() { return name; }
377 
378   /** Get the suffixes atached with this param. If it's null then there are
379    *  no suffices attached with it
380    */
381   public Set<String> getSuffixes(){ return suffixes;}
382 
383   /** Is this a run-time parameter? */
384   boolean runtime = false;
385 
386   /**
387    * The URL to the creole.xml file that defines the resource this parameter 
388    * belongs to. It is used for deriving default values for parameters of type
389    {@link URL}.
390    */
391   protected URL baseURL;
392   /** Set runtime status of this parameter */
393   public void setRuntime(boolean runtime) { this.runtime = runtime; }
394 
395   /** Is the parameter runtime? */
396   public boolean isRuntime() { return runtime; }
397 
398   /** The Class for the parameter type */
399   protected Class<?> paramClass;
400 
401   /** Find the class for this parameter type. */
402   protected Class<?> getParameterClass() throws ParameterException
403   {
404     // get java builtin classes via class; else look in the register
405     try {
406       ResourceData resData = Gate.getCreoleRegister().get(typeName);
407       if(resData == null){
408         paramClass = Gate.getClassLoader().loadClass(typeName);
409       }else{
410         paramClass = resData.getResourceClass();
411       }
412 
413 //      if(typeName.startsWith("java."))
414 //          paramClass = Class.forName(typeName);
415 //      else {
416 //        ResourceData resData =
417 //          (ResourceData) Gate.getCreoleRegister().get(typeName);
418 //        if(resData == null)
419 //          throw new ParameterException(
420 //            "No resource data for " + typeName + " in Parameter/getParamClz"
421 //          );
422 //        paramClass = resData.getResourceClass();
423 //      }
424     catch(ClassNotFoundException e) {
425       throw new ParameterException(
426         "Couldn't find class " + typeName + ": " + Strings.getNl() + e
427       );
428     }
429 
430     if(paramClass == null)
431       throw new ParameterException("Couldn't find class " + typeName);
432 
433     return paramClass;
434   // getParameterClass
435 
436   /** String representation */
437   @Override
438   public String toString() {
439     try{
440       return "Parameter: name="+ name+ "; valueString=" + typeName +
441              "; optional=" + optional +
442              "; defaultValueString=" + defaultValueString +
443              "; defaultValue=" + getDefaultValue() "; comment=" +
444              comment + "; helpURL=" +
445              helpURL + "; runtime=" + runtime +
446              "; itemClassName=" + itemClassName +
447              "; suffixes=" + suffixes;
448     }catch(ParameterException pe){
449       throw new GateRuntimeException(pe.toString());
450     }
451   }
452 
453   /**
454    * If this parameter is a List type this will return the type of the items
455    * in the list. If the type is <tt>null</tt> String will be assumed.
456    */
457   public String getItemClassName() {
458     return itemClassName;
459   // toString()
460 // class Parameter