| /*- |
| * 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.util.ArrayList; |
| import java.util.IdentityHashMap; |
| import java.util.List; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import com.sleepycat.compat.DbCompat; |
| import com.sleepycat.je.DatabaseException; |
| import com.sleepycat.je.Transaction; |
| import com.sleepycat.persist.evolve.Converter; |
| import com.sleepycat.persist.evolve.Deleter; |
| import com.sleepycat.persist.evolve.Mutation; |
| import com.sleepycat.persist.evolve.Mutations; |
| import com.sleepycat.persist.evolve.Renamer; |
| import com.sleepycat.persist.model.SecondaryKeyMetadata; |
| |
| /** |
| * Evolves each old format that is still relevant if necessary, using Mutations |
| * to configure deleters, renamers, and converters. |
| * |
| * @author Mark Hayes |
| */ |
| class Evolver { |
| |
| static final int EVOLVE_NONE = 0; |
| static final int EVOLVE_NEEDED = 1; |
| static final int EVOLVE_FAILURE = 2; |
| |
| private PersistCatalog catalog; |
| private String storePrefix; |
| private Mutations mutations; |
| private Map<String, Format> newFormats; |
| private boolean forceEvolution; |
| private boolean disallowClassChanges; |
| private boolean nestedFormatsChanged; |
| private Map<Format, Format> changedFormats; |
| private StringBuilder errors; |
| private Set<String> deleteDbs; |
| private Map<String, String> renameDbs; |
| private Map<Format, Format> renameFormats; |
| private Map<Integer, Boolean> evolvedFormats; |
| private List<Format> unprocessedFormats; |
| private Map<Format, Set<Format>> subclassMap; |
| |
| Evolver(PersistCatalog catalog, |
| String storePrefix, |
| Mutations mutations, |
| Map<String, Format> newFormats, |
| boolean forceEvolution, |
| boolean disallowClassChanges) { |
| this.catalog = catalog; |
| this.storePrefix = storePrefix; |
| this.mutations = mutations; |
| this.newFormats = newFormats; |
| this.forceEvolution = forceEvolution; |
| this.disallowClassChanges = disallowClassChanges; |
| changedFormats = new IdentityHashMap<Format, Format>(); |
| errors = new StringBuilder(); |
| deleteDbs = new HashSet<String>(); |
| renameDbs = new HashMap<String, String>(); |
| renameFormats = new IdentityHashMap<Format, Format>(); |
| evolvedFormats = new HashMap<Integer, Boolean>(); |
| unprocessedFormats = new ArrayList<Format>(); |
| subclassMap = catalog.getSubclassMap(); |
| } |
| |
| final Mutations getMutations() { |
| return mutations; |
| } |
| |
| /** |
| * Returns whether any formats were changed during evolution, and therefore |
| * need to be stored in the catalog. |
| */ |
| boolean areFormatsChanged() { |
| return !changedFormats.isEmpty(); |
| } |
| |
| /** |
| * Returns whether the given format was changed during evolution. |
| */ |
| boolean isFormatChanged(Format format) { |
| return changedFormats.containsKey(format); |
| } |
| |
| private void setFormatsChanged(Format oldFormat) { |
| checkClassChangesAllowed(oldFormat); |
| changedFormats.put(oldFormat, oldFormat); |
| nestedFormatsChanged = true; |
| /* PersistCatalog.expectNoClassChanges is true in unit tests only. */ |
| if (PersistCatalog.expectNoClassChanges) { |
| throw new IllegalStateException("expectNoClassChanges"); |
| } |
| } |
| |
| private void checkClassChangesAllowed(Format oldFormat) { |
| if (disallowClassChanges) { |
| throw new IllegalStateException |
| ("When performing an upgrade changes are not allowed " + |
| "but were made to: " + oldFormat.getClassName()); |
| } |
| } |
| |
| /** |
| * Returns the set of formats for a specific superclass format, or null if |
| * the superclass is not a complex type or has not subclasses. |
| */ |
| Set<Format> getSubclassFormats(Format superFormat) { |
| return subclassMap.get(superFormat); |
| } |
| |
| /** |
| * Returns an error string if any mutations are invalid or missing, or |
| * returns null otherwise. If non-null is returned, the store may not be |
| * opened. |
| */ |
| String getErrors() { |
| if (errors.length() > 0) { |
| return errors.toString() + "\n---\n" + |
| "(Note that when upgrading an application in a replicated " + |
| "environment, this exception may indicate that the Master " + |
| "was mistakenly upgraded before this Replica could be " + |
| "upgraded, and the solution is to upgrade this Replica.)"; |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Adds a newline and the given error. |
| */ |
| private void addError(String error) { |
| if (errors.length() > 0) { |
| errors.append("\n---\n"); |
| } |
| errors.append(error); |
| } |
| |
| private String getClassVersionLabel(Format format, String prefix) { |
| if (format != null) { |
| return prefix + |
| " class: " + format.getClassName() + |
| " version: " + format.getVersion(); |
| } else { |
| return ""; |
| } |
| } |
| |
| /** |
| * Adds a specified error when no specific mutation is involved. |
| */ |
| void addEvolveError(Format oldFormat, |
| Format newFormat, |
| String scenario, |
| String error) { |
| checkClassChangesAllowed(oldFormat); |
| if (scenario == null) { |
| scenario = "Error"; |
| } |
| addError(scenario + " when evolving" + |
| getClassVersionLabel(oldFormat, "") + |
| getClassVersionLabel(newFormat, " to") + |
| " Error: " + error); |
| } |
| |
| /** |
| * Adds an error for an invalid mutation. |
| */ |
| void addInvalidMutation(Format oldFormat, |
| Format newFormat, |
| Mutation mutation, |
| String error) { |
| checkClassChangesAllowed(oldFormat); |
| addError("Invalid mutation: " + mutation + |
| getClassVersionLabel(oldFormat, " For") + |
| getClassVersionLabel(newFormat, " New") + |
| " Error: " + error); |
| } |
| |
| /** |
| * Adds an error for a missing mutation. |
| */ |
| void addMissingMutation(Format oldFormat, |
| Format newFormat, |
| String error) { |
| checkClassChangesAllowed(oldFormat); |
| addError("Mutation is missing to evolve" + |
| getClassVersionLabel(oldFormat, "") + |
| getClassVersionLabel(newFormat, " to") + |
| " Error: " + error); |
| } |
| |
| /** |
| * Called by PersistCatalog for all non-entity formats. |
| */ |
| void addNonEntityFormat(Format oldFormat) { |
| unprocessedFormats.add(oldFormat); |
| } |
| |
| /** |
| * Called by PersistCatalog after calling evolveFormat or |
| * addNonEntityFormat for all old formats. |
| * |
| * We do not require deletion of an unreferenced class for two |
| * reasons: 1) built-in proxy classes may not be referenced, 2) the |
| * user may wish to declare persistent classes that are not yet used. |
| */ |
| void finishEvolution() { |
| /* Process unreferenced classes. */ |
| for (Format oldFormat : unprocessedFormats) { |
| oldFormat.setUnused(true); |
| evolveFormat(oldFormat); |
| } |
| } |
| |
| /** |
| * Called by PersistCatalog for all entity formats, and by Format.evolve |
| * methods for all potentially referenced non-entity formats. |
| */ |
| boolean evolveFormat(Format oldFormat) { |
| if (oldFormat.isNew()) { |
| return true; |
| } |
| boolean result; |
| Format oldEntityFormat = oldFormat.getEntityFormat(); |
| boolean trackEntityChanges = oldEntityFormat != null; |
| boolean saveNestedFormatsChanged = nestedFormatsChanged; |
| if (trackEntityChanges) { |
| nestedFormatsChanged = false; |
| } |
| Integer oldFormatId = oldFormat.getId(); |
| if (evolvedFormats.containsKey(oldFormatId)) { |
| result = evolvedFormats.get(oldFormatId); |
| } else { |
| evolvedFormats.put(oldFormatId, true); |
| result = evolveFormatInternal(oldFormat); |
| evolvedFormats.put(oldFormatId, result); |
| } |
| if (oldFormat.getLatestVersion().isNew()) { |
| nestedFormatsChanged = true; |
| } |
| if (trackEntityChanges) { |
| if (nestedFormatsChanged) { |
| Format latest = oldEntityFormat.getLatestVersion(); |
| if (latest != null) { |
| latest.setEvolveNeeded(true); |
| } |
| } |
| nestedFormatsChanged = saveNestedFormatsChanged; |
| } |
| return result; |
| } |
| |
| /** |
| * Tries to evolve a given existing format to the current version of the |
| * class and returns false if an invalid mutation is encountered or the |
| * configured mutations are not sufficient. |
| */ |
| private boolean evolveFormatInternal(Format oldFormat) { |
| |
| /* Predefined formats and deleted classes never need evolving. */ |
| if (Format.isPredefined(oldFormat) || oldFormat.isDeleted()) { |
| return true; |
| } |
| |
| /* Get class mutations. */ |
| String oldName = oldFormat.getClassName(); |
| int oldVersion = oldFormat.getVersion(); |
| Renamer renamer = mutations.getRenamer(oldName, oldVersion, null); |
| Deleter deleter = mutations.getDeleter(oldName, oldVersion, null); |
| Converter converter = |
| mutations.getConverter(oldName, oldVersion, null); |
| if (deleter != null && (converter != null || renamer != null)) { |
| addInvalidMutation |
| (oldFormat, null, deleter, |
| "Class Deleter not allowed along with a Renamer or " + |
| "Converter for the same class"); |
| return false; |
| } |
| |
| /* |
| * For determining the new name, arrays get special treatment. The |
| * component format is evolved in the process, and we disallow muations |
| * for arrays. |
| */ |
| String newName; |
| if (oldFormat.isArray()) { |
| if (deleter != null || converter != null || renamer != null) { |
| Mutation mutation = (deleter != null) ? deleter : |
| ((converter != null) ? converter : renamer); |
| addInvalidMutation |
| (oldFormat, null, mutation, |
| "Mutations not allowed for an array"); |
| return false; |
| } |
| Format compFormat = oldFormat.getComponentType(); |
| if (!evolveFormat(compFormat)) { |
| return false; |
| } |
| Format latest = compFormat.getLatestVersion(); |
| if (latest != compFormat) { |
| newName = (latest.isArray() ? "[" : "[L") + |
| latest.getClassName() + ';'; |
| } else { |
| newName = oldName; |
| } |
| } else { |
| newName = (renamer != null) ? renamer.getNewName() : oldName; |
| } |
| |
| /* Try to get the new class format. Save exception for later. */ |
| Format newFormat; |
| String newFormatException; |
| try { |
| Class newClass = catalog.resolveClass(newName); |
| try { |
| newFormat = catalog.createFormat(newClass, newFormats); |
| assert newFormat != oldFormat : newFormat.getClassName(); |
| newFormatException = null; |
| } catch (Exception e) { |
| newFormat = null; |
| newFormatException = e.toString(); |
| } |
| } catch (ClassNotFoundException e) { |
| newFormat = null; |
| newFormatException = e.toString(); |
| } |
| |
| if (newFormat != null) { |
| |
| /* |
| * If the old format is not the existing latest version and the new |
| * format is not an existing format, then we must evolve the latest |
| * old version to the new format first. We cannot evolve old |
| * format to a new format that may be discarded because it is equal |
| * to the latest existing format (which will remain the current |
| * version). |
| */ |
| if (oldFormat != oldFormat.getLatestVersion() && |
| newFormat.getPreviousVersion() == null) { |
| assert newFormats.containsValue(newFormat); |
| Format oldLatestFormat = oldFormat.getLatestVersion(); |
| if (!evolveFormat(oldLatestFormat)) { |
| return false; |
| } |
| if (oldLatestFormat == oldLatestFormat.getLatestVersion()) { |
| /* newFormat is no longer relevant [#21869]. */ |
| newFormats.remove(newFormat.getClassName()); |
| newFormat = oldLatestFormat; |
| } |
| } |
| |
| /* |
| * If the old format was previously evolved to the new format |
| * (which means the new format is actually an existing format), |
| * then there is nothing to do. This is the case where no class |
| * changes were made. |
| * |
| * However, if mutations were specified when opening the catalog |
| * that are different than the mutations last used, then we must |
| * force the re-evolution of all old formats. |
| */ |
| if (!forceEvolution && |
| newFormat == oldFormat.getLatestVersion()) { |
| return true; |
| } |
| } |
| |
| /* Apply class Renamer and continue if successful. */ |
| if (renamer != null) { |
| if (!applyClassRenamer(renamer, oldFormat, newFormat)) { |
| return false; |
| } |
| } |
| |
| /* Apply class Converter and return. */ |
| if (converter != null) { |
| if (oldFormat.isEntity()) { |
| if (newFormat == null || !newFormat.isEntity()) { |
| addInvalidMutation |
| (oldFormat, newFormat, converter, |
| "Class converter not allowed for an entity class " + |
| "that is no longer present or not having an " + |
| "@Entity annotation"); |
| return false; |
| } |
| if (!oldFormat.evolveMetadata(newFormat, converter, this)) { |
| return false; |
| } |
| } |
| return applyConverter(converter, oldFormat, newFormat); |
| } |
| |
| /* Apply class Deleter and return. */ |
| boolean needDeleter = |
| (newFormat == null) || |
| (newFormat.isEntity() != oldFormat.isEntity()); |
| if (deleter != null) { |
| if (!needDeleter) { |
| addInvalidMutation |
| (oldFormat, newFormat, deleter, |
| "Class deleter not allowed when the class and its " + |
| "@Entity or @Persistent annotation is still present"); |
| return false; |
| } |
| return applyClassDeleter(deleter, oldFormat, newFormat); |
| } else { |
| if (needDeleter) { |
| if (newFormat == null) { |
| assert newFormatException != null; |
| /* FindBugs newFormat known to be null excluded. */ |
| addMissingMutation |
| (oldFormat, newFormat, newFormatException); |
| } else { |
| addMissingMutation |
| (oldFormat, newFormat, |
| "@Entity switched to/from @Persistent"); |
| } |
| return false; |
| } |
| } |
| |
| /* |
| * Class-level mutations have been applied. Now apply field mutations |
| * (for complex classes) or special conversions (enum conversions, for |
| * example) by calling the old format's evolve method. |
| */ |
| return oldFormat.evolve(newFormat, this); |
| } |
| |
| /** |
| * Use the old format and discard the new format. Called by |
| * Format.evolve when the old and new formats are identical. |
| */ |
| void useOldFormat(Format oldFormat, Format newFormat) { |
| Format renamedFormat = renameFormats.get(oldFormat); |
| if (renamedFormat != null) { |
| |
| /* |
| * The format was renamed but, because this method is called, we |
| * know that no other class changes were made. Use the new/renamed |
| * format as the reader. |
| */ |
| assert renamedFormat == newFormat; |
| useEvolvedFormat(oldFormat, renamedFormat, renamedFormat); |
| } else if (newFormat != null && |
| (oldFormat.getVersion() != newFormat.getVersion() || |
| !oldFormat.isCurrentVersion())) { |
| |
| /* |
| * If the user wants a new version number, but ther are no other |
| * changes, we will oblige. Or, if an attempt is being made to |
| * use an old version, then the following events happened and we |
| * must evolve the old format: |
| * 1) The (previously) latest version of the format was evolved |
| * because it is not equal to the live class version. Note that |
| * evolveFormatInternal always evolves the latest version first. |
| * 2) We are now attempting to evolve an older version of the same |
| * format, and it happens to be equal to the live class version. |
| * However, we're already committed to the new format, and we must |
| * evolve all versions. |
| * [#16467] |
| */ |
| useEvolvedFormat(oldFormat, newFormat, newFormat); |
| } else { |
| /* The new format is discarded. */ |
| catalog.useExistingFormat(oldFormat); |
| if (newFormat != null) { |
| newFormats.remove(newFormat.getClassName()); |
| } |
| } |
| } |
| |
| /** |
| * Install an evolver Reader in the old format. Called by Format.evolve |
| * when the old and new formats are not identical. |
| */ |
| void useEvolvedFormat(Format oldFormat, |
| Reader evolveReader, |
| Format newFormat) { |
| oldFormat.setReader(evolveReader); |
| if (newFormat != null) { |
| oldFormat.setLatestVersion(newFormat); |
| } |
| setFormatsChanged(oldFormat); |
| } |
| |
| private boolean applyClassRenamer(Renamer renamer, |
| Format oldFormat, |
| Format newFormat) { |
| if (!checkUpdatedVersion(renamer, oldFormat, newFormat)) { |
| return false; |
| } |
| if (oldFormat.isEntity() && oldFormat.isCurrentVersion()) { |
| String newClassName = newFormat.getClassName(); |
| String oldClassName = oldFormat.getClassName(); |
| /* Queue the renaming of the primary and secondary databases. */ |
| renameDbs.put |
| (Store.makePriDbName(storePrefix, oldClassName), |
| Store.makePriDbName(storePrefix, newClassName)); |
| for (SecondaryKeyMetadata keyMeta : |
| oldFormat.getEntityMetadata().getSecondaryKeys().values()) { |
| String keyName = keyMeta.getKeyName(); |
| renameDbs.put |
| (Store.makeSecDbName(storePrefix, oldClassName, keyName), |
| Store.makeSecDbName(storePrefix, newClassName, keyName)); |
| } |
| } |
| |
| /* |
| * Link the old format to the renamed format so that we can detect the |
| * rename in useOldFormat. |
| */ |
| renameFormats.put(oldFormat, newFormat); |
| |
| setFormatsChanged(oldFormat); |
| return true; |
| } |
| |
| /** |
| * Called by ComplexFormat when a secondary key name is changed. |
| */ |
| void renameSecondaryDatabase(String oldEntityClass, |
| String newEntityClass, |
| String oldKeyName, |
| String newKeyName) { |
| renameDbs.put |
| (Store.makeSecDbName(storePrefix, oldEntityClass, oldKeyName), |
| Store.makeSecDbName(storePrefix, newEntityClass, newKeyName)); |
| } |
| |
| private boolean applyClassDeleter(Deleter deleter, |
| Format oldFormat, |
| Format newFormat) { |
| if (!checkUpdatedVersion(deleter, oldFormat, newFormat)) { |
| return false; |
| } |
| if (oldFormat.isEntity() && oldFormat.isCurrentVersion()) { |
| /* Queue the deletion of the primary and secondary databases. */ |
| String className = oldFormat.getClassName(); |
| deleteDbs.add(Store.makePriDbName(storePrefix, className)); |
| for (SecondaryKeyMetadata keyMeta : |
| oldFormat.getEntityMetadata().getSecondaryKeys().values()) { |
| deleteDbs.add(Store.makeSecDbName |
| (storePrefix, className, keyMeta.getKeyName())); |
| } |
| } |
| |
| /* |
| * Set the format to deleted last, so that the above test using |
| * isCurrentVersion works properly. |
| */ |
| oldFormat.setDeleted(true); |
| if (newFormat != null) { |
| oldFormat.setLatestVersion(newFormat); |
| } |
| |
| setFormatsChanged(oldFormat); |
| return true; |
| } |
| |
| /** |
| * Called by ComplexFormat when a secondary key is dropped. |
| */ |
| void deleteSecondaryDatabase(String oldEntityClass, String keyName) { |
| deleteDbs.add(Store.makeSecDbName |
| (storePrefix, oldEntityClass, keyName)); |
| } |
| |
| private boolean applyConverter(Converter converter, |
| Format oldFormat, |
| Format newFormat) { |
| if (!checkUpdatedVersion(converter, oldFormat, newFormat)) { |
| return false; |
| } |
| Reader reader = new ConverterReader(converter); |
| useEvolvedFormat(oldFormat, reader, newFormat); |
| return true; |
| } |
| |
| boolean isClassConverted(Format format) { |
| return format.getReader() instanceof ConverterReader; |
| } |
| |
| private boolean checkUpdatedVersion(Mutation mutation, |
| Format oldFormat, |
| Format newFormat) { |
| if (newFormat != null && |
| !oldFormat.isEnum() && |
| newFormat.getVersion() <= oldFormat.getVersion()) { |
| addInvalidMutation |
| (oldFormat, newFormat, mutation, |
| "A new higher version number must be assigned"); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| boolean checkUpdatedVersion(String scenario, |
| Format oldFormat, |
| Format newFormat) { |
| if (newFormat != null && |
| !oldFormat.isEnum() && |
| newFormat.getVersion() <= oldFormat.getVersion()) { |
| addEvolveError |
| (oldFormat, newFormat, scenario, |
| "A new higher version number must be assigned"); |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| void renameAndRemoveDatabases(Store store, Transaction txn) |
| throws DatabaseException { |
| |
| for (String dbName : deleteDbs) { |
| String[] fileAndDbNames = store.parseDbName(dbName); |
| /* Do nothing if database does not exist. */ |
| DbCompat.removeDatabase |
| (store.getEnvironment(), txn, |
| fileAndDbNames[0], fileAndDbNames[1]); |
| } |
| |
| /* |
| * An importunate locker must be used to rename databases, since rename |
| * with Database handles open is not currently possible. This is the |
| * same sort of operation as performed by the HA replayer. If the |
| * evolution (and rename here) occurs in a replication group upgrade, |
| * this will cause DatabasePreemptedException the next time the |
| * database is accessed (via the store or otherwise). In a standalone |
| * environment, this won't happen because the database won't be open; |
| * evolve occurs before opening the database in this case. [#16655] |
| */ |
| boolean saveImportunate = false; |
| if (txn != null) { |
| saveImportunate = DbCompat.setImportunate(txn, true); |
| } |
| try { |
| for (Map.Entry<String, String> entry : renameDbs.entrySet()) { |
| String oldName = entry.getKey(); |
| String newName = entry.getValue(); |
| String[] oldFileAndDbNames = store.parseDbName(oldName); |
| String[] newFileAndDbNames = store.parseDbName(newName); |
| /* Do nothing if database does not exist. */ |
| DbCompat.renameDatabase |
| (store.getEnvironment(), txn, |
| oldFileAndDbNames[0], oldFileAndDbNames[1], |
| newFileAndDbNames[0], newFileAndDbNames[1]); |
| } |
| } finally { |
| if (txn != null) { |
| DbCompat.setImportunate(txn, saveImportunate); |
| } |
| } |
| } |
| |
| /** |
| * Evolves a primary key field or composite key field. |
| */ |
| int evolveRequiredKeyField(Format oldParent, |
| Format newParent, |
| FieldInfo oldField, |
| FieldInfo newField) { |
| int result = EVOLVE_NONE; |
| String oldName = oldField.getName(); |
| final String FIELD_KIND = |
| "primary key field or composite key class field"; |
| final String FIELD_LABEL = |
| FIELD_KIND + ": " + oldName; |
| |
| if (newField == null) { |
| addMissingMutation |
| (oldParent, newParent, |
| "Field is missing and deletion is not allowed for a " + |
| FIELD_LABEL); |
| return EVOLVE_FAILURE; |
| } |
| |
| /* Check field mutations. Only a Renamer is allowed. */ |
| Deleter deleter = mutations.getDeleter |
| (oldParent.getClassName(), oldParent.getVersion(), oldName); |
| if (deleter != null) { |
| addInvalidMutation |
| (oldParent, newParent, deleter, |
| "Deleter is not allowed for a " + FIELD_LABEL); |
| return EVOLVE_FAILURE; |
| } |
| Converter converter = mutations.getConverter |
| (oldParent.getClassName(), oldParent.getVersion(), oldName); |
| if (converter != null) { |
| addInvalidMutation |
| (oldParent, newParent, converter, |
| "Converter is not allowed for a " + FIELD_LABEL); |
| return EVOLVE_FAILURE; |
| } |
| Renamer renamer = mutations.getRenamer |
| (oldParent.getClassName(), oldParent.getVersion(), oldName); |
| String newName = newField.getName(); |
| if (renamer != null) { |
| if (!renamer.getNewName().equals(newName)) { |
| addInvalidMutation |
| (oldParent, newParent, converter, |
| "Converter is not allowed for a " + FIELD_LABEL); |
| return EVOLVE_FAILURE; |
| } |
| result = EVOLVE_NEEDED; |
| } else { |
| if (!oldName.equals(newName)) { |
| addMissingMutation |
| (oldParent, newParent, |
| "Renamer is required when field name is changed from: " + |
| oldName + " to: " + newName); |
| return EVOLVE_FAILURE; |
| } |
| } |
| |
| /* |
| * Evolve the declared version of the field format. |
| */ |
| Format oldFieldFormat = oldField.getType(); |
| if (!evolveFormat(oldFieldFormat)) { |
| return EVOLVE_FAILURE; |
| } |
| Format oldLatestFormat = oldFieldFormat.getLatestVersion(); |
| if (oldFieldFormat != oldLatestFormat) { |
| result = EVOLVE_NEEDED; |
| } |
| Format newFieldFormat = newField.getType(); |
| |
| if (oldLatestFormat.getClassName().equals |
| (newFieldFormat.getClassName())) { |
| /* Formats are identical. */ |
| return result; |
| } else if ((oldLatestFormat.getWrapperFormat() != null && |
| oldLatestFormat.getWrapperFormat().getId() == |
| newFieldFormat.getId()) || |
| (newFieldFormat.getWrapperFormat() != null && |
| newFieldFormat.getWrapperFormat().getId() == |
| oldLatestFormat.getId())) { |
| /* Primitive <-> primitive wrapper type change. */ |
| return EVOLVE_NEEDED; |
| } else { |
| /* Type was changed incompatibly. */ |
| addEvolveError |
| (oldParent, newParent, |
| "Type may not be changed for a " + FIELD_KIND, |
| "Old field type: " + oldLatestFormat.getClassName() + |
| " is not compatible with the new type: " + |
| newFieldFormat.getClassName() + |
| " for field: " + oldName); |
| return EVOLVE_FAILURE; |
| } |
| } |
| } |