1   /*
2    *  Copyright (c) 1998-2001, 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.6 2001/11/18 16:10:02 valyt Exp $
12   *
13   */
14  package gate.util.persistence;
15  
16  import gate.*;
17  import gate.util.*;
18  import gate.creole.*;
19  import gate.event.*;
20  import gate.gui.MainFrame;
21  import gate.persist.PersistenceException;
22  
23  import java.util.*;
24  import java.io.*;
25  import java.text.NumberFormat;
26  import java.net.URL;
27  import java.net.MalformedURLException;
28  
29  /**
30   * This class provides utility methods for saving resources through
31   * serialisation via static methods.
32   */
33  public class PersistenceManager {
34  
35    /**
36     * A reference to an object; it uses the identity hashcode and the equals
37     * defined by object identity.
38     * These values will be used as keys in the
39     * {link #existingPersitentReplacements} map.
40     */
41    static protected class ObjectHolder{
42      ObjectHolder(Object target){
43        this.target = target;
44      }
45  
46      public int hashCode(){
47        return System.identityHashCode(target);
48      }
49  
50      public boolean equals(Object obj){
51        if(obj instanceof ObjectHolder)
52          return ((ObjectHolder)obj).target == this.target;
53        else return false;
54      }
55  
56      public Object getTarget(){
57        return target;
58      }
59  
60      private Object target;
61    }//static class ObjectHolder{
62  
63    /**
64     * This class is used as a marker for types that should NOT be serialised when
65     * saving the state of a gate object.
66     * Registering this type as the persistent equivalent for a specific class
67     * (via {@link PersistenceManager#registerPersitentEquivalent(Class , Class)})
68     * effectively stops all values of the specified type from being serialised.
69     *
70     * Maps that contain values that should not be serialised will have that entry
71     * removed. In any other places where such values occur they will be replaced
72     * by null after deserialisation.
73     */
74    public static class SlashDevSlashNull implements Persistence{
75      /**
76       * Does nothing
77       */
78      public void extractDataFromSource(Object source)throws PersistenceException{
79      }
80  
81      /**
82       * Returns null
83       */
84      public Object createObject()throws PersistenceException,
85                                         ResourceInstantiationException{
86        return null;
87      }
88      static final long serialVersionUID = -8665414981783519937L;
89    }
90  
91    /**
92     * URLs get upset when serialised and deserialised so we need to convert them
93     * to strings for storage
94     */
95    public static class URLHolder implements Persistence{
96      /**
97       * Populates this Persistence with the data that needs to be stored from the
98       * original source object.
99       */
100     public void extractDataFromSource(Object source)throws PersistenceException{
101       try{
102         urlString = ((URL)source).toExternalForm();
103       }catch(ClassCastException cce){
104         throw new PersistenceException(cce);
105       }
106     }
107 
108     /**
109      * Creates a new object from the data contained. This new object is supposed
110      * to be a copy for the original object used as source for data extraction.
111      */
112     public Object createObject()throws PersistenceException{
113       try{
114         return new URL(urlString);
115       }catch(MalformedURLException mue){
116         throw new PersistenceException(mue);
117       }
118     }
119     String urlString;
120     static final long serialVersionUID = 7943459208429026229L;
121   }
122 
123   public static class ClassComparator implements Comparator{
124     /**
125      * Compares two {@link Class} values in terms of specificity; the more
126      * specific class is said to be "smaller" than the more generic
127      * one hence the {@link Object} class is the "largest" possible
128      * class.
129      * When two classes are not comparable (i.e. not assignable from each other)
130      * in either direction a NotComparableException will be thrown.
131      * both input objects should be Class values otherwise a
132      * {@link ClassCastException} will be thrown.
133      *
134      */
135     public int compare(Object o1, Object o2){
136       Class c1 = (Class)o1;
137       Class c2 = (Class)o2;
138 
139       if(c1.equals(c2)) return 0;
140       if(c1.isAssignableFrom(c2)) return 1;
141       if(c2.isAssignableFrom(c1)) return -1;
142       throw new NotComparableException();
143     }
144   }
145 
146   /**
147    * Thrown by a comparator when the values provided for comparison are not
148    * comparable.
149    */
150   public static class NotComparableException extends RuntimeException{
151     public NotComparableException(String message){
152       super(message);
153     }
154     public NotComparableException(){
155     }
156   }
157 
158   /**
159    * Recursively traverses the provided object and replaces it and all its
160    * contents with the appropiate persistent equivalent classes.
161    *
162    * @param the object to be analised and translated into a persistent
163    * equivalent.
164    * @return the persistent equivalent value for the provided target
165    */
166   static Serializable getPersistentRepresentation(Object target)
167                       throws PersistenceException{
168     if(target == null) return null;
169     //first check we don't have it already
170     Persistence res = (Persistence)existingPersitentReplacements.
171                       get(new ObjectHolder(target));
172     if(res != null) return res;
173 
174     Class type = target.getClass();
175     Class newType = getMostSpecificPersistentType(type);
176     if(newType == null){
177       //no special handler
178       if(target instanceof Serializable) return (Serializable)target;
179       else throw new PersistenceException(
180                      "Could not find a serialisable replacement for " + type);
181     }
182 
183     //we have a new type; create the new object, populate and return it
184     try{
185       res = (Persistence)newType.newInstance();
186     }catch(Exception e){
187       throw new PersistenceException(e);
188     }
189     if(target instanceof NameBearer){
190       StatusListener sListener = (StatusListener)MainFrame.getListeners().
191                                  get("gate.event.StatusListener");
192       if(sListener != null){
193         sListener.statusChanged("Storing " + ((NameBearer)target).getName());
194       }
195     }
196     res.extractDataFromSource(target);
197     existingPersitentReplacements.put(new ObjectHolder(target), res);
198     return res;
199   }
200 
201 
202   static Object getTransientRepresentation(Object target)
203                       throws PersistenceException,
204                              ResourceInstantiationException{
205 
206     if(target == null || target instanceof SlashDevSlashNull) return null;
207     if(target instanceof Persistence){
208       Object resultKey = new ObjectHolder(target);
209       //check the cached values; maybe we have the result already
210       Object result = existingTransientValues.get(resultKey);
211       if(result != null) return result;
212 
213       //we didn't find the value: create it
214       result = ((Persistence)target).createObject();
215       existingTransientValues.put(resultKey, result);
216       return result;
217     }else return target;
218   }
219 
220 
221   /**
222    * Finds the most specific persistent replacement type for a given class.
223    * Look for a type that has a registered persistent equivalent starting from
224    * the provided class continuing with its superclass and implemented
225    * interfaces and their superclasses and implemented interfaces and so on
226    * until a type is found.
227    * Classes are considered to be more specific than interfaces and in
228    * situations of ambiguity the most specific types are considered to be the
229    * ones that don't belong to either java or GATE followed by the ones  that
230    * belong to GATE and followed by the ones that belong to java.
231    *
232    * E.g. if there are registered persitent types for {@link gate.Resource} and
233    * for {@link gate.LanguageResource} than such a request for a
234    * {@link gate.Document} will yield the registered type for
235    * {@link gate.LanguageResource}.
236    */
237   protected static Class getMostSpecificPersistentType(Class type){
238     //this list will contain all the types we need to expand to superclass +
239     //implemented interfaces. We start with the provided type and work our way
240     //up the ISA hierarchy
241     List expansionSet = new ArrayList();
242     expansionSet.add(type);
243 
244     //algorithm:
245     //1) check the current expansion set
246     //2) expand the expansion set
247 
248     //at each expansion stage we'll have a class and three lists of interfaces:
249     //the user defined ones; the GATE ones and the java ones.
250     List userInterfaces = new ArrayList();
251     List gateInterfaces = new ArrayList();
252     List javaInterfaces = new ArrayList();
253     while(!expansionSet.isEmpty()){
254       //1) check the current set
255       Iterator typesIter = expansionSet.iterator();
256       while(typesIter.hasNext()){
257         Class result = (Class)persistentReplacementTypes.get(typesIter.next());
258         if(result != null){
259           return result;
260         }
261       }
262       //2) expand the current expansion set;
263       //the expanded expansion set will need to be ordered according to the
264       //rules (class >> interface; user interf >> gate interf >> java interf)
265 
266       //at each point we only have at most one class
267       if(type != null) type = type.getSuperclass();
268 
269 
270       userInterfaces.clear();
271       gateInterfaces.clear();
272       javaInterfaces.clear();
273 
274       typesIter = expansionSet.iterator();
275       while(typesIter.hasNext()){
276         Class aType = (Class)typesIter.next();
277         Class[] interfaces = aType.getInterfaces();
278         //distribute them according to their type
279         for(int i = 0; i < interfaces.length; i++){
280           Class anIterf = interfaces[i];
281           String interfType = anIterf.getName();
282           if(interfType.startsWith("java")){
283             javaInterfaces.add(anIterf);
284           }else if(interfType.startsWith("gate")){
285             gateInterfaces.add(anIterf);
286           }else userInterfaces.add(anIterf);
287         }
288       }
289 
290       expansionSet.clear();
291       if(type != null) expansionSet.add(type);
292       expansionSet.addAll(userInterfaces);
293       expansionSet.addAll(gateInterfaces);
294       expansionSet.addAll(javaInterfaces);
295     }
296     //we got out the while loop without finding anything; return null;
297     return null;
298 
299 //    SortedSet possibleTypesSet = new TreeSet(classComparator);
300 //
301 //    Iterator typesIter = persistentReplacementTypes.keySet().iterator();
302 //    //we store all the types that could not be analysed
303 //    List lostTypes = new ArrayList();
304 //    while(typesIter.hasNext()){
305 //      Class aType = (Class)typesIter.next();
306 //      if(aType.isAssignableFrom(type)){
307 //        try{
308 //          possibleTypesSet.add(aType);
309 //        }catch(NotComparableException nce){
310 //          lostTypes.add(aType);
311 //        }
312 //      }
313 //    }
314 //
315 //    if(possibleTypesSet.isEmpty())  return null;
316 //
317 //    Class resultKey = (Class)possibleTypesSet.first();
318 //    Class result = (Class) persistentReplacementTypes.get(resultKey);
319 //
320 //    //check whether we lost anything important
321 //    typesIter = lostTypes.iterator();
322 //    while(typesIter.hasNext()){
323 //      Class aType = (Class)typesIter.next();
324 //      try{
325 //        if(classComparator.compare(aType, resultKey) < 0){
326 //          Err.prln("Found at least two incompatible most specific types for " +
327 //          type.getName() + ":\n " +
328 //          aType.toString() + " was discarded in favour of" + result.getName() +
329 //          ".\nSome of your saved results may have been lost!");
330 //        }
331 //      }catch(NotComparableException nce){
332 //        Err.prln("Found at least two incompatible most specific types for " +
333 //        type.getName() + ":\n " +
334 //        aType.toString() + " was discarded in favour of" + result.getName() +
335 //        ".\nSome of your saved results may have been lost!");
336 //      }
337 //    }
338 //
339 //    return result;
340   }
341 
342 
343   public static void saveObjectToFile(Object obj, File file)
344                      throws PersistenceException, IOException {
345     ProgressListener pListener = (ProgressListener)MainFrame.getListeners()
346                                  .get("gate.event.ProgressListener");
347     StatusListener sListener = (gate.event.StatusListener)
348                                MainFrame.getListeners().
349                                get("gate.event.StatusListener");
350     long startTime = System.currentTimeMillis();
351     if(pListener != null) pListener.progressChanged(0);
352     ObjectOutputStream oos = null;
353     try{
354       //insure a clean start
355       existingPersitentReplacements.clear();
356       existingPersitentReplacements.clear();
357 
358       oos = new ObjectOutputStream(new FileOutputStream(file));
359 
360       //always write the list of creole URLs first
361       List urlList = new ArrayList(Gate.getCreoleRegister().getDirectories());
362       Object persistentList = getPersistentRepresentation(urlList);
363       oos.writeObject(persistentList);
364 
365       //now write the object
366       Object persistentObject = getPersistentRepresentation(obj);
367       oos.writeObject(persistentObject);
368     }finally{
369       if(oos != null){
370         oos.flush();
371         oos.close();
372       }
373       long endTime = System.currentTimeMillis();
374       if(sListener != null) sListener.statusChanged(
375           "Storing completed in " +
376           NumberFormat.getInstance().format(
377           (double)(endTime - startTime) / 1000) + " seconds");
378       if(pListener != null) pListener.processFinished();
379     }
380   }
381 
382   public static Object loadObjectFromFile(File file)
383                      throws PersistenceException, IOException,
384                             ResourceInstantiationException {
385     ProgressListener pListener = (ProgressListener)MainFrame.getListeners().
386                                  get("gate.event.ProgressListener");
387     StatusListener sListener = (gate.event.StatusListener)
388                                 MainFrame.getListeners()
389                                 .get("gate.event.StatusListener");
390     if(pListener != null) pListener.progressChanged(0);
391     long startTime = System.currentTimeMillis();
392     ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
393     Object res = null;
394     try{
395       //first read the list of creole URLs
396       Iterator urlIter = ((Collection)
397                           getTransientRepresentation(ois.readObject())).
398                           iterator();
399       while(urlIter.hasNext()){
400         URL anUrl = (URL)urlIter.next();
401         try{
402           if(!Gate.getCreoleRegister().getDirectories().contains(anUrl))
403             Gate.getCreoleRegister().registerDirectories(anUrl);
404         }catch(GateException ge){
405           Err.prln("Could not reload creole directory " +
406                    anUrl.toExternalForm());
407         }
408       }
409       //now we can read the saved object
410       res = ois.readObject();
411     }catch(ClassNotFoundException cnfe){
412       if(sListener != null) sListener.statusChanged("Loading failed!");
413       if(pListener != null) pListener.processFinished();
414       throw new PersistenceException(cnfe);
415     }
416     ois.close();
417     //insure a fresh start
418     existingTransientValues.clear();
419     res = getTransientRepresentation(res);
420     existingTransientValues.clear();
421     long endTime = System.currentTimeMillis();
422             if(sListener != null) sListener.statusChanged(
423                 "Loading completed in " +
424                 NumberFormat.getInstance().format(
425                 (double)(endTime - startTime) / 1000) + " seconds");
426             if(pListener != null) pListener.processFinished();
427     return res;
428   }
429 
430 
431   /**
432    * Sets the persistent equivalent type to be used to (re)store a given type
433    * of transient objects.
434    * @param transientType the type that will be replaced during serialisation
435    * operations
436    * @param persistentType the type used to replace objects of transient type
437    * when serialising; this type needs to extend {@link Persistence}.
438    * @return the persitent type that was used before this mapping if such
439    * existed.
440    */
441   public static Class registerPersitentEquivalent(Class transientType,
442                                           Class persistentType)
443                throws PersistenceException{
444     if(!Persistence.class.isAssignableFrom(persistentType)){
445       throw new PersistenceException(
446         "Persistent equivalent types have to implement " +
447         Persistence.class.getName() + "!\n" +
448         persistentType.getName() + " does not!");
449     }
450     return (Class)persistentReplacementTypes.put(transientType, persistentType);
451   }
452 
453 
454   /**
455    * A dictionary mapping from java type (Class) to the type (Class) that can
456    * be used to store persistent data for the input type.
457    */
458   private static Map persistentReplacementTypes;
459 
460   /**
461    * Stores the persistent replacements created during a transaction in order to
462    * avoid creating two different persistent copies for the same object.
463    * The keys used are {@link ObjectHolder}s that contain the transient values
464    * being converted to persistent equivalents.
465    */
466   private static Map existingPersitentReplacements;
467 
468   /**
469    * Stores the transient values obtained from persistent replacements during a
470    * transaction in order to avoid creating two different transient copies for
471    * the same persistent replacement.
472    * The keys used are {@link ObjectHolder}s that hold persistent equivalents.
473    * The values are the transient values created by the persisten equivalents.
474    */
475   private static Map existingTransientValues;
476 
477   private static ClassComparator classComparator = new ClassComparator();
478 
479   static{
480     persistentReplacementTypes = new HashMap();
481     try{
482       //VRs don't get saved, ....sorry guys :)
483       registerPersitentEquivalent(VisualResource.class,
484                                   SlashDevSlashNull.class);
485 
486       registerPersitentEquivalent(URL.class, URLHolder.class);
487 
488       registerPersitentEquivalent(Map.class, MapPersistence.class);
489       registerPersitentEquivalent(Collection.class,
490                                   CollectionPersistence.class);
491 
492       registerPersitentEquivalent(ProcessingResource.class,
493                                   PRPersistence.class);
494 
495       registerPersitentEquivalent(DataStore.class,
496                                   DSPersistence.class);
497 
498       registerPersitentEquivalent(LanguageResource.class,
499                                   LRPersistence.class);
500 
501       registerPersitentEquivalent(Corpus.class,
502                                   CorpusPersistence.class);
503 
504       registerPersitentEquivalent(Controller.class,
505                                   ControllerPersistence.class);
506 
507       registerPersitentEquivalent(LanguageAnalyser.class,
508                                   LanguageAnalyserPersistence.class);
509 
510       registerPersitentEquivalent(SerialAnalyserController.class,
511                                   SerialAnalyserControllerPersistence.class);
512 
513       registerPersitentEquivalent(gate.persist.JDBCDataStore.class,
514                                   JDBCDSPersistence.class);
515     }catch(PersistenceException pe){
516       //builtins shouldn't raise this
517       pe.printStackTrace();
518     }
519     existingPersitentReplacements = new HashMap();
520     existingTransientValues = new HashMap();
521   }
522 }