|
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 "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 }
|
PersistenceManager |
|