All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.persist.impl.EnumFormat Maven / Gradle / Ivy

There is a newer version: 18.3.12
Show newest version
/*-
 * See the file LICENSE for redistribution information.
 *
 * Copyright (c) 2002-2010 Oracle.  All rights reserved.
 *
 * $Id: EnumFormat.java,v 1.30 2010/01/18 15:27:04 cwl Exp $
 */

package com.sleepycat.persist.impl;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.sleepycat.compat.DbCompat;
import com.sleepycat.persist.model.EntityModel;
import com.sleepycat.persist.raw.RawObject;
import com.sleepycat.je.utilint.IdentityHashMap;

/**
 * Format for all enum types.
 *
 * In this class we resort to using reflection to allocate arrays of enums.
 * If there is a need for it, reflection could be avoided in the future by
 * generating code as new array formats are encountered.
 *
 * @author Mark Hayes
 */
public class EnumFormat extends Format {

    private static final long serialVersionUID = 1069833955604373538L;

    private String[] names;
    private transient Object[] values;

    EnumFormat(Class type) {
        super(type);
        values = type.getEnumConstants();
        names = new String[values.length];
        for (int i = 0; i < names.length; i += 1) {
            names[i] = ((Enum) values[i]).name();
        }
    }

    /**
     * For use in a deserialized CompositeKeyFormat.
     */
    EnumFormat(Class type, String[] enumData) {
        super(type);
        names = enumData;
    }

    /**
     * Returns data needed for serialization of a CompositeKeyFormat.
     */
    String[] getFormatData() {
        return names;
    }

    @Override
    public boolean isEnum() {
        return true;
    }

    @Override
    public List getEnumConstants() {
        return Arrays.asList(names);
    }

    @Override
    void collectRelatedFormats(Catalog catalog,
                               Map newFormats) {
    }

    @Override
    void initialize(Catalog catalog, EntityModel model, int initVersion) {
        if (values == null) {
            initValues();
        }
    }

    private void initValues() {
        Class cls = getType();
        if (cls != null) {
            values = new Object[names.length];
            for (int i = 0; i < names.length; i += 1) {
                try {
                    values[i] = Enum.valueOf(cls, names[i]);
                } catch (IllegalArgumentException e) {
                    throw new IllegalArgumentException
                        ("Deletion and renaming of enum values is not " +
                         "supported: " + names[i], e);
                }
            }
        }
    }

    @Override
    Object newArray(int len) {
        return Array.newInstance(getType(), len);
    }

    @Override
    public Object newInstance(EntityInput input, boolean rawAccess) {
        int index = input.readEnumConstant(names);
        if (rawAccess) {
            return new RawObject(this, names[index]);
        } else {
            return values[index];
        }
    }

    @Override
    public Object readObject(Object o, EntityInput input, boolean rawAccess) {
        /* newInstance reads the value -- do nothing here. */
        return o;
    }

    @Override
    void writeObject(Object o, EntityOutput output, boolean rawAccess) {
        if (rawAccess) {
            String name = ((RawObject) o).getEnum();
            for (int i = 0; i < names.length; i += 1) {
                if (names[i].equals(name)) {
                    output.writeEnumConstant(names, i);
                    return;
                }
            }
        } else {
            for (int i = 0; i < values.length; i += 1) {
                if (o == values[i]) {
                    output.writeEnumConstant(names, i);
                    return;
                }
            }
        }
        throw DbCompat.unexpectedState("Bad enum: " + o);
    }

    @Override
    Object convertRawObject(Catalog catalog,
                            boolean rawAccess,
                            RawObject rawObject,
                            IdentityHashMap converted) {
        String name = rawObject.getEnum();
        for (int i = 0; i < names.length; i += 1) {
            if (names[i].equals(name)) {
                Object o = values[i];
                converted.put(rawObject, o);
                return o;
            }
        }
        throw new IllegalArgumentException
            ("Enum constant is not defined: " + name);
    }

    @Override
    void skipContents(RecordInput input) {
        input.skipFast(input.getPackedIntByteLength());
    }

    @Override
    void copySecKey(RecordInput input, RecordOutput output) {
        int len = input.getPackedIntByteLength();
        output.writeFast
            (input.getBufferBytes(), input.getBufferOffset(), len);
        input.skipFast(len);
    }

    @Override
    boolean evolve(Format newFormatParam, Evolver evolver) {
        if (!(newFormatParam instanceof EnumFormat)) {
            evolver.addEvolveError
                (this, newFormatParam,
                 "Incompatible enum type changed detected",
                 "An enum class may not be changed to a non-enum type");
            /* For future:
            evolver.addMissingMutation
                (this, newFormatParam,
                 "Converter is required when an enum class is changed to " +
                 "a non-enum type");
            */
            return false;
        }

        final EnumFormat newFormat = (EnumFormat) newFormatParam;

        /* Return quickly if the enum was not changed at all. */
        if (Arrays.equals(names, newFormat.names)) {
            evolver.useOldFormat(this, newFormat);
            return true;
        }

        final List newNamesList = Arrays.asList(newFormat.names);
        final Set newNamesSet = new HashSet(newNamesList);
        final List oldNamesList = Arrays.asList(names);

        /* Deletion (or renaming, which appears as deletion) is not allowed. */
        if (!newNamesSet.containsAll(oldNamesList)) {
            final Set oldNamesSet = new HashSet(oldNamesList);
            oldNamesSet.removeAll(newNamesSet);
            evolver.addEvolveError
                (this, newFormat,
                 "Incompatible enum type changed detected",
                 "Enum values may not be removed: " + oldNamesSet);
        }

        /* Use a List for additional names to preserve ordinal order. */
        final List additionalNamesList =
            new ArrayList(newNamesList);
        additionalNamesList.removeAll(oldNamesList);
        final int nAdditionalNames = additionalNamesList.size();

        /*
         * If there are no aditional names, the new and old formats are
         * equivalent.  This is the case where only the declaration order was
         * changed.
         */
        if (nAdditionalNames == 0) {
            evolver.useOldFormat(this, newFormat);
            return true;
        }

        /*
         * Evolve the new format.  It should use the old names array, but with
         * any additional names appended.  [#17140]
         */
        final int nOldNames = names.length;
        newFormat.names = new String[nOldNames + nAdditionalNames];
        System.arraycopy(names, 0, newFormat.names, 0, nOldNames);
        for (int i = 0; i < nAdditionalNames; i += 1) {
            newFormat.names[nOldNames + i] = additionalNamesList.get(i);
        }
        newFormat.initValues();

        /*
         * Because we never change the array index (stored integer value) for
         * an enum value, the new format can read the values written by the old
         * format (newFormat is used as the Reader in the 2nd param below).
         */
        evolver.useEvolvedFormat(this, newFormat, newFormat);
        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy