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;
  
Format for persistent complex classes that are not composite key classes. This includes entity classes and subclasses.

Author(s):
Mark Hayes
  
  public class ComplexFormat extends Format {
  
      private static final long serialVersionUID = -2847843033590454917L;
  
      private ClassMetadata clsMeta;
      private EntityMetadata entityMeta;
      private FieldInfo priKeyField;
      private List<FieldInfosecKeyFields;
      private List<FieldInfononKeyFields;
      private FieldReader secKeyFieldReader;
      private FieldReader nonKeyFieldReader;
      private Map<StringStringoldToNewKeyMap;
      private Map<StringStringnewToOldFieldMap;
      private boolean evolveNeeded;
      private transient Accessor objAccessor;
      private transient Accessor rawAccessor;
      private transient ComplexFormat entityFormat;
      private transient Map<StringFieldAddresssecKeyAddresses;
      private transient volatile Map<StringRawFieldrawFields;
      private transient volatile FieldInfo[] rawInputFields;
      private transient volatile int[] rawInputLevels;
      private transient volatile int rawInputDepth;

    
This field contains the names of secondary keys that are incorrectly ordered because, in an earlier version, we failed to set the dup comparator. This bug applies only when the primary key has a comparator. The bug was fixed by setting the dup comparator to the primary key comparator, for all new secondary databases. [#17252] A field containing an empty set signifies that no keys are incorrectly ordered, while a null field signifies that all keys are incorrect (when the primary key has a comparator). The field is assigned to an empty set when declared, so that it will be null only when a pre-fix version of the format is deserialized. (With Java serialization, when a field is added to a class and a previously serialized instance is deserialized, the new field will always be null). This field is used to determine when a dup comparator should be set. We cannot set the comparator for secondary databases created prior to the bug fix, since ordering cannot be changed for existing records. See isSecKeyIncorrectlyOrdered and setSecKeyCorrectlyOrdered. This field does not count in comparisons of formats during evolution. When the user wants to correct the ordering for an incorrectly ordered secondary database, she must delete the database but does not need to increment the class version. In other words, this is information about the database order but is not considered class metadata.
  
      private Set<StringincorrectlyOrderedSecKeys = new HashSet<String>();

    
In JE 5.0 we changed the format for String fields. Instead of treating the String as an object with a format ID embedded in the serialized bytes, we treat it as a primitive and do not include the format ID. This works well because a field declared to be type String cannot be used to store any other object, and because the String tuple format supports null values. A field containing false signifies that the old String format was used when the entity was written, while a true value signifies that the new String format was used. The field is assigned to true when declared, so that it will be false only when a pre-JE 5.0 version of the format is deserialized. (With Java serialization, when a boolean field is added to a class and a previously serialized instance is deserialized, the new field will always be false).
 
     private boolean newStringFormat = true;
 
     ComplexFormat(Catalog catalog,
                   Class cls,
                   ClassMetadata clsMeta,
                   EntityMetadata entityMeta) {
         super(catalogcls);
         this. = clsMeta;
         this. = entityMeta;
          = new ArrayList<FieldInfo>();
          = FieldInfo.getInstanceFields(clsclsMeta);
 
         /*
          * Validate primary key metadata and move primary key field from
          * nonKeyFields to priKeyField.
          */
         if (clsMeta.getPrimaryKey() != null) {
             String fieldName = clsMeta.getPrimaryKey().getName();
             FieldInfo field = FieldInfo.getField(fieldName);
             if (field == null) {
                 throw new IllegalArgumentException
                     ("Primary key field does not exist: " +
                      getClassName() + '.' + fieldName);
             }
             .remove(field);
              = field;
         }
 
         /*
          * Validate secondary key metadata and move secondary key fields from
          * nonKeyFields to secKeyFields.
          */
         if (clsMeta.getSecondaryKeys() != null) {
             for (SecondaryKeyMetadata secKeyMeta :
                  clsMeta.getSecondaryKeys().values()) {
                 String fieldName = secKeyMeta.getName();
                 FieldInfo field = FieldInfo.getField(fieldName);
                 if (field == null) {
                     throw new IllegalArgumentException
                         ("Secondary key field does not exist: " +
                          getClassName() + '.' + fieldName);
                 }
                 Class fieldCls = field.getFieldClass(getCatalog());
                 Relationship rel = secKeyMeta.getRelationship();
                 if (rel == . ||
                     rel == .) {
                     if (!PersistKeyCreator.isManyType(fieldCls)) {
                         throw new IllegalArgumentException
                             ("ONE_TO_MANY and MANY_TO_MANY keys must" +
                              " have an array or Collection type: " +
                              getClassName() + '.' + fieldName);
                     }
                 } else {
                     if (PersistKeyCreator.isManyType(fieldCls)) {
                         throw new IllegalArgumentException
                             ("ONE_TO_ONE and MANY_TO_ONE keys must not" +
                              " have an array or Collection type: " +
                              getClassName() + '.' + fieldName);
                     }
                 }
                 if (fieldCls.isPrimitive() &&
                     secKeyMeta.getDeleteAction() == .) {
                     throw new IllegalArgumentException
                         ("NULLIFY may not be used with primitive fields: " +
                          getClassName() + '.' + fieldName);
                 }
                 .remove(field);
                 .add(field);
             }
         }
 
         /* Sort each group of fields by name. */
         Collections.sort();
         Collections.sort();
     }
 
     @Override
     void migrateFromBeta(Map<StringFormatformatMap) {
         super.migrateFromBeta(formatMap);
         if ( != null) {
             .migrateFromBeta(formatMap);
         }
         for (FieldInfo field : ) {
             field.migrateFromBeta(formatMap);
         }
         for (FieldInfo field : ) {
             field.migrateFromBeta(formatMap);
         }
     }

    
Returns getSuperFormat cast to ComplexFormat. It is guaranteed that all super formats of a ComplexFormat are a ComplexFormat.
 
         return (ComplexFormatgetSuperFormat();
     }

    
Returns getLatestVersion cast to ComplexFormat. It is guaranteed that all versions of a ComplexFormat are a ComplexFormat.
 
     private ComplexFormat getComplexLatest() {
         return (ComplexFormatgetLatestVersion();
     }
 
         return ;
     }
 
     String getPriKeyField() {
         if (.getPrimaryKey() != null) {
             return .getPrimaryKey().getName();
         } else {
             return null;
         }
     }
 
     @Override
     boolean isEntity() {
         return .isEntityClass();
     }
 
     @Override
     boolean isModelClass() {
         return true;
     }
 
     @Override
     public ClassMetadata getClassMetadata() {
         return ;
     }
 
     @Override
     public EntityMetadata getEntityMetadata() {
         return ;
     }
 
     @Override
         if (isInitialized()) {
             /* The transient entityFormat field is set by initialize(). */
             return ;
         } else {
 
             /*
              * If not initialized, the entity format can be found by traversing
              * the super formats.  However, this is only possible for an
              * existing format which has its superFormat field set.
              */
             if (isNew()) {
                 throw DbCompat.unexpectedState(toString());
             }
             for (ComplexFormat format = this;
                  format != null;
                  format = format.getComplexSuper()) {
                 if (format.isEntity()) {
                     return format;
                 }
             }
             return null;
         }
     }
 
     @Override
     void setEvolveNeeded(boolean needed) {
          = needed;
     }
 
     @Override
     boolean getEvolveNeeded() {
         return ;
     }
 
     @Override
     boolean getNewStringFormat() {
         if (getEntityFormat() == null) {
             throw DbCompat.unexpectedState();
         }
         return ;
     }
 
     @Override
     public Map<StringRawFieldgetFields() {
 
         /*
          * Synchronization is not required since rawFields is immutable.  If
          * by chance we create two maps when two threads execute this block, no
          * harm is done.  But be sure to assign the rawFields field only after
          * the map is fully populated.
          */
         if ( == null) {
             Map<StringRawFieldmap = new HashMap<StringRawField>();
             if ( != null) {
                 map.put(.getName(), );
             }
             for (RawField field : ) {
                 map.put(field.getName(), field);
             }
             for (RawField field : ) {
                 map.put(field.getName(), field);
             }
              = map;
         }
         return ;
     }
 
     @Override
     void collectRelatedFormats(Catalog catalog,
                                Map<StringFormatnewFormats) {
         Class cls = getType();
         /* Collect field formats. */
         if ( != null) {
             .collectRelatedFormats(catalognewFormats);
         }
         for (FieldInfo field : ) {
             field.collectRelatedFormats(catalognewFormats);
         }
         for (FieldInfo field : ) {
             field.collectRelatedFormats(catalognewFormats);
         }
         /* Collect TO_MANY secondary key field element class formats. */
         if ( != null) {
             for (SecondaryKeyMetadata secKeyMeta :
                  .getSecondaryKeys().values()) {
                 String elemClsName = secKeyMeta.getElementClassName();
                 if (elemClsName != null) {
                     Class elemCls = catalog.resolveKeyClass(elemClsName);
                     catalog.createFormat(elemClsnewFormats);
                 }
             }
         }
         /* Recursively collect superclass formats. */
         Class superCls = cls.getSuperclass();
         if (superCls != Object.class) {
             Format superFormat = catalog.createFormat(superClsnewFormats);
             if (!(superFormat instanceof ComplexFormat)) {
                 throw new IllegalArgumentException
                     ("The superclass of a complex type must not be a" +
                      " composite key class or a simple type class: " +
                      superCls.getName());
             }
         }
         /* Collect proxied format. */
         String proxiedClsName = .getProxiedClassName();
         if (proxiedClsName != null) {
             catalog.createFormat(proxiedClsNamenewFormats);
         }
     }
 
     @Override
     void initialize(Catalog catalogEntityModel modelint initVersion) {
 
         Class type = getType();
         boolean useEnhanced = false;
         if (type != null) {
             useEnhanced = EnhancedAccessor.isEnhanced(type);
         }
         /* Initialize all fields. */
         if ( != null) {
             .initialize(catalogmodelinitVersion);
         }
         for (FieldInfo field : ) {
             field.initialize(catalogmodelinitVersion);
         }
         for (FieldInfo field : ) {
             field.initialize(catalogmodelinitVersion);
         }
         /* Set the superclass format for a new (never initialized) format. */
         ComplexFormat superFormat = getComplexSuper();
         if (type != null && superFormat == null) {
             Class superCls = type.getSuperclass();
             if (superCls != Object.class) {
                 superFormat =
                     (ComplexFormatcatalog.getFormat(superCls.getName());
                 setSuperFormat(superFormat);
             }
         }
         /* Initialize the superclass format and validate the super accessor. */
         if (superFormat != null) {
             superFormat.initializeIfNeeded(catalogmodel);
             Accessor superAccessor = superFormat.objAccessor;
             if (type != null && superAccessor != null) {
                 if (useEnhanced) {
                     if (!(superAccessor instanceof EnhancedAccessor)) {
                         throw new IllegalStateException
                             ("The superclass of an enhanced class must also " +
                              "be enhanced: " + getClassName() +
                              " extends " + superFormat.getClassName());
                     }
                 } else {
                     if (!(superAccessor instanceof ReflectionAccessor)) {
                         throw new IllegalStateException
                             ("The superclass of an unenhanced class must " +
                              "not be enhanced: " + getClassName() +
                              " extends " + superFormat.getClassName());
                     }
                 }
             }
         }
         /* Find entity format, if any. */
         for (ComplexFormat format = this;
              format != null;
              format = format.getComplexSuper()) {
             if (format.isEntity()) {
                  = format;
                 break;
             }
         }
 
         /*
          * Ensure that the current entity metadata is always referenced in
          * order to return it to the user and to properly construct secondary
          * key addresses.  Secondary key metadata can change in an entity
          * subclass or be created when a new subclass is used, but this will
          * not cause evolution of the entity class; instead, the metadata is
          * updated here.  [#16467]
          */
         if (isEntity() && isCurrentVersion()) {
              = model.getEntityMetadata(getClassName());
         }
 
         /* Disallow proxy class that extends an entity class. [#15950] */
         if (.getProxiedClassName() != null &&  != null) {
             throw new IllegalArgumentException
                 ("A proxy may not be an entity: " + getClassName());
         }
         /* Disallow primary keys on entity subclasses.  [#15757] */
         if ( != null &&
              != this &&
              != null) {
             throw new IllegalArgumentException
                 ("A PrimaryKey may not appear on an Entity subclass: " +
                  getClassName() + " field: " + .getName());
         }
         /* Create the accessors. */
         if (type != null) {
             if (useEnhanced) {
                  = new EnhancedAccessor(catalogtypethis);
             } else {
                 Accessor superObjAccessor =
                     (superFormat != null) ?  superFormat.objAccessor : null;
                  = new ReflectionAccessor
                     (catalogtypesuperObjAccessor,
                      );
             }
         }
         Accessor superRawAccessor =
             (superFormat != null) ? superFormat.rawAccessor : null;
          = new RawAccessor
             (thissuperRawAccessor);
 
         /* Initialize secondary key field addresses. */
         EntityMetadata latestEntityMeta = null;
         if ( != null) {
             latestEntityMeta =
                 .getLatestVersion().getEntityMetadata();
         }
         if (latestEntityMeta != null) {
              = new HashMap<StringFieldAddress>();
             ComplexFormat thisLatest = getComplexLatest();
             if (thisLatest != this) {
                 thisLatest.initializeIfNeeded(catalogmodel);
             }
             nextKeyLoop:
             for (SecondaryKeyMetadata secKeyMeta :
                  latestEntityMeta.getSecondaryKeys().values()) {
                 String clsName = secKeyMeta.getDeclaringClassName();
                 String fieldName = secKeyMeta.getName();
                 int superLevel = 0;
                 for (ComplexFormat format = this;
                      format != null;
                      format = format.getComplexSuper()) {
                     if (clsName.equals
                             (format.getLatestVersion().getClassName())) {
                         String useFieldName = null;
                         if (format.newToOldFieldMap != null &&
                             format.newToOldFieldMap.containsKey(fieldName)) {
                             useFieldName =
                                 format.newToOldFieldMap.get(fieldName);
                         } else {
                             useFieldName = fieldName;
                         }
                         boolean isSecField;
                         int fieldNum;
                         FieldInfo info = FieldInfo.getField
                             (format.secKeyFieldsuseFieldName);
                         if (info != null) {
                             isSecField = true;
                             fieldNum = format.secKeyFields.indexOf(info);
                         } else {
                             isSecField = false;
                             info = FieldInfo.getField
                                 (format.nonKeyFieldsuseFieldName);
                             if (info == null) {
                                 /* Field not present in old format. */
                                 assert thisLatest != this;
                                 thisLatest.checkNewSecKeyInitializer
                                     (secKeyMeta);
                                 continue nextKeyLoop;
                             }
                             fieldNum = format.nonKeyFields.indexOf(info);
                         }
                         FieldAddress addr = new FieldAddress
                             (isSecFieldfieldNumsuperLevelformat,
                              info.getType());
                         .put(secKeyMeta.getKeyName(), addr);
                     }
                     superLevel += 1;
                 }
             }
         }
     }

    
Checks that the type of a new secondary key is not a primitive and that the default contructor does not initialize it to a non-null value.
 
     private void checkNewSecKeyInitializer(SecondaryKeyMetadata secKeyMeta) {
         if ( != null) {
         
             /* 
              * If this format represents an abstract class, we will not do the
              * following check. When initializing this abstract class's 
              * subclass, which is not abstract, the new added secondary key 
              * will be checked then. [#19358]
              */
             if (Modifier.isAbstract(this.getType().getModifiers())) {
                 return;
             }
             FieldAddress addr = .get(secKeyMeta.getKeyName());
             Object obj = .newInstance();
             Object val = .getField
                 (objaddr.fieldNumaddr.superLeveladdr.isSecField);
             if (val != null) {
                 if (addr.keyFormat.isPrimitive()) {
                     throw new IllegalArgumentException
                         ("For a new secondary key field the field type must " +
                          "not be a primitive -- class: " +
                          secKeyMeta.getDeclaringClassName() + " field: " +
                          secKeyMeta.getName());
                 } else {
                     throw new IllegalArgumentException
                         ("For a new secondary key field the default " +
                          "constructor must not initialize the field to a " +
                          "non-null value -- class: " +
                          secKeyMeta.getDeclaringClassName() + " field: " +
                          secKeyMeta.getName());
                 }
             }
         }
     }
 
     private boolean nullOrEqual(Object o1Object o2) {
         if (o1 == null) {
             return o2 == null;
         } else {
             return o1.equals(o2);
         }
     }
 
     @Override
     Object newArray(int len) {
         return .newArray(len);
     }
 
     @Override
     public Object newInstance(EntityInput inputboolean rawAccess) {
         Accessor accessor = rawAccess ?  : ;
         return accessor.newInstance();
     }
 
     @Override
     public Object readObject(Object oEntityInput inputboolean rawAccess)
         throws RefreshException {
 
         Accessor accessor = rawAccess ?  : ;
         accessor.readSecKeyFields(oinput, 0, ., -1);
         accessor.readNonKeyFields(oinput, 0, ., -1);
         return o;
     }
 
     @Override
     void writeObject(Object oEntityOutput outputboolean rawAccess)
         throws RefreshException {
 
         Accessor accessor = rawAccess ?  : ;
         accessor.writeSecKeyFields(ooutput);
         accessor.writeNonKeyFields(ooutput);
     }
 
     @Override
     Object convertRawObject(Catalog catalog,
                             boolean rawAccess,
                             RawObject rawObject,
                             IdentityHashMap converted)
         throws RefreshException {
 
         /*
          * Synchronization is not required since rawInputFields, rawInputLevels
          * and rawInputDepth are immutable.  If by chance we create duplicate
          * values when two threads execute this block, no harm is done.  But be
          * sure to assign the fields only after the values are fully populated.
          */
         FieldInfo[] fields = ;
         int[] levels = ;
         int depth = ;
         if (fields == null || levels == null || depth == 0) {
 
             /*
              * The volatile fields are not yet set.  Prepare to process the
              * class hierarchy, storing class formats in order from the highest
              * superclass down to the current class.
              */
             depth = 0;
             int nFields = 0;
             for (ComplexFormat format = this;
                  format != null;
                  format = format.getComplexSuper()) {
                 nFields += format.getNFields();
                 depth += 1;
             }
             ComplexFormat[] hierarchy = new ComplexFormat[depth];
             int level = depth;
             for (ComplexFormat format = this;
                  format != null;
                  format = format.getComplexSuper()) {
                 level -= 1;
                 hierarchy[level] = format;
             }
             assert level == 0;
 
             /* Populate levels and fields in parallel. */
             levels = new int[nFields];
             fields = new FieldInfo[nFields];
             int index = 0;
 
             /*
              * The primary key is the first field read/written.  We use the
              * first primary key field encountered going from this class upward
              * in the class hierarchy.
              */
             if (getEntityFormat() != null) {
                 for (level = depth - 1; level >= 0; level -= 1) {
                     ComplexFormat format = hierarchy[level];
                     if (format.priKeyField != null) {
                         levels[index] = level;
                         fields[index] = format.priKeyField;
                         index += 1;
                         break;
                     }
                 }
                 assert index == 1;
             }
 
             /*
              * Secondary key fields are read/written next, from the highest
              * base class downward.
              */
             for (level = 0; level < depthlevel += 1) {
                 ComplexFormat format = hierarchy[level];
                 for (FieldInfo field : format.secKeyFields) {
                     levels[index] = level;
                     fields[index] = field;
                     index += 1;
                 }
             }
 
             /*
              * Other fields are read/written last, from the highest base class
              * downward.
              */
             for (level = 0; level < depthlevel += 1) {
                 ComplexFormat format = hierarchy[level];
                 for (FieldInfo field : format.nonKeyFields) {
                     levels[index] = level;
                     fields[index] = field;
                     index += 1;
                 }
             }
 
             /* We're finished -- update the volatile fields for next time. */
             assert index == fields.length;
              = fields;
              = levels;
              = depth;
         }
 
         /*
          * Create an objects array that is parallel to the fields and levels
          * arrays, but contains the RawObject for each slot from which the
          * field value can be retrieved.  The predetermined level for each
          * field determines which RawObject in the instance hierarchy to use.
          */
         RawObject[] objectsByLevel = new RawObject[depth];
         int level = depth;
         for (RawObject raw = rawObjectraw != nullraw = raw.getSuper()) {
             if (level == 0) {
                 throw new IllegalArgumentException
                     ("RawObject has too many superclasses: " +
                      rawObject.getType().getClassName());
             }
             level -= 1;
             objectsByLevel[level] = raw;
         }
         if (level > 0) {
             throw new IllegalArgumentException
                 ("RawObject has too few superclasses: " +
                  rawObject.getType().getClassName());
         }
         assert level == 0;
         RawObject[] objects = new RawObject[fields.length];
         for (int i = 0; i < objects.lengthi += 1) {
             objects[i] = objectsByLevel[levels[i]];
         }
 
         /* Create the persistent object and convert all RawObject fields. */
         EntityInput in = new RawComplexInput
             (catalograwAccessconvertedfieldsobjects);
         Object o = newInstance(inrawAccess);
         converted.put(rawObjecto);
         if (getEntityFormat() != null) {
             readPriKey(oinrawAccess);
         }
         return readObject(oinrawAccess);
     }
 
     @Override
     boolean isPriKeyNullOrZero(Object oboolean rawAccess) {
         Accessor accessor = rawAccess ?  : ;
         return accessor.isPriKeyFieldNullOrZero(o);
     }
 
     @Override
     void writePriKey(Object oEntityOutput outputboolean rawAccess)
         throws RefreshException {
 
         Accessor accessor = rawAccess ?  : ;
         accessor.writePriKeyField(ooutput);
     }
 
     @Override
     public void readPriKey(Object oEntityInput inputboolean rawAccess)
         throws RefreshException {
 
         Accessor accessor = rawAccess ?  : ;
         accessor.readPriKeyField(oinput);
     }
 
     @Override
     public String getOldKeyName(final String keyName) {
         if ( != null &&
             .containsKey(keyName)) {
             return .get(keyName);
         } else {
             return keyName;
         }
     }
 
     @Override
     boolean nullifySecKey(Catalog catalog,
                           Object entity,
                           String keyName,
                           Object keyElement) {
         if ( == null) {
             throw DbCompat.unexpectedState();
         }
         FieldAddress addr = .get(keyName);
         if (addr != null) {
             Object oldVal = .getField
                 (entityaddr.fieldNumaddr.superLeveladdr.isSecField);
             if (oldVal != null) {
                 if (keyElement != null) {
                     RawObject container = (RawObjectoldVal;
                     Object[] a1 = container.getElements();
                     boolean isArray = (a1 != null);
                     if (!isArray) {
                         a1 = CollectionProxy.getElements(container);
                     }
                     if (a1 != null) {
                         for (int i = 0; i < a1.lengthi += 1) {
                             if (keyElement.equals(a1[i])) {
                                 int len = a1.length - 1;
                                 Object[] a2 = new Object[len];
                                 System.arraycopy(a1, 0, a2, 0, i);
                                 System.arraycopy(a1i + 1, a2ilen - i);
                                 if (isArray) {
                                     .setField
                                         (entityaddr.fieldNum,
                                          addr.superLeveladdr.isSecField,
                                          new RawObject
                                             (container.getType(), a2));
                                 } else {
                                     CollectionProxy.setElements(containera2);
                                 }
                                 return true;
                             }
                         }
                     }
                     return false;
                 } else {
                     .setField
                         (entityaddr.fieldNumaddr.superLevel,
                          addr.isSecFieldnull);
                     return true;
                 }
             } else {
                 return false;
             }
         } else {
             return false;
         }
     }
 
     @Override
     void skipContents(RecordInput input)
         throws RefreshException {
 
         skipToSecKeyField(input.);
         skipToNonKeyField(input.);
     }
 
     @Override
     void copySecMultiKey(RecordInput inputFormat keyFormatSet results)
         throws RefreshException {
 
         CollectionProxy.copyElements(inputthiskeyFormatresults);
     }
 
     @Override
     Format skipToSecKey(RecordInput inputString keyName)
         throws RefreshException {
 
         if ( == null) {
             throw DbCompat.unexpectedState();
         }
         FieldAddress addr = .get(keyName);
         if (addr != null) {
             if (addr.isSecField) {
                 addr.clsFormat.skipToSecKeyField(inputaddr.fieldNum);
             } else {
                 skipToSecKeyField(input.);
                 addr.clsFormat.skipToNonKeyField(inputaddr.fieldNum);
             }
             return addr.keyFormat;
         } else {
             return null;
         }
     }
 
     private int getNFields() {
         return (( != null) ? 1 : 0) +
                .size() +
                .size();
     }
 
     private void skipToSecKeyField(RecordInput inputint toFieldNum)
         throws RefreshException {
 
         ComplexFormat superFormat = getComplexSuper();
         if (superFormat != null) {
             superFormat.skipToSecKeyField(input.);
         }
         int maxNum = Math.min(.size(), toFieldNum);
         for (int i = 0; i < maxNumi += 1) {
             input.skipField(.get(i).getType());
         }
     }
 
     private void skipToNonKeyField(RecordInput inputint toFieldNum)
         throws RefreshException {
 
         ComplexFormat superFormat = getComplexSuper();
         if (superFormat != null) {
             superFormat.skipToNonKeyField(input.);
         }
         int maxNum = Math.min(.size(), toFieldNum);
         for (int i = 0; i < maxNumi += 1) {
             input.skipField(.get(i).getType());
         }
     }
 
     private static class FieldAddress {
 
         boolean isSecField;
         int fieldNum;
         int superLevel;
         ComplexFormat clsFormat;
         Format keyFormat;
 
         FieldAddress(boolean isSecField,
                      int fieldNum,
                      int superLevel,
                      ComplexFormat clsFormat,
                      Format keyFormat) {
             this. = isSecField;
             this. = fieldNum;
             this. = superLevel;
             this. = clsFormat;
             this. = keyFormat;
         }
     }
 
     @Override
     boolean evolve(Format newFormatParamEvolver evolver) {
 
         /* Disallow evolution to a non-complex format. */
         if (!(newFormatParam instanceof ComplexFormat)) {
             evolver.addMissingMutation
                 (thisnewFormatParam,
                  "Converter is required when a complex type is changed " +
                  "to a simple type or enum type");
             return false;
         }
         ComplexFormat newFormat = (ComplexFormatnewFormatParam;
         Mutations mutations = evolver.getMutations();
         boolean thisChanged = false;
         boolean hierarchyChanged = false;
         Map<StringStringallKeyNameMap = new HashMap<StringString>();
 
         /* The Evolver has already ensured that entities evolve to entities. */
         assert isEntity() == newFormat.isEntity();
         assert isEntity() == ( != null);
         assert newFormat.isEntity() == (newFormat.entityMeta != null);
 
         /*
          * Keep track of the old and new entity class names for use in deleting
          * and renaming secondary keys below.  If the oldEntityClass is
          * non-null this also signifies an entity class or subclass.  Note that
          * getEntityFormat cannot be called on a newly created format during
          * evolution because its super format property is not yet initialized.
          * [#16253]
          */
         String oldEntityClass;
         String newEntityClass;
         if (isEntity()) {
             oldEntityClass = getClassName();
             newEntityClass = newFormat.getClassName();
         } else {
             oldEntityClass = null;
             newEntityClass = null;
         }
 
         /*
          * Evolve all superclass formats, even when a deleted class appears in
          * the hierarchy.  This ensures that the super format's
          * getLatestVersion/getComplexLatest method can be used accurately
          * below.
          */
         for (ComplexFormat oldSuper = getComplexSuper();
              oldSuper != null;
              oldSuper = oldSuper.getComplexSuper()) {
             Converter converter = mutations.getConverter
                 (oldSuper.getClassName(), oldSuper.getVersion(), null);
             if (converter != null) {
                 evolver.addMissingMutation
                     (thisnewFormatParam,
                      "Converter is required for this subclass when a " +
                      "Converter appears on its superclass: " + converter);
                 return false;
             }
             if (!evolver.evolveFormat(oldSuper)) {
                 return false;
             }
             if (!oldSuper.isCurrentVersion()) {
                 if (oldSuper.isDeleted()) {
                     if (!oldSuper.evolveDeletedClass(evolver)) {
                         return false;
                     }
                 }
                 if (oldSuper.oldToNewKeyMap != null) {
                     allKeyNameMap.putAll(oldSuper.oldToNewKeyMap);
                 }
                 hierarchyChanged = true;
             }
         }
 
         /*
          * Compare the old and new class hierarhies and decide whether each
          * change is allowed or not:
          * + Old deleted and removed superclass -- allowed
          * + Old empty and removed superclass -- allowed
          * + Old non-empty and removed superclass -- not allowed
          * + Old superclass repositioned in the hierarchy -- not allowed
          * + New inserted superclass -- allowed
          */
         Class newFormatCls = newFormat.getExistingType();
         Class newSuper = newFormatCls;
         List<IntegernewLevels = new ArrayList<Integer>();
         int newLevel = 0;
        newLevels.add(newLevel);
        /*
         * When this format has a new superclass, we treat it as a change to
         * this format as well as to the superclass hierarchy.
         */
        if (getSuperFormat() == null) {
            if (newFormatCls.getSuperclass() != Object.class) {
                thisChanged = true;
                hierarchyChanged = true;
            }
        } else {
            if (!getSuperFormat().getLatestVersion().getClassName().equals
                    (newFormatCls.getSuperclass().getName())) {
                thisChanged = true;
                hierarchyChanged = true;
            }
        }
        for (ComplexFormat oldSuper = getComplexSuper();
             oldSuper != null;
             oldSuper = oldSuper.getComplexSuper()) {
            /* Find the matching superclass in the new hierarchy. */
            String oldSuperName = oldSuper.getLatestVersion().getClassName();
            Class foundNewSuper = null;
            int tryNewLevel = newLevel;
            for (Class newSuper2 = newSuper.getSuperclass();
                 newSuper2 != Object.class;
                 newSuper2 = newSuper2.getSuperclass()) {
                tryNewLevel += 1;
                if (oldSuperName.equals(newSuper2.getName())) {
                    foundNewSuper = newSuper2;
                    newLevel = tryNewLevel;
                    if (oldSuper.isEntity()) {
                        assert oldEntityClass == null;
                        assert newEntityClass == null;
                        oldEntityClass = oldSuper.getClassName();
                        newEntityClass = foundNewSuper.getName();
                    }
                    break;
                }
            }
            if (foundNewSuper != null) {
                /*
                 * We found the old superclass in the new hierarchy.  Traverse
                 * through the superclass formats that were skipped over above
                 * when finding it.
                 */
                for (Class newSuper2 = newSuper.getSuperclass();
                     newSuper2 != foundNewSuper;
                     newSuper2 = newSuper2.getSuperclass()) {
                    /*
                     * The class hierarchy changed -- a new class was inserted.
                     */
                    hierarchyChanged = true;
                    /*
                     * Check that the new formats skipped over above are not at
                     * a different position in the old hierarchy.
                     */
                    for (ComplexFormat oldSuper2 = oldSuper.getComplexSuper();
                         oldSuper2 != null;
                         oldSuper2 = oldSuper2.getComplexSuper()) {
                        String oldSuper2Name =
                            oldSuper2.getLatestVersion().getClassName();
                        if (oldSuper2Name.equals(newSuper2.getName())) {
                            evolver.addMissingMutation
                                (thisnewFormatParam,
                                 "Class Converter is required when a " +
                                 "superclass is moved in the class " +
                                 "hierarchy: " + newSuper2.getName());
                            return false;
                        }
                    }
                }
                newSuper = foundNewSuper;
                newLevels.add(newLevel);
            } else {
                /*
                 * We did not find the old superclass in the new hierarchy.
                 * The class hierarchy changed, since an old class no longer
                 * appears.
                 */
                hierarchyChanged = true;
                /* Check that the old class can be safely removed. */
                if (!oldSuper.isDeleted()) {
                    ComplexFormat oldSuperLatest =
                        oldSuper.getComplexLatest();
                    if (oldSuperLatest.getNFields() != 0) {
                        evolver.addMissingMutation
                            (thisnewFormatParam,
                             "When a superclass is removed from the class " +
                             "hierarchy, the superclass or all of its " +
                             "persistent fields must be deleted with a " +
                             "Deleter: " +
                             oldSuperLatest.