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