PersistenceManager.java
0001 /*
0002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
0003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004  *
0005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0006  *  software, licenced under the GNU Library General Public License,
0007  *  Version 2, June 1991 (in the distribution as file licence.html,
0008  *  and also available at http://gate.ac.uk/gate/licence.html).
0009  *
0010  *  Valentin Tablan 25/10/2001
0011  *
0012  *  $Id: PersistenceManager.java 13597 2011-04-05 01:22:52Z johann_p $
0013  *
0014  */
0015 package gate.util.persistence;
0016 
0017 import com.thoughtworks.xstream.XStream;
0018 import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
0019 import com.thoughtworks.xstream.converters.reflection.Sun14ReflectionProvider;
0020 import com.thoughtworks.xstream.converters.reflection.XStream12FieldKeySorter;
0021 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
0022 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
0023 import com.thoughtworks.xstream.io.xml.*;
0024 
0025 import java.io.*;
0026 import java.net.*;
0027 import java.text.NumberFormat;
0028 import java.util.*;
0029 
0030 import javax.xml.stream.*;
0031 
0032 import org.apache.log4j.Logger;
0033 
0034 import gate.*;
0035 import gate.creole.*;
0036 import gate.event.ProgressListener;
0037 import gate.event.StatusListener;
0038 import gate.persist.GateAwareObjectInputStream;
0039 import gate.persist.PersistenceException;
0040 import gate.util.*;
0041 
0042 /**
0043  * This class provides utility methods for saving resources through
0044  * serialisation via static methods.
0045  *
0046  * It now supports both native and xml serialization.
0047  */
0048 public class PersistenceManager {
0049 
0050   private static final boolean DEBUG = false;
0051 
0052   /**
0053    * A reference to an object; it uses the identity hashcode and the
0054    * equals defined by object identity. These values will be used as
0055    * keys in the {link #existingPersistentReplacements} map.
0056    */
0057   static protected class ObjectHolder {
0058     ObjectHolder(Object target) {
0059       this.target = target;
0060     }
0061 
0062     public int hashCode() {
0063       return System.identityHashCode(target);
0064     }
0065 
0066     public boolean equals(Object obj) {
0067       if(obj instanceof ObjectHolder)
0068         return ((ObjectHolder)obj).target == this.target;
0069       else return false;
0070     }
0071 
0072     public Object getTarget() {
0073       return target;
0074     }
0075 
0076     private Object target;
0077   }// static class ObjectHolder{
0078 
0079   /**
0080    * This class is used as a marker for types that should NOT be
0081    * serialised when saving the state of a gate object. Registering this
0082    * type as the persistent equivalent for a specific class (via
0083    {@link PersistenceManager#registerPersistentEquivalent(Class , Class)})
0084    * effectively stops all values of the specified type from being
0085    * serialised.
0086    *
0087    * Maps that contain values that should not be serialised will have
0088    * that entry removed. In any other places where such values occur
0089    * they will be replaced by null after deserialisation.
0090    */
0091   public static class SlashDevSlashNull implements Persistence {
0092     /**
0093      * Does nothing
0094      */
0095     public void extractDataFromSource(Object source)
0096             throws PersistenceException {
0097     }
0098 
0099     /**
0100      * Returns null
0101      */
0102     public Object createObject() throws PersistenceException,
0103             ResourceInstantiationException {
0104       return null;
0105     }
0106 
0107     static final long serialVersionUID = -8665414981783519937L;
0108   }
0109 
0110   /**
0111    * URLs get upset when serialised and deserialised so we need to
0112    * convert them to strings for storage. In the case of
0113    * "file:" URLs the relative path to the persistence file
0114    * will actually be stored, except when the URL refers to a resource
0115    * within the current GATE home directory in which case the relative path
0116    * to the GATE home directory will be stored. In that case a warning
0117    * is issued to
0118    */
0119   public static class URLHolder implements Persistence {
0120     /**
0121      * Populates this Persistence with the data that needs to be stored
0122      * from the original source object.
0123      */
0124     public void extractDataFromSource(Object source)
0125             throws PersistenceException {
0126       final Logger logger = Logger.getLogger(URLHolder.class);
0127       try {
0128         URL url = (URL)source;
0129         if(url.getProtocol().equals("file")) {
0130           try {
0131             String pathMarker = relativePathMarker;
0132             File gateCanonicalHome = null;
0133             URL urlPersistenceFilePath = currentPersistenceFile().toURI().toURL();
0134             // try to get the canonical representation of the file both URLs
0135             // so that the relative paths are not getting confused by symbolic
0136             // links. If this fails, just proceed with the original URLs
0137             try {
0138               urlPersistenceFilePath =
0139                 currentPersistenceFile().getCanonicalFile().toURI().toURL();
0140               url =
0141                 Files.fileFromURL(url).getCanonicalFile().toURI().toURL();
0142             catch (IOException ex) {
0143               // ignore
0144             }
0145             
0146             // if the persistence file does NOT reside in the GATE home
0147             // tree and if the URL references something in the GATE home
0148             // tree, use $gatehome$ instead of $relpath$
0149             if(currentUseGateHome() || currentWarnAboutGateHome()) {
0150             try {
0151               String persistenceFilePathName =
0152                 currentPersistenceFile().getCanonicalPath();
0153               String gateHomePathName = Gate.getGateHome().getCanonicalPath();
0154               gateCanonicalHome = Gate.getGateHome().getCanonicalFile();
0155               String urlPathName = Files.fileFromURL(url).getCanonicalPath();
0156               //logger.debug("persistenceFilePathName "+persistenceFilePathName);
0157               //logger.debug("gateHomePathName        "+gateHomePathName);
0158               //logger.debug("urlPathName             "+urlPathName);
0159               if(!persistenceFilePathName.startsWith(gateHomePathName&&
0160                  urlPathName.startsWith(gateHomePathName)) {
0161                 //logger.debug("Setting path marker to "+gatehomePathMarker);
0162                 if(currentWarnAboutGateHome()) {
0163                   if(!currentHaveWarnedAboutGateHome().getValue()) {
0164                     logger.warn(
0165                           "\nYour application is using some of the plug-ins "+
0166                           "distributed with GATE, and may not work as expected "+
0167                           "with different versions of GATE. You should consider "+
0168                           "making private local copies of the plug-ins, and "+
0169                           "distributing those with your application.");
0170                     currentHaveWarnedAboutGateHome().setValue(true);
0171                   }
0172                   // the actual URL is shown every time
0173                   logger.warn("GATE resource referenced: "+url);
0174                 }
0175                 if(currentUseGateHome()) {
0176                   pathMarker = gatehomePathMarker;
0177                 }
0178               }
0179             catch(IOException ex) {
0180               // do nothing and proceed with using the relativePathMarker
0181             }
0182             }
0183             if(pathMarker.equals(relativePathMarker)) {
0184               urlString = pathMarker
0185                  + getRelativePath(urlPersistenceFilePath, url);
0186             else {
0187               urlString = pathMarker
0188                  + getRelativePath(gateCanonicalHome.toURI().toURL(), url);
0189             }
0190           }
0191           catch(MalformedURLException mue) {
0192             urlString = ((URL)source).toExternalForm();
0193           }
0194         }
0195         else {
0196           urlString = ((URL)source).toExternalForm();
0197         }
0198       }
0199       catch(ClassCastException cce) {
0200         throw new PersistenceException(cce);
0201       }
0202     }
0203 
0204     /**
0205      * Creates a new object from the data contained. This new object is
0206      * supposed to be a copy for the original object used as source for
0207      * data extraction.
0208      */
0209     public Object createObject() throws PersistenceException {
0210       try {
0211         if(urlString.startsWith(relativePathMarker)) {
0212           URL context = currentPersistenceURL();
0213           return new URL(context, urlString.substring(relativePathMarker
0214                   .length()));
0215         else if(urlString.startsWith(gatehomePathMarker)) {
0216           URL gatehome =  Gate.getGateHome().toURI().toURL();
0217           return new URL(gatehome, urlString.substring(gatehomePathMarker.length()));
0218         else if(urlString.startsWith(gatepluginsPathMarker)) {
0219           URL gateplugins = Gate.getPluginsHome().toURI().toURL();
0220           return new URL(gateplugins, urlString.substring(gatepluginsPathMarker.length()));
0221         else if(urlString.startsWith(syspropMarker)) {
0222           String urlRestString = urlString.substring(syspropMarker.length());
0223           int dollarindex = urlRestString.indexOf("$");
0224           if(dollarindex > 0) {
0225             String syspropname = urlRestString.substring(0,dollarindex);
0226             String propvalue = System.getProperty(syspropname);
0227             if(propvalue == null) {
0228               throw new PersistenceException("Property '"+syspropname+"' is null in "+urlString);
0229             }
0230             URL propuri = (new File(propvalue)).toURI().toURL();
0231             if(dollarindex == urlRestString.length()) {
0232               return propuri;
0233             else {
0234               return new URL(propuri, urlRestString.substring(dollarindex+1));
0235             }
0236           else if(dollarindex == 0) {
0237             throw new PersistenceException("No property name after '"+syspropMarker+"' in "+urlString);
0238           else {
0239             throw new PersistenceException("No ending $ after '"+syspropMarker+"' in "+urlString);
0240           }
0241         else {
0242           return new URL(urlString);
0243         }
0244       }
0245       catch(MalformedURLException mue) {
0246         throw new PersistenceException(mue);
0247       }
0248     }
0249 
0250     String urlString;
0251 
0252     /**
0253      * This string will be used to start the serialisation of URL that
0254      * represent relative paths.
0255      */
0256     private static final String relativePathMarker = "$relpath$";
0257     private static final String gatehomePathMarker = "$gatehome$";
0258     private static final String gatepluginsPathMarker = "$gateplugins$";
0259     private static final String syspropMarker = "$sysprop:";
0260 
0261     static final long serialVersionUID = 7943459208429026229L;
0262   }
0263 
0264   public static class ClassComparator implements Comparator {
0265     /**
0266      * Compares two {@link Class} values in terms of specificity; the
0267      * more specific class is said to be "smaller" than the
0268      * more generic one hence the {@link Object} class is the
0269      * "largest" possible class. When two classes are not
0270      * comparable (i.e. not assignable from each other) in either
0271      * direction a NotComparableException will be thrown. both input
0272      * objects should be Class values otherwise a
0273      {@link ClassCastException} will be thrown.
0274      *
0275      */
0276     public int compare(Object o1, Object o2) {
0277       Class c1 = (Class)o1;
0278       Class c2 = (Class)o2;
0279 
0280       if(c1.equals(c2)) return 0;
0281       if(c1.isAssignableFrom(c2)) return 1;
0282       if(c2.isAssignableFrom(c1)) return -1;
0283       throw new NotComparableException();
0284     }
0285   }
0286 
0287   /**
0288    * Thrown by a comparator when the values provided for comparison are
0289    * not comparable.
0290    */
0291   public static class NotComparableException extends RuntimeException {
0292     public NotComparableException(String message) {
0293       super(message);
0294     }
0295 
0296     public NotComparableException() {
0297     }
0298   }
0299 
0300   /**
0301    * Recursively traverses the provided object and replaces it and all
0302    * its contents with the appropriate persistent equivalent classes.
0303    *
0304    @param target the object to be analysed and translated into a
0305    *          persistent equivalent.
0306    @return the persistent equivalent value for the provided target
0307    */
0308   public static Serializable getPersistentRepresentation(Object target)
0309           throws PersistenceException {
0310     if(target == nullreturn null;
0311     // first check we don't have it already
0312     Persistence res = (Persistence)existingPersistentReplacements
0313             .get().getFirst().get(new ObjectHolder(target));
0314     if(res != nullreturn res;
0315 
0316     Class type = target.getClass();
0317     Class newType = getMostSpecificPersistentType(type);
0318     if(newType == null) {
0319       // no special handler
0320       if(target instanceof Serializable)
0321         return (Serializable)target;
0322       else throw new PersistenceException(
0323               "Could not find a serialisable replacement for " + type);
0324     }
0325 
0326     // we have a new type; create the new object, populate and return it
0327     try {
0328       res = (Persistence)newType.newInstance();
0329     }
0330     catch(Exception e) {
0331       throw new PersistenceException(e);
0332     }
0333     if(target instanceof NameBearer) {
0334       StatusListener sListener = (StatusListener)Gate.getListeners().get(
0335               "gate.event.StatusListener");
0336       if(sListener != null) {
0337         sListener.statusChanged("Storing " ((NameBearer)target).getName());
0338       }
0339     }
0340     res.extractDataFromSource(target);
0341     existingPersistentReplacements.get().getFirst().put(new ObjectHolder(target), res);
0342     return res;
0343   }
0344 
0345   public static Object getTransientRepresentation(Object target)
0346           throws PersistenceException, ResourceInstantiationException {
0347 
0348     if(target == null || target instanceof SlashDevSlashNullreturn null;
0349     if(target instanceof Persistence) {
0350       Object resultKey = new ObjectHolder(target);
0351       // check the cached values; maybe we have the result already
0352       Object result = existingTransientValues.get().getFirst().get(resultKey);
0353       if(result != nullreturn result;
0354 
0355       // we didn't find the value: create it
0356       result = ((Persistence)target).createObject();
0357       existingTransientValues.get().getFirst().put(resultKey, result);
0358       return result;
0359     }
0360     else return target;
0361   }
0362 
0363   /**
0364    * Finds the most specific persistent replacement type for a given
0365    * class. Look for a type that has a registered persistent equivalent
0366    * starting from the provided class continuing with its superclass and
0367    * implemented interfaces and their superclasses and implemented
0368    * interfaces and so on until a type is found. Classes are considered
0369    * to be more specific than interfaces and in situations of ambiguity
0370    * the most specific types are considered to be the ones that don't
0371    * belong to either java or GATE followed by the ones that belong to
0372    * GATE and followed by the ones that belong to java.
0373    *
0374    * E.g. if there are registered persistent types for
0375    {@link gate.Resource} and for {@link gate.LanguageResource} than
0376    * such a request for a {@link gate.Document} will yield the
0377    * registered type for {@link gate.LanguageResource}.
0378    */
0379   protected static Class getMostSpecificPersistentType(Class type) {
0380     // this list will contain all the types we need to expand to
0381     // superclass +
0382     // implemented interfaces. We start with the provided type and work
0383     // our way
0384     // up the ISA hierarchy
0385     List expansionSet = new ArrayList();
0386     expansionSet.add(type);
0387 
0388     // algorithm:
0389     // 1) check the current expansion set
0390     // 2) expand the expansion set
0391 
0392     // at each expansion stage we'll have a class and three lists of
0393     // interfaces:
0394     // the user defined ones; the GATE ones and the java ones.
0395     List userInterfaces = new ArrayList();
0396     List gateInterfaces = new ArrayList();
0397     List javaInterfaces = new ArrayList();
0398     while(!expansionSet.isEmpty()) {
0399       // 1) check the current set
0400       Iterator typesIter = expansionSet.iterator();
0401       while(typesIter.hasNext()) {
0402         Class result = (Class)persistentReplacementTypes.get(typesIter.next());
0403         if(result != null) {
0404           return result;
0405         }
0406       }
0407       // 2) expand the current expansion set;
0408       // the expanded expansion set will need to be ordered according to
0409       // the
0410       // rules (class >> interface; user interf >> gate interf >> java
0411       // interf)
0412 
0413       // at each point we only have at most one class
0414       if(type != nulltype = type.getSuperclass();
0415 
0416       userInterfaces.clear();
0417       gateInterfaces.clear();
0418       javaInterfaces.clear();
0419 
0420       typesIter = expansionSet.iterator();
0421       while(typesIter.hasNext()) {
0422         Class aType = (Class)typesIter.next();
0423         Class[] interfaces = aType.getInterfaces();
0424         // distribute them according to their type
0425         for(int i = 0; i < interfaces.length; i++) {
0426           Class anIterf = interfaces[i];
0427           String interfType = anIterf.getName();
0428           if(interfType.startsWith("java")) {
0429             javaInterfaces.add(anIterf);
0430           }
0431           else if(interfType.startsWith("gate")) {
0432             gateInterfaces.add(anIterf);
0433           }
0434           else userInterfaces.add(anIterf);
0435         }
0436       }
0437 
0438       expansionSet.clear();
0439       if(type != nullexpansionSet.add(type);
0440       expansionSet.addAll(userInterfaces);
0441       expansionSet.addAll(gateInterfaces);
0442       expansionSet.addAll(javaInterfaces);
0443     }
0444     // we got out the while loop without finding anything; return null;
0445     return null;
0446   }
0447 
0448   /**
0449    * Calculates the relative path for a file: URL starting from a given
0450    * context which is also a file: URL.
0451    *
0452    @param context the URL to be used as context.
0453    @param target the URL for which the relative path is computed.
0454    @return a String value representing the relative path. Constructing
0455    *         a URL from the context URL and the relative path should
0456    *         result in the target URL.
0457    */
0458   public static String getRelativePath(URL context, URL target) {
0459     if(context.getProtocol().equals("file")
0460             && target.getProtocol().equals("file")) {
0461       File contextFile = Files.fileFromURL(context);
0462       File targetFile = Files.fileFromURL(target);
0463 
0464       // if the original context URL ends with a slash (i.e. denotes
0465       // a directory), then we pretend we're taking a path relative to
0466       // some file in that directory.  This is because the relative
0467       // path from context file:/home/foo/bar to file:/home/foo/bar/baz
0468       // is bar/baz, whereas the path from file:/home/foo/bar/ - with
0469       // the trailing slash - is just baz.
0470       if(context.toExternalForm().endsWith("/")) {
0471         contextFile = new File(contextFile, "__dummy__");
0472       }
0473 
0474       List targetPathComponents = new ArrayList();
0475       File aFile = targetFile.getParentFile();
0476       while(aFile != null) {
0477         targetPathComponents.add(0, aFile);
0478         aFile = aFile.getParentFile();
0479       }
0480       List contextPathComponents = new ArrayList();
0481       aFile = contextFile.getParentFile();
0482       while(aFile != null) {
0483         contextPathComponents.add(0, aFile);
0484         aFile = aFile.getParentFile();
0485       }
0486       // the two lists can have 0..n common elements (0 when the files
0487       // are
0488       // on separate roots
0489       int commonPathElements = 0;
0490       while(commonPathElements < targetPathComponents.size()
0491               && commonPathElements < contextPathComponents.size()
0492               && targetPathComponents.get(commonPathElements).equals(
0493                       contextPathComponents.get(commonPathElements)))
0494         commonPathElements++;
0495       // construct the string for the relative URL
0496       String relativePath = "";
0497       for(int i = commonPathElements; i < contextPathComponents.size(); i++) {
0498         if(relativePath.length() == 0)
0499           relativePath += "..";
0500         else relativePath += "/..";
0501       }
0502       for(int i = commonPathElements; i < targetPathComponents.size(); i++) {
0503         String aDirName = ((File)targetPathComponents.get(i)).getName();
0504         if(aDirName.length() == 0) {
0505           aDirName = ((File)targetPathComponents.get(i)).getAbsolutePath();
0506           if(aDirName.endsWith(File.separator)) {
0507             aDirName = aDirName.substring(0, aDirName.length()
0508                     - File.separator.length());
0509           }
0510         }
0511         // Out.prln("Adding \"" + aDirName + "\" name for " +
0512         // targetPathComponents.get(i));
0513         if(relativePath.length() == 0) {
0514           relativePath += aDirName;
0515         }
0516         else {
0517           relativePath += "/" + aDirName;
0518         }
0519       }
0520       // we have the directory; add the file name
0521       if(relativePath.length() == 0) {
0522         relativePath += targetFile.getName();
0523       }
0524       else {
0525         relativePath += "/" + targetFile.getName();
0526       }
0527 
0528       if(target.toExternalForm().endsWith("/")) {
0529         // original target ended with a slash, so relative path should do too
0530         relativePath += "/";
0531       }
0532       try {
0533         URI relativeURI = new URI(null, null, relativePath, null, null);
0534         return relativeURI.getRawPath();
0535       }
0536       catch(URISyntaxException use) {
0537         throw new GateRuntimeException("Failed to generate relative path " +
0538             "between context: " + context + " and target: " + target, use);
0539       }
0540     }
0541     else {
0542       throw new GateRuntimeException("Both the target and the context URLs "
0543               "need to be \"file:\" URLs!");
0544     }
0545   }
0546 
0547   public static void saveObjectToFile(Object obj, File file)
0548     throws PersistenceException, IOException {
0549     saveObjectToFile(obj, file, false, false);
0550   }
0551 
0552   public static void saveObjectToFile(Object obj, File file,
0553           boolean usegatehome, boolean warnaboutgatehome)
0554           throws PersistenceException, IOException {
0555     ProgressListener pListener = (ProgressListener)Gate.getListeners()
0556             .get("gate.event.ProgressListener");
0557     StatusListener sListener = (gate.event.StatusListener)Gate
0558             .getListeners().get("gate.event.StatusListener");
0559     long startTime = System.currentTimeMillis();
0560     if(pListener != nullpListener.progressChanged(0);
0561     // The object output stream is used for native serialization,
0562     // but the xstream and filewriter are used for XML serialization.
0563     ObjectOutputStream oos = null;
0564     com.thoughtworks.xstream.XStream xstream = null;
0565     HierarchicalStreamWriter writer = null;
0566     warnAboutGateHome.get().addFirst(warnaboutgatehome);
0567     useGateHome.get().addFirst(usegatehome);
0568     startPersistingTo(file);
0569     try {
0570       if(Gate.getUseXMLSerialization()) {
0571         // Just create the xstream and the filewriter that will later be
0572         // used to serialize objects.
0573         xstream = new XStream(
0574           new Sun14ReflectionProvider(new FieldDictionary(new XStream12FieldKeySorter())),
0575           new StaxDriver(new XStream11XmlFriendlyReplacer())) {
0576           protected boolean useXStream11XmlFriendlyMapper() {
0577             return true;
0578           }
0579         };
0580         FileWriter fileWriter = new FileWriter(file);
0581         writer = new PrettyPrintWriter(fileWriter,
0582             new XmlFriendlyReplacer("-""_"));
0583       }
0584       else {
0585         oos = new ObjectOutputStream(new FileOutputStream(file));
0586       }
0587 
0588       // always write the list of creole URLs first
0589       List urlList = new ArrayList(Gate.getCreoleRegister().getDirectories());
0590       Object persistentList = getPersistentRepresentation(urlList);
0591 
0592       Object persistentObject = getPersistentRepresentation(obj);
0593 
0594       if(Gate.getUseXMLSerialization()) {
0595         // We need to put the urls and the application itself together
0596         // as xstreams can only hold one object.
0597         GateApplication gateApplication = new GateApplication();
0598         gateApplication.urlList = persistentList;
0599         gateApplication.application = persistentObject;
0600 
0601         // Then do the actual serialization.
0602         xstream.marshal(gateApplication, writer);
0603       }
0604       else {
0605         // This is for native serialization.
0606         oos.writeObject(persistentList);
0607 
0608         // now write the object
0609         oos.writeObject(persistentObject);
0610       }
0611 
0612     }
0613     finally {
0614       finishedPersisting();
0615       if(oos != null) {
0616         oos.flush();
0617         oos.close();
0618       }
0619       if(writer != null) {
0620         // Just make sure that all the xml is written, and the file
0621         // closed.
0622         writer.flush();
0623         writer.close();
0624       }
0625       long endTime = System.currentTimeMillis();
0626       if(sListener != null)
0627         sListener.statusChanged("Storing completed in "
0628                 + NumberFormat.getInstance().format(
0629                         (double)(endTime - startTime1000" seconds");
0630       if(pListener != nullpListener.processFinished();
0631     }
0632   }
0633 
0634   /**
0635    * Set up the thread-local state for a new persistence run.
0636    */
0637   private static void startPersistingTo(File file) {
0638     haveWarnedAboutGateHome.get().addFirst(new BooleanFlag(false));
0639     persistenceFile.get().addFirst(file);
0640     existingPersistentReplacements.get().addFirst(new HashMap());
0641   }
0642 
0643   /**
0644    * Get the file currently being saved by this thread.
0645    */
0646   private static File currentPersistenceFile() {
0647     return persistenceFile.get().getFirst();
0648   }
0649 
0650   private static Boolean currentWarnAboutGateHome() {
0651     return warnAboutGateHome.get().getFirst();
0652   }
0653 
0654   private static Boolean currentUseGateHome() {
0655     return useGateHome.get().getFirst();
0656   }
0657 
0658   private static BooleanFlag currentHaveWarnedAboutGateHome() {
0659     return haveWarnedAboutGateHome.get().getFirst();
0660   }
0661 
0662   /**
0663    * Clean up the thread-local state for the current persistence run.
0664    */
0665   private static void finishedPersisting() {
0666     persistenceFile.get().removeFirst();
0667     if(persistenceFile.get().isEmpty()) {
0668       persistenceFile.remove();
0669     }
0670     existingPersistentReplacements.get().removeFirst();
0671     if(existingPersistentReplacements.get().isEmpty()) {
0672       existingPersistentReplacements.remove();
0673     }
0674   }
0675 
0676   public static Object loadObjectFromFile(File file)
0677           throws PersistenceException, IOException,
0678           ResourceInstantiationException {
0679     return loadObjectFromUrl(file.toURI().toURL());
0680   }
0681 
0682   public static Object loadObjectFromUrl(URL urlthrows PersistenceException,
0683           IOException, ResourceInstantiationException {
0684     ProgressListener pListener = (ProgressListener)Gate.getListeners()
0685             .get("gate.event.ProgressListener");
0686     StatusListener sListener = (gate.event.StatusListener)Gate
0687             .getListeners().get("gate.event.StatusListener");
0688     if(pListener != nullpListener.progressChanged(0);
0689 
0690     startLoadingFrom(url);
0691     //the actual stream obtained from the URL. We keep a reference to this
0692     //so we can ensure it gets closed.
0693     InputStream rawStream = null;
0694     try {
0695       long startTime = System.currentTimeMillis();
0696       // Determine whether the file contains an application serialized in
0697       // xml
0698       // format. Otherwise we will assume that it contains native
0699       // serializations.
0700       boolean xmlStream = isXmlApplicationFile(url);
0701       ObjectInputStream ois = null;
0702       HierarchicalStreamReader reader = null;
0703       XStream xstream = null;
0704       // Make the appropriate kind of streams that will be used, depending
0705       // on
0706       // whether serialization is native or xml.
0707       if(xmlStream) {
0708         // we don't want to strip the BOM on XML.
0709         Reader inputReader = new InputStreamReader(
0710                 rawStream = url.openStream());
0711         try {
0712           XMLInputFactory inputFactory = XMLInputFactory.newInstance();
0713           XMLStreamReader xsr = inputFactory.createXMLStreamReader(
0714               url.toExternalForm(), inputReader);
0715           reader = new StaxReader(new QNameMap(), xsr);
0716         }
0717         catch(XMLStreamException xse) {
0718           // make sure the stream is closed, on error
0719           inputReader.close();
0720           throw new PersistenceException("Error creating reader", xse);
0721         }
0722 
0723         xstream = new XStream(new StaxDriver(new XStream11XmlFriendlyReplacer())) {
0724           protected boolean useXStream11XmlFriendlyMapper() {
0725             return true;
0726           }
0727         };
0728         // make XStream load classes through the GATE ClassLoader
0729         xstream.setClassLoader(Gate.getClassLoader());
0730         // make the XML stream appear as a normal ObjectInputStream
0731         ois = xstream.createObjectInputStream(reader);
0732       }
0733       else {
0734         // use GateAwareObjectInputStream to load classes through the
0735         // GATE ClassLoader if they can't be loaded through the one
0736         // ObjectInputStream would normally use
0737         ois = new GateAwareObjectInputStream(url.openStream());
0738 
0739       }
0740       Object res = null;
0741       try {
0742         // first read the list of creole URLs.
0743         Iterator urlIter =
0744           ((Collection)getTransientRepresentation(ois.readObject()))
0745           .iterator();
0746 
0747         // and re-register them
0748         while(urlIter.hasNext()) {
0749           URL anUrl = (URL)urlIter.next();
0750           try {
0751             Gate.getCreoleRegister().registerDirectories(anUrl);
0752           }
0753           catch(GateException ge) {
0754             Err.prln("Could not reload creole directory "
0755                     + anUrl.toExternalForm());
0756           }
0757         }
0758 
0759         // now we can read the saved object in the presence of all
0760         // the right plugins
0761         res = ois.readObject();
0762 
0763         // ensure a fresh start
0764         clearCurrentTransients();
0765         res = getTransientRepresentation(res);
0766         long endTime = System.currentTimeMillis();
0767         if(sListener != null)
0768           sListener.statusChanged("Loading completed in "
0769                   + NumberFormat.getInstance().format(
0770                           (double)(endTime - startTime1000" seconds");
0771         return res;
0772       }
0773       catch(ResourceInstantiationException rie) {
0774         if(sListener != nullsListener.statusChanged(
0775           "Failure during instantiation of resources.");
0776         throw rie;
0777       }
0778       catch(PersistenceException pe) {
0779         if(sListener != nullsListener.statusChanged(
0780           "Failure during persistence operations.");
0781         throw pe;
0782       }
0783       catch(Exception ex) {
0784         if(sListener != nullsListener.statusChanged("Loading failed!");
0785         throw new PersistenceException(ex);
0786       finally {
0787         //make sure the stream gets closed
0788         if (ois != nullois.close();
0789         if(reader != nullreader.close();
0790       }
0791     }
0792     finally {
0793       if(rawStream != nullrawStream.close();
0794       finishedLoading();
0795       if(pListener != nullpListener.processFinished();
0796     }
0797   }
0798 
0799   /**
0800    * Set up the thread-local state for the current loading run.
0801    */
0802   private static void startLoadingFrom(URL url) {
0803     persistenceURL.get().addFirst(url);
0804     existingTransientValues.get().addFirst(new HashMap());
0805   }
0806 
0807   /**
0808    * Clear the current list of transient replacements without
0809    * popping them off the stack.
0810    */
0811   private static void clearCurrentTransients() {
0812     existingTransientValues.get().getFirst().clear();
0813   }
0814 
0815   /**
0816    * Get the URL currently being loaded by this thread.
0817    */
0818   private static URL currentPersistenceURL() {
0819     return persistenceURL.get().getFirst();
0820   }
0821 
0822   /**
0823    * Clean up the thread-local state at the end of a loading run.
0824    */
0825   private static void finishedLoading() {
0826     persistenceURL.get().removeFirst();
0827     if(persistenceURL.get().isEmpty()) {
0828       persistenceURL.remove();
0829     }
0830     existingTransientValues.get().removeFirst();
0831     if(existingTransientValues.get().isEmpty()) {
0832       existingTransientValues.remove();
0833     }
0834   }
0835 
0836   /**
0837    * Determine whether the URL contains a GATE application serialized
0838    * using XML.
0839    *
0840    @param url The URL to check.
0841    @return true if the URL refers to an xml serialized application,
0842    *         false otherwise.
0843    */
0844   private static boolean isXmlApplicationFile(URL url)
0845           throws java.io.IOException {
0846     if(DEBUG) {
0847       System.out.println("Checking whether file is xml");
0848     }
0849     String firstLine;
0850     BufferedReader fileReader = null;
0851     try {
0852       fileReader = new BomStrippingInputStreamReader(url.openStream());
0853       firstLine = fileReader.readLine();
0854     finally {
0855       if(fileReader != nullfileReader.close();
0856     }
0857     if(firstLine == null) {
0858       return false;
0859     }
0860     for(String startOfXml : STARTOFXMLAPPLICATIONFILES) {
0861       if(firstLine.length() >= startOfXml.length()
0862               && firstLine.substring(0, startOfXml.length()).equals(startOfXml)) {
0863         if(DEBUG) {
0864           System.out.println("isXMLApplicationFile = true");
0865         }
0866         return true;
0867       }
0868     }
0869     if(DEBUG) {
0870       System.out.println("isXMLApplicationFile = false");
0871     }
0872     return false;
0873   }
0874 
0875   private static final String[] STARTOFXMLAPPLICATIONFILES = {
0876       "<gate.util.persistence.GateApplication>""<?xml""<!DOCTYPE"};
0877 
0878   /**
0879    @deprecated Use {@link #registerPersistentEquivalent(Class,Class)} instead
0880    */
0881   public static Class registerPersitentEquivalent(Class transientType,
0882           Class persistentTypethrows PersistenceException {
0883             return registerPersistentEquivalent(transientType, persistentType);
0884           }
0885 
0886   /**
0887    * Sets the persistent equivalent type to be used to (re)store a given
0888    * type of transient objects.
0889    *
0890    @param transientType the type that will be replaced during
0891    *          serialisation operations
0892    @param persistentType the type used to replace objects of transient
0893    *          type when serialising; this type needs to extend
0894    *          {@link Persistence}.
0895    @return the persitent type that was used before this mapping if
0896    *         such existed.
0897    */
0898   public static Class registerPersistentEquivalent(Class transientType,
0899           Class persistentTypethrows PersistenceException {
0900     if(!Persistence.class.isAssignableFrom(persistentType)) {
0901       throw new PersistenceException(
0902               "Persistent equivalent types have to implement "
0903                       + Persistence.class.getName() "!\n"
0904                       + persistentType.getName() " does not!");
0905     }
0906     return (Class)persistentReplacementTypes.put(transientType, persistentType);
0907   }
0908 
0909   /**
0910    * A dictionary mapping from java type (Class) to the type (Class)
0911    * that can be used to store persistent data for the input type.
0912    */
0913   private static Map persistentReplacementTypes;
0914 
0915   /**
0916    * Stores the persistent replacements created during a transaction in
0917    * order to avoid creating two different persistent copies for the
0918    * same object. The keys used are {@link ObjectHolder}s that contain
0919    * the transient values being converted to persistent equivalents.
0920    */
0921   private static ThreadLocal<LinkedList<Map>> existingPersistentReplacements;
0922 
0923   /**
0924    * Stores the transient values obtained from persistent replacements
0925    * during a transaction in order to avoid creating two different
0926    * transient copies for the same persistent replacement. The keys used
0927    * are {@link ObjectHolder}s that hold persistent equivalents. The
0928    * values are the transient values created by the persisten
0929    * equivalents.
0930    */
0931   private static ThreadLocal<LinkedList<Map>> existingTransientValues;
0932 
0933   private static ClassComparator classComparator = new ClassComparator();
0934 
0935   /**
0936    * The file currently used to write the persisten representation. Will
0937    * only have a non-null value during storing operations.
0938    */
0939   static ThreadLocal<LinkedList<File>> persistenceFile;
0940 
0941   /**
0942    * The URL currently used to read the persistent representation when
0943    * reading from a URL. Will only be non-null during restoring
0944    * operations.
0945    */
0946   static ThreadLocal<LinkedList<URL>> persistenceURL;
0947 
0948   private static final class BooleanFlag {
0949     BooleanFlag(boolean initial) {
0950       flag = initial;
0951     }
0952     private boolean flag;
0953     public void setValue(boolean value) {
0954       flag = value;
0955     }
0956     public boolean getValue() {
0957       return flag;
0958     }
0959   }
0960 
0961   private static ThreadLocal<LinkedList<Boolean>> useGateHome;
0962   private static ThreadLocal<LinkedList<Boolean>> warnAboutGateHome;
0963   private static ThreadLocal<LinkedList<BooleanFlag>> haveWarnedAboutGateHome;
0964 
0965   static {
0966     persistentReplacementTypes = new HashMap();
0967     try {
0968       // VRs don't get saved, ....sorry guys :)
0969       registerPersistentEquivalent(VisualResource.class, SlashDevSlashNull.class);
0970 
0971       registerPersistentEquivalent(URL.class, URLHolder.class);
0972 
0973       registerPersistentEquivalent(Map.class, MapPersistence.class);
0974       registerPersistentEquivalent(Collection.class, CollectionPersistence.class);
0975 
0976       registerPersistentEquivalent(ProcessingResource.class, PRPersistence.class);
0977 
0978       registerPersistentEquivalent(DataStore.class, DSPersistence.class);
0979 
0980       registerPersistentEquivalent(LanguageResource.class, LRPersistence.class);
0981 
0982       registerPersistentEquivalent(Corpus.class, CorpusPersistence.class);
0983 
0984       registerPersistentEquivalent(Controller.class, ControllerPersistence.class);
0985 
0986       registerPersistentEquivalent(ConditionalController.class,
0987               ConditionalControllerPersistence.class);
0988 
0989       registerPersistentEquivalent(ConditionalSerialAnalyserController.class,
0990               ConditionalSerialAnalyserControllerPersistence.class);
0991 
0992       registerPersistentEquivalent(LanguageAnalyser.class,
0993               LanguageAnalyserPersistence.class);
0994 
0995       registerPersistentEquivalent(SerialAnalyserController.class,
0996               SerialAnalyserControllerPersistence.class);
0997 
0998       registerPersistentEquivalent(gate.persist.JDBCDataStore.class,
0999               JDBCDSPersistence.class);
1000 
1001       registerPersistentEquivalent(gate.creole.AnalyserRunningStrategy.class,
1002               AnalyserRunningStrategyPersistence.class);
1003       
1004       registerPersistentEquivalent(gate.creole.RunningStrategy.UnconditionalRunningStrategy.class,
1005               UnconditionalRunningStrategyPersistence.class);
1006     }
1007     catch(PersistenceException pe) {
1008       // builtins shouldn't raise this
1009       pe.printStackTrace();
1010     }
1011 
1012     /**
1013      * Thread-local stack.
1014      */
1015     class ThreadLocalStack<T> extends ThreadLocal<LinkedList<T>> {
1016       @Override
1017       protected LinkedList<T> initialValue() {
1018         // TODO Auto-generated method stub
1019         return new LinkedList<T>();
1020       }
1021 
1022     }
1023 
1024     existingPersistentReplacements = new ThreadLocalStack<Map>();
1025     existingTransientValues = new ThreadLocalStack<Map>();
1026     persistenceFile = new ThreadLocalStack<File>();
1027     persistenceURL = new ThreadLocalStack<URL>();
1028     useGateHome = new ThreadLocalStack<Boolean>();
1029     warnAboutGateHome = new ThreadLocalStack<Boolean>();
1030     haveWarnedAboutGateHome = new ThreadLocalStack<BooleanFlag>();
1031   }
1032 }