| PersistenceManager |

    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 &quot;smaller&quot; than the more generic
    127      * one hence the {@link Object} class is the &quot;largest&quot; 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 }

| PersistenceManager |