|
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.11 2002/05/13 11:35:32 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 * In the case of "file:" URLs the relative path to the persistence 95 * file will actually be stored. 96 */ 97 public static class URLHolder implements Persistence{ 98 /** 99 * Populates this Persistence with the data that needs to be stored from the 100 * original source object. 101 */ 102 public void extractDataFromSource(Object source)throws PersistenceException{ 103 try{ 104 URL url = (URL)source; 105 if(url.getProtocol().equals("file")){ 106 try{ 107 urlString = relativePathMarker + 108 getRelativePath(persistenceFile.toURL(), url); 109 }catch(MalformedURLException mue){ 110 urlString = ((URL)source).toExternalForm(); 111 } 112 }else{ 113 urlString = ((URL)source).toExternalForm(); 114 } 115 }catch(ClassCastException cce){ 116 throw new PersistenceException(cce); 117 } 118 } 119 120 /** 121 * Creates a new object from the data contained. This new object is supposed 122 * to be a copy for the original object used as source for data extraction. 123 */ 124 public Object createObject()throws PersistenceException{ 125 try{ 126 if(urlString.startsWith(relativePathMarker)){ 127 URL context = persistenceFile.toURL(); 128 return new URL(context, 129 urlString.substring(relativePathMarker.length())); 130 }else return new URL(urlString); 131 }catch(MalformedURLException mue){ 132 throw new PersistenceException(mue); 133 } 134 } 135 String urlString; 136 /** 137 * This string will be used to start the serialisation of URL that represent 138 * relative paths. 139 */ 140 private static final String relativePathMarker = "$relpath$"; 141 static final long serialVersionUID = 7943459208429026229L; 142 } 143 144 public static class ClassComparator implements Comparator{ 145 /** 146 * Compares two {@link Class} values in terms of specificity; the more 147 * specific class is said to be "smaller" than the more generic 148 * one hence the {@link Object} class is the "largest" possible 149 * class. 150 * When two classes are not comparable (i.e. not assignable from each other) 151 * in either direction a NotComparableException will be thrown. 152 * both input objects should be Class values otherwise a 153 * {@link ClassCastException} will be thrown. 154 * 155 */ 156 public int compare(Object o1, Object o2){ 157 Class c1 = (Class)o1; 158 Class c2 = (Class)o2; 159 160 if(c1.equals(c2)) return 0; 161 if(c1.isAssignableFrom(c2)) return 1; 162 if(c2.isAssignableFrom(c1)) return -1; 163 throw new NotComparableException(); 164 } 165 } 166 167 /** 168 * Thrown by a comparator when the values provided for comparison are not 169 * comparable. 170 */ 171 public static class NotComparableException extends RuntimeException{ 172 public NotComparableException(String message){ 173 super(message); 174 } 175 public NotComparableException(){ 176 } 177 } 178 179 /** 180 * Recursively traverses the provided object and replaces it and all its 181 * contents with the appropiate persistent equivalent classes. 182 * 183 * @param the object to be analised and translated into a persistent 184 * equivalent. 185 * @return the persistent equivalent value for the provided target 186 */ 187 static Serializable getPersistentRepresentation(Object target) 188 throws PersistenceException{ 189 if(target == null) return null; 190 //first check we don't have it already 191 Persistence res = (Persistence)existingPersitentReplacements. 192 get(new ObjectHolder(target)); 193 if(res != null) return res; 194 195 Class type = target.getClass(); 196 Class newType = getMostSpecificPersistentType(type); 197 if(newType == null){ 198 //no special handler 199 if(target instanceof Serializable) return (Serializable)target; 200 else throw new PersistenceException( 201 "Could not find a serialisable replacement for " + type); 202 } 203 204 //we have a new type; create the new object, populate and return it 205 try{ 206 res = (Persistence)newType.newInstance(); 207 }catch(Exception e){ 208 throw new PersistenceException(e); 209 } 210 if(target instanceof NameBearer){ 211 StatusListener sListener = (StatusListener)MainFrame.getListeners(). 212 get("gate.event.StatusListener"); 213 if(sListener != null){ 214 sListener.statusChanged("Storing " + ((NameBearer)target).getName()); 215 } 216 } 217 res.extractDataFromSource(target); 218 existingPersitentReplacements.put(new ObjectHolder(target), res); 219 return res; 220 } 221 222 223 static Object getTransientRepresentation(Object target) 224 throws PersistenceException, 225 ResourceInstantiationException{ 226 227 if(target == null || target instanceof SlashDevSlashNull) return null; 228 if(target instanceof Persistence){ 229 Object resultKey = new ObjectHolder(target); 230 //check the cached values; maybe we have the result already 231 Object result = existingTransientValues.get(resultKey); 232 if(result != null) return result; 233 234 //we didn't find the value: create it 235 result = ((Persistence)target).createObject(); 236 existingTransientValues.put(resultKey, result); 237 return result; 238 }else return target; 239 } 240 241 242 /** 243 * Finds the most specific persistent replacement type for a given class. 244 * Look for a type that has a registered persistent equivalent starting from 245 * the provided class continuing with its superclass and implemented 246 * interfaces and their superclasses and implemented interfaces and so on 247 * until a type is found. 248 * Classes are considered to be more specific than interfaces and in 249 * situations of ambiguity the most specific types are considered to be the 250 * ones that don't belong to either java or GATE followed by the ones that 251 * belong to GATE and followed by the ones that belong to java. 252 * 253 * E.g. if there are registered persitent types for {@link gate.Resource} and 254 * for {@link gate.LanguageResource} than such a request for a 255 * {@link gate.Document} will yield the registered type for 256 * {@link gate.LanguageResource}. 257 */ 258 protected static Class getMostSpecificPersistentType(Class type){ 259 //this list will contain all the types we need to expand to superclass + 260 //implemented interfaces. We start with the provided type and work our way 261 //up the ISA hierarchy 262 List expansionSet = new ArrayList(); 263 expansionSet.add(type); 264 265 //algorithm: 266 //1) check the current expansion set 267 //2) expand the expansion set 268 269 //at each expansion stage we'll have a class and three lists of interfaces: 270 //the user defined ones; the GATE ones and the java ones. 271 List userInterfaces = new ArrayList(); 272 List gateInterfaces = new ArrayList(); 273 List javaInterfaces = new ArrayList(); 274 while(!expansionSet.isEmpty()){ 275 //1) check the current set 276 Iterator typesIter = expansionSet.iterator(); 277 while(typesIter.hasNext()){ 278 Class result = (Class)persistentReplacementTypes.get(typesIter.next()); 279 if(result != null){ 280 return result; 281 } 282 } 283 //2) expand the current expansion set; 284 //the expanded expansion set will need to be ordered according to the 285 //rules (class >> interface; user interf >> gate interf >> java interf) 286 287 //at each point we only have at most one class 288 if(type != null) type = type.getSuperclass(); 289 290 291 userInterfaces.clear(); 292 gateInterfaces.clear(); 293 javaInterfaces.clear(); 294 295 typesIter = expansionSet.iterator(); 296 while(typesIter.hasNext()){ 297 Class aType = (Class)typesIter.next(); 298 Class[] interfaces = aType.getInterfaces(); 299 //distribute them according to their type 300 for(int i = 0; i < interfaces.length; i++){ 301 Class anIterf = interfaces[i]; 302 String interfType = anIterf.getName(); 303 if(interfType.startsWith("java")){ 304 javaInterfaces.add(anIterf); 305 }else if(interfType.startsWith("gate")){ 306 gateInterfaces.add(anIterf); 307 }else userInterfaces.add(anIterf); 308 } 309 } 310 311 expansionSet.clear(); 312 if(type != null) expansionSet.add(type); 313 expansionSet.addAll(userInterfaces); 314 expansionSet.addAll(gateInterfaces); 315 expansionSet.addAll(javaInterfaces); 316 } 317 //we got out the while loop without finding anything; return null; 318 return null; 319 320 // SortedSet possibleTypesSet = new TreeSet(classComparator); 321 // 322 // Iterator typesIter = persistentReplacementTypes.keySet().iterator(); 323 // //we store all the types that could not be analysed 324 // List lostTypes = new ArrayList(); 325 // while(typesIter.hasNext()){ 326 // Class aType = (Class)typesIter.next(); 327 // if(aType.isAssignableFrom(type)){ 328 // try{ 329 // possibleTypesSet.add(aType); 330 // }catch(NotComparableException nce){ 331 // lostTypes.add(aType); 332 // } 333 // } 334 // } 335 // 336 // if(possibleTypesSet.isEmpty()) return null; 337 // 338 // Class resultKey = (Class)possibleTypesSet.first(); 339 // Class result = (Class) persistentReplacementTypes.get(resultKey); 340 // 341 // //check whether we lost anything important 342 // typesIter = lostTypes.iterator(); 343 // while(typesIter.hasNext()){ 344 // Class aType = (Class)typesIter.next(); 345 // try{ 346 // if(classComparator.compare(aType, resultKey) < 0){ 347 // Err.prln("Found at least two incompatible most specific types for " + 348 // type.getName() + ":\n " + 349 // aType.toString() + " was discarded in favour of" + result.getName() + 350 // ".\nSome of your saved results may have been lost!"); 351 // } 352 // }catch(NotComparableException nce){ 353 // Err.prln("Found at least two incompatible most specific types for " + 354 // type.getName() + ":\n " + 355 // aType.toString() + " was discarded in favour of" + result.getName() + 356 // ".\nSome of your saved results may have been lost!"); 357 // } 358 // } 359 // 360 // return result; 361 } 362 363 /** 364 * Calculates the relative path for a file: URL starting from a given context 365 * which is also a file: URL. 366 * @param context the URL to be used as context. 367 * @param target the URL for which the relative path is computed. 368 * @return a String value representing the relative path. Constructing a URL 369 * from the context URL and the relative path should result in the target URL. 370 */ 371 public static String getRelativePath(URL context, URL target){ 372 if(context.getProtocol().equals("file") && 373 target.getProtocol().equals("file")){ 374 375 //normalise the two file URLS 376 try{ 377 context = new File(context.getPath()).toURL(); 378 }catch(MalformedURLException mue){ 379 throw new GateRuntimeException("Could not normalise the file URL:\n"+ 380 context + "\nThe problem was:\n" +mue); 381 } 382 try{ 383 target = new File(target.getPath()).toURL(); 384 }catch(MalformedURLException mue){ 385 throw new GateRuntimeException("Could not normalise the file URL:\n"+ 386 target + "\nThe problem was:\n" +mue); 387 } 388 List targetPathComponents = new ArrayList(); 389 File aFile = new File(target.getPath()).getParentFile(); 390 while(aFile != null){ 391 targetPathComponents.add(0, aFile); 392 aFile = aFile.getParentFile(); 393 } 394 List contextPathComponents = new ArrayList(); 395 aFile = new File(context.getPath()).getParentFile(); 396 while(aFile != null){ 397 contextPathComponents.add(0, aFile); 398 aFile = aFile.getParentFile(); 399 } 400 //the two lists can have 0..n common elements (0 when the files are 401 //on separate roots 402 int commonPathElements = 0; 403 while(commonPathElements < targetPathComponents.size() && 404 commonPathElements < contextPathComponents.size() && 405 targetPathComponents.get(commonPathElements). 406 equals(contextPathComponents.get(commonPathElements))) 407 commonPathElements++; 408 //construct the string for the relative URL 409 String relativePath = ""; 410 for(int i = commonPathElements; 411 i < contextPathComponents.size(); i++){ 412 if(relativePath.length() == 0) relativePath += ".."; 413 else relativePath += "/.."; 414 } 415 for(int i = commonPathElements; i < targetPathComponents.size(); i++){ 416 String aDirName = ((File)targetPathComponents.get(i)).getName(); 417 if(aDirName.length() == 0){ 418 aDirName = ((File)targetPathComponents.get(i)).getAbsolutePath(); 419 if(aDirName.endsWith(File.separator)){ 420 aDirName = aDirName.substring(0, aDirName.length() - 421 File.separator.length()); 422 } 423 } 424 //Out.prln("Adding \"" + aDirName + "\" name for " + targetPathComponents.get(i)); 425 if(relativePath.length() == 0){ 426 relativePath += aDirName; 427 }else{ 428 relativePath += "/" + aDirName; 429 } 430 } 431 //we have the directory; add the file name 432 if(relativePath.length() == 0){ 433 relativePath += new File(target.getPath()).getName(); 434 }else{ 435 relativePath += "/" + new File(target.getPath()).getName(); 436 } 437 438 return relativePath; 439 }else{ 440 throw new GateRuntimeException("Both the target and the context URLs " + 441 "need to be \"file:\" URLs!"); 442 } 443 } 444 445 public static void saveObjectToFile(Object obj, File file) 446 throws PersistenceException, IOException { 447 ProgressListener pListener = (ProgressListener)MainFrame.getListeners() 448 .get("gate.event.ProgressListener"); 449 StatusListener sListener = (gate.event.StatusListener) 450 MainFrame.getListeners(). 451 get("gate.event.StatusListener"); 452 long startTime = System.currentTimeMillis(); 453 if(pListener != null) pListener.progressChanged(0); 454 ObjectOutputStream oos = null; 455 persistenceFile = file; 456 try{ 457 //insure a clean start 458 existingPersitentReplacements.clear(); 459 existingPersitentReplacements.clear(); 460 461 oos = new ObjectOutputStream(new FileOutputStream(file)); 462 463 //always write the list of creole URLs first 464 List urlList = new ArrayList(Gate.getCreoleRegister().getDirectories()); 465 Object persistentList = getPersistentRepresentation(urlList); 466 oos.writeObject(persistentList); 467 468 //now write the object 469 Object persistentObject = getPersistentRepresentation(obj); 470 oos.writeObject(persistentObject); 471 }finally{ 472 persistenceFile = null; 473 if(oos != null){ 474 oos.flush(); 475 oos.close(); 476 } 477 long endTime = System.currentTimeMillis(); 478 if(sListener != null) sListener.statusChanged( 479 "Storing completed in " + 480 NumberFormat.getInstance().format( 481 (double)(endTime - startTime) / 1000) + " seconds"); 482 if(pListener != null) pListener.processFinished(); 483 } 484 } 485 486 public static Object loadObjectFromFile(File file) 487 throws PersistenceException, IOException, 488 ResourceInstantiationException { 489 exceptionOccured = false; 490 ProgressListener pListener = (ProgressListener)MainFrame.getListeners(). 491 get("gate.event.ProgressListener"); 492 StatusListener sListener = (gate.event.StatusListener) 493 MainFrame.getListeners() 494 .get("gate.event.StatusListener"); 495 if(pListener != null) pListener.progressChanged(0); 496 long startTime = System.currentTimeMillis(); 497 persistenceFile = file; 498 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file)); 499 Object res = null; 500 try{ 501 //first read the list of creole URLs 502 Iterator urlIter = ((Collection) 503 getTransientRepresentation(ois.readObject())). 504 iterator(); 505 while(urlIter.hasNext()){ 506 URL anUrl = (URL)urlIter.next(); 507 try{ 508 if(!Gate.getCreoleRegister().getDirectories().contains(anUrl)) 509 Gate.getCreoleRegister().registerDirectories(anUrl); 510 }catch(GateException ge){ 511 Err.prln("Could not reload creole directory " + 512 anUrl.toExternalForm()); 513 } 514 } 515 //now we can read the saved object 516 res = ois.readObject(); 517 ois.close(); 518 519 //ensure a fresh start 520 existingTransientValues.clear(); 521 res = getTransientRepresentation(res); 522 existingTransientValues.clear(); 523 long endTime = System.currentTimeMillis(); 524 if(sListener != null) sListener.statusChanged( 525 "Loading completed in " + 526 NumberFormat.getInstance().format( 527 (double)(endTime - startTime) / 1000) + " seconds"); 528 if(pListener != null) pListener.processFinished(); 529 if(exceptionOccured){ 530 throw new PersistenceException("There were errors!\n" + 531 "See messages for details..."); 532 } 533 return res; 534 }catch(ResourceInstantiationException rie){ 535 if(sListener != null) sListener.statusChanged("Loading failed!"); 536 if(pListener != null) pListener.processFinished(); 537 throw rie; 538 }catch(Exception ex){ 539 if(sListener != null) sListener.statusChanged("Loading failed!"); 540 if(pListener != null) pListener.processFinished(); 541 throw new PersistenceException(ex); 542 }finally{ 543 persistenceFile = null; 544 } 545 } 546 547 548 /** 549 * Sets the persistent equivalent type to be used to (re)store a given type 550 * of transient objects. 551 * @param transientType the type that will be replaced during serialisation 552 * operations 553 * @param persistentType the type used to replace objects of transient type 554 * when serialising; this type needs to extend {@link Persistence}. 555 * @return the persitent type that was used before this mapping if such 556 * existed. 557 */ 558 public static Class registerPersitentEquivalent(Class transientType, 559 Class persistentType) 560 throws PersistenceException{ 561 if(!Persistence.class.isAssignableFrom(persistentType)){ 562 throw new PersistenceException( 563 "Persistent equivalent types have to implement " + 564 Persistence.class.getName() + "!\n" + 565 persistentType.getName() + " does not!"); 566 } 567 return (Class)persistentReplacementTypes.put(transientType, persistentType); 568 } 569 570 571 /** 572 * A dictionary mapping from java type (Class) to the type (Class) that can 573 * be used to store persistent data for the input type. 574 */ 575 private static Map persistentReplacementTypes; 576 577 /** 578 * Stores the persistent replacements created during a transaction in order to 579 * avoid creating two different persistent copies for the same object. 580 * The keys used are {@link ObjectHolder}s that contain the transient values 581 * being converted to persistent equivalents. 582 */ 583 private static Map existingPersitentReplacements; 584 585 /** 586 * Stores the transient values obtained from persistent replacements during a 587 * transaction in order to avoid creating two different transient copies for 588 * the same persistent replacement. 589 * The keys used are {@link ObjectHolder}s that hold persistent equivalents. 590 * The values are the transient values created by the persisten equivalents. 591 */ 592 private static Map existingTransientValues; 593 594 private static ClassComparator classComparator = new ClassComparator(); 595 596 /** 597 * This flag is set to true when an exception occurs. It is used in order to 598 * allow error reporting without interrupting the current operation. 599 */ 600 static boolean exceptionOccured = false; 601 602 /** 603 * The file currently used to write/read the persisten representation. 604 * Will only have a non-null value during storing and restorin operations. 605 */ 606 static File persistenceFile; 607 608 static{ 609 persistentReplacementTypes = new HashMap(); 610 try{ 611 //VRs don't get saved, ....sorry guys :) 612 registerPersitentEquivalent(VisualResource.class, 613 SlashDevSlashNull.class); 614 615 registerPersitentEquivalent(URL.class, URLHolder.class); 616 617 registerPersitentEquivalent(Map.class, MapPersistence.class); 618 registerPersitentEquivalent(Collection.class, 619 CollectionPersistence.class); 620 621 registerPersitentEquivalent(ProcessingResource.class, 622 PRPersistence.class); 623 624 registerPersitentEquivalent(DataStore.class, 625 DSPersistence.class); 626 627 registerPersitentEquivalent(LanguageResource.class, 628 LRPersistence.class); 629 630 registerPersitentEquivalent(Corpus.class, 631 CorpusPersistence.class); 632 633 registerPersitentEquivalent(Controller.class, 634 ControllerPersistence.class); 635 636 registerPersitentEquivalent(ConditionalController.class, 637 ConditionalControllerPersistence.class); 638 639 registerPersitentEquivalent(LanguageAnalyser.class, 640 LanguageAnalyserPersistence.class); 641 642 registerPersitentEquivalent(SerialAnalyserController.class, 643 SerialAnalyserControllerPersistence.class); 644 645 registerPersitentEquivalent(gate.persist.JDBCDataStore.class, 646 JDBCDSPersistence.class); 647 registerPersitentEquivalent(gate.creole.AnalyserRunningStrategy.class, 648 AnalyserRunningStrategyPersistence.class); 649 }catch(PersistenceException pe){ 650 //builtins shouldn't raise this 651 pe.printStackTrace(); 652 } 653 existingPersitentReplacements = new HashMap(); 654 existingTransientValues = new HashMap(); 655 } 656 }
|
PersistenceManager |
|