1   /*
2    *  Copyright (c) 1998-2005, The University of Sheffield.
3    *
4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
5    *  software, licenced under the GNU Library General Public License,
6    *  Version 2, June 1991 (in the distribution as file licence.html,
7    *  and also available at http://gate.ac.uk/gate/licence.html).
8    *
9    *  Valentin Tablan 25/10/2001
10   *
11   *  $Id: PersistenceManager.java,v 1.18 2005/02/17 16:53:58 ian Exp $
12   *
13   */
14  package gate.util.persistence;
15  
16  import java.io.*;
17  import java.net.MalformedURLException;
18  import java.net.URL;
19  import java.text.NumberFormat;
20  import java.util.*;
21  
22  import gate.*;
23  import gate.creole.*;
24  import gate.event.ProgressListener;
25  import gate.event.StatusListener;
26  import gate.gui.MainFrame;
27  import gate.persist.PersistenceException;
28  import gate.util.*;
29  
30  /**
31   * This class provides utility methods for saving resources through
32   * serialisation via static methods.
33   * 
34   * It now supports both native and xml serialization.
35   */
36  public class PersistenceManager {
37    
38    private static final boolean DEBUG = false;
39  
40    /**
41     * A reference to an object; it uses the identity hashcode and the equals
42     * defined by object identity.
43     * These values will be used as keys in the
44     * {link #existingPersitentReplacements} map.
45     */
46    static protected class ObjectHolder{
47      ObjectHolder(Object target){
48        this.target = target;
49      }
50  
51      public int hashCode(){
52        return System.identityHashCode(target);
53      }
54  
55      public boolean equals(Object obj){
56        if(obj instanceof ObjectHolder)
57          return ((ObjectHolder)obj).target == this.target;
58        else return false;
59      }
60  
61      public Object getTarget(){
62        return target;
63      }
64  
65      private Object target;
66    }//static class ObjectHolder{
67  
68    /**
69     * This class is used as a marker for types that should NOT be serialised when
70     * saving the state of a gate object.
71     * Registering this type as the persistent equivalent for a specific class
72     * (via {@link PersistenceManager#registerPersitentEquivalent(Class , Class)})
73     * effectively stops all values of the specified type from being serialised.
74     *
75     * Maps that contain values that should not be serialised will have that entry
76     * removed. In any other places where such values occur they will be replaced
77     * by null after deserialisation.
78     */
79    public static class SlashDevSlashNull implements Persistence{
80      /**
81       * Does nothing
82       */
83      public void extractDataFromSource(Object source)throws PersistenceException{
84      }
85  
86      /**
87       * Returns null
88       */
89      public Object createObject()throws PersistenceException,
90                                         ResourceInstantiationException{
91        return null;
92      }
93      static final long serialVersionUID = -8665414981783519937L;
94    }
95  
96    /**
97     * URLs get upset when serialised and deserialised so we need to convert them
98     * to strings for storage.
99     * In the case of "file:" URLs the relative path to the persistence
100    * file will actually be stored.
101    */
102   public static class URLHolder implements Persistence{
103     /**
104      * Populates this Persistence with the data that needs to be stored from the
105      * original source object.
106      */
107     public void extractDataFromSource(Object source)throws PersistenceException{
108       try{
109         URL url = (URL)source;
110         if(url.getProtocol().equals("file")){
111           try{
112             urlString = relativePathMarker +
113                         getRelativePath(persistenceFile.toURL(), url);
114           }catch(MalformedURLException mue){
115             urlString = ((URL)source).toExternalForm();
116           }
117         }else{
118           urlString = ((URL)source).toExternalForm();
119         }
120       }catch(ClassCastException cce){
121         throw new PersistenceException(cce);
122       }
123     }
124 
125     /**
126      * Creates a new object from the data contained. This new object is supposed
127      * to be a copy for the original object used as source for data extraction.
128      */
129     public Object createObject()throws PersistenceException{
130       try{
131         if(urlString.startsWith(relativePathMarker)){
132           URL context = persistenceURL;
133           return new URL(context,
134                          urlString.substring(relativePathMarker.length()));
135         }else return new URL(urlString);
136       }catch(MalformedURLException mue){
137         throw new PersistenceException(mue);
138       }
139     }
140     String urlString;
141     /**
142      * This string will be used to start the serialisation of URL that represent
143      * relative paths.
144      */
145     private static final String relativePathMarker = "$relpath$";
146     static final long serialVersionUID = 7943459208429026229L;
147   }
148 
149   public static class ClassComparator implements Comparator{
150     /**
151      * Compares two {@link Class} values in terms of specificity; the more
152      * specific class is said to be "smaller" than the more generic
153      * one hence the {@link Object} class is the "largest" possible
154      * class.
155      * When two classes are not comparable (i.e. not assignable from each other)
156      * in either direction a NotComparableException will be thrown.
157      * both input objects should be Class values otherwise a
158      * {@link ClassCastException} will be thrown.
159      *
160      */
161     public int compare(Object o1, Object o2){
162       Class c1 = (Class)o1;
163       Class c2 = (Class)o2;
164 
165       if(c1.equals(c2)) return 0;
166       if(c1.isAssignableFrom(c2)) return 1;
167       if(c2.isAssignableFrom(c1)) return -1;
168       throw new NotComparableException();
169     }
170   }
171 
172   /**
173    * Thrown by a comparator when the values provided for comparison are not
174    * comparable.
175    */
176   public static class NotComparableException extends RuntimeException{
177     public NotComparableException(String message){
178       super(message);
179     }
180     public NotComparableException(){
181     }
182   }
183 
184   /**
185    * Recursively traverses the provided object and replaces it and all its
186    * contents with the appropriate persistent equivalent classes.
187    *
188    * @param target the object to be analysed and translated into a persistent
189    * equivalent.
190    * @return the persistent equivalent value for the provided target
191    */
192   static Serializable getPersistentRepresentation(Object target)
193                       throws PersistenceException{
194     if(target == null) return null;
195     //first check we don't have it already
196     Persistence res = (Persistence)existingPersitentReplacements.
197                       get(new ObjectHolder(target));
198     if(res != null) return res;
199 
200     Class type = target.getClass();
201     Class newType = getMostSpecificPersistentType(type);
202     if(newType == null){
203       //no special handler
204       if(target instanceof Serializable) return (Serializable)target;
205       else throw new PersistenceException(
206                      "Could not find a serialisable replacement for " + type);
207     }
208 
209     //we have a new type; create the new object, populate and return it
210     try{
211       res = (Persistence)newType.newInstance();
212     }catch(Exception e){
213       throw new PersistenceException(e);
214     }
215     if(target instanceof NameBearer){
216       StatusListener sListener = (StatusListener)MainFrame.getListeners().
217                                  get("gate.event.StatusListener");
218       if(sListener != null){
219         sListener.statusChanged("Storing " + ((NameBearer)target).getName());
220       }
221     }
222     res.extractDataFromSource(target);
223     existingPersitentReplacements.put(new ObjectHolder(target), res);
224     return res;
225   }
226 
227 
228   static Object getTransientRepresentation(Object target)
229                       throws PersistenceException,
230                              ResourceInstantiationException{
231 
232     if(target == null || target instanceof SlashDevSlashNull) return null;
233     if(target instanceof Persistence){
234       Object resultKey = new ObjectHolder(target);
235       //check the cached values; maybe we have the result already
236       Object result = existingTransientValues.get(resultKey);
237       if(result != null) return result;
238 
239       //we didn't find the value: create it
240       result = ((Persistence)target).createObject();
241       existingTransientValues.put(resultKey, result);
242       return result;
243     }else return target;
244   }
245 
246 
247   /**
248    * Finds the most specific persistent replacement type for a given class.
249    * Look for a type that has a registered persistent equivalent starting from
250    * the provided class continuing with its superclass and implemented
251    * interfaces and their superclasses and implemented interfaces and so on
252    * until a type is found.
253    * Classes are considered to be more specific than interfaces and in
254    * situations of ambiguity the most specific types are considered to be the
255    * ones that don't belong to either java or GATE followed by the ones  that
256    * belong to GATE and followed by the ones that belong to java.
257    *
258    * E.g. if there are registered persitent types for {@link gate.Resource} and
259    * for {@link gate.LanguageResource} than such a request for a
260    * {@link gate.Document} will yield the registered type for
261    * {@link gate.LanguageResource}.
262    */
263   protected static Class getMostSpecificPersistentType(Class type){
264     //this list will contain all the types we need to expand to superclass +
265     //implemented interfaces. We start with the provided type and work our way
266     //up the ISA hierarchy
267     List expansionSet = new ArrayList();
268     expansionSet.add(type);
269 
270     //algorithm:
271     //1) check the current expansion set
272     //2) expand the expansion set
273 
274     //at each expansion stage we'll have a class and three lists of interfaces:
275     //the user defined ones; the GATE ones and the java ones.
276     List userInterfaces = new ArrayList();
277     List gateInterfaces = new ArrayList();
278     List javaInterfaces = new ArrayList();
279     while(!expansionSet.isEmpty()){
280       //1) check the current set
281       Iterator typesIter = expansionSet.iterator();
282       while(typesIter.hasNext()){
283         Class result = (Class)persistentReplacementTypes.get(typesIter.next());
284         if(result != null){
285           return result;
286         }
287       }
288       //2) expand the current expansion set;
289       //the expanded expansion set will need to be ordered according to the
290       //rules (class >> interface; user interf >> gate interf >> java interf)
291 
292       //at each point we only have at most one class
293       if(type != null) type = type.getSuperclass();
294 
295 
296       userInterfaces.clear();
297       gateInterfaces.clear();
298       javaInterfaces.clear();
299 
300       typesIter = expansionSet.iterator();
301       while(typesIter.hasNext()){
302         Class aType = (Class)typesIter.next();
303         Class[] interfaces = aType.getInterfaces();
304         //distribute them according to their type
305         for(int i = 0; i < interfaces.length; i++){
306           Class anIterf = interfaces[i];
307           String interfType = anIterf.getName();
308           if(interfType.startsWith("java")){
309             javaInterfaces.add(anIterf);
310           }else if(interfType.startsWith("gate")){
311             gateInterfaces.add(anIterf);
312           }else userInterfaces.add(anIterf);
313         }
314       }
315 
316       expansionSet.clear();
317       if(type != null) expansionSet.add(type);
318       expansionSet.addAll(userInterfaces);
319       expansionSet.addAll(gateInterfaces);
320       expansionSet.addAll(javaInterfaces);
321     }
322     //we got out the while loop without finding anything; return null;
323     return null;
324 
325 //    SortedSet possibleTypesSet = new TreeSet(classComparator);
326 //
327 //    Iterator typesIter = persistentReplacementTypes.keySet().iterator();
328 //    //we store all the types that could not be analysed
329 //    List lostTypes = new ArrayList();
330 //    while(typesIter.hasNext()){
331 //      Class aType = (Class)typesIter.next();
332 //      if(aType.isAssignableFrom(type)){
333 //        try{
334 //          possibleTypesSet.add(aType);
335 //        }catch(NotComparableException nce){
336 //          lostTypes.add(aType);
337 //        }
338 //      }
339 //    }
340 //
341 //    if(possibleTypesSet.isEmpty())  return null;
342 //
343 //    Class resultKey = (Class)possibleTypesSet.first();
344 //    Class result = (Class) persistentReplacementTypes.get(resultKey);
345 //
346 //    //check whether we lost anything important
347 //    typesIter = lostTypes.iterator();
348 //    while(typesIter.hasNext()){
349 //      Class aType = (Class)typesIter.next();
350 //      try{
351 //        if(classComparator.compare(aType, resultKey) < 0){
352 //          Err.prln("Found at least two incompatible most specific types for " +
353 //          type.getName() + ":\n " +
354 //          aType.toString() + " was discarded in favour of" + result.getName() +
355 //          ".\nSome of your saved results may have been lost!");
356 //        }
357 //      }catch(NotComparableException nce){
358 //        Err.prln("Found at least two incompatible most specific types for " +
359 //        type.getName() + ":\n " +
360 //        aType.toString() + " was discarded in favour of" + result.getName() +
361 //        ".\nSome of your saved results may have been lost!");
362 //      }
363 //    }
364 //
365 //    return result;
366   }
367 
368   /**
369    * Calculates the relative path for a file: URL starting from a given context
370    * which is also a file: URL.
371    * @param context the URL to be used as context.
372    * @param target the URL for which the relative path is computed.
373    * @return a String value representing the relative path. Constructing a URL
374    * from the context URL and the relative path should result in the target URL.
375    */
376   public static String getRelativePath(URL context, URL target){
377     if(context.getProtocol().equals("file") &&
378        target.getProtocol().equals("file")){
379 
380       //normalise the two file URLS
381       try{
382         context = new File(context.getPath()).toURL();
383       }catch(MalformedURLException mue){
384         throw new GateRuntimeException("Could not normalise the file URL:\n"+
385                                        context + "\nThe problem was:\n" +mue);
386       }
387       try{
388         target = new File(target.getPath()).toURL();
389       }catch(MalformedURLException mue){
390         throw new GateRuntimeException("Could not normalise the file URL:\n"+
391                                        target + "\nThe problem was:\n" +mue);
392       }
393       List targetPathComponents = new ArrayList();
394       File aFile = new File(target.getPath()).getParentFile();
395       while(aFile != null){
396         targetPathComponents.add(0, aFile);
397         aFile = aFile.getParentFile();
398       }
399       List contextPathComponents = new ArrayList();
400       aFile = new File(context.getPath()).getParentFile();
401       while(aFile != null){
402         contextPathComponents.add(0, aFile);
403         aFile = aFile.getParentFile();
404       }
405       //the two lists can have 0..n common elements (0 when the files are
406       //on separate roots
407       int commonPathElements = 0;
408       while(commonPathElements < targetPathComponents.size() &&
409             commonPathElements < contextPathComponents.size() &&
410             targetPathComponents.get(commonPathElements).
411             equals(contextPathComponents.get(commonPathElements)))
412         commonPathElements++;
413       //construct the string for the relative URL
414       String relativePath = "";
415       for(int i = commonPathElements;
416           i < contextPathComponents.size(); i++){
417         if(relativePath.length() == 0) relativePath += "..";
418         else relativePath += "/..";
419       }
420       for(int i = commonPathElements; i < targetPathComponents.size(); i++){
421         String aDirName = ((File)targetPathComponents.get(i)).getName();
422         if(aDirName.length() == 0){
423           aDirName = ((File)targetPathComponents.get(i)).getAbsolutePath();
424           if(aDirName.endsWith(File.separator)){
425             aDirName = aDirName.substring(0, aDirName.length() -
426                                              File.separator.length());
427           }
428         }
429 //Out.prln("Adding \"" + aDirName + "\" name for " + targetPathComponents.get(i));
430         if(relativePath.length() == 0){
431           relativePath += aDirName;
432         }else{
433           relativePath += "/" + aDirName;
434         }
435       }
436       //we have the directory; add the file name
437       if(relativePath.length() == 0){
438         relativePath += new File(target.getPath()).getName();
439       }else{
440         relativePath += "/" + new File(target.getPath()).getName();
441       }
442 
443       return relativePath;
444     }else{
445       throw new GateRuntimeException("Both the target and the context URLs " +
446                                      "need to be \"file:\" URLs!");
447     }
448   }
449 
450   public static void saveObjectToFile(Object obj, File file)
451                      throws PersistenceException, IOException {
452     ProgressListener pListener = (ProgressListener)MainFrame.getListeners()
453                                  .get("gate.event.ProgressListener");
454     StatusListener sListener = (gate.event.StatusListener)
455                                MainFrame.getListeners().
456                                get("gate.event.StatusListener");
457     long startTime = System.currentTimeMillis();
458     if(pListener != null) pListener.progressChanged(0);
459     // The object output stream is used for native serialization,
460     // but the xstream and filewriter are used for XML serialization.
461     ObjectOutputStream oos = null;
462     com.thoughtworks.xstream.XStream xstream = null;
463     FileWriter fileWriter = null;
464     persistenceFile = file;
465     try{
466       //insure a clean start
467       existingPersitentReplacements.clear();
468       existingPersitentReplacements.clear();
469 
470       if (Gate.getUseXMLSerialization()) {
471         // Just create the xstream and the filewriter that will later be
472         // used to serialize objects.
473         xstream = new com.thoughtworks.xstream.XStream();
474         fileWriter = new FileWriter(file);
475       } else {
476         oos = new ObjectOutputStream(new FileOutputStream(file));
477       }
478 
479       //always write the list of creole URLs first
480       List urlList = new ArrayList(Gate.getCreoleRegister().getDirectories());
481       Object persistentList = getPersistentRepresentation(urlList);
482       
483       Object persistentObject = getPersistentRepresentation(obj);
484 
485       if (Gate.getUseXMLSerialization()) {
486         // We need to put the urls and the application itself together
487         // as xstreams can only hold one object.
488         GateApplication gateApplication = new GateApplication();
489         gateApplication.urlList = persistentList;
490         gateApplication.application  = persistentObject;
491         
492         // Then do the actual serialization.
493         xstream.toXML(gateApplication, fileWriter);
494       } else {
495         // This is for native serialization.
496         oos.writeObject(persistentList);
497 
498         //now write the object
499         oos.writeObject(persistentObject);
500       }
501 
502     }finally{
503       persistenceFile = null;
504       if(oos != null){
505         oos.flush();
506         oos.close();
507       }
508       if (fileWriter != null) {
509         // Just make sure that all the xml is written, and the file closed.
510         fileWriter.flush();
511         fileWriter.close();
512       }
513       long endTime = System.currentTimeMillis();
514       if(sListener != null) sListener.statusChanged(
515           "Storing completed in " +
516           NumberFormat.getInstance().format(
517           (double)(endTime - startTime) / 1000) + " seconds");
518       if(pListener != null) pListener.processFinished();
519     }
520   }
521 
522   public static Object loadObjectFromFile(File file)
523                      throws PersistenceException, IOException,
524                             ResourceInstantiationException {
525     return loadObjectFromUrl(file.toURL());
526   }
527 
528   public static Object loadObjectFromUrl(URL url)
529                      throws PersistenceException, IOException,
530                             ResourceInstantiationException {
531     exceptionOccured = false;
532     ProgressListener pListener = (ProgressListener)MainFrame.getListeners().
533                                  get("gate.event.ProgressListener");
534     StatusListener sListener = (gate.event.StatusListener)
535                                 MainFrame.getListeners()
536                                 .get("gate.event.StatusListener");
537     if(pListener != null) pListener.progressChanged(0);
538     long startTime = System.currentTimeMillis();
539     persistenceURL = url;
540     // Determine whether the file contains an application serialized in xml
541     // format. Otherwise we will assume that it contains native serializations.
542     boolean xmlStream = isXmlApplicationFile(url);
543     ObjectInputStream ois = null;
544     java.io.Reader reader = null;
545     com.thoughtworks.xstream.XStream xstream = null;
546     // Make the appropriate kind of streams that will be used, depending on
547     // whether serialization is native or xml.
548     if (xmlStream) { 
549       reader = new java.io.InputStreamReader(url.openStream());
550       xstream = new com.thoughtworks.xstream.XStream();
551     } else {
552       ois = new ObjectInputStream(url.openStream());
553     }
554     Object res = null;
555     try{
556       Iterator urlIter;
557       // If we're using xml serialization, first read everything from
558       // the file.
559       GateApplication gateApplication = null;
560       if (xmlStream) {
561         if (DEBUG)
562           System.out.println("About to load application");
563         // Actually load the application
564         gateApplication = (GateApplication)xstream.fromXML(reader);
565         reader.close();
566         if (DEBUG)
567           System.out.println("About to extract url list");
568         // Extract an iterator to the URLs.
569         urlIter = 
570           ((Collection)getTransientRepresentation(gateApplication.urlList))
571       .iterator();
572         if (DEBUG)
573           System.out.println("URL list loaded");
574       } else {
575         // first read the list of creole URLs. This is for when we are using
576         // native serialization.
577         urlIter = ((Collection)
578        
579                           getTransientRepresentation(ois.readObject())).
580                           iterator();
581       }
582       while(urlIter.hasNext()){
583         URL anUrl = (URL)urlIter.next();
584         try{
585           Gate.getCreoleRegister().registerDirectories(anUrl);
586         }catch(GateException ge){
587           Err.prln("Could not reload creole directory " +
588                    anUrl.toExternalForm());
589         }
590       }
591       
592       //now we can read the saved object
593       if (xmlStream) {
594         if (DEBUG)
595           System.out.println("About to load application itself");
596         // With an xml stream, we already read the object, so we just
597         // have to extract it.
598         res = gateApplication.application;
599         if (DEBUG)
600           System.out.println("Application loaded");
601       } else {
602         // With a native stream just read the object from it.
603         res = ois.readObject();
604         ois.close();
605       }
606 
607       //ensure a fresh start
608       existingTransientValues.clear();
609       res = getTransientRepresentation(res);
610       existingTransientValues.clear();
611       long endTime = System.currentTimeMillis();
612               if(sListener != null) sListener.statusChanged(
613                   "Loading completed in " +
614                   NumberFormat.getInstance().format(
615                   (double)(endTime - startTime) / 1000) + " seconds");
616               if(pListener != null) pListener.processFinished();
617       if(exceptionOccured){
618         throw new PersistenceException("There were errors!\n" +
619                                        "See messages for details...");
620       }
621       return res;
622     }catch(ResourceInstantiationException rie){
623       if(sListener != null) sListener.statusChanged("Loading failed!");
624       if(pListener != null) pListener.processFinished();
625       throw rie;
626     }catch(Exception ex){
627       if(sListener != null) sListener.statusChanged("Loading failed!");
628       if(pListener != null) pListener.processFinished();
629       throw new PersistenceException(ex);
630     }finally{
631       persistenceURL = null;
632     }
633   }
634 
635   /**
636    * Determine whether the URL contains a GATE application serialized 
637    * using XML.
638    * 
639    * @param url The URL to check.
640    * @return true if the URL refers to an xml serialized application,
641    * false otherwise.
642    */
643   private static boolean isXmlApplicationFile(URL url) 
644   throws java.io.IOException {
645     if (DEBUG) {
646       System.out.println("Checking whether file is xml");
647     }
648   java.io.BufferedReader fileReader = 
649       new java.io.BufferedReader(
650           new java.io.InputStreamReader(url.openStream()));
651     String firstLine = fileReader.readLine();
652     fileReader.close();
653     
654     if (DEBUG) {
655       System.out.println("isXMLApplicationFile = " + 
656           (firstLine.length() >=  STARTOFXMLAPPLICATIONFILES.length()
657         && firstLine.substring(0, STARTOFXMLAPPLICATIONFILES.length())
658         .equals(STARTOFXMLAPPLICATIONFILES)));
659     }
660     
661     return firstLine.length() >=  STARTOFXMLAPPLICATIONFILES.length()
662   && firstLine.substring(0, STARTOFXMLAPPLICATIONFILES.length())
663   .equals(STARTOFXMLAPPLICATIONFILES);
664   }
665   
666   private static final String STARTOFXMLAPPLICATIONFILES =
667     "<gate.util.persistence.GateApplication>";
668   
669   /**
670    * Sets the persistent equivalent type to be used to (re)store a given type
671    * of transient objects.
672    * @param transientType the type that will be replaced during serialisation
673    * operations
674    * @param persistentType the type used to replace objects of transient type
675    * when serialising; this type needs to extend {@link Persistence}.
676    * @return the persitent type that was used before this mapping if such
677    * existed.
678    */
679   public static Class registerPersitentEquivalent(Class transientType,
680                                           Class persistentType)
681                throws PersistenceException{
682     if(!Persistence.class.isAssignableFrom(persistentType)){
683       throw new PersistenceException(
684         "Persistent equivalent types have to implement " +
685         Persistence.class.getName() + "!\n" +
686         persistentType.getName() + " does not!");
687     }
688     return (Class)persistentReplacementTypes.put(transientType, persistentType);
689   }
690 
691 
692   /**
693    * A dictionary mapping from java type (Class) to the type (Class) that can
694    * be used to store persistent data for the input type.
695    */
696   private static Map persistentReplacementTypes;
697 
698   /**
699    * Stores the persistent replacements created during a transaction in order to
700    * avoid creating two different persistent copies for the same object.
701    * The keys used are {@link ObjectHolder}s that contain the transient values
702    * being converted to persistent equivalents.
703    */
704   private static Map existingPersitentReplacements;
705 
706   /**
707    * Stores the transient values obtained from persistent replacements during a
708    * transaction in order to avoid creating two different transient copies for
709    * the same persistent replacement.
710    * The keys used are {@link ObjectHolder}s that hold persistent equivalents.
711    * The values are the transient values created by the persisten equivalents.
712    */
713   private static Map existingTransientValues;
714 
715   private static ClassComparator classComparator = new ClassComparator();
716 
717   /**
718    * This flag is set to true when an exception occurs. It is used in order to
719    * allow error reporting without interrupting the current operation.
720    */
721   static boolean exceptionOccured = false;
722 
723   /**
724    * The file currently used to write the persisten representation.
725    * Will only have a non-null value during storing operations.
726    */
727   static File persistenceFile;
728 
729   /**
730    * The URL currently used to read the persistent representation when reading
731    * from a URL.  Will only be non-null during restoring operations.
732    */
733   static URL persistenceURL;
734 
735   static{
736     persistentReplacementTypes = new HashMap();
737     try{
738       //VRs don't get saved, ....sorry guys :)
739       registerPersitentEquivalent(VisualResource.class,
740                                   SlashDevSlashNull.class);
741 
742       registerPersitentEquivalent(URL.class, URLHolder.class);
743 
744       registerPersitentEquivalent(Map.class, MapPersistence.class);
745       registerPersitentEquivalent(Collection.class,
746                                   CollectionPersistence.class);
747 
748       registerPersitentEquivalent(ProcessingResource.class,
749                                   PRPersistence.class);
750 
751       registerPersitentEquivalent(DataStore.class,
752                                   DSPersistence.class);
753 
754       registerPersitentEquivalent(LanguageResource.class,
755                                   LRPersistence.class);
756 
757       registerPersitentEquivalent(Corpus.class,
758                                   CorpusPersistence.class);
759 
760       registerPersitentEquivalent(Controller.class,
761                                   ControllerPersistence.class);
762 
763       registerPersitentEquivalent(ConditionalController.class,
764                                   ConditionalControllerPersistence.class);
765 
766       registerPersitentEquivalent(LanguageAnalyser.class,
767                                   LanguageAnalyserPersistence.class);
768 
769       registerPersitentEquivalent(SerialAnalyserController.class,
770                                   SerialAnalyserControllerPersistence.class);
771 
772       registerPersitentEquivalent(gate.persist.JDBCDataStore.class,
773                                   JDBCDSPersistence.class);
774       registerPersitentEquivalent(gate.creole.AnalyserRunningStrategy.class,
775                                   AnalyserRunningStrategyPersistence.class);
776     }catch(PersistenceException pe){
777       //builtins shouldn't raise this
778       pe.printStackTrace();
779     }
780     existingPersitentReplacements = new HashMap();
781     existingTransientValues = new HashMap();
782   }
783 }
784