PersistenceManager.java
0001 /*
0002  *  Copyright (c) 1995-2012, 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 19755 2016-11-18 19:18:15Z ian_roberts $
0013  *
0014  */
0015 package gate.util.persistence;
0016 
0017 import gate.Controller;
0018 import gate.Corpus;
0019 import gate.DataStore;
0020 import gate.Gate;
0021 import gate.LanguageAnalyser;
0022 import gate.LanguageResource;
0023 import gate.ProcessingResource;
0024 import gate.VisualResource;
0025 import gate.creole.ConditionalController;
0026 import gate.creole.ConditionalSerialAnalyserController;
0027 import gate.creole.ResourceInstantiationException;
0028 import gate.creole.SerialAnalyserController;
0029 import gate.event.ProgressListener;
0030 import gate.event.StatusListener;
0031 import gate.persist.GateAwareObjectInputStream;
0032 import gate.persist.PersistenceException;
0033 import gate.util.BomStrippingInputStreamReader;
0034 import gate.util.Err;
0035 import gate.util.Files;
0036 import gate.util.GateException;
0037 import gate.util.GateRuntimeException;
0038 import gate.util.NameBearer;
0039 
0040 import java.io.BufferedReader;
0041 import java.io.File;
0042 import java.io.FileOutputStream;
0043 import java.io.FileWriter;
0044 import java.io.IOException;
0045 import java.io.InputStream;
0046 import java.io.InputStreamReader;
0047 import java.io.ObjectInputStream;
0048 import java.io.ObjectOutputStream;
0049 import java.io.Reader;
0050 import java.io.Serializable;
0051 import java.net.MalformedURLException;
0052 import java.net.URI;
0053 import java.net.URISyntaxException;
0054 import java.net.URL;
0055 import java.nio.file.FileSystems;
0056 import java.nio.file.LinkOption;
0057 import java.nio.file.Path;
0058 import java.nio.file.Paths;
0059 import java.text.NumberFormat;
0060 import java.util.ArrayList;
0061 import java.util.Collection;
0062 import java.util.Comparator;
0063 import java.util.HashMap;
0064 import java.util.HashSet;
0065 import java.util.Iterator;
0066 import java.util.LinkedList;
0067 import java.util.List;
0068 import java.util.Map;
0069 import java.util.Set;
0070 import java.util.regex.Matcher;
0071 import java.util.regex.Pattern;
0072 
0073 import javax.xml.stream.XMLInputFactory;
0074 import javax.xml.stream.XMLStreamException;
0075 import javax.xml.stream.XMLStreamReader;
0076 
0077 import org.apache.log4j.Logger;
0078 
0079 import com.thoughtworks.xstream.XStream;
0080 import com.thoughtworks.xstream.converters.reflection.FieldDictionary;
0081 import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
0082 import com.thoughtworks.xstream.converters.reflection.XStream12FieldKeySorter;
0083 import com.thoughtworks.xstream.io.HierarchicalStreamReader;
0084 import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
0085 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
0086 import com.thoughtworks.xstream.io.xml.QNameMap;
0087 import com.thoughtworks.xstream.io.xml.StaxDriver;
0088 import com.thoughtworks.xstream.io.xml.StaxReader;
0089 import com.thoughtworks.xstream.io.xml.XStream11NameCoder;
0090 import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
0091 
0092 /**
0093  * This class provides utility methods for saving resources through
0094  * serialisation via static methods.
0095  *
0096  * It now supports both native and xml serialization.
0097  */
0098 public class PersistenceManager {
0099 
0100   private static final boolean DEBUG = false;
0101 
0102   /**
0103    * A reference to an object; it uses the identity hashcode and the
0104    * equals defined by object identity. These values will be used as
0105    * keys in the {link #existingPersistentReplacements} map.
0106    */
0107   static protected class ObjectHolder {
0108     ObjectHolder(Object target) {
0109       this.target = target;
0110     }
0111 
0112     @Override
0113     public int hashCode() {
0114       return System.identityHashCode(target);
0115     }
0116 
0117     @Override
0118     public boolean equals(Object obj) {
0119       if(obj instanceof ObjectHolder)
0120         return ((ObjectHolder)obj).target == this.target;
0121       else return false;
0122     }
0123 
0124     public Object getTarget() {
0125       return target;
0126     }
0127 
0128     private Object target;
0129   }// static class ObjectHolder{
0130 
0131   /**
0132    * This class is used as a marker for types that should NOT be
0133    * serialised when saving the state of a gate object. Registering this
0134    * type as the persistent equivalent for a specific class (via
0135    {@link PersistenceManager#registerPersistentEquivalent(Class , Class)})
0136    * effectively stops all values of the specified type from being
0137    * serialised.
0138    *
0139    * Maps that contain values that should not be serialised will have
0140    * that entry removed. In any other places where such values occur
0141    * they will be replaced by null after deserialisation.
0142    */
0143   public static class SlashDevSlashNull implements Persistence {
0144     /**
0145      * Does nothing
0146      */
0147     @Override
0148     public void extractDataFromSource(Object source)
0149             throws PersistenceException {
0150     }
0151 
0152     /**
0153      * Returns null
0154      */
0155     @Override
0156     public Object createObject() throws PersistenceException,
0157             ResourceInstantiationException {
0158       return null;
0159     }
0160 
0161     static final long serialVersionUID = -8665414981783519937L;
0162   }
0163 
0164   /**
0165    * URLs get upset when serialised and deserialised so we need to
0166    * convert them to strings for storage. 
0167    * In the case of
0168    * "file:" URLs the relative path to the persistence file
0169    * will actually be stored, except when {@link #saveObjectToFile(Object,File,boolean,boolean)} was
0170    * used and there is one of the following cases:
0171    <ul>
0172    <li>when the URL refers to a resource
0173    * within the current GATE home directory and the persistence file is not within the GATE home
0174    * directory, in which case the relative path
0175    * to the GATE home directory will be stored. 
0176    <li>Otherwise, if the persistence file is not within the GATE directory and if the property 
0177    * gate.user.resourceshome is set to a directory path and the URL refers 
0178    * to a resource inside this directory, the relative path to this directory
0179    * will be stored.
0180    <ul>
0181    * If resources are stored relative to gate home or
0182    * resources home, a warning will also be logged.
0183    <p>
0184    * The real path as returned by getCanonicalPath() is used to check if the resource is 
0185    * within a special directory (e.g. GATE HOME). Once we know this, and the resource is in
0186    * a special directory, the canonical paths are used to generate the relative path.
0187    * If we are not inside a special directory, then the non-canonical path names will be used, so
0188    * that any symbolic links are NOT getting resolved, which is usually what you want. 
0189    * The non-canonical path names will get normalized to remove things like "../dir" and the 
0190    * result of the normalization is checked using getCanonicalPath() to see if it refers to 
0191    * the same location as the non-normalized one (it could be different if the .. follows a
0192    * symbolic link, for example). If it is the same, then the normalized version is used, otherwise
0193    * the original version is used. 
0194    
0195    */
0196   public static class URLHolder implements Persistence {
0197     /**
0198      * Populates this Persistence with the data that needs to be stored
0199      * from the original source object.
0200      */
0201     static final Logger logger = Logger.getLogger(URLHolder.class);
0202     @Override
0203     public void extractDataFromSource(Object source)
0204             throws PersistenceException {
0205       try {
0206         URL url = (URL)source;
0207         if(url.getProtocol().equals("file")) {
0208             // url is what we want to convert to something that is relative to $relpath$, 
0209             // $resourceshome$ or $gatehome$ 
0210             
0211             // The file in which the URL should get persisted, the directory this file is in
0212             // is the location to which we want to make the URL relative, unless we want to 
0213             // use $gatehome$ or $resourceshome$
0214             File outFile = currentPersistenceFile();
0215             File outFileReal = getCanonicalFileIfPossible(outFile);
0216             // Note: the outDir will be correct if the application file itself is not a link
0217             // If the application file is a link than its parent is the parent of the real
0218             // path. 
0219             File outDir = outFile.getParentFile()
0220             File outDirReal = getCanonicalFileIfPossible(outDir);
0221             File outDirOfReal = getCanonicalFileIfPossible(outFileReal.getParentFile());
0222             
0223             File urlFile = Files.fileFromURL(url);
0224             File urlFileReal = getCanonicalFileIfPossible(urlFile);
0225             
0226             logger.debug("Trying to persist "+url+" for "+outFile);
0227             logger.debug("urlFile="+urlFile+", urlFileReal"+urlFileReal);
0228             logger.debug("outDir="+outDir+", outDirReal"+outDirReal);
0229             
0230             // by default, use just the relative path
0231             String pathMarker = relativePathMarker;
0232             
0233               // - this returns the canonical path to GATE_HOME
0234             File gateHomePathReal = getGateHomePath();
0235 
0236             // get the canonical path of the resources home directory, if set, otherwise null
0237             File resourceshomeDirReal = getResourceshomePath();
0238             
0239             // Only if we actually *want* to consider GATE_HOME and other special directories,
0240             // do all of this ...
0241             // Note: the semantics of this has slightly changed to what it was before: we only
0242             // do this is the use gatehome flag is set, not if it is not set but the warn flag
0243             // is set (previsouly, the warn flag alone was sufficient too).
0244             if(currentUseGateHome()) {
0245               // First of all, check if we want to use GATE_HOME instead: this happens 
0246               // if the real location of the url is inside the real location of GATE_HOME            
0247               // and the location of the outfile is outside of GATE_HOME
0248 
0249               if (!isContainedWithin(outFileReal, gateHomePathReal&&
0250                   isContainedWithin(urlFileReal, gateHomePathReal)) {
0251                 logger.debug("Setting path marker to "+gatehomePathMarker);
0252                 if(currentWarnAboutGateHome()) {
0253                   if(!currentHaveWarnedAboutGateHome().getValue()) {
0254                     logger.warn(
0255                           "\nYour application is using some of the resources/plugins "+
0256                           "distributed with GATE, and may not work as expected "+
0257                           "with different versions of GATE. You should consider "+
0258                           "making private local copies of the plug-ins, and "+
0259                           "distributing those with your application.");
0260                     currentHaveWarnedAboutGateHome().setValue(true);
0261                   }
0262                   // the actual URL is shown every time
0263                   logger.warn("GATE resource referenced: "+url);
0264                 }
0265                 if(currentUseGateHome()) {
0266                   pathMarker = gatehomePathMarker;
0267                 }
0268               else 
0269                 // Otherwise, do the same check for the resources home path, but only if it
0270                 // is actuallys set
0271                 if(resourceshomeDirReal != null &&
0272                   !isContainedWithin(outFileReal, resourceshomeDirReal&&
0273                   isContainedWithin(urlFileReal, resourceshomeDirReal)) {
0274                  if(currentWarnAboutGateHome()) {
0275                   if(!currentHaveWarnedAboutResourceshome().getValue()) {
0276                     logger.warn(
0277                           "\nYour application is using resources from your project "+
0278                           "path at "+getResourceshomePath()+". Restoring the application "+
0279                           "will only work if the same project path is set.");
0280                     currentHaveWarnedAboutResourceshome().setValue(true);
0281                   }
0282                   // the actual URL is shown every time
0283                   logger.warn("Resource referenced: "+url);
0284                 }
0285                 if(currentUseGateHome()) {
0286                   pathMarker = resourceshomePathMarker;
0287                 }
0288                
0289               }
0290             }
0291 
0292             String relPath = null;
0293                         
0294             if(pathMarker.equals(relativePathMarker)) {
0295               // In theory we should just relativize here using the original paths, without
0296               // following any symbolic links. We do not want to follow symbolic links because 
0297               // somebody may want to use a symbolic link to a completely different location
0298               // to include some resources into the project directory that contains the 
0299               // pipeline (and the link to the resource).
0300               // However, if the project directory itself is within a linked location, then 
0301               // GATE will sometimes use the linked and sometimes the non-linked path 
0302               // (for some reason it uses the non-linked path in the plugin manager but it uses
0303               // the linked path when the application file is choosen). This means that 
0304               // in those cases, there would be a large number of unwanted ../../../../some/dir/to/get/back
0305               // If we solve this by using the canonical paths, it will do the wrong thing for 
0306               // links to resources. So we choose to make a compromise: if we can create a relative
0307               // path that does not generate any ../ at the beginning of the relative part, then
0308               // we use that, otherwise we use the real paths 
0309               
0310               relPath = getRelativeFilePathString(outDir, urlFile);
0311               logger.debug("First relative path string attempt got "+relPath);
0312               if(relPath.startsWith("../")) {
0313                 // if we want to actually use the real path, we have to be careful which is the 
0314                 // real parent of the out file: if outFile is a symbolic link then it is 
0315                 // outDirOfReal otherwise it is outDirReal
0316                 logger.debug("upslength is "+upsLength(relPath));
0317                 String tmpPath = relPath;
0318                 if(java.nio.file.Files.isSymbolicLink(outFile.toPath())) {
0319                   tmpPath = getRelativeFilePathString(outDirOfReal, urlFileReal);
0320                   logger.debug("trying outDirOfReal "+tmpPath);
0321                   logger.debug("upslength is "+upsLength(tmpPath));
0322                 else {
0323                   tmpPath = getRelativeFilePathString(outDirReal, urlFileReal);                                  
0324                   logger.debug("trying outDirReal "+tmpPath);
0325                   logger.debug("upslength is "+upsLength(tmpPath));
0326                 }
0327                 if(upsLength(tmpPath< upsLength(relPath)) {
0328                   relPath = tmpPath;
0329                 }
0330                 logger.debug("Using tmpPath "+relPath);
0331               }
0332               // if we still get something that starts with ../ then our only remaining option is
0333               // to find if a parent 
0334             else if(pathMarker.equals(gatehomePathMarker)) {
0335               relPath = getRelativeFilePathString(gateHomePathReal, urlFileReal);
0336             else if(pathMarker.equals(resourceshomePathMarker)) {
0337               relPath = getRelativeFilePathString(resourceshomeDirReal, urlFileReal);
0338             else {
0339               // this should really never happen!
0340               throw new GateRuntimeException("Unexpected error when persisting URL "+url);
0341             }
0342 
0343             Path rel = Paths.get(relPath);
0344             String uriPath = "";
0345             boolean first = true;
0346             for(Path component : rel) {
0347               if(!firsturiPath += "/";
0348               uriPath += component.toString();
0349               first = false;
0350             }
0351             if(urlFile.isDirectory()) {
0352               // trailing slash
0353               uriPath += "/";
0354             }
0355             // construct the final properly encoded relative URI
0356             URI finalRelUri = new URI(null, null, uriPath, null);
0357             urlString = pathMarker + finalRelUri.getRawPath();
0358         // if protocol is file
0359         else {
0360           // protocol was not file:
0361           urlString = ((URL)source).toExternalForm();
0362         }
0363       }
0364       catch(ClassCastException | URISyntaxException e) {
0365         throw new PersistenceException(e);
0366       }
0367     }
0368 
0369     /**
0370      * Creates a new object from the data contained. This new object is
0371      * supposed to be a copy for the original object used as source for
0372      * data extraction.
0373      */
0374     // TODO: this always uses the one-argument constructor for URL. This may not always
0375     // do the right thing though, we should test this (e.g. when the URL contains %-encoded parts)
0376     @Override
0377     public Object createObject() throws PersistenceException {
0378       try {
0379         if(urlString.startsWith(relativePathMarker)) {
0380           URL context = currentPersistenceURL();
0381           // If the part after the $relpath$ marker is empty, the normal method
0382           // would get us the URL of the context which will be the URL of the pipeline, not
0383           // of the directory where the pipeline is located. 
0384           // If the part after the $relpath$ marker starts with a slash, the normal method 
0385           // would cause the $relpath$ marker to get ignored, but what we really want is probably
0386           // that the slash gets ignored and the part that follows be appended to the 
0387           // directory part of the context. 
0388           // If the relative part is empty, we make an attempt to remove the last part of the 
0389           // URL path from context and use that as the parent/context instead
0390           // If the relative part is starting with a slash, we remove that
0391           //URL ret =  new URL(context, urlString.substring(relativePathMarker
0392           //        .length()));
0393           URL ret = combineContextAndRelative(context, urlString.substring(relativePathMarker
0394                   .length())true);
0395           logger.debug("CurrentPresistenceURL is "+context+" created="+ret);
0396           return ret;
0397         else if(urlString.startsWith(gatehomePathMarker)) {
0398           URL gatehome =  getCanonicalFileIfPossible(getGateHomePath()).toURI().toURL();
0399           //return new URL(gatehome, urlString.substring(gatehomePathMarker.length()));
0400           return combineContextAndRelative(gatehome, urlString.substring(gatehomePathMarker.length()),false);
0401         else if(urlString.startsWith(gatepluginsPathMarker)) {
0402           URL gateplugins = Gate.getPluginsHome().toURI().toURL();
0403           //return new URL(gateplugins, urlString.substring(gatepluginsPathMarker.length()));
0404           return combineContextAndRelative(gateplugins, urlString.substring(gatepluginsPathMarker.length()),false);
0405         else if(urlString.startsWith(resourceshomePathMarker)) {
0406           if(getResourceshomePath() == null) {
0407             throw new GateRuntimeException("Cannot restore URL "+urlString+
0408                     "property "+resourceshomePropertyName+" is not set");
0409           }
0410           URL resourceshomeurl = getResourceshomePath().toURI().toURL();          
0411           //return new URL(resourceshomeurl, urlString.substring(resourceshomePathMarker.length()));
0412           return combineContextAndRelative(resourceshomeurl, urlString.substring(resourceshomePathMarker.length()),false);
0413         else if(urlString.startsWith(syspropMarker)) {
0414           String urlRestString = urlString.substring(syspropMarker.length());
0415           int dollarindex = urlRestString.indexOf("$");
0416           if(dollarindex > 0) {
0417             String syspropname = urlRestString.substring(0,dollarindex);
0418             String propvalue = System.getProperty(syspropname);
0419             if(propvalue == null) {
0420               throw new PersistenceException("Property '"+syspropname+"' is null in "+urlString);
0421             }
0422             // TODO: we should only assume a file if what we get does not start with a protocol
0423             URL propuri = (new File(propvalue)).toURI().toURL();
0424             if(dollarindex == urlRestString.length()) {
0425               return propuri;
0426             else {
0427               //return new URL(propuri, urlRestString.substring(dollarindex+1));
0428               return combineContextAndRelative(propuri, urlRestString.substring(dollarindex+1),false);
0429             }
0430           else if(dollarindex == 0) {
0431             throw new PersistenceException("No property name after '"+syspropMarker+"' in "+urlString);
0432           else {
0433             throw new PersistenceException("No ending $ after '"+syspropMarker+"' in "+urlString);
0434           }
0435         else {
0436           return new URL(urlString);
0437         }
0438       }
0439       catch(MalformedURLException mue) {
0440         throw new PersistenceException(mue);
0441       }
0442     }
0443     
0444     String urlString;
0445     
0446     //******** Helper methods just used inside the URLHolder class
0447    
0448     private URL combineContextAndRelative(URL context, String relative, boolean contextIsFile) {
0449       // make sure we always have a proper string
0450       if(relative==nullrelative = "";
0451       // we always want to interpret the part after the marker as a relative path, so 
0452       // remove any starting slash (for performance reasons, only one is removed, so 
0453       // an absolute path could get hacked by using two slashes)
0454       if(relative.startsWith("/")) relative = relative.substring(1);
0455       // if the relative path is empty, return the directory part of the context (if contextIsFile
0456       // is true, then the parent, otherwise the context unchanged)
0457       if(relative.isEmpty()) {
0458         if(!contextIsFilereturn context;
0459         // context is file, get the parent and return that
0460         URI tmpUri;
0461         try {
0462           tmpUri = context.toURI();
0463         catch (URISyntaxException ex) {
0464           throw new GateRuntimeException("Could not convert context URL to URI: "+context,ex);
0465         }
0466         // find the parent: in our use cases, the context should always be a file 
0467         tmpUri = tmpUri.resolve(".");
0468         try {
0469           context = tmpUri.toURL();
0470         catch (Exception ex) {
0471           throw new GateRuntimeException("Could not convert context URI to URL: "+tmpUri,ex);
0472         }
0473         return context;
0474       else {
0475         // use the URL constructor to splice the two parts together
0476         URL tmpUrl;
0477         try {
0478           tmpUrl = new URL(context,relative);
0479         catch (Exception ex) {
0480           throw new GateRuntimeException("Could not create a URL from context "+context+" and relative part "+relative,ex);
0481         }
0482         return tmpUrl;
0483       }
0484     }
0485     
0486     
0487     private File getGateHomePath() {
0488       if(gatehomePath != null) {
0489         return gatehomePath;
0490       else {
0491         gatehomePath = getCanonicalFileIfPossible(Gate.getGateHome());
0492         return gatehomePath;
0493       }
0494     }
0495     
0496     private File getResourceshomePath() {
0497       if(haveResourceshomePath == null) {
0498         String resourceshomeString = System.getProperty(resourceshomePropertyName);
0499         if(resourceshomeString == null) {
0500           haveResourceshomePath = false;
0501           return null;
0502         }
0503         resourceshomePath = new File(resourceshomeString);
0504         resourceshomePath = getCanonicalFileIfPossible(resourceshomePath);
0505         haveResourceshomePath = true;
0506         return resourceshomePath;
0507       else if(haveResourceshomePath) {
0508         return resourceshomePath;
0509       else {
0510         return null;
0511       }
0512     }
0513 
0514 
0515     private File getCanonicalFileIfPossible(File file) {
0516       File tmp = file;
0517       try {
0518         tmp = tmp.getCanonicalFile();
0519       catch (IOException ex) {
0520         // ignore
0521       }
0522       return tmp;
0523     }
0524     
0525     /** 
0526      * Creates the string that needs to get appended to the path of dir to obtain the path
0527      * to file. 
0528      * This does NOT follow any symbolic links - if true locations should be used, they 
0529      * have to get passed to this method. This will make an attempt to access the file system
0530      * to see of normalization can be done without changing the meaning of the path, but if 
0531      * accessing the file system fails, this method will silently use the non-normalized 
0532      * path instead.
0533      @param dir the directory starting from
0534      @param file the file we want to reference
0535      @return the path appended to dir to get to file
0536      */
0537     private String getRelativeFilePathString(File dir, File file) {
0538       Path dirPath = dir.toPath();
0539       Path filePath = file.toPath();
0540       try {
0541         // create a normalized version of the paths, but without following symbolic links
0542         dirPath = dirPath.toRealPath(LinkOption.NOFOLLOW_LINKS);
0543       catch (IOException ex) {
0544         // ignore and use original 
0545       }
0546       try {
0547         filePath = filePath.toRealPath(LinkOption.NOFOLLOW_LINKS);
0548       catch (IOException ex) {
0549         // ignore and use original
0550       }
0551       return dirPath.relativize(filePath).toString();
0552     }
0553 
0554     private int upsLength(String path) {
0555       Matcher m = goUpPattern.matcher(path);
0556       if(m.matches()) {
0557         return m.group(1).length();
0558       else {
0559         return 0;
0560       }
0561     }
0562     
0563     
0564     /**
0565      * This string will be used to start the serialisation of URL that
0566      * represent relative paths.
0567      */
0568     private static final String relativePathMarker = "$relpath$";
0569     private static final String gatehomePathMarker = "$gatehome$";
0570     private static final String gatepluginsPathMarker = "$gateplugins$";
0571     private static final String syspropMarker = "$sysprop:";
0572     private static final String resourceshomePathMarker = "$resourceshome$";
0573     private static final String resourceshomePropertyName = "gate.user.resourceshome";
0574     
0575     // After initialisation this is either the canonical path to the project
0576     // home as set by the property resourceshomePropertyName or null if the
0577     // property has not been set.
0578     private static File resourceshomePath = null;
0579     private static Boolean haveResourceshomePath = null;
0580     
0581     // The canoncial gate home path gets cached in this field 
0582     private static File gatehomePath = null;
0583     
0584     static final long serialVersionUID = 7943459208429026229L;
0585     
0586     private static final Pattern goUpPattern = 
0587             Pattern.compile(
0588                     "^((?:\\.\\."+Pattern.quote(FileSystems.getDefault().getSeparator())+")+).*");
0589     
0590   // class URLHolder 
0591 
0592   public static class ClassComparator implements Comparator<Class<?>> {
0593     /**
0594      * Compares two {@link Class} values in terms of specificity; the
0595      * more specific class is said to be &quot;smaller&quot; than the
0596      * more generic one hence the {@link Object} class is the
0597      * &quot;largest&quot; possible class. When two classes are not
0598      * comparable (i.e. not assignable from each other) in either
0599      * direction a NotComparableException will be thrown. both input
0600      * objects should be Class values otherwise a
0601      {@link ClassCastException} will be thrown.
0602      *
0603      */
0604     @Override
0605     public int compare(Class<?> c1, Class<?> c2) {
0606       
0607       if(c1.equals(c2)) return 0;
0608       if(c1.isAssignableFrom(c2)) return 1;
0609       if(c2.isAssignableFrom(c1)) return -1;
0610       throw new NotComparableException();
0611     }
0612   }
0613 
0614   /**
0615    * Thrown by a comparator when the values provided for comparison are
0616    * not comparable.
0617    */
0618   @SuppressWarnings("serial")
0619   public static class NotComparableException extends RuntimeException {
0620     public NotComparableException(String message) {
0621       super(message);
0622     }
0623 
0624     public NotComparableException() {
0625     }
0626   }
0627 
0628   /**
0629    * Recursively traverses the provided object and replaces it and all
0630    * its contents with the appropriate persistent equivalent classes.
0631    *
0632    @param target the object to be analysed and translated into a
0633    *          persistent equivalent.
0634    @return the persistent equivalent value for the provided target
0635    */
0636   public static Serializable getPersistentRepresentation(Object target)
0637           throws PersistenceException {
0638     if(target == nullreturn null;
0639     // first check we don't have it already
0640     Persistence res = existingPersistentReplacements
0641             .get().getFirst().get(new ObjectHolder(target));
0642     if(res != nullreturn res;
0643 
0644     Class<? extends Object> type = target.getClass();
0645     Class<?> newType = getMostSpecificPersistentType(type);
0646     if(newType == null) {
0647       // no special handler
0648       if(target instanceof Serializable)
0649         return (Serializable)target;
0650       else throw new PersistenceException(
0651               "Could not find a serialisable replacement for " + type);
0652     }
0653 
0654     // we have a new type; create the new object, populate and return it
0655     try {
0656       res = (Persistence)newType.newInstance();
0657     }
0658     catch(Exception e) {
0659       throw new PersistenceException(e);
0660     }
0661     if(target instanceof NameBearer) {
0662       StatusListener sListener = (StatusListener)Gate.getListeners().get(
0663               "gate.event.StatusListener");
0664       if(sListener != null) {
0665         sListener.statusChanged("Storing " ((NameBearer)target).getName());
0666       }
0667     }
0668     res.extractDataFromSource(target);
0669     existingPersistentReplacements.get().getFirst().put(new ObjectHolder(target), res);
0670     return res;
0671   }
0672 
0673   public static Object getTransientRepresentation(Object target)
0674           throws PersistenceException, ResourceInstantiationException {
0675     return getTransientRepresentation(target,null,null);
0676   }
0677   
0678   public static Object getTransientRepresentation(Object target, 
0679           String containingControllerName, Map<String,Map<String,Object>> initParamOverrides)
0680           throws PersistenceException, ResourceInstantiationException {
0681 
0682     if(target == null || target instanceof SlashDevSlashNullreturn null;
0683     if(target instanceof Persistence) {
0684       ObjectHolder resultKey = new ObjectHolder(target);
0685       // check the cached values; maybe we have the result already
0686       Object result = existingTransientValues.get().getFirst().get(resultKey);
0687       if(result != nullreturn result;
0688 
0689       // we didn't find the value: create it
0690       if(containingControllerName != null && target instanceof AbstractPersistence) {
0691         ((AbstractPersistence)target).containingControllerName = containingControllerName;
0692         ((AbstractPersistence)target).initParamOverrides = initParamOverrides;
0693       }
0694       result = ((Persistence)target).createObject();
0695       existingTransientValues.get().getFirst().put(resultKey, result);
0696       return result;
0697     }
0698     else return target;
0699   }
0700 
0701   /**
0702    * Finds the most specific persistent replacement type for a given
0703    * class. Look for a type that has a registered persistent equivalent
0704    * starting from the provided class continuing with its superclass and
0705    * implemented interfaces and their superclasses and implemented
0706    * interfaces and so on until a type is found. Classes are considered
0707    * to be more specific than interfaces and in situations of ambiguity
0708    * the most specific types are considered to be the ones that don't
0709    * belong to either java or GATE followed by the ones that belong to
0710    * GATE and followed by the ones that belong to java.
0711    *
0712    * E.g. if there are registered persistent types for
0713    {@link gate.Resource} and for {@link gate.LanguageResource} than
0714    * such a request for a {@link gate.Document} will yield the
0715    * registered type for {@link gate.LanguageResource}.
0716    */
0717   protected static Class<?> getMostSpecificPersistentType(Class<?> type) {
0718     // this list will contain all the types we need to expand to
0719     // superclass +
0720     // implemented interfaces. We start with the provided type and work
0721     // our way
0722     // up the ISA hierarchy
0723     List<Class<?>> expansionSet = new ArrayList<Class<?>>();
0724     expansionSet.add(type);
0725 
0726     // algorithm:
0727     // 1) check the current expansion set
0728     // 2) expand the expansion set
0729 
0730     // at each expansion stage we'll have a class and three lists of
0731     // interfaces:
0732     // the user defined ones; the GATE ones and the java ones.
0733     List<Class<?>> userInterfaces = new ArrayList<Class<?>>();
0734     List<Class<?>> gateInterfaces = new ArrayList<Class<?>>();
0735     List<Class<?>> javaInterfaces = new ArrayList<Class<?>>();
0736     while(!expansionSet.isEmpty()) {
0737       // 1) check the current set
0738       Iterator<Class<?>> typesIter = expansionSet.iterator();
0739       while(typesIter.hasNext()) {
0740         Class<?> result = persistentReplacementTypes.get(typesIter.next());
0741         if(result != null) {
0742           return result;
0743         }
0744       }
0745       // 2) expand the current expansion set;
0746       // the expanded expansion set will need to be ordered according to
0747       // the
0748       // rules (class >> interface; user interf >> gate interf >> java
0749       // interf)
0750 
0751       // at each point we only have at most one class
0752       if(type != nulltype = type.getSuperclass();
0753 
0754       userInterfaces.clear();
0755       gateInterfaces.clear();
0756       javaInterfaces.clear();
0757 
0758       typesIter = expansionSet.iterator();
0759       while(typesIter.hasNext()) {
0760         Class<?> aType = typesIter.next();
0761         Class<?>[] interfaces = aType.getInterfaces();
0762         // distribute them according to their type
0763         for(int i = 0; i < interfaces.length; i++) {
0764           Class<?> anIterf = interfaces[i];
0765           String interfType = anIterf.getName();
0766           if(interfType.startsWith("java")) {
0767             javaInterfaces.add(anIterf);
0768           }
0769           else if(interfType.startsWith("gate")) {
0770             gateInterfaces.add(anIterf);
0771           }
0772           else userInterfaces.add(anIterf);
0773         }
0774       }
0775 
0776       expansionSet.clear();
0777       if(type != nullexpansionSet.add(type);
0778       expansionSet.addAll(userInterfaces);
0779       expansionSet.addAll(gateInterfaces);
0780       expansionSet.addAll(javaInterfaces);
0781     }
0782     // we got out the while loop without finding anything; return null;
0783     return null;
0784   }
0785   
0786   /**
0787    * This method can be used to determine if a specified file (or directory) is
0788    * contained within a given directory.
0789    
0790    * This will check if the real location of file (with all symbolic links removed) is within
0791    * the real location of directory with all symbolic links removed. If part of the file path
0792    * is within the directory but eventually a symbolic link leads out of that directory, the
0793    * file is considered to NOT be inside the directory. 
0794    <p>
0795    * NOTE: this accesses the file system to find the real locations for file and directory. 
0796    * However, if there is a problem accessing the file system, the method will fall back to 
0797    * using the literal path names for checking and NOT produce an error. This is done so that
0798    * any saving operation during which this method is invoked is not aborted and proceeds, 
0799    * creating a file that may be correct apart from one or more serialized URLs. 
0800    
0801    @param file
0802    *          is this file contained within
0803    @param directory
0804    *          this directory
0805    @return true if the file is contained within the directory, false otherwise
0806    */
0807   public static boolean isContainedWithin(File file, File directory) {
0808     // JP: new experimental solution using java.nio 
0809     Path dir = directory.toPath();
0810     try {
0811       dir = dir.toRealPath();
0812     catch (IOException ex) {
0813       // ignore and use the original
0814     }
0815     Path filePath = file.toPath();
0816     try {
0817       filePath = filePath.toRealPath();
0818     catch (IOException ex) {
0819       // ignore and use the original
0820     }
0821     return filePath.startsWith(dir);
0822     
0823     
0824       // JP: Old solution using java.io and walking the tree and getCanonicalFile
0825       /*
0826       File dir = directory;
0827       try {
0828       dir = directory.getCanonicalFile();
0829       } catch (IOException ex) {
0830       // ignore
0831       }
0832       if(!dir.isDirectory()) { return false; }
0833       
0834       File parent = file.getParentFile();
0835       while (parent != null)
0836       {
0837       try {
0838       parent = parent.getCanonicalFile();
0839       } catch (IOException ex) {
0840       // ignore
0841       }
0842       if (parent.equals(dir)) return true;
0843       parent = parent.getParentFile();
0844       }
0845       return false;
0846       */
0847   }
0848   
0849    /**
0850    * Calculates the relative path for a file: URL starting from a given
0851    * context which is also a file: URL.
0852    *
0853    @param context the URL to be used as context.
0854    @param target the URL for which the relative path is computed.
0855    @return a String value representing the relative path. Constructing
0856    *         a URL from the context URL and the relative path should
0857    *         result in the target URL.
0858    */
0859   public static String getRelativePath(URL context, URL target) {
0860     if(context.getProtocol().equals("file")
0861             && target.getProtocol().equals("file")) {
0862       File contextFile = Files.fileFromURL(context);
0863       File targetFile = Files.fileFromURL(target);
0864 
0865       // if the original context URL ends with a slash (i.e. denotes
0866       // a directory), then we pretend we're taking a path relative to
0867       // some file in that directory.  This is because the relative
0868       // path from context file:/home/foo/bar to file:/home/foo/bar/baz
0869       // is bar/baz, whereas the path from file:/home/foo/bar/ - with
0870       // the trailing slash - is just baz.
0871       if(context.toExternalForm().endsWith("/")) {
0872         contextFile = new File(contextFile, "__dummy__");
0873       }
0874 
0875       List<File> targetPathComponents = new ArrayList<File>();
0876       File aFile = targetFile.getParentFile();
0877       while(aFile != null) {
0878         targetPathComponents.add(0, aFile);
0879         aFile = aFile.getParentFile();
0880       }
0881       List<File> contextPathComponents = new ArrayList<File>();
0882       aFile = contextFile.getParentFile();
0883       while(aFile != null) {
0884         contextPathComponents.add(0, aFile);
0885         aFile = aFile.getParentFile();
0886       }
0887       // the two lists can have 0..n common elements (0 when the files
0888       // are
0889       // on separate roots
0890       int commonPathElements = 0;
0891       while(commonPathElements < targetPathComponents.size()
0892               && commonPathElements < contextPathComponents.size()
0893               && targetPathComponents.get(commonPathElements).equals(
0894                       contextPathComponents.get(commonPathElements)))
0895         commonPathElements++;
0896       // construct the string for the relative URL
0897       String relativePath = "";
0898       for(int i = commonPathElements; i < contextPathComponents.size(); i++) {
0899         if(relativePath.length() == 0)
0900           relativePath += "..";
0901         else relativePath += "/..";
0902       }
0903       for(int i = commonPathElements; i < targetPathComponents.size(); i++) {
0904         String aDirName = targetPathComponents.get(i).getName();
0905         if(aDirName.length() == 0) {
0906           aDirName = targetPathComponents.get(i).getAbsolutePath();
0907           if(aDirName.endsWith(File.separator)) {
0908             aDirName = aDirName.substring(0, aDirName.length()
0909                     - File.separator.length());
0910           }
0911         }
0912         // Out.prln("Adding \"" + aDirName + "\" name for " +
0913         // targetPathComponents.get(i));
0914         if(relativePath.length() == 0) {
0915           relativePath += aDirName;
0916         }
0917         else {
0918           relativePath += "/" + aDirName;
0919         }
0920       }
0921       // we have the directory; add the file name
0922       if(relativePath.length() == 0) {
0923         relativePath += targetFile.getName();
0924       }
0925       else {
0926         relativePath += "/" + targetFile.getName();
0927       }
0928 
0929       if(target.toExternalForm().endsWith("/")) {
0930         // original target ended with a slash, so relative path should do too
0931         relativePath += "/";
0932       }
0933       try {
0934         URI relativeURI = new URI(null, null, relativePath, null, null);
0935         return relativeURI.getRawPath();
0936       }
0937       catch(URISyntaxException use) {
0938         throw new GateRuntimeException("Failed to generate relative path " +
0939             "between context: " + context + " and target: " + target, use);
0940       }
0941     }
0942     else {
0943       throw new GateRuntimeException("Both the target and the context URLs "
0944               "need to be \"file:\" URLs!");
0945     }
0946   }
0947 
0948   /**
0949    * Save the given object to the file, without using $gatehome$.
0950    * This is equivalent to  {@link #saveObjectToFile(Object,File,boolean,boolean)} with the
0951    * third and fourth parameter set to false.
0952    * This method exists with this definition to stay backwards compatible with 
0953    * code that was using this method before paths relative to $gatehome$ were supported. 
0954    @param obj The object to persist
0955    @param file The file where to persists to
0956    @throws PersistenceException
0957    @throws IOException 
0958    */
0959   public static void saveObjectToFile(Object obj, File file)
0960     throws PersistenceException, IOException {
0961     saveObjectToFile(obj, file, false, false);
0962   }
0963 
0964   /**
0965    * Save the given object to the given file.
0966    
0967    @param obj The object to persist.
0968    @param file The file where to persist to
0969    @param usegatehome if true (recommended) use $gatehome$ and $resourceshome$ instead of 
0970    * $relpath$ in any saved path URLs if the location of that URL is inside GATE home or 
0971    * inside the resources home directory (if set). 
0972    @param warnaboutgatehome if true, issue a warning message when a saved URL uses $gatehome$
0973    * or $resourceshome$. 
0974    @throws PersistenceException
0975    @throws IOException 
0976    */
0977   public static void saveObjectToFile(Object obj, File file,
0978           boolean usegatehome, boolean warnaboutgatehome)
0979           throws PersistenceException, IOException {
0980     ProgressListener pListener = (ProgressListener)Gate.getListeners()
0981             .get("gate.event.ProgressListener");
0982     StatusListener sListener = (gate.event.StatusListener)Gate
0983             .getListeners().get("gate.event.StatusListener");
0984     long startTime = System.currentTimeMillis();
0985     if(pListener != nullpListener.progressChanged(0);
0986     // The object output stream is used for native serialization,
0987     // but the xstream and filewriter are used for XML serialization.
0988     ObjectOutputStream oos = null;
0989     com.thoughtworks.xstream.XStream xstream = null;
0990     HierarchicalStreamWriter writer = null;
0991     warnAboutGateHome.get().addFirst(warnaboutgatehome);
0992     useGateHome.get().addFirst(usegatehome);
0993     startPersistingTo(file);
0994     try {
0995       if(Gate.getUseXMLSerialization()) {
0996         // Just create the xstream and the filewriter that will later be
0997         // used to serialize objects.
0998         xstream = new XStream(
0999           new SunUnsafeReflectionProvider(new FieldDictionary(new XStream12FieldKeySorter())),
1000           new StaxDriver(new XStream11NameCoder())) {
1001           @Override
1002           protected boolean useXStream11XmlFriendlyMapper() {
1003             return true;
1004           }
1005         };
1006         FileWriter fileWriter = new FileWriter(file);
1007         writer = new PrettyPrintWriter(fileWriter,
1008             new XmlFriendlyNameCoder("-""_"));
1009       }
1010       else {
1011         oos = new ObjectOutputStream(new FileOutputStream(file));
1012       }
1013 
1014       // always write the list of creole URLs first
1015       List<URL> urlList = new ArrayList<URL>(Gate.getCreoleRegister().getDirectories());
1016       // go through all the URLs in the urlList and make sure that there are no duplicate
1017       // file: entries
1018       Set<String> realPaths = new HashSet<String>();
1019       List<URL> cleanedList = new ArrayList<URL>();
1020       for(URL url : urlList) {
1021         if(url.getProtocol().equals("file:")) {
1022           File dir = Files.fileFromURL(url);
1023           try {
1024             dir = dir.getCanonicalFile();
1025           catch(Exception ex) {
1026             // ignore
1027           }
1028           if(!realPaths.contains(dir.getPath())) {
1029             realPaths.add(dir.getPath());
1030             cleanedList.add(url)// add the original URL, we just wanted dir to check if we already had it!
1031           }
1032         else {
1033           cleanedList.add(url);
1034         }
1035       }
1036       Object persistentList = getPersistentRepresentation(cleanedList);
1037 
1038       Object persistentObject = getPersistentRepresentation(obj);
1039 
1040       if(Gate.getUseXMLSerialization()) {
1041         // We need to put the urls and the application itself together
1042         // as xstreams can only hold one object.
1043         GateApplication gateApplication = new GateApplication();
1044         gateApplication.urlList = persistentList;
1045         gateApplication.application = persistentObject;
1046 
1047         // Then do the actual serialization.
1048         xstream.marshal(gateApplication, writer);
1049       }
1050       else {
1051         // This is for native serialization.
1052         oos.writeObject(persistentList);
1053 
1054         // now write the object
1055         oos.writeObject(persistentObject);
1056       }
1057 
1058     }
1059     finally {
1060       finishedPersisting();
1061       if(oos != null) {
1062         oos.flush();
1063         oos.close();
1064       }
1065       if(writer != null) {
1066         // Just make sure that all the xml is written, and the file
1067         // closed.
1068         writer.flush();
1069         writer.close();
1070       }
1071       long endTime = System.currentTimeMillis();
1072       if(sListener != null)
1073         sListener.statusChanged("Storing completed in "
1074                 + NumberFormat.getInstance().format(
1075                         (double)(endTime - startTime1000" seconds");
1076       if(pListener != nullpListener.processFinished();
1077     }
1078   }
1079 
1080   /**
1081    * Set up the thread-local state for a new persistence run.
1082    */
1083   private static void startPersistingTo(File file) {
1084     haveWarnedAboutGateHome.get().addFirst(new BooleanFlag(false));
1085     haveWarnedAboutResourceshome.get().addFirst(new BooleanFlag(false));
1086     persistenceFile.get().addFirst(file);
1087     existingPersistentReplacements.get().addFirst(new HashMap<ObjectHolder,Persistence>());
1088   }
1089 
1090   /**
1091    * Get the file currently being saved by this thread.
1092    */
1093   private static File currentPersistenceFile() {
1094     return persistenceFile.get().getFirst();
1095   }
1096 
1097   private static Boolean currentWarnAboutGateHome() {
1098     return warnAboutGateHome.get().getFirst();
1099   }
1100 
1101   private static Boolean currentUseGateHome() {
1102     return useGateHome.get().getFirst();
1103   }
1104 
1105   private static BooleanFlag currentHaveWarnedAboutGateHome() {
1106     return haveWarnedAboutGateHome.get().getFirst();
1107   }
1108   private static BooleanFlag currentHaveWarnedAboutResourceshome() {
1109     return haveWarnedAboutResourceshome.get().getFirst();
1110   }
1111 
1112   /**
1113    * Clean up the thread-local state for the current persistence run.
1114    */
1115   private static void finishedPersisting() {
1116     persistenceFile.get().removeFirst();
1117     if(persistenceFile.get().isEmpty()) {
1118       persistenceFile.remove();
1119     }
1120     existingPersistentReplacements.get().removeFirst();
1121     if(existingPersistentReplacements.get().isEmpty()) {
1122       existingPersistentReplacements.remove();
1123     }
1124   }
1125 
1126   public static Object loadObjectFromFile(File file)
1127           throws PersistenceException, IOException,
1128           ResourceInstantiationException {
1129     return loadObjectFromUrl(file.toURI().toURL());
1130   }
1131 
1132   public static Object loadObjectFromUrl(URL urlthrows PersistenceException,
1133           IOException, ResourceInstantiationException {
1134     
1135     if(!Gate.isInitialised())
1136       throw new ResourceInstantiationException(
1137               "You must call Gate.init() before you can restore resources");
1138     
1139     ProgressListener pListener = (ProgressListener)Gate.getListeners()
1140             .get("gate.event.ProgressListener");
1141     StatusListener sListener = (gate.event.StatusListener)Gate
1142             .getListeners().get("gate.event.StatusListener");
1143     if(pListener != nullpListener.progressChanged(0);
1144 
1145     startLoadingFrom(url);
1146     //the actual stream obtained from the URL. We keep a reference to this
1147     //so we can ensure it gets closed.
1148     InputStream rawStream = null;
1149     try {
1150       long startTime = System.currentTimeMillis();
1151       // Determine whether the file contains an application serialized in
1152       // xml
1153       // format. Otherwise we will assume that it contains native
1154       // serializations.
1155       boolean xmlStream = isXmlApplicationFile(url);
1156       ObjectInputStream ois = null;
1157       HierarchicalStreamReader reader = null;
1158       XStream xstream = null;
1159       // Make the appropriate kind of streams that will be used, depending
1160       // on
1161       // whether serialization is native or xml.
1162       if(xmlStream) {
1163         // we don't want to strip the BOM on XML.
1164         Reader inputReader = new InputStreamReader(
1165                 rawStream = url.openStream());
1166         try {
1167           XMLInputFactory inputFactory = XMLInputFactory.newInstance();
1168           inputFactory.setProperty(XMLInputFactory.IS_COALESCING, true);
1169           XMLStreamReader xsr = inputFactory.createXMLStreamReader(
1170               url.toExternalForm(), inputReader);
1171           reader = new StaxReader(new QNameMap(), xsr);
1172         }
1173         catch(XMLStreamException xse) {
1174           // make sure the stream is closed, on error
1175           inputReader.close();
1176           throw new PersistenceException("Error creating reader", xse);
1177         }
1178 
1179         xstream = new XStream(new StaxDriver(new XStream11NameCoder())) {
1180           @Override
1181           protected boolean useXStream11XmlFriendlyMapper() {
1182             return true;
1183           }
1184         };
1185         // make XStream load classes through the GATE ClassLoader
1186         xstream.setClassLoader(Gate.getClassLoader());
1187         // make the XML stream appear as a normal ObjectInputStream
1188         ois = xstream.createObjectInputStream(reader);
1189       }
1190       else {
1191         // use GateAwareObjectInputStream to load classes through the
1192         // GATE ClassLoader if they can't be loaded through the one
1193         // ObjectInputStream would normally use
1194         ois = new GateAwareObjectInputStream(url.openStream());
1195 
1196       }
1197       Object res = null;
1198       try {
1199         // first read the list of creole URLs.
1200         @SuppressWarnings("unchecked")
1201         Iterator<URL> urlIter =
1202           ((Collection<URL>)getTransientRepresentation(ois.readObject()))
1203           .iterator();
1204 
1205         // and re-register them
1206         while(urlIter.hasNext()) {
1207           URL anUrl = urlIter.next();
1208           try {
1209             Gate.getCreoleRegister().registerDirectories(anUrl,false);
1210           }
1211           catch(GateException ge) {
1212             Err.prln("Could not reload creole directory "
1213                     + anUrl.toExternalForm());
1214             ge.printStackTrace(Err.getPrintWriter());
1215           }
1216         }
1217 
1218         // now we can read the saved object in the presence of all
1219         // the right plugins
1220         res = ois.readObject();
1221 
1222         // ensure a fresh start
1223         clearCurrentTransients();
1224         res = getTransientRepresentation(res);
1225         long endTime = System.currentTimeMillis();
1226         if(sListener != null)
1227           sListener.statusChanged("Loading completed in "
1228                   + NumberFormat.getInstance().format(
1229                           (double)(endTime - startTime1000" seconds");
1230         return res;
1231       }
1232       catch(ResourceInstantiationException rie) {
1233         if(sListener != nullsListener.statusChanged(
1234           "Failure during instantiation of resources.");
1235         throw rie;
1236       }
1237       catch(PersistenceException pe) {
1238         if(sListener != nullsListener.statusChanged(
1239           "Failure during persistence operations.");
1240         throw pe;
1241       }
1242       catch(Exception ex) {
1243         if(sListener != nullsListener.statusChanged("Loading failed!");
1244         throw new PersistenceException(ex);
1245       finally {
1246         //make sure the stream gets closed
1247         if (ois != nullois.close();
1248         if(reader != nullreader.close();
1249       }
1250     }
1251     finally {
1252       if(rawStream != nullrawStream.close();
1253       finishedLoading();
1254       if(pListener != nullpListener.processFinished();
1255     }
1256   }
1257 
1258   /**
1259    * Set up the thread-local state for the current loading run.
1260    */
1261   private static void startLoadingFrom(URL url) {
1262     persistenceURL.get().addFirst(url);
1263     existingTransientValues.get().addFirst(new HashMap<ObjectHolder,Object>());
1264   }
1265 
1266   /**
1267    * Clear the current list of transient replacements without
1268    * popping them off the stack.
1269    */
1270   private static void clearCurrentTransients() {
1271     existingTransientValues.get().getFirst().clear();
1272   }
1273 
1274   /**
1275    * Get the URL currently being loaded by this thread.
1276    */
1277   private static URL currentPersistenceURL() {
1278     return persistenceURL.get().getFirst();
1279   }
1280 
1281   /**
1282    * Clean up the thread-local state at the end of a loading run.
1283    */
1284   private static void finishedLoading() {
1285     persistenceURL.get().removeFirst();
1286     if(persistenceURL.get().isEmpty()) {
1287       persistenceURL.remove();
1288     }
1289     existingTransientValues.get().removeFirst();
1290     if(existingTransientValues.get().isEmpty()) {
1291       existingTransientValues.remove();
1292     }
1293   }
1294 
1295   /**
1296    * Determine whether the URL contains a GATE application serialized
1297    * using XML.
1298    *
1299    @param url The URL to check.
1300    @return true if the URL refers to an xml serialized application,
1301    *         false otherwise.
1302    */
1303   private static boolean isXmlApplicationFile(URL url)
1304           throws java.io.IOException {
1305     if(DEBUG) {
1306       System.out.println("Checking whether file is xml");
1307     }
1308     String firstLine;
1309     BufferedReader fileReader = null;
1310     try {
1311       fileReader = new BomStrippingInputStreamReader(url.openStream());
1312       firstLine = fileReader.readLine();
1313     finally {
1314       if(fileReader != nullfileReader.close();
1315     }
1316     if(firstLine == null) {
1317       return false;
1318     }
1319     for(String startOfXml : STARTOFXMLAPPLICATIONFILES) {
1320       if(firstLine.length() >= startOfXml.length()
1321               && firstLine.substring(0, startOfXml.length()).equals(startOfXml)) {
1322         if(DEBUG) {
1323           System.out.println("isXMLApplicationFile = true");
1324         }
1325         return true;
1326       }
1327     }
1328     if(DEBUG) {
1329       System.out.println("isXMLApplicationFile = false");
1330     }
1331     return false;
1332   }
1333 
1334   private static final String[] STARTOFXMLAPPLICATIONFILES = {
1335       "<gate.util.persistence.GateApplication>""<?xml""<!DOCTYPE"};
1336 
1337   /**
1338    * Sets the persistent equivalent type to be used to (re)store a given
1339    * type of transient objects.
1340    *
1341    @param transientType the type that will be replaced during
1342    *          serialisation operations
1343    @param persistentType the type used to replace objects of transient
1344    *          type when serialising; this type needs to extend
1345    *          {@link Persistence}.
1346    @return the persitent type that was used before this mapping if
1347    *         such existed.
1348    */
1349   public static Class<?> registerPersistentEquivalent(Class<?> transientType,
1350           Class<?> persistentTypethrows PersistenceException {
1351     if(!Persistence.class.isAssignableFrom(persistentType)) {
1352       throw new PersistenceException(
1353               "Persistent equivalent types have to implement "
1354                       + Persistence.class.getName() "!\n"
1355                       + persistentType.getName() " does not!");
1356     }
1357     return persistentReplacementTypes.put(transientType, persistentType);
1358   }
1359 
1360   /**
1361    * A dictionary mapping from java type (Class) to the type (Class)
1362    * that can be used to store persistent data for the input type.
1363    */
1364   private static Map<Class<?>,Class<?>> persistentReplacementTypes;
1365 
1366   /**
1367    * Stores the persistent replacements created during a transaction in
1368    * order to avoid creating two different persistent copies for the
1369    * same object. The keys used are {@link ObjectHolder}s that contain
1370    * the transient values being converted to persistent equivalents.
1371    */
1372   private static ThreadLocal<LinkedList<Map<ObjectHolder,Persistence>>> existingPersistentReplacements;
1373 
1374   /**
1375    * Stores the transient values obtained from persistent replacements
1376    * during a transaction in order to avoid creating two different
1377    * transient copies for the same persistent replacement. The keys used
1378    * are {@link ObjectHolder}s that hold persistent equivalents. The
1379    * values are the transient values created by the persistence
1380    * equivalents.
1381    */
1382   private static ThreadLocal<LinkedList<Map<ObjectHolder,Object>>> existingTransientValues;
1383 
1384   //private static ClassComparator classComparator = new ClassComparator();
1385 
1386   /**
1387    * The file currently used to write the persisten representation. Will
1388    * only have a non-null value during storing operations.
1389    */
1390   static ThreadLocal<LinkedList<File>> persistenceFile;
1391 
1392   /**
1393    * The URL currently used to read the persistent representation when
1394    * reading from a URL. Will only be non-null during restoring
1395    * operations.
1396    */
1397   static ThreadLocal<LinkedList<URL>> persistenceURL;
1398 
1399   private static final class BooleanFlag {
1400     BooleanFlag(boolean initial) {
1401       flag = initial;
1402     }
1403     private boolean flag;
1404     public void setValue(boolean value) {
1405       flag = value;
1406     }
1407     public boolean getValue() {
1408       return flag;
1409     }
1410   }
1411 
1412   private static ThreadLocal<LinkedList<Boolean>> useGateHome;
1413   private static ThreadLocal<LinkedList<Boolean>> warnAboutGateHome;
1414   private static ThreadLocal<LinkedList<BooleanFlag>> haveWarnedAboutGateHome;
1415   private static ThreadLocal<LinkedList<BooleanFlag>> haveWarnedAboutResourceshome;
1416   
1417   
1418   static {
1419     persistentReplacementTypes = new HashMap<Class<?>, Class<?>>();
1420     try {
1421       // VRs don't get saved, ....sorry guys :)
1422       registerPersistentEquivalent(VisualResource.class, SlashDevSlashNull.class);
1423 
1424       registerPersistentEquivalent(URL.class, URLHolder.class);
1425 
1426       registerPersistentEquivalent(Map.class, MapPersistence.class);
1427       registerPersistentEquivalent(Collection.class, CollectionPersistence.class);
1428 
1429       registerPersistentEquivalent(ProcessingResource.class, PRPersistence.class);
1430 
1431       registerPersistentEquivalent(DataStore.class, DSPersistence.class);
1432 
1433       registerPersistentEquivalent(LanguageResource.class, LRPersistence.class);
1434 
1435       registerPersistentEquivalent(Corpus.class, CorpusPersistence.class);
1436 
1437       registerPersistentEquivalent(Controller.class, ControllerPersistence.class);
1438 
1439       registerPersistentEquivalent(ConditionalController.class,
1440               ConditionalControllerPersistence.class);
1441 
1442       registerPersistentEquivalent(ConditionalSerialAnalyserController.class,
1443               ConditionalSerialAnalyserControllerPersistence.class);
1444 
1445       registerPersistentEquivalent(LanguageAnalyser.class,
1446               LanguageAnalyserPersistence.class);
1447 
1448       registerPersistentEquivalent(SerialAnalyserController.class,
1449               SerialAnalyserControllerPersistence.class);
1450 
1451       registerPersistentEquivalent(gate.creole.AnalyserRunningStrategy.class,
1452               AnalyserRunningStrategyPersistence.class);
1453       
1454       registerPersistentEquivalent(gate.creole.RunningStrategy.UnconditionalRunningStrategy.class,
1455               UnconditionalRunningStrategyPersistence.class);
1456     }
1457     catch(PersistenceException pe) {
1458       // builtins shouldn't raise this
1459       pe.printStackTrace();
1460     }
1461 
1462     /**
1463      * Thread-local stack.
1464      */
1465     class ThreadLocalStack<T> extends ThreadLocal<LinkedList<T>> {
1466       @Override
1467       protected LinkedList<T> initialValue() {
1468         return new LinkedList<T>();
1469       }
1470     }
1471 
1472     existingPersistentReplacements = new ThreadLocalStack<Map<ObjectHolder,Persistence>>();
1473     existingTransientValues = new ThreadLocalStack<Map<ObjectHolder,Object>>();
1474     persistenceFile = new ThreadLocalStack<File>();
1475     persistenceURL = new ThreadLocalStack<URL>();
1476     useGateHome = new ThreadLocalStack<Boolean>();
1477     warnAboutGateHome = new ThreadLocalStack<Boolean>();
1478     haveWarnedAboutGateHome = new ThreadLocalStack<BooleanFlag>();
1479     haveWarnedAboutResourceshome = new ThreadLocalStack<BooleanFlag>();
1480   }
1481 }