1   /*
2    *  Copyright (c) 1998-2004, 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.16 2004/12/02 19:02:49 valyt 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 = persistenceFile.toURL();
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     exceptionOccured = false;
526     ProgressListener pListener = (ProgressListener)MainFrame.getListeners().
527                                  get("gate.event.ProgressListener");
528     StatusListener sListener = (gate.event.StatusListener)
529                                 MainFrame.getListeners()
530                                 .get("gate.event.StatusListener");
531     if(pListener != null) pListener.progressChanged(0);
532     long startTime = System.currentTimeMillis();
533     persistenceFile = file;
534     // Determine whether the file contains an application serialized in xml
535     // format. Otherwise we will assume that it contains native serializations.
536     boolean xmlStream = isXmlApplicationFile(file);
537     ObjectInputStream ois = null;
538     java.io.FileReader fileReader = null;
539     com.thoughtworks.xstream.XStream xstream = null;
540     // Make the appropriate kind of streams that will be used, depending on
541     // whether serialization is native or xml.
542     if (xmlStream) { 
543       fileReader = new java.io.FileReader(file);
544       xstream = new com.thoughtworks.xstream.XStream();
545     } else {
546       ois = new ObjectInputStream(new FileInputStream(file));
547     }
548     Object res = null;
549     try{
550       Iterator urlIter;
551       // If we're using xml serialization, first read everything from
552       // the file.
553       GateApplication gateApplication = null;
554       if (xmlStream) {
555         if (DEBUG)
556           System.out.println("About to load application");
557         // Actually load the application
558         gateApplication = (GateApplication)xstream.fromXML(fileReader);
559         fileReader.close();
560         if (DEBUG)
561           System.out.println("About to extract url list");
562         // Extract an iterator to the URLs.
563         urlIter = 
564           ((Collection)getTransientRepresentation(gateApplication.urlList))
565       .iterator();
566         if (DEBUG)
567           System.out.println("URL list loaded");
568       } else {
569         // first read the list of creole URLs. This is for when we are using
570         // native serialization.
571         urlIter = ((Collection)
572        
573                           getTransientRepresentation(ois.readObject())).
574                           iterator();
575       }
576       while(urlIter.hasNext()){
577         URL anUrl = (URL)urlIter.next();
578         try{
579           Gate.getCreoleRegister().registerDirectories(anUrl);
580         }catch(GateException ge){
581           Err.prln("Could not reload creole directory " +
582                    anUrl.toExternalForm());
583         }
584       }
585       
586       //now we can read the saved object
587       if (xmlStream) {
588         if (DEBUG)
589           System.out.println("About to load application itself");
590         // With an xml stream, we already read the object, so we just
591         // have to extract it.
592         res = gateApplication.application;
593         if (DEBUG)
594           System.out.println("Application loaded");
595       } else {
596         // With a native stream just read the object from it.
597         res = ois.readObject();
598         ois.close();
599       }
600 
601       //ensure a fresh start
602       existingTransientValues.clear();
603       res = getTransientRepresentation(res);
604       existingTransientValues.clear();
605       long endTime = System.currentTimeMillis();
606               if(sListener != null) sListener.statusChanged(
607                   "Loading completed in " +
608                   NumberFormat.getInstance().format(
609                   (double)(endTime - startTime) / 1000) + " seconds");
610               if(pListener != null) pListener.processFinished();
611       if(exceptionOccured){
612         throw new PersistenceException("There were errors!\n" +
613                                        "See messages for details...");
614       }
615       return res;
616     }catch(ResourceInstantiationException rie){
617       if(sListener != null) sListener.statusChanged("Loading failed!");
618       if(pListener != null) pListener.processFinished();
619       throw rie;
620     }catch(Exception ex){
621       if(sListener != null) sListener.statusChanged("Loading failed!");
622       if(pListener != null) pListener.processFinished();
623       throw new PersistenceException(ex);
624     }finally{
625       persistenceFile = null;
626     }
627   }
628 
629   /**
630    * Determine whether the file contains a GATE application serialized 
631    * using XML.
632    * 
633    * @param file The name of the file.
634    * @return true if the file contains an xml serialized application,
635    * false otherwise.
636    */
637   private static boolean isXmlApplicationFile(File file) 
638   throws java.io.IOException {
639     if (DEBUG) {
640       System.out.println("Checking whether file is xml");
641     }
642   java.io.BufferedReader fileReader = 
643       new java.io.BufferedReader(new java.io.FileReader(file));
644     String firstLine = fileReader.readLine();
645     fileReader.close();
646     
647     if (DEBUG) {
648       System.out.println("isXMLApplicationFile = " + 
649           (firstLine.length() >=  STARTOFXMLAPPLICATIONFILES.length()
650         && firstLine.substring(0, STARTOFXMLAPPLICATIONFILES.length())
651         .equals(STARTOFXMLAPPLICATIONFILES)));
652     }
653     
654     return firstLine.length() >=  STARTOFXMLAPPLICATIONFILES.length()
655   && firstLine.substring(0, STARTOFXMLAPPLICATIONFILES.length())
656   .equals(STARTOFXMLAPPLICATIONFILES);
657   }
658   
659   private static final String STARTOFXMLAPPLICATIONFILES =
660     "<gate.util.persistence.GateApplication>";
661   
662   /**
663    * Sets the persistent equivalent type to be used to (re)store a given type
664    * of transient objects.
665    * @param transientType the type that will be replaced during serialisation
666    * operations
667    * @param persistentType the type used to replace objects of transient type
668    * when serialising; this type needs to extend {@link Persistence}.
669    * @return the persitent type that was used before this mapping if such
670    * existed.
671    */
672   public static Class registerPersitentEquivalent(Class transientType,
673                                           Class persistentType)
674                throws PersistenceException{
675     if(!Persistence.class.isAssignableFrom(persistentType)){
676       throw new PersistenceException(
677         "Persistent equivalent types have to implement " +
678         Persistence.class.getName() + "!\n" +
679         persistentType.getName() + " does not!");
680     }
681     return (Class)persistentReplacementTypes.put(transientType, persistentType);
682   }
683 
684 
685   /**
686    * A dictionary mapping from java type (Class) to the type (Class) that can
687    * be used to store persistent data for the input type.
688    */
689   private static Map persistentReplacementTypes;
690 
691   /**
692    * Stores the persistent replacements created during a transaction in order to
693    * avoid creating two different persistent copies for the same object.
694    * The keys used are {@link ObjectHolder}s that contain the transient values
695    * being converted to persistent equivalents.
696    */
697   private static Map existingPersitentReplacements;
698 
699   /**
700    * Stores the transient values obtained from persistent replacements during a
701    * transaction in order to avoid creating two different transient copies for
702    * the same persistent replacement.
703    * The keys used are {@link ObjectHolder}s that hold persistent equivalents.
704    * The values are the transient values created by the persisten equivalents.
705    */
706   private static Map existingTransientValues;
707 
708   private static ClassComparator classComparator = new ClassComparator();
709 
710   /**
711    * This flag is set to true when an exception occurs. It is used in order to
712    * allow error reporting without interrupting the current operation.
713    */
714   static boolean exceptionOccured = false;
715 
716   /**
717    * The file currently used to write/read the persisten representation.
718    * Will only have a non-null value during storing and restorin operations.
719    */
720   static File persistenceFile;
721 
722   static{
723     persistentReplacementTypes = new HashMap();
724     try{
725       //VRs don't get saved, ....sorry guys :)
726       registerPersitentEquivalent(VisualResource.class,
727                                   SlashDevSlashNull.class);
728 
729       registerPersitentEquivalent(URL.class, URLHolder.class);
730 
731       registerPersitentEquivalent(Map.class, MapPersistence.class);
732       registerPersitentEquivalent(Collection.class,
733                                   CollectionPersistence.class);
734 
735       registerPersitentEquivalent(ProcessingResource.class,
736                                   PRPersistence.class);
737 
738       registerPersitentEquivalent(DataStore.class,
739                                   DSPersistence.class);
740 
741       registerPersitentEquivalent(LanguageResource.class,
742                                   LRPersistence.class);
743 
744       registerPersitentEquivalent(Corpus.class,
745                                   CorpusPersistence.class);
746 
747       registerPersitentEquivalent(Controller.class,
748                                   ControllerPersistence.class);
749 
750       registerPersitentEquivalent(ConditionalController.class,
751                                   ConditionalControllerPersistence.class);
752 
753       registerPersitentEquivalent(LanguageAnalyser.class,
754                                   LanguageAnalyserPersistence.class);
755 
756       registerPersitentEquivalent(SerialAnalyserController.class,
757                                   SerialAnalyserControllerPersistence.class);
758 
759       registerPersitentEquivalent(gate.persist.JDBCDataStore.class,
760                                   JDBCDSPersistence.class);
761       registerPersitentEquivalent(gate.creole.AnalyserRunningStrategy.class,
762                                   AnalyserRunningStrategyPersistence.class);
763     }catch(PersistenceException pe){
764       //builtins shouldn't raise this
765       pe.printStackTrace();
766     }
767     existingPersitentReplacements = new HashMap();
768     existingTransientValues = new HashMap();
769   }
770 }