Factory.java
0001 /*
0002  *  Factory.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Hamish Cunningham, 25/May/2000
0013  *
0014  *  $Id: Factory.java 17866 2014-04-18 09:16:19Z markagreenwood $
0015  */
0016 
0017 package gate;
0018 
0019 import gate.Gate.DirectoryInfo;
0020 import gate.annotation.ImmutableAnnotationSetImpl;
0021 import gate.creole.AbstractProcessingResource;
0022 import gate.creole.AbstractResource;
0023 import gate.creole.AnnotationSchema;
0024 import gate.creole.ConditionalController;
0025 import gate.creole.CustomDuplication;
0026 import gate.creole.ParameterException;
0027 import gate.creole.ParameterList;
0028 import gate.creole.ResourceData;
0029 import gate.creole.ResourceInstantiationException;
0030 import gate.creole.gazetteer.Gazetteer;
0031 import gate.event.CreoleEvent;
0032 import gate.event.CreoleListener;
0033 import gate.jape.constraint.ConstraintFactory;
0034 import gate.jape.parser.ParseCpsl;
0035 import gate.persist.PersistenceException;
0036 import gate.persist.SerialDataStore;
0037 import gate.util.BomStrippingInputStreamReader;
0038 import gate.util.Out;
0039 import gate.util.SimpleFeatureMapImpl;
0040 import gate.util.Strings;
0041 
0042 import java.io.IOException;
0043 import java.io.Reader;
0044 import java.io.Serializable;
0045 import java.lang.reflect.Constructor;
0046 import java.lang.reflect.InvocationTargetException;
0047 import java.net.URI;
0048 import java.net.URL;
0049 import java.util.Collection;
0050 import java.util.EventListener;
0051 import java.util.HashMap;
0052 import java.util.IdentityHashMap;
0053 import java.util.Map;
0054 import java.util.Set;
0055 import java.util.Vector;
0056 
0057 import org.apache.log4j.Logger;
0058 
0059 /** Provides static methods for the creation of Resources.
0060   */
0061 public abstract class Factory {
0062   /** Debug flag */
0063   private static final boolean DEBUG = false;
0064   
0065   private static final boolean DEBUG_DUPLICATION = false;
0066   
0067   private static final Logger log = Logger.getLogger(Factory.class);
0068 
0069   /** An object to source events from. */
0070   private static CreoleProxy creoleProxy;
0071 
0072   /** Create an instance of a resource using default parameter values.
0073     @see #createResource(String,FeatureMap)
0074     */
0075   public static Resource createResource(String resourceClassName)
0076   throws ResourceInstantiationException
0077   {
0078     // get the resource metadata
0079     ResourceData resData = Gate.getCreoleRegister().get(resourceClassName);
0080     if(resData == null) {
0081       Set<DirectoryInfo> plugins = Gate.getDirectoryInfo(resourceClassName);
0082       
0083       StringBuilder msg = new StringBuilder();
0084       msg.append("Couldn't get resource data for ").append(resourceClassName).append(".\n\n");
0085             
0086       if (plugins.isEmpty()) {
0087         msg.append("You may need first to load the plugin that contains your resource.\n");
0088         msg.append("For example, to create a gate.creole.tokeniser.DefaultTokeniser\n");
0089         msg.append("you need first to load the ANNIE plugin.\n\n");
0090       }
0091       else if (plugins.size() == 1){
0092         msg.append(resourceClassName).append(" can be found in the ").append(plugins.iterator().next().getName()).append(" plugin\n\n");
0093       }
0094       else {
0095         msg.append(resourceClassName).append(" can be found in the following plugins\n   ");
0096         for (DirectoryInfo dInfo : plugins) {
0097           msg.append(dInfo.getName()).append(", ");
0098         }
0099         
0100         msg.setLength(msg.length()-2);
0101         msg.append("\n\n");
0102       }
0103       
0104       msg.append("Go to the menu File->Manage CREOLE plugins or use the method\n");
0105       msg.append("Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL).");
0106       
0107       throw new ResourceInstantiationException(msg.toString());
0108     }
0109 
0110     // get the parameter list and default values
0111     ParameterList paramList = resData.getParameterList();
0112     FeatureMap parameterValues = null;
0113     try {
0114       parameterValues = paramList.getInitimeDefaults();
0115     catch(ParameterException e) {
0116       throw new ResourceInstantiationException(
0117         "Couldn't get default parameters for " + resourceClassName + ": " + e
0118       );
0119     }
0120 
0121     return createResource(resourceClassName, parameterValues);
0122   // createResource(resClassName)
0123 
0124   /** Create an instance of a resource, and return it.
0125     * Callers of this method are responsible for
0126     * querying the resource's parameter lists, putting together a set that
0127     * is complete apart from runtime parameters, and passing a feature map
0128     * containing these parameter settings.
0129     *
0130     @param resourceClassName the name of the class implementing the resource.
0131     @param parameterValues the feature map containing intialisation time
0132     *   parameterValues for the resource.
0133     @return an instantiated resource.
0134     */
0135   public static Resource createResource(
0136     String resourceClassName, FeatureMap parameterValues
0137   throws ResourceInstantiationException
0138   {
0139     return createResource(resourceClassName, parameterValues, null, null);
0140   // createResource(resClassName, paramVals, listeners)
0141 
0142   /** Create an instance of a resource, and return it.
0143     * Callers of this method are responsible for
0144     * querying the resource's parameter lists, putting together a set that
0145     * is complete apart from runtime parameters, and passing a feature map
0146     * containing these parameter settings.
0147     *
0148     @param resourceClassName the name of the class implementing the resource.
0149     @param parameterValues the feature map containing intialisation time
0150     *   parameterValues for the resource.
0151     @param features the features for the new resource
0152     @return an instantiated resource.
0153     */
0154   public static Resource createResource(
0155     String resourceClassName, FeatureMap parameterValues,
0156     FeatureMap features
0157     throws ResourceInstantiationException
0158    {
0159       return createResource(resourceClassName, parameterValues,
0160                             features, null);
0161    }
0162 
0163   /** Create an instance of a resource, and return it.
0164     * Callers of this method are responsible for
0165     * querying the resource's parameter lists, putting together a set that
0166     * is complete apart from runtime parameters, and passing a feature map
0167     * containing these parameter settings.
0168     *
0169     * In the case of ProcessingResources they will have their runtime parameters
0170     * initialised to their default values.
0171     *
0172     @param resourceClassName the name of the class implementing the resource.
0173     @param parameterValues the feature map containing intialisation time
0174     *   parameterValues for the resource.
0175     @param features the features for the new resource or null to not assign
0176     *   any (new) features.
0177     @param resourceName the name to be given to the resource or null to assign
0178     *   a default name.
0179     @return an instantiated resource.
0180     */
0181   public static Resource createResource(
0182     String resourceClassName, FeatureMap parameterValues,
0183     FeatureMap features, String resourceName
0184   throws ResourceInstantiationException
0185    {
0186     // get the resource metadata
0187     ResourceData resData = Gate.getCreoleRegister().get(resourceClassName);
0188     if(resData == null) {
0189       Set<DirectoryInfo> plugins = Gate.getDirectoryInfo(resourceClassName);
0190       
0191       StringBuilder msg = new StringBuilder();
0192       msg.append("Couldn't get resource data for ").append(resourceClassName).append(".\n\n");
0193             
0194       if (plugins.isEmpty()) {
0195         msg.append("You may need first to load the plugin that contains your resource.\n");
0196         msg.append("For example, to create a gate.creole.tokeniser.DefaultTokeniser\n");
0197         msg.append("you need first to load the ANNIE plugin.\n\n");
0198       }
0199       else if (plugins.size() == 1){
0200         msg.append(resourceClassName).append(" can be found in the ").append(plugins.iterator().next().getName()).append(" plugin\n\n");
0201       }
0202       else {
0203         msg.append(resourceClassName).append(" can be found in the following plugins\n   ");
0204         for (DirectoryInfo dInfo : plugins) {
0205           msg.append(dInfo.getName()).append(", ");
0206         }
0207         
0208         msg.setLength(msg.length()-2);
0209         msg.append("\n\n");
0210       }
0211       
0212       msg.append("Go to the menu File->Manage CREOLE plugins or use the method\n");
0213       msg.append("Gate.getCreoleRegister().registerDirectories(pluginDirectoryURL).");
0214       
0215       throw new ResourceInstantiationException(msg.toString());
0216     }
0217     // get the default implementation class
0218     Class<? extends Resource> resClass = null;
0219     try {
0220       resClass = resData.getResourceClass();
0221     catch(ClassNotFoundException e) {
0222       throw new ResourceInstantiationException(
0223         "Couldn't get resource class from the resource data:"+Strings.getNl()+e
0224       );
0225     }
0226 
0227     //create a pointer for the resource
0228     Resource res = null;
0229 
0230     //if the object is an LR and it should come from a DS then create that way
0231     DataStore dataStore;
0232     if(LanguageResource.class.isAssignableFrom(resClass&&
0233        ((dataStore = (DataStore)parameterValues.
0234                      get(DataStore.DATASTORE_FEATURE_NAME)) != null)
0235       ){
0236       //ask the datastore to create our object
0237       if(dataStore instanceof SerialDataStore) {
0238         // SDS doesn't need a wrapper class; just check for serialisability
0239         if(! Serializable.class.isAssignableFrom(resClass))
0240           throw new ResourceInstantiationException(
0241             "Resource cannot be (de-)serialized: " + resClass.getName()
0242           );
0243       }
0244 
0245       // get the datastore instance id and retrieve the resource
0246       Object instanceId = parameterValues.get(DataStore.LR_ID_FEATURE_NAME);
0247       if(instanceId == null)
0248         throw new
0249           ResourceInstantiationException("No instance id for " + resClass);
0250       try {
0251         res = dataStore.getLr(resClass.getName(), instanceId);
0252       catch(PersistenceException pe) {
0253         throw new ResourceInstantiationException("Bad read from DB: " + pe);
0254       catch(SecurityException se) {
0255         throw new ResourceInstantiationException("Insufficient permissions: " + se);
0256       }
0257       resData.addInstantiation(res);
0258       if(features != null){
0259         if(res.getFeatures() == null){
0260           res.setFeatures(newFeatureMap());
0261         }
0262         res.getFeatures().putAll(features);
0263       }
0264 
0265       //set the name
0266       if(res.getName() == null){
0267         res.setName(resourceName == null ?
0268                     resData.getName() "_" + Gate.genSym() :
0269                     resourceName);
0270       }
0271 
0272       // fire the event
0273       creoleProxy.fireResourceLoaded(
0274         new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
0275       );
0276 
0277       return res;
0278     }
0279 
0280     //The resource is not a persistent LR; use a constructor
0281 
0282     // create an object using the resource's default constructor
0283     try {
0284       if(DEBUGOut.prln("Creating resource " + resClass.getName());
0285       res = resClass.newInstance();
0286     catch(IllegalAccessException e) {
0287       throw new ResourceInstantiationException(
0288         "Couldn't create resource instance, access denied: " + e
0289       );
0290     catch(InstantiationException e) {
0291       throw new ResourceInstantiationException(
0292         "Couldn't create resource instance due to newInstance() failure: " + e
0293       );
0294     }
0295 
0296 
0297     if(LanguageResource.class.isAssignableFrom(resClass)) {
0298       // type-specific stuff for LRs
0299       if(DEBUGOut.prln(resClass.getName() " is a LR");
0300     else if(ProcessingResource.class.isAssignableFrom(resClass)) {
0301       // type-specific stuff for PRs
0302       if(DEBUGOut.prln(resClass.getName() " is a PR");
0303       //set the runtime parameters to their defaults
0304       try{
0305         FeatureMap parameters = newFeatureMap();
0306         parameters.putAll(resData.getParameterList().getRuntimeDefaults());
0307         res.setParameterValues(parameters);
0308       }catch(ParameterException pe){
0309         throw new ResourceInstantiationException(
0310                   "Could not set the runtime parameters " +
0311                   "to their default values for: " + res.getClass().getName() +
0312                   " :\n" + pe.toString()
0313                   );
0314       }
0315     // type-specific stuff for VRs
0316     else if(VisualResource.class.isAssignableFrom(resClass)) {
0317       if(DEBUGOut.prln(resClass.getName() " is a VR");
0318     else if(Controller.class.isAssignableFrom(resClass)){
0319       //type specific stuff for Controllers
0320     }
0321 
0322 
0323     //set the parameterValues of the resource
0324     try{
0325       FeatureMap parameters = newFeatureMap();
0326       //put the defaults
0327       parameters.putAll(resData.getParameterList().getInitimeDefaults());
0328       //overwrite the defaults with the user provided values
0329       parameters.putAll(parameterValues);
0330       res.setParameterValues(parameters);
0331     }catch(ParameterException pe){
0332         throw new ResourceInstantiationException(
0333                     "Could not set the init parameters for: " +
0334                     res.getClass().getName() " :\n" + pe.toString()
0335                   );
0336     }
0337 
0338     //set the name
0339     // if we have an explicitly provided name, use that, otherwise generate a
0340     // suitable name if the resource doesn't already have one
0341     if(resourceName != null && resourceName.trim().length() 0) {
0342       res.setName(resourceName);
0343     }
0344     else if(res.getName() == null) {
0345       //no name provided, and the resource doesn't have a name already (e.g. calculated in init())
0346       // -> let's try and find a reasonable one
0347       try{
0348         //first try to get a filename from the various parameters
0349         URL sourceUrl = null;
0350         if(res instanceof SimpleDocument){
0351           sourceUrl = ((SimpleDocument)res).getSourceUrl();
0352         else if(res instanceof AnnotationSchema){
0353           sourceUrl = ((AnnotationSchema)res).getXmlFileUrl();
0354         else if(res.getClass().getName().startsWith("gate.creole.ontology.owlim.")){
0355           // get the name for the OWLIM2 ontology LR
0356           java.lang.reflect.Method m = resClass.getMethod("getRdfXmlURL");
0357           sourceUrl = (java.net.URL)m.invoke(res);
0358           if(sourceUrl == null){
0359             m = resClass.getMethod("getN3URL");
0360             sourceUrl = (java.net.URL)m.invoke(res);
0361           }
0362           if(sourceUrl == null){
0363             m = resClass.getMethod("getNtriplesURL");
0364             sourceUrl = (java.net.URL)m.invoke(res);
0365           }
0366           if(sourceUrl == null){
0367             m = resClass.getMethod("getTurtleURL");
0368             sourceUrl = (java.net.URL)m.invoke(res);
0369           }
0370         else if (res.getClass().getName().startsWith("gate.creole.ontology.impl.")) {
0371           java.lang.reflect.Method m = resClass.getMethod("getSourceURL");
0372           sourceUrl = (java.net.URL)m.invoke(res);
0373         }
0374         if(sourceUrl != null){
0375           URI sourceURI = sourceUrl.toURI();
0376           resourceName = sourceURI.getPath().trim();
0377           if(resourceName == null ||
0378              resourceName.length() == ||
0379              resourceName.equals("/")){
0380             //this URI has no path -> use the whole string
0381             resourceName = sourceURI.toString();
0382           }else{
0383             //there is a significant path value -> get the last element
0384             int lastSlash = resourceName.lastIndexOf('/');
0385             if(lastSlash >=0){
0386               String subStr = resourceName.substring(lastSlash + 1);
0387               if(subStr.trim().length() 0resourceName = subStr;
0388             }
0389           }
0390         }
0391       catch (RuntimeException t) {
0392         //even runtime exceptions are safe to ignore at this point
0393       catch(Exception t) {
0394         //there were problems while trying to guess a name
0395         //we can safely ignore them
0396       finally{
0397         //make sure there is a name provided, whatever happened
0398         if(resourceName == null || resourceName.trim().length() == 0){
0399           resourceName = resData.getName();
0400         }
0401       }
0402       resourceName += "_" + Gate.genSym();
0403       res.setName(resourceName);
0404     // else if(res.getName() == null)
0405     // if res.getName() != null, leave it as it is
0406 
0407     Map<String, EventListener> listeners = new HashMap<String, EventListener>(gate.Gate.getListeners());
0408     // set the listeners if any
0409     if(listeners != null && !listeners.isEmpty()) {
0410       try {
0411         if(DEBUGOut.prln("Setting the listeners for  " + res.toString());
0412         AbstractResource.setResourceListeners(res, listeners);
0413       catch(Exception e) {
0414         if(DEBUGOut.prln("Failed to set listeners for " + res.toString());
0415         throw new
0416           ResourceInstantiationException("Parameterisation failure" + e);
0417       }
0418     }
0419 
0420     // if the features of the resource have not been explicitly set,
0421     // set them to the features of the resource data
0422     if(res.getFeatures() == null || res.getFeatures().isEmpty()){
0423       FeatureMap fm = newFeatureMap();
0424       fm.putAll(resData.getFeatures());
0425       res.setFeatures(fm);
0426     }
0427     // add the features specified by the user
0428     if(features != nullres.getFeatures().putAll(features);
0429 
0430     // initialise the resource
0431     if(DEBUGOut.prln("Initialising resource " + res.toString());
0432     res = res.init();
0433 
0434     // remove the listeners if any
0435     if(listeners != null && !listeners.isEmpty()) {
0436       try {
0437         if(DEBUGOut.prln("Removing the listeners for  " + res.toString());
0438         AbstractResource.removeResourceListeners(res, listeners);
0439       catch(Exception e) {
0440         if (DEBUGOut.prln(
0441           "Failed to remove the listeners for " + res.toString()
0442         );
0443         throw new
0444           ResourceInstantiationException("Parameterisation failure" + e);
0445       }
0446     }
0447     // record the instantiation on the resource data's stack
0448     resData.addInstantiation(res);
0449     // fire the event
0450     creoleProxy.fireResourceLoaded(
0451       new CreoleEvent(res, CreoleEvent.RESOURCE_LOADED)
0452     );
0453     return res;
0454   // create(resourceClassName, parameterValues, features, listeners)
0455 
0456   /** Delete an instance of a resource. This involves removing it from
0457     * the stack of instantiations maintained by this resource type's
0458     * resource data. Deletion does not guarantee that the resource will
0459     * become a candidate for garbage collection, just that the GATE framework
0460     * is no longer holding references to the resource.
0461     *
0462     @param resource the resource to be deleted.
0463     */
0464   public static void deleteResource(Resource resource) {
0465     ResourceData rd =
0466       Gate.getCreoleRegister().get(resource.getClass().getName());
0467     if(rd!= null && rd.removeInstantiation(resource)) {
0468       creoleProxy.fireResourceUnloaded(
0469         new CreoleEvent(resource, CreoleEvent.RESOURCE_UNLOADED)
0470       );
0471       resource.cleanup();
0472     }
0473   // deleteResource
0474 
0475   /** Create a new transient Corpus. */
0476   public static Corpus newCorpus(String name)
0477                                           throws ResourceInstantiationException
0478   {
0479     return (CorpuscreateResource("gate.corpora.CorpusImpl", newFeatureMap(), newFeatureMap(), name);
0480   // newCorpus
0481 
0482   /** Create a new transient Document from a URL. */
0483   public static Document newDocument(URL sourceUrl)
0484                                           throws ResourceInstantiationException
0485   {
0486     FeatureMap parameterValues = newFeatureMap();
0487     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
0488     return
0489       (DocumentcreateResource("gate.corpora.DocumentImpl", parameterValues);
0490   // newDocument(URL)
0491 
0492   /** Create a new transient Document from a URL and an encoding. */
0493   public static Document newDocument(URL sourceUrl, String encoding)
0494                                           throws ResourceInstantiationException
0495   {
0496     FeatureMap parameterValues = newFeatureMap();
0497     parameterValues.put(Document.DOCUMENT_URL_PARAMETER_NAME, sourceUrl);
0498     parameterValues.put(Document.DOCUMENT_ENCODING_PARAMETER_NAME, encoding);
0499     return
0500       (DocumentcreateResource("gate.corpora.DocumentImpl", parameterValues);
0501   // newDocument(URL)
0502 
0503   /** Create a new transient textual Document from a string. */
0504   public static Document newDocument(String content)
0505                                           throws ResourceInstantiationException
0506   {
0507     FeatureMap params = newFeatureMap();
0508     params.put(Document.DOCUMENT_STRING_CONTENT_PARAMETER_NAME, content);
0509     Document doc =
0510       (DocumentcreateResource("gate.corpora.DocumentImpl", params);
0511 /*
0512     // laziness: should fit this into createResource by adding a new
0513     // document parameter, but haven't time right now...
0514     doc.setContent(new DocumentContentImpl(content));
0515 */
0516     // various classes are in the habit of assuming that a document
0517     // inevitably has a source URL...  so give it a dummy one
0518 /*    try {
0519       doc.setSourceUrl(new URL("http://localhost/"));
0520     } catch(MalformedURLException e) {
0521       throw new ResourceInstantiationException(
0522         "Couldn't create dummy URL in newDocument(String): " + e
0523       );
0524     }
0525 */
0526     doc.setSourceUrl(null);
0527     return doc;
0528   // newDocument(String)
0529 
0530   
0531   /**
0532    * Utility method to create an immutable annotation set. If the provided 
0533    * collection of annotations is <code>null</null>, the newly created set will
0534    * be empty.
0535    @param document the document this set belongs to. 
0536    @param annotations the set of annotations that should be contained in the
0537    * returned {@link AnnotationSet}
0538    @return an {@link AnnotationSet} that throws exceptions on all attempts 
0539    * to modify it.
0540    */
0541   public static AnnotationSet createImmutableAnnotationSet(Document document, 
0542           Collection<Annotation> annotations) {
0543     return new ImmutableAnnotationSetImpl(document, annotations);
0544   }
0545   
0546   /**
0547    <p>
0548    * Create a <i>duplicate</i> of the given resource.  A duplicate is a
0549    * an independent copy of the resource that has the same name and the
0550    * same behaviour.  It does <i>not necessarily</i> have the same concrete
0551    * class as the original, but if the original resource implements any of
0552    * the following interfaces then the duplicate can be assumed to
0553    * implement the same ones:
0554    </p>
0555    <ul>
0556    <li>{@link ProcessingResource}</li>
0557    <li>{@link LanguageAnalyser}</li>
0558    <li>{@link Controller}</li>
0559    <li>{@link CorpusController}</li>
0560    <li>{@link ConditionalController}</li>
0561    <li>{@link Gazetteer}</li>
0562    <li>{@link LanguageResource}</li>
0563    <li>{@link gate.creole.ontology.Ontology}</li>
0564    <li>{@link Document}</li>
0565    <li>{@link Corpus}</li>
0566    </ul>
0567    <p>
0568    * The default duplication algorithm simply calls
0569    {@link #createResource(String, FeatureMap, FeatureMap, String) createResource}
0570    * with the type and name of the original resource, and with parameters
0571    * and features which are copies of those from the original resource,
0572    * but any Resource values in the maps will themselves be duplicated.
0573    * A context is passed around all the duplicate calls that stem from the
0574    * same call to this method so that if the same resource is referred to
0575    * in different places, the same duplicate can be used in the
0576    * corresponding places in the duplicated object graph.
0577    </p>
0578    <p>
0579    * This default behaviour is sufficient for most resource types (and
0580    * is roughly the equivalent of saving the resource's state using the
0581    * persistence manager and then reloading it), but individual resource
0582    * classes can override it by implementing the {@link CustomDuplication}
0583    * interface.  This may be necessary for semantic reasons (e.g.
0584    * controllers need to recursively duplicate the PRs they contain),
0585    * or desirable for performance or memory consumption reasons (e.g. the
0586    * behaviour of a DefaultGazetteer can be duplicated by a
0587    * SharedDefaultGazetteer that shares the internal data structures).
0588    </p>
0589    *
0590    @param res the resource to duplicate
0591    @return an independent duplicate copy of the resource
0592    @throws ResourceInstantiationException if an exception occurs while
0593    *         constructing the duplicate.
0594    */
0595   public static Resource duplicate(Resource res)
0596           throws ResourceInstantiationException {
0597     DuplicationContext ctx = new DuplicationContext();
0598     try {
0599       return duplicate(res, ctx);
0600     }
0601     finally {
0602       // de-activate the context
0603       ctx.active = false;
0604     }
0605   }
0606 
0607   private static long dupIndex = 0;
0608   
0609   /**
0610    * Create a duplicate of the given resource, using the provided context.
0611    * This method is intended for use by resources that implement the
0612    {@link CustomDuplication} interface when they need to duplicate
0613    * their child resources.  Calls made to this method outside the scope of
0614    * such a {@link CustomDuplication#duplicate CustomDuplication.duplicate}
0615    * call will fail with a runtime exception.
0616    *
0617    @see #duplicate(Resource)
0618    @param res the resource to duplicate
0619    @param ctx the current context as passed to the
0620    *         {@link CustomDuplication#duplicate} method.
0621    @return the duplicated resource
0622    @throws ResourceInstantiationException if an error occurs while
0623    *         constructing the duplicate.
0624    */
0625   public static Resource duplicate(Resource res,
0626           DuplicationContext ctx)
0627             throws ResourceInstantiationException {
0628     long myDupIndex = -1, startTime = -1;
0629     if(DEBUG_DUPLICATION) {
0630       myDupIndex = dupIndex++;
0631       log.debug(myDupIndex + ": Duplicating \"" ((res == null"null" : res.getName())
0632           "\" (a " ((res == null"null" : res.getClass().getName()) ")");
0633       startTime = System.currentTimeMillis();
0634     }
0635     try {
0636       checkDuplicationContext(ctx);
0637       // check for null
0638       if(res == null) {
0639         return null;
0640       }
0641       // check if we've seen this resource before
0642       else if(ctx.knownResources.containsKey(res)) {
0643         if(DEBUG_DUPLICATION) {
0644           log.debug(myDupIndex + ": Resource already duplicated in context");
0645         }
0646         return ctx.knownResources.get(res);
0647       }
0648       else {
0649         // create the duplicate
0650         Resource newRes = null;
0651         if(res instanceof CustomDuplication) {
0652           // use custom duplicate if available
0653           newRes = ((CustomDuplication)res).duplicate(ctx);
0654         }
0655         else {
0656           newRes = defaultDuplicate(res, ctx);
0657         }
0658         // remember this duplicate in the context
0659         ctx.knownResources.put(res, newRes);
0660         return newRes;
0661       }
0662     finally {
0663       if(DEBUG_DUPLICATION) {
0664         log.debug(myDupIndex + ": Duplication took " (System.currentTimeMillis() - startTime" ms");
0665       }
0666     }
0667   }
0668 
0669   /**
0670    * Implementation of the default duplication algorithm described
0671    * in the comment for {@link #duplicate(Resource)}.  This method is
0672    * public for the benefit of resources that implement
0673    {@link CustomDuplication} but only need to do some post-processing
0674    * after the default duplication algorithm; they can call this method
0675    * to obtain an initial duplicate and then post-process it before
0676    * returning.  If they need to duplicate child resources they should
0677    * call {@link #duplicate(Resource, DuplicationContext)} in the normal
0678    * way.  Calls to this method made outside the context of a
0679    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
0680    * call will fail with a runtime exception.
0681    *
0682    @param res the resource to duplicate
0683    @param ctx the current context
0684    @return a duplicate of the given resource, constructed using the
0685    *         default algorithm.  In particular, if <code>res</code>
0686    *         implements {@link CustomDuplication} its own duplicate method
0687    *         will <i>not</i> be called.
0688    @throws ResourceInstantiationException if an error occurs
0689    *         while duplicating the given resource.
0690    */
0691   public static Resource defaultDuplicate(Resource res,
0692           DuplicationContext ctx)
0693             throws ResourceInstantiationException {
0694     checkDuplicationContext(ctx);
0695     String className = res.getClass().getName();
0696     ResourceData resData = Gate.getCreoleRegister().get(className);
0697     if(resData == null) {
0698       throw new ResourceInstantiationException(
0699           "Could not find CREOLE data for " + className);
0700     }
0701     String resName = res.getName();
0702 
0703     FeatureMap newResFeatures = duplicate(res.getFeatures(), ctx);
0704 
0705     // init parameters
0706     FeatureMap initParams = AbstractResource.getInitParameterValues(res);
0707     // remove parameters that are also sharable properties
0708     for(String propName : resData.getSharableProperties()) {
0709       initParams.remove(propName);
0710     }
0711     // duplicate any Resources in the params map (excluding sharable ones)
0712     initParams = duplicate(initParams, ctx);
0713     // add sharable properties to the params map (unduplicated).  Some of these
0714     // may be registered parameters but others may not be.
0715     for(String propName : resData.getSharableProperties()) {
0716       initParams.put(propName, res.getParameterValue(propName));
0717     }
0718 
0719     // create the new resource
0720     Resource newResource = createResource(className, initParams, newResFeatures, resName);
0721     if(newResource instanceof ProcessingResource) {
0722       // runtime params
0723       FeatureMap runtimeParams = AbstractProcessingResource.getRuntimeParameterValues(res);
0724       // remove parameters that are also sharable properties
0725       for(String propName : resData.getSharableProperties()) {
0726         runtimeParams.remove(propName);
0727       }
0728       // duplicate any Resources in the params map (excluding sharable ones)
0729       runtimeParams = duplicate(runtimeParams, ctx);
0730       // do not need to add sharable properties here, they have already
0731       // been injected by createResource
0732       
0733       newResource.setParameterValues(runtimeParams);
0734     }
0735 
0736     return newResource;
0737   }
0738 
0739   /**
0740    * Construct a feature map that is a copy of the one provided except
0741    * that any {@link Resource} values in the map are replaced by their
0742    * duplicates.  This method is public for the benefit of resources
0743    * that implement {@link CustomDuplication} and will fail if called
0744    * outside of a
0745    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
0746    * implementation.
0747    *
0748    @param fm the feature map to duplicate
0749    @param ctx the current context
0750    @return a duplicate feature map
0751    @throws ResourceInstantiationException if an error occurs while
0752    *         duplicating any Resource in the feature map.
0753    */
0754   public static FeatureMap duplicate(FeatureMap fm,
0755           DuplicationContext ctx)
0756             throws ResourceInstantiationException {
0757     checkDuplicationContext(ctx);
0758     FeatureMap newFM = Factory.newFeatureMap();
0759     for(Map.Entry<Object, Object> entry : fm.entrySet()) {
0760       Object value = entry.getValue();
0761       if(value instanceof Resource) {
0762         value = duplicate((Resource)value, ctx);
0763       }
0764       newFM.put(entry.getKey(), value);
0765     }
0766     return newFM;
0767   }
0768 
0769   /**
0770    * Opaque memo object passed to
0771    {@link CustomDuplication#duplicate CustomDuplication.duplicate}
0772    * methods to encapsulate the state of the current duplication run.
0773    * If the duplicate method itself needs to duplicate any objects it
0774    * should pass this context back to
0775    {@link #duplicate(Resource,DuplicationContext)}.
0776    */
0777   public static class DuplicationContext {
0778     IdentityHashMap<Resource, Resource> knownResources =
0779       new IdentityHashMap<Resource, Resource>();
0780 
0781     /**
0782      * Whether this duplication context is part of an active duplicate
0783      * call.
0784      */
0785     boolean active = true;
0786 
0787     /**
0788      * Overridden to ensure no public constructor.
0789      */
0790     DuplicationContext() {
0791     }
0792   }
0793 
0794   /**
0795    * Throws an exception if the specified duplication context is
0796    * null or not active.  This is to ensure that the Factory
0797    * helper methods that take a DuplicationContext parameter can
0798    * only be called in the context of a
0799    {@link #duplicate(Resource)} call.
0800    @param ctx the context to check.
0801    @throws NullPointerException if the provided context is null.
0802    @throws IllegalStateException if the provided context is not
0803    *         active.
0804    */
0805   protected static void checkDuplicationContext(DuplicationContext ctx) {
0806     if(ctx == null) {
0807       throw new NullPointerException("No DuplicationContext provided");
0808     }
0809     if(!ctx.active) {
0810       throw new IllegalStateException(
0811               new Throwable().getStackTrace()[1].getMethodName()
0812               " helper method called outside an active duplicate call");
0813     }
0814   }
0815 
0816   static Class<? extends ParseCpsl> japeParserClass = ParseCpsl.class;
0817   public static Class<? extends ParseCpsl> getJapeParserClass() {
0818       return japeParserClass;
0819   }
0820 
0821   public static void setJapeParserClass(Class<? extends ParseCpsl> newClass) {
0822       japeParserClass = newClass;
0823   }
0824 
0825   public static ParseCpsl newJapeParser(java.io.Reader stream, Map<String,Object> existingMacros) {
0826       try {
0827           Constructor<? extends ParseCpsl> c = japeParserClass.getConstructor
0828               (new Class<?>[] {Reader.class, Map.class});
0829           return c.newInstance(new Object[] {stream, existingMacros});
0830       catch (NoSuchMethodException e) { // Shouldn't happen
0831           throw new RuntimeException(e);
0832       catch (IllegalArgumentException e) { // Shouldn't happen
0833           throw new RuntimeException(e);
0834       catch (InstantiationException e) { // Shouldn't happen
0835           throw new RuntimeException(e);
0836       catch (IllegalAccessException e) { // Shouldn't happen
0837           throw new RuntimeException(e);
0838       catch (InvocationTargetException e) { // Happens if the constructor throws an exception
0839           throw new RuntimeException(e);
0840       }
0841   }
0842 
0843   public static ParseCpsl newJapeParser(URL japeURL, String encodingthrows IOException {
0844     // the stripping stream is buffered, no need to buffer the URL stream.
0845       java.io.Reader stream = new BomStrippingInputStreamReader(japeURL.openStream(), encoding);
0846 
0847       ParseCpsl parser = newJapeParser(stream, new HashMap<String,Object>());
0848       parser.setBaseURL(japeURL);
0849       parser.setEncoding(encoding);
0850       return parser;
0851   }
0852 
0853   /**
0854    * Active ConstraintFactory for creating and initializing Jape <b>Constraint</b>s.
0855    */
0856   private static ConstraintFactory japeConstraintFactory = new ConstraintFactory();
0857 
0858   /**
0859    * Return the active {@link ConstraintFactory} for creating and initializing Jape
0860    <b>Constraint</b>s.
0861    */
0862   public static ConstraintFactory getConstraintFactory() {
0863     return japeConstraintFactory;
0864   }
0865 
0866   /** Create a new FeatureMap. */
0867   public static FeatureMap newFeatureMap() {
0868     return new SimpleFeatureMapImpl();
0869   // newFeatureMap
0870 
0871   /** Open an existing DataStore. */
0872   public static DataStore openDataStore(
0873     String dataStoreClassName, String storageUrl
0874   throws PersistenceException {
0875     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
0876     ds.open();
0877     if(Gate.getDataStoreRegister().add(ds))
0878       creoleProxy.fireDatastoreOpened(
0879         new CreoleEvent(ds, CreoleEvent.DATASTORE_OPENED)
0880       );
0881 
0882     return ds;
0883   // openDataStore()
0884 
0885   /** Create a new DataStore and open it. <B>NOTE:</B> for some data stores
0886     * creation is an system administrator task; in such cases this
0887     * method will throw an UnsupportedOperationException.
0888     */
0889   public static DataStore createDataStore(
0890     String dataStoreClassName, String storageUrl
0891   throws PersistenceException, UnsupportedOperationException {
0892     DataStore ds = instantiateDataStore(dataStoreClassName, storageUrl);
0893     ds.create();
0894     ds.open();
0895     if(Gate.getDataStoreRegister().add(ds))
0896       creoleProxy.fireDatastoreCreated(
0897         new CreoleEvent(ds, CreoleEvent.DATASTORE_CREATED)
0898       );
0899 
0900     return ds;
0901   // createDataStore()
0902 
0903   /** Instantiate a DataStore (not open or created). */
0904   protected static DataStore instantiateDataStore(
0905     String dataStoreClassName, String storageUrl
0906   throws PersistenceException {
0907     DataStore godfreyTheDataStore = null;
0908     try {
0909       godfreyTheDataStore =
0910         (DataStoreGate.getClassLoader().
0911                     loadClass(dataStoreClassName).newInstance();
0912     catch(Exception e) {
0913       throw new PersistenceException("Couldn't create DS class: " + e);
0914     }
0915 
0916     godfreyTheDataStore.setStorageUrl(storageUrl.toString());
0917 
0918     return godfreyTheDataStore;
0919   // instantiateDS(dataStoreClassName, storageURL)
0920 
0921   /** Add a listener */
0922   public static synchronized void addCreoleListener(CreoleListener l){
0923     creoleProxy.addCreoleListener(l);
0924   // addCreoleListener(CreoleListener)
0925 
0926   /** Static initialiser to set up the CreoleProxy event source object */
0927   static {
0928     creoleProxy = new CreoleProxy();
0929   // static initialiser
0930 
0931 // abstract Factory
0932 
0933 
0934 /**
0935  * Factory is basically a collection of static methods but events need to
0936  * have as source an object and not a class. The CreolProxy class addresses
0937  * this issue acting as source for all events fired by the Factory class.
0938  */
0939 class CreoleProxy {
0940 
0941   public synchronized void removeCreoleListener(CreoleListener l) {
0942     if (creoleListeners != null && creoleListeners.contains(l)) {
0943       @SuppressWarnings("unchecked")
0944       Vector<CreoleListener> v = (Vector<CreoleListener>creoleListeners.clone();
0945       v.removeElement(l);
0946       creoleListeners = v;
0947     }// if
0948   }// removeCreoleListener(CreoleListener l)
0949 
0950   public synchronized void addCreoleListener(CreoleListener l) {
0951     @SuppressWarnings("unchecked")
0952     Vector<CreoleListener> v =
0953       creoleListeners == null new Vector<CreoleListener>(2(Vector<CreoleListener>creoleListeners.clone();
0954     if (!v.contains(l)) {
0955       v.addElement(l);
0956       creoleListeners = v;
0957     }// if
0958   }// addCreoleListener(CreoleListener l)
0959 
0960   protected void fireResourceLoaded(CreoleEvent e) {
0961     if (creoleListeners != null) {
0962       int count = creoleListeners.size();
0963       for (int i = 0; i < count; i++) {
0964         creoleListeners.elementAt(i).resourceLoaded(e);
0965       }// for
0966     }// if
0967   }// fireResourceLoaded(CreoleEvent e)
0968 
0969   protected void fireResourceUnloaded(CreoleEvent e) {
0970     if (creoleListeners != null) {
0971       int count = creoleListeners.size();
0972       for (int i = 0; i < count; i++) {
0973         creoleListeners.elementAt(i).resourceUnloaded(e);
0974       }// for
0975     }// if
0976   }// fireResourceUnloaded(CreoleEvent e)
0977 
0978   protected void fireDatastoreOpened(CreoleEvent e) {
0979     if (creoleListeners != null) {
0980       int count = creoleListeners.size();
0981       for (int i = 0; i < count; i++) {
0982         creoleListeners.elementAt(i).datastoreOpened(e);
0983       }// for
0984     }// if
0985   }// fireDatastoreOpened(CreoleEvent e)
0986 
0987   protected void fireDatastoreCreated(CreoleEvent e) {
0988     if (creoleListeners != null) {
0989       int count = creoleListeners.size();
0990       for (int i = 0; i < count; i++) {
0991         creoleListeners.elementAt(i).datastoreCreated(e);
0992       }// for
0993     }// if
0994   }// fireDatastoreCreated(CreoleEvent e)
0995 
0996   protected void fireDatastoreClosed(CreoleEvent e) {
0997     if (creoleListeners != null) {
0998       int count = creoleListeners.size();
0999       for (int i = 0; i < count; i++) {
1000         creoleListeners.elementAt(i).datastoreClosed(e);
1001       }// for
1002     }// if
1003   }// fireDatastoreClosed(CreoleEvent e)
1004 
1005   private transient Vector<CreoleListener> creoleListeners;
1006 }//class CreoleProxy