Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
   /*-
    * See the file LICENSE for redistribution information.
    *
    * Copyright (c) 2002, 2013 Oracle and/or its affiliates.  All rights reserved.
    *
    */
   
   package com.sleepycat.persist.impl;
   
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  /* <!-- begin JE only --> */
  /* <!-- end JE only --> */
The catalog of class formats for a store, along with its associated model and mutations.

Author(s):
Mark Hayes
  
  public class PersistCatalog implements Catalog {
      
      private static final int MAX_TXN_RETRIES = 10;

    
Key to Data record in the catalog database. In the JE 3.0.12 beta version the formatList record is stored under this key and is converted to a Data object when it is read.
  
      private static final byte[] DATA_KEY = getIntBytes(-1);

    
Key to a JE 3.0.12 beta version mutations record in the catalog database. This record is no longer used because mutations are stored in the Data record and is deleted when the beta version is detected.
  
      private static final byte[] BETA_MUTATIONS_KEY = getIntBytes(-2);
  
      private static byte[] getIntBytes(int val) {
          DatabaseEntry entry = new DatabaseEntry();
          IntegerBinding.intToEntry(valentry);
          assert entry.getSize() == 4 && entry.getData().length == 4;
          return entry.getData();
      }

    
Used by unit tests.
  
      public static boolean expectNoClassChanges;
      public static boolean unevolvedFormatsEncountered;

    
The object stored under DATA_KEY in the catalog database.
  
      private static class Data implements Serializable {
  
          static final long serialVersionUID = 7515058069137413261L;
  
         List<FormatformatList;
         Mutations mutations;
         int version;
     }

    
A list of all formats indexed by formatId. Element zero is unused and null, since IDs start at one; this avoids adjusting the ID to index the list. Some elements are null to account for predefined IDs that are not used.

This field, like formatMap, is volatile because it is reassigned when dynamically adding new formats. See addNewFormat.

 
     private volatile List<FormatformatList;

    
A map of the current/live formats in formatList, indexed by class name.

This field, like formatList, is volatile because it is reassigned when dynamically adding new formats. See addNewFormat.

 
     private volatile Map<StringFormatformatMap;

    
A map of the latest formats (includes deleted formats) in formatList, indexed by class name.

This field, like formatMap, is volatile because it is reassigned when dynamically adding new formats. See addNewFormat.

 
     private volatile Map<StringFormatlatestFormatMap;

    
A temporary map of proxied class name to proxy class name. Used during catalog creation, and then set to null. This map is used to force proxy formats to be created prior to proxied formats. [#14665]
 
     private Map<StringStringproxyClassMap;
 
     private final Environment env;
     private final boolean rawAccess;
     private EntityModel model;
     private StoredModel storedModel;
     private Mutations mutations;
     private final Database db;
     private int openCount;
     private boolean readOnly;
     private final boolean transactional;

    
If a Replica is upgraded, local in-memory evolution may take place prior to the Master being upgraded. In that case, the size of the formatList will be greater than nStoredFormats. In this case, the readOnly state field will be set to true. We must be sure not to write the metadata in this state. [#16655]
 
     private volatile int nStoredFormats;

    
The Store is normally present but may be null in unit tests (for example, BindingTest).
 
     private final Store store;

    
The Evolver and catalog Data are non-null during catalog initialization, and null otherwise.
 
     private Evolver initEvolver;
     private Data initData;

    
Creates a new catalog, opening the database and reading it from a given catalog database if it already exists. All predefined formats and formats for the given model are added. For modified classes, old formats are defined based on the rules for compatible class changes and the given mutations. If any format is changed or added, and the database is not read-only, write the initialized catalog to the database.
 
     public PersistCatalog(final Environment env,
                           final String storePrefix,
                           final String dbName,
                           final DatabaseConfig dbConfig,
                           final EntityModel modelParam,
                           final Mutations mutationsParam,
                           final boolean rawAccess,
                           final Store store)
         throws StoreExistsException,
                StoreNotFoundException,
                IncompatibleClassException,
                DatabaseException {
 
         this. = env;
         this. = rawAccess;
         this. = store;
         this. = dbConfig.getTransactional();
 
         /* store may be null for testing. */
         String[] fileAndDbNames = (store != null) ?
             store.parseDbName(dbName) :
             Store.parseDbName(dbName.);
 
         /*
          * Use a null (auto-commit) transaction for opening the database, so
          * that the database is opened even if a ReplicaWriteException occurs
          * when attempting to evolve the metadata.  We will close the database
          * if another exception occurs in the finally statement below.
          */
          = DbCompat.openDatabase(envnull /*txn*/fileAndDbNames[0],
                                    fileAndDbNames[1], dbConfig);
         if ( == null) {
             String dbNameMsg = store.getDbNameMessage(fileAndDbNames);
             if (dbConfig.getExclusiveCreate()) {
                 throw new StoreExistsException
                     ("Catalog DB already exists and ExclusiveCreate=true, " +
                      dbNameMsg);
             } else {
                 assert !dbConfig.getAllowCreate();
                 throw new StoreNotFoundException
                     ("Catalog DB does not exist and AllowCreate=false, " +
                      dbNameMsg);
             }
         }
          = 1;
         boolean success = false;
         try {
             initAndRetry(storePrefixmodelParammutationsParam);
             success = true;
         } finally {
             if (!success) {
                 close();
             }
         }
     }

    
Creates a new catalog when a Replica refresh occurs. Uses some information from the old catalog directly in the new catalog, but all formats are created from scratch and class evolution is attempted.
 
     PersistCatalog(final PersistCatalog oldCatalogfinal String storePrefix)
         throws DatabaseException {
 
          = oldCatalog.db;
          = oldCatalog.store;
          = oldCatalog.env;
          = oldCatalog.rawAccess;
          = oldCatalog.openCount;
          = oldCatalog.transactional;
 
         initAndRetry(storePrefixoldCatalog.modeloldCatalog.mutations);
     }
 
     private void initAndRetry(final String storePrefix,
                               final EntityModel modelParam,
                               final Mutations mutationsParam)
         throws DatabaseException {
 
         for (int i = 0;; i += 1) {
             Transaction txn = null;
             if ( && DbCompat.getThreadTransaction() == null) {
                 txn =
                     .beginTransaction(null.getAutoCommitTxnConfig());
             }
             boolean success = false;
             try {
                 init(txnstorePrefixmodelParammutationsParam);
                 success = true;
                 return;
             } catch (LockConflictException e) {
 
                 /*
                  * It is very unlikely that two threads opening the same
                  * EntityStore will cause a lock conflict.  However, because we
                  * read-modify-update the catalog record,
                  * LockPreemptedException must be handled in a replicated JE
                  * environment.  Since LockPreemptedException is a
                  * LockConfictException, it is simplest to retry when any
                  * LockConfictException occurs.
                  */
                 if (i >= ) {
                     throw e;
                 }
                 continue;
             } finally {
 
                 /*
                  * If the catalog is read-only we abort rather than commit,
                  * because a ReplicaWriteException may have occurred.
                  * ReplicaWriteException invalidates the transaction, and there
                  * are no writes to commit anyway. [#16655]
                  */
                 if (txn != null) {
                     if (success && !isReadOnly()) {
                         txn.commit();
                     } else {
                         txn.abort();
                     }
                 }
             }
         }
     }
 
     private void init(final Transaction txn,
                       final String storePrefix,
                       final EntityModel modelParam,
                       final Mutations mutationsParam)
         throws DatabaseException {
 
         try {
              = readData(txn);
              = .;
             if ( == null) {
                  = new Mutations();
             }
 
             /*
              * When the beta version is detected, force a re-write of the
              * catalog and disallow class changes.  This brings the catalog up
              * to date so that evolution can proceed correctly from then on.
              */
             boolean betaVersion = (. == );
             boolean needWrite = betaVersion;
             boolean disallowClassChanges = betaVersion;
 
             /*
              * Store the given mutations if they are different from the stored
              * mutations, and force evolution to apply the new mutations.
              */
             boolean forceEvolution = false;
             if (mutationsParam != null &&
                 !.equals(mutationsParam)) {
                  = mutationsParam;
                 needWrite = true;
                 forceEvolution = true;
             }
 
             final ClassLoader envClassLoader = DbCompat.getClassLoader();
 
             /* Get the existing format list, or copy it from SimpleCatalog. */
              = .;
             if ( == null) {
                  = SimpleCatalog.getAllSimpleFormats(envClassLoader);
 
                 /*
                  * Special cases: Object and Number are predefined but are not
                  * simple types.
                  */
                 Format format = new NonPersistentFormat(thisObject.class);
                 format.setId(.);
                 .set(.format);
                 format = new NonPersistentFormat(thisNumber.class);
                 format.setId(.);
                 .set(.format);
             } else {
                 /* Pick up any new predefined simple types. */
                 if (SimpleCatalog.addMissingSimpleFormats(envClassLoader,
                                                           )) {
                     needWrite = true;
                 }
                  = .size();
             }
 
             /* Initialize transient catalog field before further use. */
             for (Format format : ) {
                 if (format != null) {
                     format.initCatalog(this);
                 }
             }
 
             /* Special handling for JE 3.0.12 beta formats. */
             if (betaVersion) {
                 Map<StringFormatformatMap = new HashMap<StringFormat>();
                 for (Format format : ) {
                     if (format != null) {
                         formatMap.put(format.getClassName(), format);
                     }
                 }
                 for (Format format : ) {
                     if (format != null) {
                         format.migrateFromBeta(formatMap);
                     }
                 }
             }
 
             /*
              * If we should not use the current model, initialize the stored
              * model and return.
              */
              = new HashMap<StringFormat>(.size());
              = new HashMap<StringFormat>(.size());
             if () {
                 for (Format format : ) {
                     if (format != null) {
                         String name = format.getClassName();
                         if (format.isCurrentVersion()) {
                             .put(nameformat);
                         }
                         if (format == format.getLatestVersion()) {
                             .put(nameformat);
                         }
                     }
                 }
                 if (modelParam != null) {
                      = modelParam;
                      = (StoredModelmodelParam;
                 } else {
                      = new StoredModel(this);
                      = ;
                 }
                 ModelInternal.setClassLoader(envClassLoader);
                 for (Format format : ) {
                     if (format != null) {
                         format.initializeIfNeeded(this);
                     }
                 }
                 initModelAndMutations();
                 return;
             }
 
             /*
              * We are opening a store that uses the current model. Default to
              * the AnnotationModel if no model is specified.
              */
             if (modelParam != null) {
                  = modelParam;
             } else {
                  = new AnnotationModel();
             }
             ModelInternal.setClassLoader(envClassLoader);
              = null;
 
             /*
              * Add all predefined (simple) formats to the format map.  The
              * current version of other formats will be added below.
              */
             for (int i = 0; i <= .i += 1) {
                 Format simpleFormat = .get(i);
                 if (simpleFormat != null) {
                     .put(simpleFormat.getClassName(), simpleFormat);
                 }
             }
 
             /*
              * Known classes are those explicitly registered by the user via
              * the model, plus the predefined proxy classes.
              */
             List<StringknownClasses =
                 new ArrayList<String>(.getKnownClasses());
             /* Also adds the special classes, i.e., enum or array. [#19377] */
             knownClasses.addAll(.getKnownSpecialClasses());
             addPredefinedProxies(knownClasses);
 
             /*
              * Create a temporary map of proxied class name to proxy class
              * name, using all known formats and classes.  This map is used to
              * force proxy formats to be created prior to proxied formats.
              * [#14665]
              */
              = new HashMap<StringString>();
             for (Format oldFormat : ) {
                 if (oldFormat == null || Format.isPredefined(oldFormat)) {
                     continue;
                 }
                 String oldName = oldFormat.getClassName();
                 Renamer renamer = .getRenamer
                     (oldNameoldFormat.getVersion(), null);
                 String newName =
                     (renamer != null) ? renamer.getNewName() : oldName;
                 addProxiedClass(newNamefalse /*isKnownClass*/);
             }
             for (String className : knownClasses) {
                 addProxiedClass(classNametrue /*isKnownClass*/);
             }
 
             /*
              * Add known formats from the model and the predefined proxies.
              * In general, classes will not be present in an AnnotationModel
              * until an instance is stored, in which case an old format exists.
              * However, registered proxy classes are an exception and must be
              * added in advance.  And the user may choose to register new
              * classes in advance.  The more formats we define in advance, the
              * less times we have to write to the catalog database.
              */
             Map<StringFormatnewFormats = new HashMap<StringFormat>();
             for (String className : knownClasses) {
                 createFormat(classNamenewFormats);
             }
 
             /*
              * Perform class evolution for all old formats, and throw an
              * exception that contains the messages for all of the errors in
              * mutations or in the definition of new classes.
              */
              = new Evolver
                 (thisstorePrefixnewFormatsforceEvolution,
                  disallowClassChanges);
             for (Format oldFormat : ) {
                 if (oldFormat == null || Format.isPredefined(oldFormat)) {
                     continue;
                 }
                 if (oldFormat.isEntity()) {
                     .evolveFormat(oldFormat);
                 } else {
                     .addNonEntityFormat(oldFormat);
                 }
             }
             .finishEvolution();
             String errors = .getErrors();
             if (errors != null) {
                 throw new IncompatibleClassException(errors);
             }
 
             /*
              * Add the new formats remaining.  New formats that are equal to
              * old formats were removed from the newFormats map above.
              */
             for (Format newFormat : newFormats.values()) {
                 addFormat(newFormat);
             }
 
             /* Initialize all formats. */
             for (Format format : ) {
                 if (format != null) {
                     format.initializeIfNeeded(this);
                     if (format == format.getLatestVersion()) {
                         .put(format.getClassName(), format);
                     }
                 }
             }
 
             final boolean formatsChanged =
                  newFormats.size() > 0 ||
                  .areFormatsChanged();
             needWrite |= formatsChanged;
 
             /* For unit testing. */
             if ( && formatsChanged) {
                 throw new IllegalStateException
                     ("Unexpected changes " +
                      " newFormats.size=" + newFormats.size() +
                      " areFormatsChanged=" + .areFormatsChanged());
             }
 
              = .getConfig().getReadOnly();
 
             /* Write the catalog if anything changed. */
             if (needWrite && !) {
 
                 /* <!-- begin JE only --> */
                 try {
                 /* <!-- end JE only --> */
 
                     /*
                      * Only rename/remove databases if we are going to update
                      * the catalog to reflect those class changes.
                      */
                     .renameAndRemoveDatabases(txn);
 
                     /*
                      * Note that we use the Data object that was read above,
                      * and the beta version determines whether to delete the
                      * old mutations record.
                      */
                     . = ;
                     . = ;
                     writeData(txn);
                 /* <!-- begin JE only --> */
                 } catch (ReplicaWriteException e) {
                      = true;
                 }
                 /* <!-- end JE only --> */
             }
             initModelAndMutations();
         } finally {
 
             /*
              * Fields needed only for the duration of this ctor and which
              * should be null afterwards.
              */
              = null;
              = null;
              = null;
         }
     }
 
     private void initModelAndMutations() {
 
         /*
          * Give the model a reference to the catalog to fully initialize
          * the model.  Only then may we initialize the Converter mutations,
          * which themselves may call model methods and expect the model to
          * be fully initialized.
          */
         ModelInternal.setCatalog(this);
         for (Converter converter : .getConverters()) {
             converter.getConversion().initialize();
         }
     }
 
     public void getEntityFormats(Collection<FormatentityFormats) {
         for (Format format : .values()) {
             if (format.isEntity()) {
                 entityFormats.add(format);
             }
         }
     }
 
     private void addProxiedClass(String classNameboolean isKnownClass) {
         ClassMetadata metadata = .getClassMetadata(className);
         if (metadata != null) {
             String proxiedClassName = metadata.getProxiedClassName();
             if (proxiedClassName != null) {
                 
                 /* 
                  * If the class is a registered known class, need to check if 
                  * registering proxy class is allowed or not. Currently, only 
                  * SimpleType is not allowed to register a proxy class.
                  */
                 if (isKnownClass) {
                     try {
                         Class type = resolveClass(proxiedClassName);
                         
                         /*
                          * Check if the proxied class is allowed to register a 
                          * proxy class. If not, IllegalArgumentException will 
                          * be thrown.
                          */
                         if(!SimpleCatalog.allowRegisterProxy(type)) {
                             throw new IllegalArgumentException
                             ("Registering proxy is not allowed for " + 
                              proxiedClassName + 
                              ", which is a built-in simple type.");
                         }
                     } catch (ClassNotFoundException e) {
                         throw DbCompat.unexpectedState
                         ("Class does not exist: " + proxiedClassName);
                     }
                 }
                 .put(proxiedClassNameclassName);
             }
         }
     }
 
     private void addPredefinedProxies(List<StringknownClasses) {
         knownClasses.add(CollectionProxy.ArrayListProxy.class.getName());
         knownClasses.add(CollectionProxy.LinkedListProxy.class.getName());
         knownClasses.add(CollectionProxy.HashSetProxy.class.getName());
         knownClasses.add(CollectionProxy.TreeSetProxy.class.getName());
         knownClasses.add(MapProxy.HashMapProxy.class.getName());
         knownClasses.add(MapProxy.TreeMapProxy.class.getName());
         knownClasses.add(MapProxy.LinkedHashMapProxy.class.getName());
     }

    
Returns a map from format to a set of its superclass formats. The format for simple types, enums and class Object are not included. Only complex types have superclass formats as defined by Format.getSuperFormat.
 
     Map<FormatSet<Format>> getSubclassMap() {
         Map<FormatSet<Format>> subclassMap =
             new HashMap<FormatSet<Format>>();
         for (Format format : ) {
             if (format == null || Format.isPredefined(format)) {
                 continue;
             }
             Format superFormat = format.getSuperFormat();
             if (superFormat != null) {
                 Set<Formatsubclass = subclassMap.get(superFormat);
                 if (subclass == null) {
                     subclass = new HashSet<Format>();
                     subclassMap.put(superFormatsubclass);
                 }
                 subclass.add(format);
             }
         }
         return subclassMap;
     }

    
Returns the model parameter, default model or stored model.
 
     public EntityModel getResolvedModel() {
         return ;
     }

    
Increments the reference count for a catalog that is already open.
 
     public void openExisting() {
          += 1;
     }

    
Returns true if the user opened the store read-only, or we're running in Replica upgrade mode.
 
     public boolean isReadOnly() {
         return ;
     }

    
Decrements the reference count and closes the catalog DB when it reaches zero. Returns true if the database was closed or false if the reference count is still non-zero and the database was left open.
 
     public boolean close()
         throws DatabaseException {
 
         if ( == 0) {
             throw DbCompat.unexpectedState("Catalog is not open");
         } else {
              -= 1;
             if ( == 0) {
                 .close();
                 return true;
             } else {
                 return false;
             }
         }
     }

    
Returns the current merged mutations.
 
     public Mutations getMutations() {
         return ;
     }

    
Convenience method that gets the class for the given class name and calls createFormat with the class object.
 
     public Format createFormat(String clsName,
                                Map<StringFormatnewFormats) {
         Class type;
         try {
             type = resolveClass(clsName);
         } catch (ClassNotFoundException e) {
             throw DbCompat.unexpectedState
                 ("Class does not exist: " + clsName);
         }
         return createFormat(typenewFormats);
     }

    
If the given class format is not already present in the given map and a format for this class name does not already exist, creates an uninitialized format, adds it to the map, and also collects related formats in the map.
 
     public Format createFormat(Class typeMap<StringFormatnewFormats) {
         /* Return a new or existing format for this class. */
         String className = type.getName();
         Format format = getFormatFromMap(typenewFormats);
         if (format != null) {
             return format;
         }
         format = getFormatFromMap(type);
         if (format != null) {
             return format;
         }
         /* Simple types are predefined. */
         assert !SimpleCatalog.isSimpleType(type) : className;
 
         /*
          * Although metadata is only needed for a complex type, call
          * getClassMetadata for all types to support checks for illegal
          * metadata on other types.
          */
         ClassMetadata metadata = .getClassMetadata(className);
         /* Create format of the appropriate type. */
         String proxyClassName = null;
         if ( != null) {
             proxyClassName = .get(className);
         }
         if (proxyClassName != null) {
             format = new ProxiedFormat(thistypeproxyClassName);
         } else if (type.isArray()) {
             format = type.getComponentType().isPrimitive() ?
                 (new PrimitiveArrayFormat(thistype)) :
                 (new ObjectArrayFormat(thistype));
         } else if (type.isEnum()) {
             format = new EnumFormat(thistype);
         } else if (type.getEnclosingClass() != null && 
                    type.getEnclosingClass().isEnum()) {
 
             /* 
              * If the type is an anonymous class of an enum class, the format
              * which represents the enum class will be created. [#18357]
              */
             format = new EnumFormat(thistype.getEnclosingClass());
         } else if (type == Object.class || type.isInterface()) {
             format = new NonPersistentFormat(thistype);
         } else {
             if (metadata == null) {
                 throw new IllegalArgumentException
                     ("Class could not be loaded or is not persistent: " +
                      className);
             }
             if (metadata.getCompositeKeyFields() != null &&
                 (metadata.getPrimaryKey() != null ||
                  metadata.getSecondaryKeys() != null)) {
                 throw new IllegalArgumentException
                     ("A composite key class may not have primary or" +
                      " secondary key fields: " + type.getName());
             }
 
             /*
              * Check for inner class before default constructor, to give a
              * specific error message for each.
              */
             if (type.getEnclosingClass() != null &&
                 !Modifier.isStatic(type.getModifiers())) {
                 throw new IllegalArgumentException
                     ("Inner classes not allowed: " + type.getName());
             }
             try {
                 type.getDeclaredConstructor();
             } catch (NoSuchMethodException e) {
                 throw new IllegalArgumentException
                     ("No default constructor: " + type.getName(), e);
             }
             if (metadata.getCompositeKeyFields() != null) {
                 format = new CompositeKeyFormat
                     (thistypemetadata,
                      metadata.getCompositeKeyFields());
             } else {
                 EntityMetadata entityMetadata =
                     .getEntityMetadata(className);
                 format =
                     new ComplexFormat(thistypemetadataentityMetadata);
             }
         }
         /* Collect new format along with any related new formats. */
         newFormats.put(classNameformat);
         format.collectRelatedFormats(thisnewFormats);
 
         return format;
     }
     
     private Format getFormatFromMap(Class type
                                     Map<StringFormatformats) {
         Format format = formats.get(type.getName());
         if (format != null) {
             return format;
         } else if (type.getEnclosingClass() != null && 
                    type.getEnclosingClass().isEnum()) {
 
             /* 
              * If the type is an anonymous class of this enum class, the format
              * which represents the enum class will be returned. [#18357]
              */
             format = formats.get(type.getEnclosingClass().getName());
             if (format != null) {
                 return format;
             }
         }
         return null;
     }

    
Adds a format and makes it the current format for the class.
 
     private void addFormat(Format format) {
         addFormat(format);
     }

    
Adds a format to the given the format collections, for use when dynamically adding formats.
 
     private void addFormat(Format format,
                            List<Formatlist,
                            Map<StringFormatmap) {
         format.setId(list.size());
         list.add(format);
         map.put(format.getClassName(), format);
     }

    
Installs an existing format when no evolution is needed, i.e, when the new and old formats are identical.
 
     void useExistingFormat(Format oldFormat) {
         assert oldFormat.isCurrentVersion();
         .put(oldFormat.getClassName(), oldFormat);
     }

    
Returns a set of all persistent (non-simple type) class names.
 
     Set<StringgetModelClasses() {
         Set<Stringclasses = new HashSet<String>();
         for (Format format : .values()) {
             if (format.isModelClass()) {
                 classes.add(format.getClassName());
             }
         }
         return Collections.unmodifiableSet(classes);
     }

    
Returns all formats as RawTypes.
 
     public List<RawTypegetAllRawTypes() {
         List<RawTypelist = new ArrayList<RawType>();
         for (RawType type : ) {
             if (type != null) {
                 list.add(type);
             }
         }
         return Collections.unmodifiableList(list);
     }

    
When a format is intialized, this method is called to get the version of the serialized object to be initialized. See Catalog.
 
     public int getInitVersion(Format formatboolean forReader) {
 
         if ( == null || . == null ||
             format.getId() >= ..size()) {
 
             /*
              * For new formats, use the current version.  If initData is null,
              * the Catalog ctor is finished and the format must be new.  If the
              * ctor is in progress, the format is new if its ID is greater than
              * the ID of all pre-existing formats.
              */
             return .;
         } else {
 
             /*
              * Get the version of a pre-existing format during execution of the
              * Catalog ctor.  The initData field is non-null, but initEvolver
              * may be null if the catalog is opened in raw mode.
              */
             assert  != null;
 
             if (forReader) {
 
                 /*
                  * Get the version of the evolution reader for a pre-existing
                  * format.  Use the current version if the format changed
                  * during class evolution, otherwise use the stored version.
                  */
                 return ( != null &&
                         .isFormatChanged(format)) ?
                        . : .;
             } else {
                 /* Always used the stored version for a pre-existing format. */
                 return .;
             }
         }
     }
 
     public Format getFormat(final int formatIdfinal boolean expectStored)
         throws RefreshException {
 
         if (formatId < 0) {
             throw DbCompat.unexpectedState
                 ("Format ID " + formatId + " is negative," +
                  " may indicate data corruption.");
         }

        
If we're attempting to read a record containing a format ID that is greater than the maximum known stored format, then we refresh the formats from disk, expecting that the stored formats have been updated by the Master node. Note that format IDs greater than nStoredFormats may exist in the formatList, if evolution took place on this Replica in a read-only mode. Such formats are never written (Replicas do not write) and cannot be used for reading an existing record. [#16655] Do not perform this check if we did not get the format ID from a stored record (expectStored is false). For example, this would cause an erroneous RefreshException when this method is called during a convertRawObject operation, which calls this method to get a fresh copy of a format that may not be stored. [#18690]
 
         if (expectStored && formatId >= ) {
             assert  != null;
             throw new RefreshException(thisformatId);
         }
 
         Format format = .get(formatId);
         if (format == null) {
             throw DbCompat.unexpectedState
                 ("Format ID " + formatId + " has null format," +
                  " may indicate data corruption.");
         }
 
         /*
          * Currently we can't throw DeletedClassException because we should not
          * do this if we're being called during a Conversion, and we don't have
         * that state information available.
         */
        /*
        if (format.isDeleted()) {
            throw new DeletedClassException
                ("Class " + format.getClassName() +
                 " was deleted with a Deleter muation, format ID " +
                 formatId + '.');
        }
        */
        return format;
    }

    
Get a format for a given class, creating it if it does not exist.

This method is called for top level entity instances by PersistEntityBinding. When a new entity subclass format is added we call Store.checkEntitySubclassSecondaries to ensure that all secondary databases have been opened, before storing the entity. We do this here while not holding a synchronization mutex, not in addNewFormat, to avoid deadlocks. checkEntitySubclassSecondaries synchronizes on the Store. [#16399]

Historical note: At one time we opened / created the secondary databases rather than requiring the user to open them, see [#15247]. Later we found this to be problematic since a user txn may have locked primary records, see [#16399].

    public Format getFormat(Class clsboolean checkEntitySubclassIndexes)
        throws RefreshException {
        Format format = .get(cls.getName());
        if (format == null) {
            if ( != null) {
                format = addNewFormat(cls);
                /* Detect and handle new entity subclass. [#15247] */
                if (checkEntitySubclassIndexes &&  != null) {
                    Format entityFormat = format.getEntityFormat();
                    if (entityFormat != null && entityFormat != format) {
                        try {
                            .checkEntitySubclassSecondaries
                                (entityFormat.getEntityMetadata(),
                                 cls.getName());
                        } catch (DatabaseException e) {
                            throw RuntimeExceptionWrapper.wrapIfNeeded(e);
                        }
                    }
                }
            }
            if (format == null) {
                throw new IllegalArgumentException
                    ("Class is not persistent: " + cls.getName());
            }
        }
        return format;
    }
    public Format getFormat(String className) {
        return .get(className);
    }
    public Format getLatestVersion(String className) {
        return .get(className);
    }

    
Returns the name of an entity class to be used to form the database name. Normally this is the same as the class name, but in replica upgrade mode it may be an earlier version of a renamed class. Returns null if there is no stored version of the class. [#16655]
    public String getDatabaseClassName(final String className) {
        final Format format = getStoredFormat(className);
        if (format == null) {
            return null;
        }
        return format.getClassName();
    }

    
Similar to getDatabaseClassName but instead handles an earlier version of a renamed key. [#16655]
    public String getDatabaseKeyName(final String className,
                                     final String keyName) {
        final Format format = getStoredFormat(className);
        if (format == null) {
            return null;
        }
        return format.getOldKeyName(keyName);
    }
    private Format getStoredFormat(final String className) {
        Format format = getFormat(className);
        while (format != null && format.getId() >= ) {
            format = format.getPreviousVersion();
        }
        return format;
    }

    
Metadata needs refreshing when a Replica with stale metadata is elected master, and then a user write operation is attempted. [#16655]
        throws RefreshException {
        if ( < .size()) {
            throw new RefreshException(this, -1 /*formatId*/);
        }
    }

    
For unit testing.
    boolean isReplicaUpgradeMode() {
        return  < .size();
    }

    
Adds a format for a new class. Returns the format added for the given class, or throws an exception if the given class is not persistent.

This method uses a copy-on-write technique to add new formats without impacting other threads.

    private synchronized Format addNewFormat(Class cls)
        throws RefreshException {
        /*
         * After synchronizing, check whether another thread has added the
         * format needed.  Note that this is not the double-check technique
         * because the formatMap field is volatile and is not itself checked
         * for null.  (The double-check technique is known to be flawed in
         * Java.)
         */
        Format format = getFormatFromMap(cls);
        if (format != null) {
            return format;
        }
        /* Copy the read-only format collections. */
        List<FormatnewFormatList = new ArrayList<Format>();
        Map<StringFormatnewFormatMap =
            new HashMap<StringFormat>();
        Map<StringFormatnewLatestFormatMap =
            new HashMap<StringFormat>();
        /* Add the new format and all related new formats. */
        Map<StringFormatnewFormats = new HashMap<StringFormat>();
        format = createFormat(clsnewFormats);
        for (Format newFormat : newFormats.values()) {
            addFormat(newFormatnewFormatListnewFormatMap);
        }
        /*
         * Initialize new formats using a read-only catalog because we can't
         * update this catalog until after we store it (below).
         */
        Catalog newFormatCatalog = new ReadOnlyCatalog
            (ModelInternal.getClassLoader(), newFormatListnewFormatMap);
        for (Format newFormat : newFormats.values()) {
            newFormat.initializeIfNeeded(newFormatCatalog);
            newLatestFormatMap.put(newFormat.getClassName(), newFormat);
        }
        /*
         * Write the updated catalog using auto-commit, then assign the new
         * collections.  The database write must occur before the collections
         * are used, since a format must be persistent before it can be
         * referenced by a data record.
         *
         * In readOnly mode, which includes Replica upgrade mode, we should not
         * attempt to write since we could be elected Master and write stale
         * metadata.  If ReplicaWriteException occurs then we transition to
         * Replica upgrade mode in the same manner as in the init() method.
         * This can happen when no schema change is made except for one or more
         * new entity classes.  The new entity class will not be detected by
         * evolution (during init()) but will be detected here if the user
         * calls getPrimaryIndex.  [#16655]
         */
        if (!) {
            try {
                Data newData = new Data();
                newData.formatList = newFormatList;
                newData.mutations = ;
                writeDataCheckStale(newData);
            /* <!-- begin JE only --> */
            } catch (ReplicaWriteException e) {
                 = true;
            /* <!-- end JE only --> */
            } catch (DatabaseException e) {
                throw RuntimeExceptionWrapper.wrapIfNeeded(e);
            }
        }
         = newFormatList;
         = newFormatMap;
         = newLatestFormatMap;
        return format;
    }

    
Used to write the catalog when a format has been changed, for example, when Store.evolve has updated a Format's EvolveNeeded property. Uses auto-commit.
    public synchronized void flush(Transaction txn)
        throws DatabaseException {
        Data newData = new Data();
        newData.formatList = ;
        newData.mutations = ;
        writeData(txnnewData);
    }

    
Returns the number of stored formats.
    int getNFormats() {
        return ;
    }

    
Reads catalog Data, converting old versions as necessary. An empty Data object is returned if no catalog data currently exists. Null is never returned.
    private Data readData(Transaction txn)
        throws DatabaseException {
        Data oldData;
        DatabaseEntry key = new DatabaseEntry();
        DatabaseEntry data = new DatabaseEntry();
        OperationStatus status = .get(txnkeydatanull);
        if (status == .) {
            ByteArrayInputStream bais = new ByteArrayInputStream
                (data.getData(), data.getOffset(), data.getSize());
            try {
                ObjectInputStream ois = new ObjectInputStream(bais);
                Object object = ois.readObject();
                assert ois.available() == 0;
                if (object instanceof Data) {
                    oldData = (Dataobject;
                } else {
                    if (!(object instanceof List)) {
                        throw DbCompat.unexpectedState
                            (object.getClass().getName());
                    }
                    oldData = new Data();
                    oldData.formatList = (Listobject;