| /*- |
| * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved. |
| * |
| * This file was distributed by Oracle as part of a version of Oracle Berkeley |
| * DB Java Edition made available at: |
| * |
| * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html |
| * |
| * Please see the LICENSE file included in the top-level directory of the |
| * appropriate version of Oracle Berkeley DB Java Edition for a copy of the |
| * license and additional information. |
| */ |
| |
| package com.sleepycat.persist.impl; |
| |
| import java.io.Serializable; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import com.sleepycat.compat.DbCompat; |
| import com.sleepycat.persist.evolve.Converter; |
| import com.sleepycat.persist.evolve.Deleter; |
| import com.sleepycat.persist.evolve.EntityConverter; |
| import com.sleepycat.persist.evolve.Mutations; |
| import com.sleepycat.persist.evolve.Renamer; |
| import com.sleepycat.persist.model.ClassMetadata; |
| import com.sleepycat.persist.model.DeleteAction; |
| import com.sleepycat.persist.model.EntityMetadata; |
| import com.sleepycat.persist.model.EntityModel; |
| import com.sleepycat.persist.model.FieldMetadata; |
| import com.sleepycat.persist.model.Relationship; |
| import com.sleepycat.persist.model.SecondaryKeyMetadata; |
| import com.sleepycat.persist.raw.RawField; |
| import com.sleepycat.persist.raw.RawObject; |
| |
| /** |
| * Format for persistent complex classes that are not composite key classes. |
| * This includes entity classes and subclasses. |
| * |
| * @author Mark Hayes |
| */ |
| public class ComplexFormat extends Format { |
| |
| private static final long serialVersionUID = -2847843033590454917L; |
| |
| private ClassMetadata clsMeta; |
| private EntityMetadata entityMeta; |
| private FieldInfo priKeyField; |
| private List<FieldInfo> secKeyFields; |
| private List<FieldInfo> nonKeyFields; |
| private FieldReader secKeyFieldReader; |
| private FieldReader nonKeyFieldReader; |
| private Map<String, String> oldToNewKeyMap; |
| private Map<String, String> newToOldFieldMap; |
| private boolean evolveNeeded; |
| private transient Accessor objAccessor; |
| private transient Accessor rawAccessor; |
| private transient ComplexFormat entityFormat; |
| private transient Map<String, FieldAddress> secKeyAddresses; |
| private transient volatile Map<String, RawField> rawFields; |
| 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<String> incorrectlyOrderedSecKeys = 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(catalog, cls); |
| this.clsMeta = clsMeta; |
| this.entityMeta = entityMeta; |
| secKeyFields = new ArrayList<FieldInfo>(); |
| nonKeyFields = FieldInfo.getInstanceFields(cls, clsMeta); |
| |
| /* |
| * 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(nonKeyFields, fieldName); |
| if (field == null) { |
| throw new IllegalArgumentException |
| ("Primary key field does not exist: " + |
| getClassName() + '.' + fieldName); |
| } |
| nonKeyFields.remove(field); |
| priKeyField = 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(nonKeyFields, 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 == Relationship.ONE_TO_MANY || |
| rel == Relationship.MANY_TO_MANY) { |
| 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() == DeleteAction.NULLIFY) { |
| throw new IllegalArgumentException |
| ("NULLIFY may not be used with primitive fields: " + |
| getClassName() + '.' + fieldName); |
| } |
| nonKeyFields.remove(field); |
| secKeyFields.add(field); |
| } |
| } |
| |
| /* Sort each group of fields by name. */ |
| Collections.sort(secKeyFields); |
| Collections.sort(nonKeyFields); |
| } |
| |
| @Override |
| void migrateFromBeta(Map<String, Format> formatMap) { |
| super.migrateFromBeta(formatMap); |
| if (priKeyField != null) { |
| priKeyField.migrateFromBeta(formatMap); |
| } |
| for (FieldInfo field : secKeyFields) { |
| field.migrateFromBeta(formatMap); |
| } |
| for (FieldInfo field : nonKeyFields) { |
| field.migrateFromBeta(formatMap); |
| } |
| } |
| |
| /** |
| * Returns getSuperFormat cast to ComplexFormat. It is guaranteed that all |
| * super formats of a ComplexFormat are a ComplexFormat. |
| */ |
| ComplexFormat getComplexSuper() { |
| return (ComplexFormat) getSuperFormat(); |
| } |
| |
| /** |
| * Returns getLatestVersion cast to ComplexFormat. It is guaranteed that |
| * all versions of a ComplexFormat are a ComplexFormat. |
| */ |
| private ComplexFormat getComplexLatest() { |
| return (ComplexFormat) getLatestVersion(); |
| } |
| |
| FieldInfo getPriKeyFieldInfo() { |
| return priKeyField; |
| } |
| |
| String getPriKeyField() { |
| if (clsMeta.getPrimaryKey() != null) { |
| return clsMeta.getPrimaryKey().getName(); |
| } else { |
| return null; |
| } |
| } |
| |
| @Override |
| boolean isEntity() { |
| return clsMeta.isEntityClass(); |
| } |
| |
| @Override |
| boolean isModelClass() { |
| return true; |
| } |
| |
| @Override |
| public ClassMetadata getClassMetadata() { |
| return clsMeta; |
| } |
| |
| @Override |
| public EntityMetadata getEntityMetadata() { |
| return entityMeta; |
| } |
| |
| @Override |
| ComplexFormat getEntityFormat() { |
| if (isInitialized()) { |
| /* The transient entityFormat field is set by initialize(). */ |
| return entityFormat; |
| } 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) { |
| evolveNeeded = needed; |
| } |
| |
| @Override |
| boolean getEvolveNeeded() { |
| return evolveNeeded; |
| } |
| |
| @Override |
| boolean getNewStringFormat() { |
| if (getEntityFormat() == null) { |
| throw DbCompat.unexpectedState(); |
| } |
| return newStringFormat; |
| } |
| |
| @Override |
| public Map<String, RawField> getFields() { |
| |
| /* |
| * 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 (rawFields == null) { |
| Map<String, RawField> map = new HashMap<String, RawField>(); |
| if (priKeyField != null) { |
| map.put(priKeyField.getName(), priKeyField); |
| } |
| for (RawField field : secKeyFields) { |
| map.put(field.getName(), field); |
| } |
| for (RawField field : nonKeyFields) { |
| map.put(field.getName(), field); |
| } |
| rawFields = map; |
| } |
| return rawFields; |
| } |
| |
| @Override |
| void collectRelatedFormats(Catalog catalog, |
| Map<String, Format> newFormats) { |
| Class cls = getType(); |
| /* Collect field formats. */ |
| if (priKeyField != null) { |
| priKeyField.collectRelatedFormats(catalog, newFormats); |
| } |
| for (FieldInfo field : secKeyFields) { |
| field.collectRelatedFormats(catalog, newFormats); |
| } |
| for (FieldInfo field : nonKeyFields) { |
| field.collectRelatedFormats(catalog, newFormats); |
| } |
| /* Collect TO_MANY secondary key field element class formats. */ |
| if (entityMeta != null) { |
| for (SecondaryKeyMetadata secKeyMeta : |
| entityMeta.getSecondaryKeys().values()) { |
| String elemClsName = secKeyMeta.getElementClassName(); |
| if (elemClsName != null) { |
| Class elemCls = catalog.resolveKeyClass(elemClsName); |
| catalog.createFormat(elemCls, newFormats); |
| } |
| } |
| } |
| /* Recursively collect superclass formats. */ |
| Class superCls = cls.getSuperclass(); |
| if (superCls != Object.class) { |
| Format superFormat = catalog.createFormat(superCls, newFormats); |
| 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 = clsMeta.getProxiedClassName(); |
| if (proxiedClsName != null) { |
| catalog.createFormat(proxiedClsName, newFormats); |
| } |
| } |
| |
| @Override |
| void initialize(Catalog catalog, EntityModel model, int initVersion) { |
| |
| Class type = getType(); |
| boolean useEnhanced = false; |
| if (type != null) { |
| useEnhanced = EnhancedAccessor.isEnhanced(type); |
| } |
| /* Initialize all fields. */ |
| if (priKeyField != null) { |
| priKeyField.initialize(catalog, model, initVersion); |
| } |
| for (FieldInfo field : secKeyFields) { |
| field.initialize(catalog, model, initVersion); |
| } |
| for (FieldInfo field : nonKeyFields) { |
| field.initialize(catalog, model, initVersion); |
| } |
| /* 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 = |
| (ComplexFormat) catalog.getFormat(superCls.getName()); |
| setSuperFormat(superFormat); |
| } |
| } |
| /* Initialize the superclass format and validate the super accessor. */ |
| if (superFormat != null) { |
| superFormat.initializeIfNeeded(catalog, model); |
| 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()) { |
| entityFormat = 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()) { |
| entityMeta = model.getEntityMetadata(getClassName()); |
| } |
| |
| /* Disallow proxy class that extends an entity class. [#15950] */ |
| if (clsMeta.getProxiedClassName() != null && entityFormat != null) { |
| throw new IllegalArgumentException |
| ("A proxy may not be an entity: " + getClassName()); |
| } |
| /* Disallow primary keys on entity subclasses. [#15757] */ |
| if (entityFormat != null && |
| entityFormat != this && |
| priKeyField != null) { |
| throw new IllegalArgumentException |
| ("A PrimaryKey may not appear on an Entity subclass: " + |
| getClassName() + " field: " + priKeyField.getName()); |
| } |
| /* Create the accessors. */ |
| if (type != null) { |
| if (useEnhanced) { |
| objAccessor = new EnhancedAccessor(catalog, type, this); |
| } else { |
| Accessor superObjAccessor = |
| (superFormat != null) ? superFormat.objAccessor : null; |
| objAccessor = new ReflectionAccessor |
| (catalog, type, superObjAccessor, priKeyField, |
| secKeyFields, nonKeyFields); |
| } |
| } |
| Accessor superRawAccessor = |
| (superFormat != null) ? superFormat.rawAccessor : null; |
| rawAccessor = new RawAccessor |
| (this, superRawAccessor, priKeyField, secKeyFields, nonKeyFields); |
| |
| /* Initialize secondary key field addresses. */ |
| EntityMetadata latestEntityMeta = null; |
| if (entityFormat != null) { |
| latestEntityMeta = |
| entityFormat.getLatestVersion().getEntityMetadata(); |
| } |
| if (latestEntityMeta != null) { |
| secKeyAddresses = new HashMap<String, FieldAddress>(); |
| ComplexFormat thisLatest = getComplexLatest(); |
| if (thisLatest != this) { |
| thisLatest.initializeIfNeeded(catalog, model); |
| } |
| 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.secKeyFields, useFieldName); |
| if (info != null) { |
| isSecField = true; |
| fieldNum = format.secKeyFields.indexOf(info); |
| } else { |
| isSecField = false; |
| info = FieldInfo.getField |
| (format.nonKeyFields, useFieldName); |
| 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 |
| (isSecField, fieldNum, superLevel, format, |
| info.getType()); |
| secKeyAddresses.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 (objAccessor != 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 = secKeyAddresses.get(secKeyMeta.getKeyName()); |
| Object obj = objAccessor.newInstance(); |
| Object val = objAccessor.getField |
| (obj, addr.fieldNum, addr.superLevel, addr.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 o1, Object o2) { |
| if (o1 == null) { |
| return o2 == null; |
| } else { |
| return o1.equals(o2); |
| } |
| } |
| |
| @Override |
| Object newArray(int len) { |
| return objAccessor.newArray(len); |
| } |
| |
| @Override |
| public Object newInstance(EntityInput input, boolean rawAccess) { |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| return accessor.newInstance(); |
| } |
| |
| @Override |
| public Object readObject(Object o, EntityInput input, boolean rawAccess) |
| throws RefreshException { |
| |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| accessor.readSecKeyFields(o, input, 0, Accessor.MAX_FIELD_NUM, -1); |
| accessor.readNonKeyFields(o, input, 0, Accessor.MAX_FIELD_NUM, -1); |
| return o; |
| } |
| |
| @Override |
| void writeObject(Object o, EntityOutput output, boolean rawAccess) |
| throws RefreshException { |
| |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| accessor.writeSecKeyFields(o, output); |
| accessor.writeNonKeyFields(o, output); |
| } |
| |
| @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 = rawInputFields; |
| int[] levels = rawInputLevels; |
| int depth = rawInputDepth; |
| 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 < depth; level += 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 < depth; level += 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; |
| rawInputFields = fields; |
| rawInputLevels = levels; |
| rawInputDepth = 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 = rawObject; raw != null; raw = 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.length; i += 1) { |
| objects[i] = objectsByLevel[levels[i]]; |
| } |
| |
| /* Create the persistent object and convert all RawObject fields. */ |
| EntityInput in = new RawComplexInput |
| (catalog, rawAccess, converted, fields, objects); |
| Object o = newInstance(in, rawAccess); |
| converted.put(rawObject, o); |
| if (getEntityFormat() != null) { |
| readPriKey(o, in, rawAccess); |
| } |
| return readObject(o, in, rawAccess); |
| } |
| |
| @Override |
| boolean isPriKeyNullOrZero(Object o, boolean rawAccess) { |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| return accessor.isPriKeyFieldNullOrZero(o); |
| } |
| |
| @Override |
| void writePriKey(Object o, EntityOutput output, boolean rawAccess) |
| throws RefreshException { |
| |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| accessor.writePriKeyField(o, output); |
| } |
| |
| @Override |
| public void readPriKey(Object o, EntityInput input, boolean rawAccess) |
| throws RefreshException { |
| |
| Accessor accessor = rawAccess ? rawAccessor : objAccessor; |
| accessor.readPriKeyField(o, input); |
| } |
| |
| @Override |
| public String getOldKeyName(final String keyName) { |
| if (newToOldFieldMap != null && |
| newToOldFieldMap.containsKey(keyName)) { |
| return newToOldFieldMap.get(keyName); |
| } else { |
| return keyName; |
| } |
| } |
| |
| @Override |
| boolean nullifySecKey(Catalog catalog, |
| Object entity, |
| String keyName, |
| Object keyElement) { |
| if (secKeyAddresses == null) { |
| throw DbCompat.unexpectedState(); |
| } |
| FieldAddress addr = secKeyAddresses.get(keyName); |
| if (addr != null) { |
| Object oldVal = rawAccessor.getField |
| (entity, addr.fieldNum, addr.superLevel, addr.isSecField); |
| if (oldVal != null) { |
| if (keyElement != null) { |
| RawObject container = (RawObject) oldVal; |
| Object[] a1 = container.getElements(); |
| boolean isArray = (a1 != null); |
| if (!isArray) { |
| a1 = CollectionProxy.getElements(container); |
| } |
| if (a1 != null) { |
| for (int i = 0; i < a1.length; i += 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(a1, i + 1, a2, i, len - i); |
| if (isArray) { |
| rawAccessor.setField |
| (entity, addr.fieldNum, |
| addr.superLevel, addr.isSecField, |
| new RawObject |
| (container.getType(), a2)); |
| } else { |
| CollectionProxy.setElements(container, a2); |
| } |
| return true; |
| } |
| } |
| } |
| return false; |
| } else { |
| rawAccessor.setField |
| (entity, addr.fieldNum, addr.superLevel, |
| addr.isSecField, null); |
| return true; |
| } |
| } else { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| void skipContents(RecordInput input) |
| throws RefreshException { |
| |
| skipToSecKeyField(input, Accessor.MAX_FIELD_NUM); |
| skipToNonKeyField(input, Accessor.MAX_FIELD_NUM); |
| } |
| |
| @Override |
| void copySecMultiKey(RecordInput input, Format keyFormat, Set results) |
| throws RefreshException { |
| |
| CollectionProxy.copyElements(input, this, keyFormat, results); |
| } |
| |
| @Override |
| Format skipToSecKey(RecordInput input, String keyName) |
| throws RefreshException { |
| |
| if (secKeyAddresses == null) { |
| throw DbCompat.unexpectedState(); |
| } |
| FieldAddress addr = secKeyAddresses.get(keyName); |
| if (addr != null) { |
| if (addr.isSecField) { |
| addr.clsFormat.skipToSecKeyField(input, addr.fieldNum); |
| } else { |
| skipToSecKeyField(input, Accessor.MAX_FIELD_NUM); |
| addr.clsFormat.skipToNonKeyField(input, addr.fieldNum); |
| } |
| return addr.keyFormat; |
| } else { |
| return null; |
| } |
| } |
| |
| private int getNFields() { |
| return ((priKeyField != null) ? 1 : 0) + |
| secKeyFields.size() + |
| nonKeyFields.size(); |
| } |
| |
| private void skipToSecKeyField(RecordInput input, int toFieldNum) |
| throws RefreshException { |
| |
| ComplexFormat superFormat = getComplexSuper(); |
| if (superFormat != null) { |
| superFormat.skipToSecKeyField(input, Accessor.MAX_FIELD_NUM); |
| } |
| int maxNum = Math.min(secKeyFields.size(), toFieldNum); |
| for (int i = 0; i < maxNum; i += 1) { |
| input.skipField(secKeyFields.get(i).getType()); |
| } |
| } |
| |
| private void skipToNonKeyField(RecordInput input, int toFieldNum) |
| throws RefreshException { |
| |
| ComplexFormat superFormat = getComplexSuper(); |
| if (superFormat != null) { |
| superFormat.skipToNonKeyField(input, Accessor.MAX_FIELD_NUM); |
| } |
| int maxNum = Math.min(nonKeyFields.size(), toFieldNum); |
| for (int i = 0; i < maxNum; i += 1) { |
| input.skipField(nonKeyFields.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 = isSecField; |
| this.fieldNum = fieldNum; |
| this.superLevel = superLevel; |
| this.clsFormat = clsFormat; |
| this.keyFormat = keyFormat; |
| } |
| } |
| |
| @Override |
| boolean evolve(Format newFormatParam, Evolver evolver) { |
| |
| /* Disallow evolution to a non-complex format. */ |
| if (!(newFormatParam instanceof ComplexFormat)) { |
| evolver.addMissingMutation |
| (this, newFormatParam, |
| "Converter is required when a complex type is changed " + |
| "to a simple type or enum type"); |
| return false; |
| } |
| ComplexFormat newFormat = (ComplexFormat) newFormatParam; |
| Mutations mutations = evolver.getMutations(); |
| boolean thisChanged = false; |
| boolean hierarchyChanged = false; |
| Map<String, String> allKeyNameMap = new HashMap<String, String>(); |
| |
| /* The Evolver has already ensured that entities evolve to entities. */ |
| assert isEntity() == newFormat.isEntity(); |
| assert isEntity() == (entityMeta != 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 |
| (this, newFormatParam, |
| "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<Integer> newLevels = 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 |
| (this, newFormatParam, |
| "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 |
| (this, newFormatParam, |
| "When a superclass is removed from the class " + |
| "hierarchy, the superclass or all of its " + |
| "persistent fields must be deleted with a " + |
| "Deleter: " + |
| oldSuperLatest.getClassName()); |
| return false; |
| } |
| } |
| |
| if (oldEntityClass != null && isCurrentVersion()) { |
| Map<String, SecondaryKeyMetadata> secKeys = |
| oldSuper.clsMeta.getSecondaryKeys(); |
| for (FieldInfo field : oldSuper.secKeyFields) { |
| SecondaryKeyMetadata meta = |
| getSecondaryKeyMetadataByFieldName |
| (secKeys, field.getName()); |
| assert meta != null; |
| allKeyNameMap.put(meta.getKeyName(), null); |
| } |
| } |
| |
| /* |
| * Add the DO_NOT_READ_ACCESSOR level to prevent an empty class |
| * (no persistent fields) from being read via the Accessor. |
| */ |
| newLevels.add(EvolveReader.DO_NOT_READ_ACCESSOR); |
| } |
| } |
| |
| /* Make FieldReaders for this format if needed. */ |
| int result = evolveAllFields(newFormat, evolver); |
| if (result == Evolver.EVOLVE_FAILURE) { |
| return false; |
| } |
| if (result == Evolver.EVOLVE_NEEDED) { |
| thisChanged = true; |
| } |
| if (oldToNewKeyMap != null) { |
| allKeyNameMap.putAll(oldToNewKeyMap); |
| } |
| |
| /* Require new version number if this class was changed. */ |
| if (thisChanged && |
| !evolver.checkUpdatedVersion |
| ("Changes to the fields or superclass were detected", this, |
| newFormat)) { |
| return false; |
| } |
| |
| /* Rename and delete the secondary databases. */ |
| if (allKeyNameMap.size() > 0 && |
| oldEntityClass != null && |
| newEntityClass != null && |
| isCurrentVersion()) { |
| for (Map.Entry<String, String> entry : allKeyNameMap.entrySet()) { |
| String oldKeyName = entry.getKey(); |
| String newKeyName = entry.getValue(); |
| if (newKeyName != null) { |
| evolver.renameSecondaryDatabase |
| (oldEntityClass, newEntityClass, |
| oldKeyName, newKeyName); |
| } else { |
| evolver.deleteSecondaryDatabase |
| (oldEntityClass, oldKeyName); |
| } |
| } |
| } |
| |
| /* |
| * Use an EvolveReader if needed. |
| * |
| * We force evolution to occur if the old format did not use the new |
| * String format. We do not require the user to bump the version |
| * number, since the format change is internal. Note that we could |
| * optimize by only forcing evolution if this format may contain |
| * Strings. [#19247] |
| */ |
| if (hierarchyChanged || thisChanged || !newStringFormat) { |
| Reader reader = new EvolveReader(newLevels); |
| evolver.useEvolvedFormat(this, reader, newFormat); |
| } else { |
| evolver.useOldFormat(this, newFormat); |
| } |
| return true; |
| } |
| |
| @Override |
| boolean evolveMetadata(Format newFormatParam, |
| Converter converter, |
| Evolver evolver) { |
| assert !isDeleted(); |
| assert isEntity(); |
| assert newFormatParam.isEntity(); |
| ComplexFormat newFormat = (ComplexFormat) newFormatParam; |
| |
| if (!checkKeyTypeChange |
| (newFormat, entityMeta.getPrimaryKey(), |
| newFormat.entityMeta.getPrimaryKey(), "primary key", |
| evolver)) { |
| return false; |
| } |
| |
| Set<String> deletedKeys; |
| if (converter instanceof EntityConverter) { |
| EntityConverter entityConverter = (EntityConverter) converter; |
| deletedKeys = entityConverter.getDeletedKeys(); |
| } else { |
| deletedKeys = Collections.emptySet(); |
| } |
| |
| Map<String, SecondaryKeyMetadata> oldSecondaryKeys = |
| entityMeta.getSecondaryKeys(); |
| Map<String, SecondaryKeyMetadata> newSecondaryKeys = |
| newFormat.entityMeta.getSecondaryKeys(); |
| Set<String> insertedKeys = |
| new HashSet<String>(newSecondaryKeys.keySet()); |
| |
| for (SecondaryKeyMetadata oldMeta : oldSecondaryKeys.values()) { |
| String keyName = oldMeta.getKeyName(); |
| if (deletedKeys.contains(keyName)) { |
| if (isCurrentVersion()) { |
| evolver.deleteSecondaryDatabase(getClassName(), keyName); |
| } |
| } else { |
| SecondaryKeyMetadata newMeta = newSecondaryKeys.get(keyName); |
| if (newMeta == null) { |
| evolver.addInvalidMutation |
| (this, newFormat, converter, |
| "Existing key not found in new entity metadata: " + |
| keyName); |
| return false; |
| } |
| insertedKeys.remove(keyName); |
| String keyLabel = "secondary key: " + keyName; |
| if (!checkKeyTypeChange |
| (newFormat, oldMeta, newMeta, keyLabel, evolver)) { |
| return false; |
| } |
| if (!checkSecKeyMetadata |
| (newFormat, oldMeta, newMeta, evolver)) { |
| return false; |
| } |
| } |
| } |
| |
| if (!insertedKeys.isEmpty()) { |
| evolver.addEvolveError |
| (this, newFormat, "Error", |
| "New keys " + insertedKeys + |
| " not allowed when using a Converter with an entity class"); |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Checks that changes to secondary key metadata are legal. |
| */ |
| private boolean checkSecKeyMetadata(Format newFormat, |
| SecondaryKeyMetadata oldMeta, |
| SecondaryKeyMetadata newMeta, |
| Evolver evolver) { |
| if (oldMeta.getRelationship() != newMeta.getRelationship()) { |
| evolver.addEvolveError |
| (this, newFormat, |
| "Change detected in the relate attribute (Relationship) " + |
| "of a secondary key", |
| "Old key: " + oldMeta.getKeyName() + |
| " relate: " + oldMeta.getRelationship() + |
| " new key: " + newMeta.getKeyName() + |
| " relate: " + newMeta.getRelationship()); |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Checks that the type of a key field did not change, as known from |
| * metadata when a class conversion is used. |
| */ |
| private boolean checkKeyTypeChange(Format newFormat, |
| FieldMetadata oldMeta, |
| FieldMetadata newMeta, |
| String keyLabel, |
| Evolver evolver) { |
| String oldClass = oldMeta.getClassName(); |
| String newClass = newMeta.getClassName(); |
| if (!oldClass.equals(newClass)) { |
| Format oldType = getCatalog().getFormat(oldClass); |
| Format newType = getCatalog().getFormat(newClass); |
| if (oldType == null || newType == null || |
| ((oldType.getWrapperFormat() == null || |
| oldType.getWrapperFormat().getId() != |
| newType.getId()) && |
| (newType.getWrapperFormat() == null || |
| newType.getWrapperFormat().getId() != |
| oldType.getId()))) { |
| evolver.addEvolveError |
| (this, newFormat, |
| "Type change detected for " + keyLabel, |
| "Old field type: " + oldClass + |
| " is not compatible with the new type: " + |
| newClass + |
| " old field: " + oldMeta.getName() + |
| " new field: " + newMeta.getName()); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Special case for creating FieldReaders for a deleted class when it |
| * appears in the class hierarchy of its non-deleted subclass. |
| */ |
| private boolean evolveDeletedClass(Evolver evolver) { |
| assert isDeleted(); |
| if (secKeyFieldReader == null || nonKeyFieldReader == null) { |
| if (priKeyField != null && |
| getEntityFormat() != null && |
| !getEntityFormat().isDeleted()) { |
| evolver.addEvolveError |
| (this, this, |
| "Class containing primary key field was deleted ", |
| "Primary key is needed in an entity class hierarchy: " + |
| priKeyField.getName()); |
| return false; |
| } else { |
| secKeyFieldReader = new SkipFieldReader(0, secKeyFields); |
| nonKeyFieldReader = new SkipFieldReader(0, nonKeyFields); |
| return true; |
| } |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * Creates a FieldReader for secondary key fields and non-key fields if |
| * necessary. Checks the primary key field if necessary. Does not evolve |
| * superclass format fields. |
| */ |
| private int evolveAllFields(ComplexFormat newFormat, Evolver evolver) { |
| |
| assert !isDeleted(); |
| secKeyFieldReader = null; |
| nonKeyFieldReader = null; |
| oldToNewKeyMap = null; |
| |
| /* Evolve primary key field. */ |
| boolean evolveFailure = false; |
| boolean localEvolveNeeded = false; |
| if (priKeyField != null) { |
| int result = evolver.evolveRequiredKeyField |
| (this, newFormat, priKeyField, newFormat.priKeyField); |
| if (result == Evolver.EVOLVE_FAILURE) { |
| evolveFailure = true; |
| } else if (result == Evolver.EVOLVE_NEEDED) { |
| localEvolveNeeded = true; |
| } |
| } |
| |
| /* Copy the incorrectlyOrderedSecKeys from old format to new format. */ |
| copyIncorrectlyOrderedSecKeys(newFormat); |
| |
| /* Evolve secondary key fields. */ |
| FieldReader reader = evolveFieldList |
| (secKeyFields, newFormat.secKeyFields, true, |
| newFormat.nonKeyFields, newFormat, evolver); |
| if (reader == FieldReader.EVOLVE_FAILURE) { |
| evolveFailure = true; |
| } else if (reader != null) { |
| localEvolveNeeded = true; |
| } |
| if (reader != FieldReader.EVOLVE_NEEDED) { |
| secKeyFieldReader = reader; |
| } |
| |
| /* Evolve non-key fields. */ |
| reader = evolveFieldList |
| (nonKeyFields, newFormat.nonKeyFields, false, |
| newFormat.secKeyFields, newFormat, evolver); |
| if (reader == FieldReader.EVOLVE_FAILURE) { |
| evolveFailure = true; |
| } else if (reader != null) { |
| localEvolveNeeded = true; |
| } |
| if (reader != FieldReader.EVOLVE_NEEDED) { |
| nonKeyFieldReader = reader; |
| } |
| |
| /* Return result. */ |
| if (evolveFailure) { |
| return Evolver.EVOLVE_FAILURE; |
| } else if (localEvolveNeeded) { |
| return Evolver.EVOLVE_NEEDED; |
| } else { |
| return Evolver.EVOLVE_NONE; |
| } |
| } |
| |
| /** |
| * Returns a FieldReader that reads no fields. |
| * |
| * Instead of adding a DoNothingFieldReader class, we use a |
| * MultiFieldReader with an empty field list. We do not add a new |
| * FieldReader class to avoid changing the catalog format. [#15524] |
| */ |
| private FieldReader getDoNothingFieldReader() { |
| List<FieldReader> emptyList = Collections.emptyList(); |
| return new MultiFieldReader(emptyList); |
| } |
| |
| /** |
| * Evolves a list of fields, either secondary key or non-key fields, for a |
| * single class format. |
| * |
| * @return a FieldReader if field evolution is needed, null if no evolution |
| * is needed, or FieldReader.EVOLVE_FAILURE if an evolution error occurs. |
| */ |
| private FieldReader evolveFieldList(List<FieldInfo> oldFields, |
| List<FieldInfo> newFields, |
| boolean isOldSecKeyField, |
| List<FieldInfo> otherNewFields, |
| ComplexFormat newFormat, |
| Evolver evolver) { |
| Mutations mutations = evolver.getMutations(); |
| boolean evolveFailure = false; |
| boolean localEvolveNeeded = false; |
| boolean readerNeeded = false; |
| List<FieldReader> fieldReaders = new ArrayList<FieldReader>(); |
| FieldReader currentReader = null; |
| int newFieldsMatched = 0; |
| |
| /* |
| * Add FieldReaders to the list in old field storage order, since that |
| * is the order in which field values must be read. |
| */ |
| fieldLoop: |
| for (int oldFieldIndex = 0; |
| oldFieldIndex < oldFields.size(); |
| oldFieldIndex += 1) { |
| |
| FieldInfo oldField = oldFields.get(oldFieldIndex); |
| String oldName = oldField.getName(); |
| SecondaryKeyMetadata oldMeta = null; |
| if (isOldSecKeyField) { |
| oldMeta = getSecondaryKeyMetadataByFieldName |
| (clsMeta.getSecondaryKeys(), oldName); |
| assert oldMeta != null; |
| } |
| |
| /* Get field mutations. */ |
| Renamer renamer = mutations.getRenamer |
| (getClassName(), getVersion(), oldName); |
| Deleter deleter = mutations.getDeleter |
| (getClassName(), getVersion(), oldName); |
| Converter converter = mutations.getConverter |
| (getClassName(), getVersion(), oldName); |
| if (deleter != null && (converter != null || renamer != null)) { |
| evolver.addInvalidMutation |
| (this, newFormat, deleter, |
| "Field Deleter is not allowed along with a Renamer or " + |
| "Converter for the same field: " + oldName); |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| |
| /* |
| * Match old and new field by name, taking into account the Renamer |
| * mutation. If the @SecondaryKey annotation was added or removed, |
| * the field will have moved from one of the two field lists to the |
| * other. |
| */ |
| String newName = (renamer != null) ? |
| renamer.getNewName() : oldName; |
| boolean nameChanged = false; |
| if (!oldName.equals(newName)) { |
| if (newToOldFieldMap == null) { |
| newToOldFieldMap = new HashMap<String, String>(); |
| } |
| newToOldFieldMap.put(newName, oldName); |
| nameChanged = true; |
| } |
| int newFieldIndex = FieldInfo.getFieldIndex(newFields, newName); |
| FieldInfo newField = null; |
| boolean isNewSecKeyField = isOldSecKeyField; |
| if (newFieldIndex >= 0) { |
| newField = newFields.get(newFieldIndex); |
| |
| /* |
| * Change the key name in incorrectlyOrderedSecKeys of the new |
| * format. |
| */ |
| if (nameChanged && |
| newFormat.incorrectlyOrderedSecKeys != null && |
| newFormat.incorrectlyOrderedSecKeys.remove(oldName)) { |
| newFormat.incorrectlyOrderedSecKeys.add(newName); |
| } |
| |
| /* |
| * [#18961] If the order of the field has been changed, we will |
| * create a PlainFieldReader for it. |
| */ |
| if (newFieldIndex != oldFieldIndex) { |
| localEvolveNeeded = true; |
| readerNeeded = true; |
| } |
| } else { |
| newFieldIndex = FieldInfo.getFieldIndex |
| (otherNewFields, newName); |
| if (newFieldIndex >= 0) { |
| newField = otherNewFields.get(newFieldIndex); |
| isNewSecKeyField = !isOldSecKeyField; |
| } |
| localEvolveNeeded = true; |
| readerNeeded = true; |
| |
| /* |
| * Remove the key in incorrectlyOrderedSecKeys of the new |
| * format. |
| */ |
| if (newFormat.incorrectlyOrderedSecKeys != null) { |
| newFormat.incorrectlyOrderedSecKeys.remove(oldName); |
| } |
| } |
| |
| /* Apply field Deleter and continue. */ |
| if (deleter != null) { |
| if (newField != null) { |
| evolver.addInvalidMutation |
| (this, newFormat, deleter, |
| "Field Deleter is not allowed when the persistent " + |
| "field is still present: " + oldName); |
| evolveFailure = true; |
| } |
| /* A SkipFieldReader can read multiple sequential fields. */ |
| if (currentReader instanceof SkipFieldReader && |
| currentReader.acceptField |
| (oldFieldIndex, newFieldIndex, isNewSecKeyField)) { |
| currentReader.addField(oldField); |
| } else { |
| currentReader = new SkipFieldReader |
| (oldFieldIndex, oldField); |
| fieldReaders.add(currentReader); |
| readerNeeded = true; |
| localEvolveNeeded = true; |
| } |
| if (isOldSecKeyField) { |
| if (oldToNewKeyMap == null) { |
| oldToNewKeyMap = new HashMap<String, String>(); |
| } |
| oldToNewKeyMap.put(oldMeta.getKeyName(), null); |
| } |
| continue fieldLoop; |
| } else { |
| if (newField == null) { |
| evolver.addMissingMutation |
| (this, newFormat, |
| "Field is not present or not persistent: " + |
| oldName); |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| } |
| |
| /* |
| * The old field corresponds to a known new field, and no Deleter |
| * mutation applies. |
| */ |
| newFieldsMatched += 1; |
| |
| /* Get and process secondary key metadata changes. */ |
| SecondaryKeyMetadata newMeta = null; |
| if (isOldSecKeyField && isNewSecKeyField) { |
| newMeta = getSecondaryKeyMetadataByFieldName |
| (newFormat.clsMeta.getSecondaryKeys(), newName); |
| assert newMeta != null; |
| |
| /* Validate metadata changes. */ |
| if (!checkSecKeyMetadata |
| (newFormat, oldMeta, newMeta, evolver)) { |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| |
| /* |
| * Check for a renamed key and save the old-to-new mapping for |
| * use in renaming the secondary database and for key |
| * extraction. |
| */ |
| String oldKeyName = oldMeta.getKeyName(); |
| String newKeyName = newMeta.getKeyName(); |
| if (!oldKeyName.equals(newKeyName)) { |
| if (oldToNewKeyMap == null) { |
| oldToNewKeyMap = new HashMap<String, String>(); |
| } |
| oldToNewKeyMap.put(oldName, newName); |
| localEvolveNeeded = true; |
| } |
| } else if (isOldSecKeyField && !isNewSecKeyField) { |
| if (oldToNewKeyMap == null) { |
| oldToNewKeyMap = new HashMap<String, String>(); |
| } |
| oldToNewKeyMap.put(oldMeta.getKeyName(), null); |
| } |
| |
| /* Apply field Converter and continue. */ |
| if (converter != null) { |
| if (isOldSecKeyField) { |
| evolver.addInvalidMutation |
| (this, newFormat, converter, |
| "Field Converter is not allowed for secondary key " + |
| "fields: " + oldName); |
| evolveFailure = true; |
| } else { |
| currentReader = new ConvertFieldReader |
| (converter, oldFieldIndex, newFieldIndex, |
| isNewSecKeyField); |
| fieldReaders.add(currentReader); |
| readerNeeded = true; |
| localEvolveNeeded = true; |
| } |
| continue fieldLoop; |
| } |
| |
| /* |
| * Evolve the declared version of the field format and all versions |
| * more recent, and the formats for all of their subclasses. While |
| * we're at it, check to see if all possible classes are converted. |
| */ |
| boolean allClassesConverted = true; |
| Format oldFieldFormat = oldField.getType(); |
| for (Format formatVersion = oldFieldFormat.getLatestVersion(); |
| true; |
| formatVersion = formatVersion.getPreviousVersion()) { |
| assert formatVersion != null; |
| if (!evolver.evolveFormat(formatVersion)) { |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| if (!formatVersion.isNew() && |
| !evolver.isClassConverted(formatVersion)) { |
| allClassesConverted = false; |
| } |
| Set<Format> subclassFormats = |
| evolver.getSubclassFormats(formatVersion); |
| if (subclassFormats != null) { |
| for (Format format2 : subclassFormats) { |
| if (!evolver.evolveFormat(format2)) { |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| if (!format2.isNew() && |
| !evolver.isClassConverted(format2)) { |
| allClassesConverted = false; |
| } |
| } |
| } |
| if (formatVersion == oldFieldFormat) { |
| break; |
| } |
| } |
| |
| /* |
| * Check for compatible field types and apply a field widener if |
| * needed. If no widener is needed, fall through and apply a |
| * PlainFieldReader. |
| */ |
| Format oldLatestFormat = oldFieldFormat.getLatestVersion(); |
| Format newFieldFormat = newField.getType(); |
| if (oldLatestFormat.getClassName().equals |
| (newFieldFormat.getClassName()) && |
| !oldLatestFormat.isDeleted()) { |
| /* Formats are identical. Fall through. */ |
| } else if (allClassesConverted) { |
| /* All old classes will be converted. Fall through. */ |
| localEvolveNeeded = true; |
| } else if (WidenerInput.isWideningSupported |
| (oldLatestFormat, newFieldFormat, isOldSecKeyField)) { |
| /* Apply field widener and continue. */ |
| currentReader = new WidenFieldReader |
| (oldLatestFormat, newFieldFormat, newFieldIndex, |
| isNewSecKeyField); |
| fieldReaders.add(currentReader); |
| readerNeeded = true; |
| localEvolveNeeded = true; |
| continue fieldLoop; |
| } else { |
| boolean refWidened = false; |
| if (!newFieldFormat.isPrimitive() && |
| !oldLatestFormat.isPrimitive() && |
| !oldLatestFormat.isDeleted() && |
| !evolver.isClassConverted(oldLatestFormat)) { |
| Class oldCls = oldLatestFormat.getExistingType(); |
| Class newCls = newFieldFormat.getExistingType(); |
| if (newCls.isAssignableFrom(oldCls)) { |
| refWidened = true; |
| } |
| } |
| if (refWidened) { |
| /* A reference type has been widened. Fall through. */ |
| localEvolveNeeded = true; |
| } else { |
| /* Types are not compatible. */ |
| evolver.addMissingMutation |
| (this, newFormat, |
| "Old field type: " + oldLatestFormat.getClassName() + |
| " is not compatible with the new type: " + |
| newFieldFormat.getClassName() + |
| " for field: " + oldName); |
| evolveFailure = true; |
| continue fieldLoop; |
| } |
| } |
| |
| /* |
| * Old to new field conversion is not needed or is automatic. Read |
| * fields as if no evolution is needed. A PlainFieldReader can |
| * read multiple sequential fields. |
| */ |
| if (currentReader instanceof PlainFieldReader && |
| currentReader.acceptField |
| (oldFieldIndex, newFieldIndex, isNewSecKeyField)) { |
| currentReader.addField(oldField); |
| } else { |
| currentReader = new PlainFieldReader |
| (oldFieldIndex, newFieldIndex, isNewSecKeyField); |
| fieldReaders.add(currentReader); |
| } |
| } |
| |
| /* |
| * If there are new fields, then the old fields must be read using a |
| * reader, even if the old field list is empty. Using the accessor |
| * directly will read fields in the wrong order and will read fields |
| * that were moved between lists (when adding and dropping |
| * @SecondaryKey). [#15524] |
| */ |
| if (newFieldsMatched < newFields.size()) { |
| localEvolveNeeded = true; |
| readerNeeded = true; |
| } |
| |
| if (evolveFailure) { |
| return FieldReader.EVOLVE_FAILURE; |
| } else if (readerNeeded) { |
| if (fieldReaders.size() == 0) { |
| return getDoNothingFieldReader(); |
| } else if (fieldReaders.size() == 1) { |
| return fieldReaders.get(0); |
| } else { |
| return new MultiFieldReader(fieldReaders); |
| } |
| } else if (localEvolveNeeded) { |
| return FieldReader.EVOLVE_NEEDED; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Base class for all FieldReader subclasses. A FieldReader reads one or |
| * more fields in the old format data, and may call the new format Accessor |
| * to set the field values. |
| */ |
| private static abstract class FieldReader implements Serializable { |
| |
| static final FieldReader EVOLVE_NEEDED = |
| new PlainFieldReader(0, 0, false); |
| static final FieldReader EVOLVE_FAILURE = |
| new PlainFieldReader(0, 0, false); |
| |
| private static final long serialVersionUID = 866041475399255164L; |
| |
| FieldReader() { |
| } |
| |
| void initialize(Catalog catalog, |
| int initVersion, |
| ComplexFormat oldParentFormat, |
| ComplexFormat newParentFormat, |
| boolean isOldSecKey) { |
| } |
| |
| boolean acceptField(int oldFieldIndex, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| return false; |
| } |
| |
| void addField(FieldInfo oldField) { |
| throw DbCompat.unexpectedState(); |
| } |
| |
| abstract void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException; |
| } |
| |
| /** |
| * Reads a continguous block of fields that have the same format in the old |
| * and new formats. |
| */ |
| private static class PlainFieldReader extends FieldReader { |
| |
| private static final long serialVersionUID = 1795593463439931402L; |
| |
| private int startField; |
| private int endField; |
| private boolean secKeyField; |
| private transient int endOldField; |
| |
| PlainFieldReader(int oldFieldIndex, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| endOldField = oldFieldIndex; |
| startField = newFieldIndex; |
| endField = newFieldIndex; |
| secKeyField = isNewSecKeyField; |
| } |
| |
| @Override |
| boolean acceptField(int oldFieldIndex, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| return oldFieldIndex == endOldField + 1 && |
| newFieldIndex == endField + 1 && |
| secKeyField == isNewSecKeyField; |
| } |
| |
| @Override |
| void addField(FieldInfo oldField) { |
| endField += 1; |
| endOldField += 1; |
| } |
| |
| @Override |
| final void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException { |
| |
| if (secKeyField) { |
| accessor.readSecKeyFields |
| (o, input, startField, endField, superLevel); |
| } else { |
| accessor.readNonKeyFields |
| (o, input, startField, endField, superLevel); |
| } |
| } |
| } |
| |
| /** |
| * Skips a continguous block of fields that exist in the old format but not |
| * in the new format. |
| */ |
| private static class SkipFieldReader extends FieldReader { |
| |
| private static final long serialVersionUID = -3060281692155253098L; |
| |
| private List<Format> fieldFormats; |
| private transient int endField; |
| |
| SkipFieldReader(int startField, List<FieldInfo> fields) { |
| endField = startField + fields.size() - 1; |
| fieldFormats = new ArrayList<Format>(fields.size()); |
| for (FieldInfo field : fields) { |
| fieldFormats.add(field.getType()); |
| } |
| } |
| |
| SkipFieldReader(int startField, FieldInfo oldField) { |
| endField = startField; |
| fieldFormats = new ArrayList<Format>(); |
| fieldFormats.add(oldField.getType()); |
| } |
| |
| @Override |
| boolean acceptField(int oldFieldIndex, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| return oldFieldIndex == endField + 1; |
| } |
| |
| @Override |
| void addField(FieldInfo oldField) { |
| endField += 1; |
| fieldFormats.add(oldField.getType()); |
| } |
| |
| @Override |
| final void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException { |
| |
| for (Format format : fieldFormats) { |
| input.skipField(format); |
| } |
| } |
| } |
| |
| /** |
| * Converts a single field using a field Converter. |
| */ |
| private static class ConvertFieldReader extends FieldReader { |
| |
| private static final long serialVersionUID = 8736410481633998710L; |
| |
| private Converter converter; |
| private int oldFieldNum; |
| private int fieldNum; |
| private boolean secKeyField; |
| private transient Format oldFormat; |
| private transient Format newFormat; |
| |
| ConvertFieldReader(Converter converter, |
| int oldFieldIndex, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| this.converter = converter; |
| oldFieldNum = oldFieldIndex; |
| fieldNum = newFieldIndex; |
| secKeyField = isNewSecKeyField; |
| } |
| |
| @Override |
| void initialize(Catalog catalog, |
| int initVersion, |
| ComplexFormat oldParentFormat, |
| ComplexFormat newParentFormat, |
| boolean isOldSecKey) { |
| |
| /* |
| * The oldFieldNum field was added as part of a bug fix. If not |
| * present in this version of the catalog, we assume it is equal to |
| * the new field index. The code prior to the bug fix assumes the |
| * old and new fields have the same index. [#15797] |
| */ |
| if (initVersion < 1) { |
| oldFieldNum = fieldNum; |
| } |
| |
| if (isOldSecKey) { |
| oldFormat = |
| oldParentFormat.secKeyFields.get(oldFieldNum).getType(); |
| } else { |
| oldFormat = |
| oldParentFormat.nonKeyFields.get(oldFieldNum).getType(); |
| } |
| if (secKeyField) { |
| newFormat = |
| newParentFormat.secKeyFields.get(fieldNum).getType(); |
| } else { |
| newFormat = |
| newParentFormat.nonKeyFields.get(fieldNum).getType(); |
| } |
| } |
| |
| @Override |
| final void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException { |
| |
| /* Create and read the old format instance in raw mode. */ |
| boolean currentRawMode = input.setRawAccess(true); |
| Object value; |
| try { |
| if (oldFormat.isPrimitive()) { |
| value = input.readKeyObject(oldFormat); |
| } else if (oldFormat.getId() == Format.ID_STRING) { |
| value = input.readStringObject(); |
| } else { |
| value = input.readObject(); |
| } |
| } finally { |
| input.setRawAccess(currentRawMode); |
| } |
| |
| /* Convert the raw instance to the current format. */ |
| Catalog catalog = input.getCatalog(); |
| value = converter.getConversion().convert(value); |
| |
| /* Use a RawSingleInput to convert and type-check the value. */ |
| EntityInput rawInput = new RawSingleInput |
| (catalog, currentRawMode, null, value, newFormat); |
| |
| if (secKeyField) { |
| accessor.readSecKeyFields |
| (o, rawInput, fieldNum, fieldNum, superLevel); |
| } else { |
| accessor.readNonKeyFields |
| (o, rawInput, fieldNum, fieldNum, superLevel); |
| } |
| } |
| } |
| |
| /** |
| * Widens a single field using a field Converter. |
| */ |
| private static class WidenFieldReader extends FieldReader { |
| |
| private static final long serialVersionUID = -2054520670170407282L; |
| |
| private int fromFormatId; |
| private int toFormatId; |
| private int fieldNum; |
| private boolean secKeyField; |
| |
| WidenFieldReader(Format oldFormat, |
| Format newFormat, |
| int newFieldIndex, |
| boolean isNewSecKeyField) { |
| fromFormatId = oldFormat.getId(); |
| toFormatId = newFormat.getId(); |
| fieldNum = newFieldIndex; |
| secKeyField = isNewSecKeyField; |
| } |
| |
| @Override |
| final void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException { |
| |
| /* The Accessor reads the field value from a WidenerInput. */ |
| EntityInput widenerInput = new WidenerInput |
| (input, fromFormatId, toFormatId); |
| |
| if (secKeyField) { |
| accessor.readSecKeyFields |
| (o, widenerInput, fieldNum, fieldNum, superLevel); |
| } else { |
| accessor.readNonKeyFields |
| (o, widenerInput, fieldNum, fieldNum, superLevel); |
| } |
| } |
| } |
| |
| /** |
| * A FieldReader composed of other FieldReaders, and that calls them in |
| * sequence. Used when more than one FieldReader is needed for a list of |
| * fields. |
| */ |
| private static class MultiFieldReader extends FieldReader { |
| |
| private static final long serialVersionUID = -6035976787562441473L; |
| |
| private List<FieldReader> subReaders; |
| |
| MultiFieldReader(List<FieldReader> subReaders) { |
| this.subReaders = subReaders; |
| } |
| |
| @Override |
| void initialize(Catalog catalog, |
| int initVersion, |
| ComplexFormat oldParentFormat, |
| ComplexFormat newParentFormat, |
| boolean isOldSecKey) { |
| for (FieldReader reader : subReaders) { |
| reader.initialize |
| (catalog, initVersion, oldParentFormat, newParentFormat, |
| isOldSecKey); |
| } |
| } |
| |
| @Override |
| final void readFields(Object o, |
| EntityInput input, |
| Accessor accessor, |
| int superLevel) |
| throws RefreshException { |
| |
| for (FieldReader reader : subReaders) { |
| reader.readFields(o, input, accessor, superLevel); |
| } |
| } |
| } |
| |
| /** |
| * The Reader for evolving ComplexFormat instances. Reads the old format |
| * data one class (one level in the class hierarchy) at a time. If an |
| * Accessor is used at a given level, the Accessor is used for the |
| * corresponding level in the new class hierarchy (classes may be |
| * inserted/deleted during evolution). At each level, a FieldReader is |
| * called to evolve the secondary key and non-key lists of fields. |
| */ |
| private static class EvolveReader implements Reader { |
| |
| static final int DO_NOT_READ_ACCESSOR = Integer.MAX_VALUE; |
| |
| private static final long serialVersionUID = -1016140948306913283L; |
| |
| private transient ComplexFormat newFormat; |
| |
| /** |
| * oldHierarchy contains the formats of the old class hierarchy in most |
| * to least derived class order. |
| */ |
| private transient ComplexFormat[] oldHierarchy; |
| |
| /** |
| * newHierarchyLevels contains the corresponding level in the new |
| * hierarchy for each format in oldHierarchy. newHierarchyLevels is |
| * indexed by the oldHierarchy index. |
| */ |
| private int[] newHierarchyLevels; |
| |
| EvolveReader(List<Integer> newHierarchyLevelsList) { |
| int oldDepth = newHierarchyLevelsList.size(); |
| newHierarchyLevels = new int[oldDepth]; |
| newHierarchyLevelsList.toArray(); |
| for (int i = 0; i < oldDepth; i += 1) { |
| newHierarchyLevels[i] = newHierarchyLevelsList.get(i); |
| } |
| } |
| |
| public void initializeReader(Catalog catalog, |
| EntityModel model, |
| int initVersion, |
| Format oldFormatParam) { |
| |
| ComplexFormat oldFormat = (ComplexFormat) oldFormatParam; |
| newFormat = oldFormat.getComplexLatest(); |
| newFormat.initializeIfNeeded(catalog, model); |
| |
| /* Create newHierarchy array. */ |
| int newDepth = 0; |
| for (Format format = newFormat; |
| format != null; |
| format = format.getSuperFormat()) { |
| newDepth += 1; |
| } |
| ComplexFormat[] newHierarchy = new ComplexFormat[newDepth]; |
| int level = 0; |
| for (ComplexFormat format = newFormat; |
| format != null; |
| format = format.getComplexSuper()) { |
| newHierarchy[level] = format; |
| level += 1; |
| } |
| assert level == newDepth; |
| |
| /* Create oldHierarchy array and initialize FieldReaders. */ |
| int oldDepth = newHierarchyLevels.length; |
| oldHierarchy = new ComplexFormat[oldDepth]; |
| level = 0; |
| for (ComplexFormat oldFormat2 = oldFormat; |
| oldFormat2 != null; |
| oldFormat2 = oldFormat2.getComplexSuper()) { |
| oldHierarchy[level] = oldFormat2; |
| int level2 = newHierarchyLevels[level]; |
| ComplexFormat newFormat2 = (level2 != DO_NOT_READ_ACCESSOR) ? |
| newHierarchy[level2] : null; |
| level += 1; |
| if (oldFormat2.secKeyFieldReader != null) { |
| oldFormat2.secKeyFieldReader.initialize |
| (catalog, initVersion, oldFormat2, newFormat2, true); |
| } |
| if (oldFormat2.nonKeyFieldReader != null) { |
| oldFormat2.nonKeyFieldReader.initialize |
| (catalog, initVersion, oldFormat2, newFormat2, false); |
| } |
| } |
| assert level == oldDepth; |
| } |
| |
| public Object newInstance(EntityInput input, boolean rawAccess) { |
| return newFormat.newInstance(input, rawAccess); |
| } |
| |
| public void readPriKey(Object o, |
| EntityInput input, |
| boolean rawAccess) |
| throws RefreshException { |
| |
| /* No conversion necessary for primary keys. */ |
| newFormat.readPriKey(o, input, rawAccess); |
| } |
| |
| public Object readObject(Object o, |
| EntityInput input, |
| boolean rawAccess) |
| throws RefreshException { |
| |
| /* Use the Accessor for the new format. */ |
| Accessor accessor = rawAccess ? newFormat.rawAccessor |
| : newFormat.objAccessor; |
| |
| /* Read old format fields from the top-most class downward. */ |
| int maxMinusOne = oldHierarchy.length - 1; |
| |
| /* Read secondary key fields with the adjusted superclass level. */ |
| for (int i = maxMinusOne; i >= 0; i -= 1) { |
| FieldReader reader = oldHierarchy[i].secKeyFieldReader; |
| int newLevel = newHierarchyLevels[i]; |
| if (reader != null) { |
| reader.readFields(o, input, accessor, newLevel); |
| } else if (newLevel != DO_NOT_READ_ACCESSOR) { |
| accessor.readSecKeyFields |
| (o, input, 0, Accessor.MAX_FIELD_NUM, newLevel); |
| } |
| } |
| |
| /* Read non-key fields with the adjusted superclass level. */ |
| for (int i = maxMinusOne; i >= 0; i -= 1) { |
| FieldReader reader = oldHierarchy[i].nonKeyFieldReader; |
| int newLevel = newHierarchyLevels[i]; |
| if (reader != null) { |
| reader.readFields(o, input, accessor, newLevel); |
| } else if (newLevel != DO_NOT_READ_ACCESSOR) { |
| accessor.readNonKeyFields |
| (o, input, 0, Accessor.MAX_FIELD_NUM, newLevel); |
| } |
| } |
| return o; |
| } |
| |
| public Accessor getAccessor(boolean rawAccess) { |
| return newFormat.getAccessor(rawAccess); |
| } |
| } |
| |
| /** |
| * The secondary key metadata map (ClassMetadata.getSecondaryKeys) is keyed |
| * by key name, not field name. Key name can be different than field name |
| * when a @SecondaryKey name property is specified. To look up metadata |
| * by field name, we must do a linear search. Luckily, the number of keys |
| * per class is never very large. [#16819] |
| */ |
| static SecondaryKeyMetadata |
| getSecondaryKeyMetadataByFieldName(Map<String, SecondaryKeyMetadata> |
| secKeys, |
| String fieldName) { |
| for (SecondaryKeyMetadata meta : secKeys.values()) { |
| if (meta.getName().equals(fieldName)) { |
| return meta; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Called when opening an existing secondary database that should have a |
| * dup comparator configured. If true is returned, then this secondary |
| * index may have been previously opened without a dup comparator set, and |
| * therefore no dup comparator should be set on this database. If false is |
| * returned, the dup comparator should be set by the caller. |
| */ |
| boolean isSecKeyIncorrectlyOrdered(String keyName) { |
| return incorrectlyOrderedSecKeys == null || |
| incorrectlyOrderedSecKeys.contains(keyName); |
| } |
| |
| /** |
| * Called when creating a new secondary database that should have a dup |
| * comparator configured. If true is returned, then this secondary index |
| * may have been previously opened without a dup comparator set; this |
| * method will update this format to indicate that the dup comparator is |
| * now allowed, and the caller should flush the catalog. If false is |
| * returned, the caller need not flush the catalog. |
| */ |
| boolean setSecKeyCorrectlyOrdered(String keyName) { |
| if (incorrectlyOrderedSecKeys != null) { |
| return incorrectlyOrderedSecKeys.remove(keyName); |
| } |
| incorrectlyOrderedSecKeys = new HashSet<String>(); |
| assert entityMeta != null; |
| for (String name : entityMeta.getSecondaryKeys().keySet()) { |
| if (!name.equals(keyName)) { |
| incorrectlyOrderedSecKeys.add(name); |
| } |
| } |
| return true; |
| } |
| |
| /* |
| * Copy the incorrectlyOrderedSecKeys of old format to new format. Used |
| * during evolution. |
| */ |
| private void copyIncorrectlyOrderedSecKeys(ComplexFormat newFormat) { |
| /* Only copy from the latest version format. */ |
| if (this == this.getLatestVersion()) { |
| newFormat.incorrectlyOrderedSecKeys = |
| this.incorrectlyOrderedSecKeys == null ? |
| null : |
| new HashSet<String>(this.incorrectlyOrderedSecKeys); |
| } |
| } |
| |
| /** |
| * For unit testing. |
| */ |
| public Set<String> getIncorrectlyOrderedSecKeys() { |
| return incorrectlyOrderedSecKeys; |
| } |
| |
| @Override |
| public Accessor getAccessor(boolean rawAccess) { |
| return rawAccess ? rawAccessor : objAccessor; |
| } |
| } |