Factory.java
001 /*
002  *  Factory.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, 25/May/2000
013  *
014  *  $Id: Factory.java 13570 2011-03-28 14:16:13Z ian_roberts $
015  */
016 
017 package gate;
018 
019 import java.io.BufferedInputStream;
020 import java.io.IOException;
021 import java.io.InputStreamReader;
022 import java.io.Serializable;
023 import java.lang.reflect.Constructor;
024 import java.lang.reflect.InvocationTargetException;
025 import java.net.*;
026 import java.util.*;
027 
028 import gate.creole.*;
029 import gate.creole.gazetteer.Gazetteer;
030 //import gate.creole.ontology.owlim.OWLIMOntologyLR;
031 import gate.event.CreoleEvent;
032 import gate.event.CreoleListener;
033 import gate.jape.constraint.ConstraintFactory;
034 import gate.jape.parser.ParseCpsl;
035 import gate.persist.PersistenceException;
036 import gate.persist.SerialDataStore;
037 import gate.security.*;
038 import gate.security.SecurityException;
039 import gate.util.*;
040 import gate.annotation.ImmutableAnnotationSetImpl;
041 
042 /** Provides static methods for the creation of Resources.
043   */
044 public abstract class Factory {
045   /** Debug flag */
046   private static final boolean DEBUG = false;
047 
048   /** The CREOLE register */
049   private static CreoleRegister reg = Gate.getCreoleRegister();
050 
051   /** The DataStore register */
052   private static DataStoreRegister dsReg = Gate.getDataStoreRegister();
053 
054   /** An object to source events from. */
055   private static CreoleProxy creoleProxy;
056 
057   /** An object to source events from. */
058   private static HashMap accessControllerPool;
059 
060   /** Create an instance of a resource using default parameter values.
061     @see #createResource(String,FeatureMap)
062     */
063   public static Resource createResource(String resourceClassName)
064   throws ResourceInstantiationException
065   {
066     // get the resource metadata
067     ResourceData resData = (ResourceDatareg.get(resourceClassName);
068     if(resData == null)
069       throw new ResourceInstantiationException(
070         "Couldn't get resource data for " + resourceClassName + ".\n\n" +
071         "You may need first to load the plugin that contains your resource.\n" +
072         "For example, to create a gate.creole.tokeniser.DefaultTokeniser\n" +
073         "you need first to load the ANNIE plugin.\n\n" +
074         "Go to the menu File->Manage CREOLE plugins or use the method\n" +
075         "Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL)."
076       );
077 
078     // get the parameter list and default values
079     ParameterList paramList = resData.getParameterList();
080     FeatureMap parameterValues = null;
081     try {
082       parameterValues = paramList.getInitimeDefaults();
083     catch(ParameterException e) {
084       throw new ResourceInstantiationException(
085         "Couldn't get default parameters for " + resourceClassName + ": " + e
086       );
087     }
088 
089     return createResource(resourceClassName, parameterValues);
090   // createResource(resClassName)
091 
092   /** Create an instance of a resource, and return it.
093     * Callers of this method are responsible for
094     * querying the resource's parameter lists, putting together a set that
095     * is complete apart from runtime parameters, and passing a feature map
096     * containing these parameter settings.
097     *
098     @param resourceClassName the name of the class implementing the resource.
099     @param parameterValues the feature map containing intialisation time
100     *   parameterValues for the resource.
101     @return an instantiated resource.
102     */
103   public static Resource createResource(
104     String resourceClassName, FeatureMap parameterValues
105   throws ResourceInstantiationException
106   {
107     return createResource(resourceClassName, parameterValues, null, null);
108   // createResource(resClassName, paramVals, listeners)
109 
110   /** Create an instance of a resource, and return it.
111     * Callers of this method are responsible for
112     * querying the resource's parameter lists, putting together a set that
113     * is complete apart from runtime parameters, and passing a feature map
114     * containing these parameter settings.
115     *
116     @param resourceClassName the name of the class implementing the resource.
117     @param parameterValues the feature map containing intialisation time
118     *   parameterValues for the resource.
119     @param features the features for the new resource
120     @return an instantiated resource.
121     */
122   public static Resource createResource(
123     String resourceClassName, FeatureMap parameterValues,
124     FeatureMap features
125     throws ResourceInstantiationException
126    {
127       return createResource(resourceClassName, parameterValues,
128                             features, null);
129    }
130 
131   /** Create an instance of a resource, and return it.
132     * Callers of this method are responsible for
133     * querying the resource's parameter lists, putting together a set that
134     * is complete apart from runtime parameters, and passing a feature map
135     * containing these parameter settings.
136     *
137     * In the case of ProcessingResources they will have their runtime parameters
138     * initialised to their default values.
139     *
140     @param resourceClassName the name of the class implementing the resource.
141     @param parameterValues the feature map containing intialisation time
142     *   parameterValues for the resource.
143     @param features the features for the new resource or null to not assign
144     *   any (new) features.
145     @param resourceName the name to be given to the resource or null to assign
146     *   a default name.
147     @return an instantiated resource.
148     */
149   public static Resource createResource(
150     String resourceClassName, FeatureMap parameterValues,
151     FeatureMap features, String resourceName
152   throws ResourceInstantiationException
153    {
154     // get the resource metadata
155     ResourceData resData = (ResourceDatareg.get(resourceClassName);
156     if(resData == null)
157       throw new ResourceInstantiationException(
158         "Couldn't get resource data for " + resourceClassName + ".\n\n" +
159         "You may need first to load the plugin that contains your resource.\n" +
160         "For example, to create a gate.creole.tokeniser.DefaultTokeniser\n" +
161         "you need first to load the ANNIE plugin.\n\n" +
162         "Go to the menu File->Manage CREOLE plugins or use the method\n" +
163         "Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL)."
164       );
165     // get the default implementation class
166     Class resClass = null;
167     try {
168       resClass = resData.getResourceClass();
169     catch(ClassNotFoundException e) {
170       throw new ResourceInstantiationException(
171         "Couldn't get resource class from the resource data:"+Strings.getNl()+e
172       );
173     }
174 
175     //create a pointer for the resource
176     Resource res = null;
177 
178     //if the object is an LR and it should come from a DS then create that way
179     DataStore dataStore;
180     if(LanguageResource.class.isAssignableFrom(resClass&&
181        ((dataStore = (DataStore)parameterValues.
182                      get(DataStore.DATASTORE_FEATURE_NAME)) != null)
183       ){
184       //ask the datastore to create our object
185       if(dataStore instanceof SerialDataStore) {
186         // SDS doesn't need a wrapper class; just check for serialisability
187         if(! Serializable.class.isAssignableFrom(resClass))
188           throw new ResourceInstantiationException(
189             "Resource cannot be (de-)serialized: " + resClass.getName()
190           );
191       }
192 
193       // get the datastore instance id and retrieve the resource
194       Object instanceId = parameterValues.get(DataStore.LR_ID_FEATURE_NAME);
195       if(instanceId == null)
196         throw new
197           ResourceInstantiationException("No instance id for " + resClass);
198       try {
199         res = dataStore.getLr(resClass.getName(), instanceId);
200       catch(PersistenceException pe) {
201         throw new ResourceInstantiationException("Bad read from DB: " + pe);
202       catch(SecurityException se) {
203         throw new ResourceInstantiationException("Insufficient permissions: " + se);
204       }
205       resData.addInstantiation(res);
206       if(features != null){
207         if(res.getFeatures() == null){
208           res.setFeatures(newFeatureMap());
209         }
210         res.getFeatures().putAll(features);
211       }
212 
213       //set the name
214       if(res.getName() == null){
215         res.setName(resourceName == null ?
216                     resData.getName() "_" + Gate.genSym() :
217                     resourceName);
218       }
219 
220       // fire the event
221       creoleProxy.fireResourceLoaded(
222         new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
223       );
224 
225       return res;
226     }
227 
228     //The resource is not a persistent LR; use a constructor
229 
230     // create an object using the resource's default constructor
231     try {
232       if(DEBUGOut.prln("Creating resource " + resClass.getName());
233       res = (ResourceresClass.newInstance();
234     catch(IllegalAccessException e) {
235       throw new ResourceInstantiationException(
236         "Couldn't create resource instance, access denied: " + e
237       );
238     catch(InstantiationException e) {
239       throw new ResourceInstantiationException(
240         "Couldn't create resource instance due to newInstance() failure: " + e
241       );
242     }
243 
244 
245     if(LanguageResource.class.isAssignableFrom(resClass)) {
246       // type-specific stuff for LRs
247       if(DEBUGOut.prln(resClass.getName() " is a LR");
248     else if(ProcessingResource.class.isAssignableFrom(resClass)) {
249       // type-specific stuff for PRs
250       if(DEBUGOut.prln(resClass.getName() " is a PR");
251       //set the runtime parameters to their defaults
252       try{
253         FeatureMap parameters = newFeatureMap();
254         parameters.putAll(resData.getParameterList().getRuntimeDefaults());
255         res.setParameterValues(parameters);
256       }catch(ParameterException pe){
257         throw new ResourceInstantiationException(
258                   "Could not set the runtime parameters " +
259                   "to their default values for: " + res.getClass().getName() +
260                   " :\n" + pe.toString()
261                   );
262       }
263     // type-specific stuff for VRs
264     else if(VisualResource.class.isAssignableFrom(resClass)) {
265       if(DEBUGOut.prln(resClass.getName() " is a VR");
266     else if(Controller.class.isAssignableFrom(resClass)){
267       //type specific stuff for Controllers
268     }
269 
270 
271     //set the parameterValues of the resource
272     try{
273       FeatureMap parameters = newFeatureMap();
274       //put the defaults
275       parameters.putAll(resData.getParameterList().getInitimeDefaults());
276       //overwrite the defaults with the user provided values
277       parameters.putAll(parameterValues);
278       res.setParameterValues(parameters);
279     }catch(ParameterException pe){
280         throw new ResourceInstantiationException(
281                     "Could not set the init parameters for: " +
282                     res.getClass().getName() " :\n" + pe.toString()
283                   );
284     }
285 
286     //set the name
287     // if we have an explicitly provided name, use that, otherwise generate a
288     // suitable name if the resource doesn't already have one
289     if(resourceName != null && resourceName.trim().length() 0) {
290       res.setName(resourceName);
291     }
292     else if(res.getName() == null) {
293       //no name provided, and the resource doesn't have a name already (e.g. calculated in init())
294       // -> let's try and find a reasonable one
295       try{
296         //first try to get a filename from the various parameters
297         URL sourceUrl = null;
298         if(res instanceof SimpleDocument){
299           sourceUrl = ((SimpleDocument)res).getSourceUrl();
300         else if(res instanceof AnnotationSchema){
301           sourceUrl = ((AnnotationSchema)res).getXmlFileUrl();
302         else if(res.getClass().getName().startsWith("gate.creole.ontology.owlim.")){
303           // get the name for the OWLIM2 ontology LR
304           java.lang.reflect.Method m = resClass.getMethod("getRdfXmlURL");
305           sourceUrl = (java.net.URL)m.invoke(res);
306           if(sourceUrl == null){
307             m = resClass.getMethod("getN3URL");
308             sourceUrl = (java.net.URL)m.invoke(res);
309           }
310           if(sourceUrl == null){
311             m = resClass.getMethod("getNtriplesURL");
312             sourceUrl = (java.net.URL)m.invoke(res);
313           }
314           if(sourceUrl == null){
315             m = resClass.getMethod("getTurtleURL");
316             sourceUrl = (java.net.URL)m.invoke(res);
317           }
318         else if (res.getClass().getName().startsWith("gate.creole.ontology.impl.")) {
319           java.lang.reflect.Method m = resClass.getMethod("getSourceURL");
320           sourceUrl = (java.net.URL)m.invoke(res);
321         }
322         if(sourceUrl != null){
323           URI sourceURI = sourceUrl.toURI();
324           resourceName = sourceURI.getPath().trim();
325           if(resourceName == null ||
326              resourceName.length() == ||
327              resourceName.equals("/")){
328             //this URI has no path -> use the whole string
329             resourceName = sourceURI.toString();
330           }else{
331             //there is a significant path value -> get the last element
332             int lastSlash = resourceName.lastIndexOf('/');
333             if(lastSlash >=0){
334               String subStr = resourceName.substring(lastSlash + 1);
335               if(subStr.trim().length() 0resourceName = subStr;
336             }
337           }
338         }
339       }catch(Exception t){
340         //there were problems while trying to guess a name
341         //we can safely ignore them
342       }finally{
343         //make sure there is a name provided, whatever happened
344         if(resourceName == null || resourceName.trim().length() == 0){
345           resourceName = resData.getName();
346         }
347       }
348       resourceName += "_" + Gate.genSym();
349       res.setName(resourceName);
350     // else if(res.getName() == null)
351     // if res.getName() != null, leave it as it is
352 
353     Map listeners = new HashMap(gate.Gate.getListeners());
354     // set the listeners if any
355     if(listeners != null && !listeners.isEmpty()) {
356       try {
357         if(DEBUGOut.prln("Setting the listeners for  " + res.toString());
358         AbstractResource.setResourceListeners(res, listeners);
359       catch(Exception e) {
360         if(DEBUGOut.prln("Failed to set listeners for " + res.toString());
361         throw new
362           ResourceInstantiationException("Parameterisation failure" + e);
363       }
364     }
365 
366     // if the features of the resource have not been explicitly set,
367     // set them to the features of the resource data
368     if(res.getFeatures() == null || res.getFeatures().isEmpty()){
369       FeatureMap fm = newFeatureMap();
370       fm.putAll(resData.getFeatures());
371       res.setFeatures(fm);
372     }
373     // add the features specified by the user
374     if(features != nullres.getFeatures().putAll(features);
375 
376     // initialise the resource
377     if(DEBUGOut.prln("Initialising resource " + res.toString());
378     res = res.init();
379 
380     // remove the listeners if any
381     if(listeners != null && !listeners.isEmpty()) {
382       try {
383         if(DEBUGOut.prln("Removing the listeners for  " + res.toString());
384         AbstractResource.removeResourceListeners(res, listeners);
385       catch(Exception e) {
386         if (DEBUGOut.prln(
387           "Failed to remove the listeners for " + res.toString()
388         );
389         throw new
390           ResourceInstantiationException("Parameterisation failure" + e);
391       }
392     }
393     // record the instantiation on the resource data's stack
394     resData.addInstantiation(res);
395     // fire the event
396     creoleProxy.fireResourceLoaded(
397       new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
398     );
399     return res;
400   // create(resourceClassName, parameterValues, features, listeners)
401 
402   /** Delete an instance of a resource. This involves removing it from
403     * the stack of instantiations maintained by this resource type's
404     * resource data. Deletion does not guarantee that the resource will
405     * become a candidate for garbage collection, just that the GATE framework
406     * is no longer holding references to the resource.
407     *
408     @param resource the resource to be deleted.
409     */
410   public static void deleteResource(Resource resource) {
411     ResourceData rd =
412       (ResourceDatareg.get(resource.getClass().getName());
413     if(rd!= null && rd.removeInstantiation(resource)) {
414       creoleProxy.fireResourceUnloaded(
415         new CreoleEvent(resource, CreoleEvent.RESOURCE_UNLOADED)
416       );
417       resource.cleanup();
418     }
419   // deleteResource
420 
421   /** Create a new transient Corpus. */
422   public static Corpus newCorpus(String name)
423                                           throws ResourceInstantiationException
424   {
425     return (CorpuscreateResource("gate.corpora.CorpusImpl", newFeatureMap(), newFeatureMap(), name);
426   // newCorpus
427 
428   /** Create a new transient Document from a URL. */
429   public static Document newDocument(URL sourceUrl)
430                                           throws ResourceInstantiationException
431   {
432     FeatureMap parameterValues = newFeatureMap();
433     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
434     return
435       (DocumentcreateResource("gate.corpora.DocumentImpl", parameterValues);
436   // newDocument(URL)
437 
438   /** Create a new transient Document from a URL and an encoding. */
439   public static Document newDocument(URL sourceUrl, String encoding)
440                                           throws ResourceInstantiationException
441   {
442     FeatureMap parameterValues = newFeatureMap();
443     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
444     parameterValues.put(Document.DOCUMENT_ENCODING_PARAMETER_NAME, encoding);
445     return
446       (DocumentcreateResource("gate.corpora.DocumentImpl", parameterValues);
447   // newDocument(URL)
448 
449   /** Create a new transient textual Document from a string. */
450   public static Document newDocument(String content)
451                                           throws ResourceInstantiationException
452   {
453     FeatureMap params = newFeatureMap();
454     params.put(Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, content);
455     Document doc =
456       (DocumentcreateResource("gate.corpora.DocumentImpl", params);
457 /*
458     // laziness: should fit this into createResource by adding a new
459     // document parameter, but haven't time right now...
460     doc.setContent(new DocumentContentImpl(content));
461 */
462     // various classes are in the habit of assuming that a document
463     // inevitably has a source URL...  so give it a dummy one
464 /*    try {
465       doc.setSourceUrl(new URL("http://localhost/"));
466     } catch(MalformedURLException e) {
467       throw new ResourceInstantiationException(
468         "Couldn't create dummy URL in newDocument(String): " + e
469       );
470     }
471 */
472     doc.setSourceUrl(null);
473     return doc;
474   // newDocument(String)
475 
476   
477   /**
478    * Utility method to create an immutable annotation set. If the provided 
479    * collection of annotations is <code>null</null>, the newly created set will
480    * be empty.
481    @param document the document this set belongs to. 
482    @param annotations the set of annotations that should be contained in the
483    * returned {@link AnnotationSet}
484    @return an {@link AnnotationSet} that throws exceptions on all attempts 
485    * to modify it.
486    */
487   public static AnnotationSet createImmutableAnnotationSet(Document document, 
488           Collection<Annotation> annotations) {
489     return new ImmutableAnnotationSetImpl(document, annotations){
490       private static final long serialVersionUID = -6703131102439043539L;
491     };
492   }
493   
494   /**
495    <p>
496    * Create a <i>duplicate</i> of the given resource.  A duplicate is a
497    * an independent copy of the resource that has the same name and the
498    * same behaviour.  It does <i>not necessarily</i> have the same concrete
499    * class as the original, but if the original resource implements any of
500    * the following interfaces then the duplicate can be assumed to
501    * implement the same ones:
502    </p>
503    <ul>
504    <li>{@link ProcessingResource}</li>
505    <li>{@link LanguageAnalyser}</li>
506    <li>{@link Controller}</li>
507    <li>{@link CorpusController}</li>
508    <li>{@link ConditionalController}</li>
509    <li>{@link Gazetteer}</li>
510    <li>{@link LanguageResource}</li>
511    <li>{@link gate.creole.ontology.Ontology}</li>
512    <li>{@link Document}</li>
513    <li>{@link Corpus}</li>
514    </ul>
515    <p>
516    * The default duplication algorithm simply calls
517    {@link #createResource(String, FeatureMap, FeatureMap, String) createResource}
518    * with the type and name of the original resource, and with parameters
519    * and features which are copies of those from the original resource,
520    * but any Resource values in the maps will themselves be duplicated.
521    * A context is passed around all the duplicate calls that stem from the
522    * same call to this method so that if the same resource is referred to
523    * in different places, the same duplicate can be used in the
524    * corresponding places in the duplicated object graph.
525    </p>
526    <p>
527    * This default behaviour is sufficient for most resource types (and
528    * is roughly the equivalent of saving the resource's state using the
529    * persistence manager and then reloading it), but individual resource
530    * classes can override it by implementing the {@link CustomDuplication}
531    * interface.  This may be necessary for semantic reasons (e.g.
532    * controllers need to recursively duplicate the PRs they contain),
533    * or desirable for performance or memory consumption reasons (e.g. the
534    * behaviour of a DefaultGazetteer can be duplicated by a
535    * SharedDefaultGazetteer that shares the internal data structures).
536    </p>
537    *
538    @param res the resource to duplicate
539    @return an independent duplicate copy of the resource
540    @throws ResourceInstantiationException if an exception occurs while
541    *         constructing the duplicate.
542    */
543   public static Resource duplicate(Resource res)
544           throws ResourceInstantiationException {
545     DuplicationContext ctx = new DuplicationContext();
546     try {
547       return duplicate(res, ctx);
548     }
549     finally {
550       // de-activate the context
551       ctx.active = false;
552     }
553   }
554 
555   /**
556    * Create a duplicate of the given resource, using the provided context.
557    * This method is intended for use by resources that implement the
558    {@link CustomDuplication} interface when they need to duplicate
559    * their child resources.  Calls made to this method outside the scope of
560    * such a {@link CustomDuplication#duplicate CustomDuplication.duplicate}
561    * call will fail with a runtime exception.
562    *
563    @see #duplicate(Resource)
564    @param res the resource to duplicate
565    @param ctx the current context as passed to the
566    *         {@link CustomDuplication#duplicate} method.
567    @return the duplicated resource
568    @throws ResourceInstantiationException if an error occurs while
569    *         constructing the duplicate.
570    */
571   public static Resource duplicate(Resource res,
572           DuplicationContext ctx)
573             throws ResourceInstantiationException {
574     checkDuplicationContext(ctx);
575     // check for null
576     if(res == null) {
577       return null;
578     }
579     // check if we've seen this resource before
580     else if(ctx.knownResources.containsKey(res)) {
581       return ctx.knownResources.get(res);
582     }
583     else {
584       // create the duplicate
585       Resource newRes = null;
586       if(res instanceof CustomDuplication) {
587         // use custom duplicate if available
588         newRes = ((CustomDuplication)res).duplicate(ctx);
589       }
590       else {
591         newRes = defaultDuplicate(res, ctx);
592       }
593       // remember this duplicate in the context
594       ctx.knownResources.put(res, newRes);
595       return newRes;
596     }
597   }
598 
599   /**
600    * Implementation of the default duplication algorithm described
601    * in the comment for {@link #duplicate(Resource)}.  This method is
602    * public for the benefit of resources that implement
603    {@link CustomDuplication} but only need to do some post-processing
604    * after the default duplication algorithm; they can call this method
605    * to obtain an initial duplicate and then post-process it before
606    * returning.  If they need to duplicate child resources they should
607    * call {@link #duplicate(Resource, DuplicationContext)} in the normal
608    * way.  Calls to this method made outside the context of a
609    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
610    * call will fail with a runtime exception.
611    *
612    @param res the resource to duplicate
613    @param ctx the current context
614    @return a duplicate of the given resource, constructed using the
615    *         default algorithm.  In particular, if <code>res</code>
616    *         implements {@link CustomDuplication} its own duplicate method
617    *         will <i>not</i> be called.
618    @throws ResourceInstantiationException if an error occurs
619    *         while duplicating the given resource.
620    */
621   public static Resource defaultDuplicate(Resource res,
622           DuplicationContext ctx)
623             throws ResourceInstantiationException {
624     checkDuplicationContext(ctx);
625     String className = res.getClass().getName();
626     String resName = res.getName();
627 
628     FeatureMap newResFeatures = duplicate(res.getFeatures(), ctx);
629 
630     // get the resource data to extract the parameters
631     ResourceData rData = (ResourceData)Gate.getCreoleRegister().get(className);
632     if(rData == null)
633       throw new ResourceInstantiationException(
634               "Could not find CREOLE data for " + className);
635 
636     ParameterList params = rData.getParameterList();
637     // init parameters
638     FeatureMap initParams = Factory.newFeatureMap();
639     for(List<Parameter> parDisjunction : params.getInitimeParameters()) {
640       for(Parameter p : parDisjunction) {
641         initParams.put(p.getName(), res.getParameterValue(p.getName()));
642       }
643     }
644     // duplicate any Resources in the params map
645     initParams = duplicate(initParams, ctx);
646 
647     // create the new resource
648     Resource newResource = createResource(className, initParams, newResFeatures, resName);
649     if(newResource instanceof ProcessingResource) {
650       // runtime params
651       FeatureMap runtimeParams = Factory.newFeatureMap();
652       for(List<Parameter> parDisjunction : params.getRuntimeParameters()) {
653         for(Parameter p : parDisjunction) {
654           runtimeParams.put(p.getName(), res.getParameterValue(p.getName()));
655         }
656       }
657       // duplicate any Resources in the params map
658       runtimeParams = duplicate(runtimeParams, ctx);
659       newResource.setParameterValues(runtimeParams);
660     }
661 
662     return newResource;
663   }
664 
665   /**
666    * Construct a feature map that is a copy of the one provided except
667    * that any {@link Resource} values in the map are replaced by their
668    * duplicates.  This method is public for the benefit of resources
669    * that implement {@link CustomDuplication} and will fail if called
670    * outside of a
671    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
672    * implementation.
673    *
674    @param fm the feature map to duplicate
675    @param ctx the current context
676    @return a duplicate feature map
677    @throws ResourceInstantiationException if an error occurs while
678    *         duplicating any Resource in the feature map.
679    */
680   public static FeatureMap duplicate(FeatureMap fm,
681           DuplicationContext ctx)
682             throws ResourceInstantiationException {
683     checkDuplicationContext(ctx);
684     FeatureMap newFM = Factory.newFeatureMap();
685     for(Map.Entry<Object, Object> entry : fm.entrySet()) {
686       Object value = entry.getValue();
687       if(value instanceof Resource) {
688         value = duplicate((Resource)value, ctx);
689       }
690       newFM.put(entry.getKey(), value);
691     }
692     return newFM;
693   }
694 
695   /**
696    * Opaque memo object passed to
697    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
698    * methods to encapsulate the state of the current duplication run.
699    * If the duplicate method itself needs to duplicate any objects it
700    * should pass this context back to
701    {@link #duplicate(Resource,DuplicationContext)}.
702    */
703   public static class DuplicationContext {
704     IdentityHashMap<Resource, Resource> knownResources =
705       new IdentityHashMap<Resource, Resource>();
706 
707     /**
708      * Whether this duplication context is part of an active duplicate
709      * call.
710      */
711     boolean active = true;
712 
713     /**
714      * Overridden to ensure no public constructor.
715      */
716     DuplicationContext() {
717     }
718   }
719 
720   /**
721    * Throws an exception if the specified duplication context is
722    * null or not active.  This is to ensure that the Factory
723    * helper methods that take a DuplicationContext parameter can
724    * only be called in the context of a
725    {@link #duplicate(Resource)} call.
726    @param ctx the context to check.
727    @throws NullPointerException if the provided context is null.
728    @throws IllegalStateException if the provided context is not
729    *         active.
730    */
731   protected static void checkDuplicationContext(DuplicationContext ctx) {
732     if(ctx == null) {
733       throw new NullPointerException("No DuplicationContext provided");
734     }
735     if(!ctx.active) {
736       throw new IllegalStateException(
737               new Throwable().getStackTrace()[1].getMethodName()
738               " helper method called outside an active duplicate call");
739     }
740   }
741 
742   static Class japeParserClass = ParseCpsl.class;
743   public static Class getJapeParserClass() {
744       return japeParserClass;
745   }
746   public static void setJapeParserClass(Class newClass) {
747       if (! ParseCpsl.class.isAssignableFrom(newClass))
748           throw new IllegalArgumentException("Parser class must inherit from " + ParseCpsl.class);
749       japeParserClass = newClass;
750   }
751 
752   public static ParseCpsl newJapeParser(java.io.Reader stream, HashMap existingMacros) {
753       try {
754           Constructor c = japeParserClass.getConstructor
755               (new Class[] {java.io.Reader.class, existingMacros.getClass()});
756           return (ParseCpslc.newInstance(new Object[] {stream, existingMacros});
757       catch (NoSuchMethodException e) { // Shouldn't happen
758           throw new RuntimeException(e);
759       catch (IllegalArgumentException e) { // Shouldn't happen
760           throw new RuntimeException(e);
761       catch (InstantiationException e) { // Shouldn't happen
762           throw new RuntimeException(e);
763       catch (IllegalAccessException e) { // Shouldn't happen
764           throw new RuntimeException(e);
765       catch (InvocationTargetException e) { // Happens if the constructor throws an exception
766           throw new RuntimeException(e);
767       }
768   }
769 
770   public static ParseCpsl newJapeParser(URL japeURL, String encodingthrows IOException {
771     // the stripping stream is buffered, no need to buffer the URL stream.
772       java.io.Reader stream = new BomStrippingInputStreamReader(japeURL.openStream(), encoding);
773 
774       ParseCpsl parser = newJapeParser(stream, new HashMap());
775       parser.setBaseURL(japeURL);
776       parser.setEncoding(encoding);
777       return parser;
778   }
779 
780   /**
781    * Active ConstraintFactory for creating and initializing Jape <b>Constraint</b>s.
782    */
783   private static ConstraintFactory japeConstraintFactory = new ConstraintFactory();
784   /**
785    * Return the active {@link ConstraintFactory} for creating and initializing Jape
786    <b>Constraint</b>s.
787    @return
788    */
789   public static ConstraintFactory getConstraintFactory() {
790     return japeConstraintFactory;
791   }
792 
793   /** Create a new FeatureMap. */
794   public static FeatureMap newFeatureMap() {
795     return new SimpleFeatureMapImpl();
796   // newFeatureMap
797 
798   /** Open an existing DataStore. */
799   public static DataStore openDataStore(
800     String dataStoreClassName, String storageUrl
801   throws PersistenceException {
802     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
803     ds.open();
804     if(dsReg.add(ds))
805       creoleProxy.fireDatastoreOpened(
806         new CreoleEvent(ds, CreoleEvent.DATASTORE_OPENED)
807       );
808 
809     return ds;
810   // openDataStore()
811 
812   /** Create a new DataStore and open it. <B>NOTE:</B> for some data stores
813     * creation is an system administrator task; in such cases this
814     * method will throw an UnsupportedOperationException.
815     */
816   public static DataStore createDataStore(
817     String dataStoreClassName, String storageUrl
818   throws PersistenceException, UnsupportedOperationException {
819     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
820     ds.create();
821     ds.open();
822     if(dsReg.add(ds))
823       creoleProxy.fireDatastoreCreated(
824         new CreoleEvent(ds, CreoleEvent.DATASTORE_CREATED)
825       );
826 
827     return ds;
828   // createDataStore()
829 
830   /** Instantiate a DataStore (not open or created). */
831   protected static DataStore instantiateDataStore(
832     String dataStoreClassName, String storageUrl
833   throws PersistenceException {
834     DataStore godfreyTheDataStore = null;
835     try {
836       godfreyTheDataStore =
837         (DataStoreGate.getClassLoader().
838                     loadClass(dataStoreClassName).newInstance();
839     catch(Exception e) {
840       throw new PersistenceException("Couldn't create DS class: " + e);
841     }
842 
843     if(dsReg == null// static init ran before Gate.init....
844       dsReg = Gate.getDataStoreRegister();
845     godfreyTheDataStore.setStorageUrl(storageUrl.toString());
846 
847     return godfreyTheDataStore;
848   // instantiateDS(dataStoreClassName, storageURL)
849 
850   /** Add a listener */
851   public static synchronized void addCreoleListener(CreoleListener l){
852     creoleProxy.addCreoleListener(l);
853   // addCreoleListener(CreoleListener)
854 
855   /** Static initialiser to set up the CreoleProxy event source object */
856   static {
857     creoleProxy = new CreoleProxy();
858     accessControllerPool = new HashMap();
859   // static initialiser
860 
861 
862   /**
863    * Creates and opens a new AccessController (if not available in the pool).
864   */
865   public static synchronized AccessController createAccessController(String jdbcURL)
866     throws PersistenceException {
867 
868     if (false == accessControllerPool.containsKey(jdbcURL)) {
869       AccessController ac = new AccessControllerImpl(jdbcURL);
870       ac.open();
871       accessControllerPool.put(jdbcURL,ac);
872     }
873 
874     return (AccessController)accessControllerPool.get(jdbcURL);
875   // createAccessController()
876 
877 // abstract Factory
878 
879 
880 /**
881  * Factory is basically a collection of static methods but events need to
882  * have as source an object and not a class. The CreolProxy class addresses
883  * this issue acting as source for all events fired by the Factory class.
884  */
885 class CreoleProxy {
886 
887   public synchronized void removeCreoleListener(CreoleListener l) {
888     if (creoleListeners != null && creoleListeners.contains(l)) {
889       Vector v = (VectorcreoleListeners.clone();
890       v.removeElement(l);
891       creoleListeners = v;
892     }// if
893   }// removeCreoleListener(CreoleListener l)
894 
895   public synchronized void addCreoleListener(CreoleListener l) {
896     Vector v =
897       creoleListeners == null new Vector(2(VectorcreoleListeners.clone();
898     if (!v.contains(l)) {
899       v.addElement(l);
900       creoleListeners = v;
901     }// if
902   }// addCreoleListener(CreoleListener l)
903 
904   protected void fireResourceLoaded(CreoleEvent e) {
905     if (creoleListeners != null) {
906       Vector listeners = creoleListeners;
907       int count = listeners.size();
908       for (int i = 0; i < count; i++) {
909         ((CreoleListenerlisteners.elementAt(i)).resourceLoaded(e);
910       }// for
911     }// if
912   }// fireResourceLoaded(CreoleEvent e)
913 
914   protected void fireResourceUnloaded(CreoleEvent e) {
915     if (creoleListeners != null) {
916       Vector listeners = creoleListeners;
917       int count = listeners.size();
918       for (int i = 0; i < count; i++) {
919         ((CreoleListenerlisteners.elementAt(i)).resourceUnloaded(e);
920       }// for
921     }// if
922   }// fireResourceUnloaded(CreoleEvent e)
923 
924   protected void fireDatastoreOpened(CreoleEvent e) {
925     if (creoleListeners != null) {
926       Vector listeners = creoleListeners;
927       int count = listeners.size();
928       for (int i = 0; i < count; i++) {
929         ((CreoleListenerlisteners.elementAt(i)).datastoreOpened(e);
930       }// for
931     }// if
932   }// fireDatastoreOpened(CreoleEvent e)
933 
934   protected void fireDatastoreCreated(CreoleEvent e) {
935     if (creoleListeners != null) {
936       Vector listeners = creoleListeners;
937       int count = listeners.size();
938       for (int i = 0; i < count; i++) {
939         ((CreoleListenerlisteners.elementAt(i)).datastoreCreated(e);
940       }// for
941     }// if
942   }// fireDatastoreCreated(CreoleEvent e)
943 
944   protected void fireDatastoreClosed(CreoleEvent e) {
945     if (creoleListeners != null) {
946       Vector listeners = creoleListeners;
947       int count = listeners.size();
948       for (int i = 0; i < count; i++) {
949         ((CreoleListenerlisteners.elementAt(i)).datastoreClosed(e);
950       }// for
951     }// if
952   }// fireDatastoreClosed(CreoleEvent e)
953 
954   private transient Vector creoleListeners;
955 }//class CreoleProxy