Package com.persistit

Source Code of com.persistit.DefaultValueCoder$ShortFieldAccessor

/**
* Copyright © 2005-2012 Akiban Technologies, Inc.  All rights reserved.
*
* This program and the accompanying materials are made available
* under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* This program may also be available under different license terms.
* For more information, see www.akiban.com or contact licensing@akiban.com.
*
* Contributors:
* Akiban Technologies, Inc.
*/

package com.persistit;

import java.io.Externalizable;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.io.Serializable;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

import com.persistit.encoding.CoderContext;
import com.persistit.encoding.CoderManager;
import com.persistit.encoding.ValueCoder;
import com.persistit.encoding.ValueRenderer;
import com.persistit.exception.ConversionException;

/**
* <p>
* A {@link ValueCoder} that uses reflection to access and modify the fields of
* an object. This implementation provides the default serialization mechanism
* for Persistit version 1.1. See <a
* href="../../../Object_Serialization_Notes.html"> Persistit JSA 1.1 Object
* Serialization</a> for details.
* </p>
* <p>
* <code>DefaultValueCoder</code> may only be used to serialize and deserialize
* serializable objects (i.e., instances of classes that implement
* <code>java.io.Serializable</code>). To the extent possible, the semantics of
* serialization within this <code>DefaultValueCoder</code> match the <a href=
* "http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/serial-arch.html"
* > Java Object Serialization Specification</a>.
* </p>
* </p> In particular, for Serializable classes, objects are constructed using
* the no-argument constructor of the nearest non-serializable superclass, as
* required by the specification. (This behavior can be modified with the
* <code>constructorOverride</code> system property.) To do so, this
* implementation invokes a platform-specific, non-public API method of
* <code>java.io.ObjectStreamClass</code> or
* </code>java.io.ObjectInputStream</code>. </p>
* <p>
* This implementation invokes the <code>readExternal</code> and
* <code>writeExternal</code> methods of <code>Externalizable</code> classes,
* and the <code>readObject</code>, <code>writeObject</code>,
* <code>readResolve</code> and <code>writeReplace</code> methods of
* <code>Serializable</code> classes as required by the specification. A special
* extended <code>ObjectInputStream</code> or <code>ObjectOutputStream</code>
* implementation is provided when necessary. Although the semantics of the
* specification are followed, the format of serialized data is optimized for
* Persistit and does not conform to the specification.
* </p>
* <p>
* Currently the <code>readObjectNoData</code> method, the <code>readLine</code>
* method, and the <code>PutField</code>/<code>GetField</code> API elements are
* not implemented by <code>DefaultValueCoder</code>.
* </p>
*
* @since 1.1
* @version 1.1
*/
public class DefaultValueCoder implements ValueRenderer {
    private final static Object[] EMPTY_OBJECT_ARRAY = {};
    private final static Class[] EMPTY_CLASS_ARRAY = {};
    private final static Class[] OOS_CLASS_ARRAY = { ObjectOutputStream.class };
    private final static Class[] OIS_CLASS_ARRAY = { ObjectInputStream.class };

    private final static Comparator FIELD_COMPARATOR = new Comparator() {
        @Override
        public int compare(final Object o1, final Object o2) {
            final Field f1 = (Field) o1;
            final Field f2 = (Field) o2;
            if (f1.getType().isPrimitive() && !f2.getType().isPrimitive()) {
                return -1;
            }
            return f1.getName().compareTo(f2.getName());
        }
    };

    private Class _clazz;
    private Persistit _persistit;
    private boolean _serializable;
    private boolean _externalizable;

    private ObjectStreamClass _classDescriptor;
    private Builder _valueBuilder;

    private Method _readResolveMethod = null;
    private Method _writeReplaceMethod = null;
    private Method _readObjectMethod = null;
    private Method _writeObjectMethod = null;

    private ValueRenderer _superClassValueRenderer = null;

    private final static String GET_14_NEW_INSTANCE_METHOD_NAME = "newInstance";

    private final static Class[] GET_14_NEW_INSTANCE_METHOD_TYPES = EMPTY_CLASS_ARRAY;

    private final static String GET_13_NEW_INSTANCE_METHOD_NAME = "allocateNewObject";

    private final static Class[] GET_13_NEW_INSTANCE_METHOD_TYPES = { Class.class, Class.class, };

    private Method _newInstanceMethod;
    private Object[] _newInstanceArguments;
    private Constructor _newInstanceConstructor;

    /**
     * <p>
     * Contructs a DefaultValueCoder for the specified <code>clientClass</code>.
     * The resulting coder is capable of efficiently serializing and
     * deserializing instances of the class in Persistit records.
     * </p>
     * <p>
     * The resulting <code>DefaultValueCoder</code> serializes and deserializes
     * the fields defined by standard Java serialization (see the <a href=
     * "http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/serial-arch.html"
     * > Java Object Serialization Specification</a>. If there is a final static
     * field <code>serialPersistitFields</code> then it defines the fields to be
     * serialized. Otherwise all non-static, non-transient fields, including
     * those defined in superclasses, are included.
     * </p>
     *
     * @param clientClass
     *            A (<code>java.io.Serializable</code>) <code>Class</code> of
     *            objects to be serialized and deserialized by this
     *            <code>DefaultValueCoder</code>
     *
     * @throws SecurityException
     *             if permission to make a non-public field accessible is not
     *             granted by the current security policy
     *
     * @throws ConversionException
     *             if the <code>clientClass</code> does not implement
     *             <code>java.io.Serializable</code>, or if the attempt to find
     *             an appropriate method for constructing deserialized objects
     *             fails.
     */
    public DefaultValueCoder(final Persistit persistit, final Class clientClass) throws SecurityException {
        init(persistit, clientClass, true);

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                @Override
                public Object run() {
                    List list;

                    if (_classDescriptor != null) {
                        final ObjectStreamField[] osFields = _classDescriptor.getFields();

                        //
                        // The Sun implementation has already sorted this
                        // array to conform to serialization order, but the
                        // sort order is not defined by the spec so we'll
                        // just do it again to be sure this works on all VMs.
                        //
                        Arrays.sort(osFields);
                        list = new ArrayList(osFields.length);
                        for (int index = 0; index < osFields.length; index++) {
                            Field field;
                            final String name = osFields[index].getName();
                            try {
                                field = clientClass.getDeclaredField(name);
                                list.add(field);
                            } catch (final NoSuchFieldException nsfe) {
                                throw new ConversionException(clientClass + " unmatched serializable field '" + name
                                        + "' declared");
                            }
                        }
                    } else {
                        final Field[] fields = clientClass.getDeclaredFields();
                        Arrays.sort(fields, FIELD_COMPARATOR);
                        list = new ArrayList(fields.length);

                        for (int index = 0; index < fields.length; index++) {
                            final int modifier = fields[index].getModifiers();
                            if (!Modifier.isTransient(modifier) && !Modifier.isStatic(modifier)) {
                                list.add(fields[index]);
                            }
                        }
                    }
                    final Field[] fields = (Field[]) list.toArray(new Field[list.size()]);

                    _valueBuilder = new Builder("value", fields, clientClass);
                    lookupDefaultConstructor(persistit.getConfiguration().isConstructorOverride());
                    lookupSerializationMethods();
                    return null;
                }
            });
        } catch (final PrivilegedActionException pae) {
            throw (RuntimeException) pae.getException();
        }
    }

    /**
     * <p>
     * Contructs a DefaultValueCoder for the specified <code>clientClass</code>.
     * The resulting coder is capable of efficiently serializing and
     * deserializing instances of the class in Persistit records. The resulting
     * <code>DefaultValueCoder</code> serializes and deserializes only the
     * specified <code>fields</code>.
     * </p>
     *
     * @param clientClass
     *            A (<code>java.io.Serializable</code>) <code>Class</code> of
     *            objects to be serialized and deserialized by this
     *            <code>DefaultValueCoder</code>
     *
     * @param fields
     *            An array of Fields of this class to serialize and deserialize.
     *
     * @throws SecurityException
     *             if permission to make a non-public field accessible is not
     *             granted by the current security policy
     *
     * @throws ConversionException
     *             if the <code>clientClass</code> does not implement
     *             <code>java.io.Serializable</code>, or if the attempt to find
     *             an appropriate method for constructing deserialized objects
     *             fails.
     */
    public DefaultValueCoder(final Persistit persistit, final Class clientClass, final Field[] fields) {
        init(persistit, clientClass, false);
        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                @Override
                public Object run() {
                    _valueBuilder = new Builder("value", fields, clientClass);
                    lookupDefaultConstructor(persistit.getConfiguration().isConstructorOverride());
                    lookupSerializationMethods();
                    return null;
                }
            });
        } catch (final PrivilegedActionException pae) {
            throw (RuntimeException) pae.getException();
        }
    }

    DefaultValueCoder(final Persistit persistit, final Class clientClass, final Builder valueBuilder) {
        init(persistit, clientClass, false);

        try {
            AccessController.doPrivileged(new PrivilegedExceptionAction() {
                @Override
                public Object run() {
                    _valueBuilder = valueBuilder;
                    lookupDefaultConstructor(true);
                    return null;
                }
            });
        } catch (final PrivilegedActionException pae) {
            throw (RuntimeException) pae.getException();
        }
    }

    /**
     * Performs unprivileged initialization logic common to both constructors.
     *
     * @param clientClass
     * @param mustBeSerializable
     */
    private void init(final Persistit persistit, final Class clientClass, final boolean mustBeSerializable) {
        _clazz = clientClass;
        _persistit = persistit;
        _serializable = Serializable.class.isAssignableFrom(clientClass);
        if (_serializable) {
            _externalizable = Externalizable.class.isAssignableFrom(clientClass);
            _classDescriptor = ObjectStreamClass.lookup(_clazz);
        } else if (mustBeSerializable) {
            throw new ConversionException("Not Serializable: " + clientClass.getName());
        }

        final Class superClass = clientClass.getSuperclass();
        if (superClass != null && Serializable.class.isAssignableFrom(superClass)) {
            ValueCoder coder = null;
            final CoderManager cm = _persistit.getCoderManager();
            if (cm != null) {
                coder = cm.lookupValueCoder(superClass);
            }
            if (!(coder instanceof DefaultValueCoder)) {
                coder = new DefaultValueCoder(persistit, superClass);
            }
            if (coder instanceof ValueRenderer) {
                _superClassValueRenderer = (ValueRenderer) coder;
            }
        }
    }

    private void lookupDefaultConstructor(final boolean constructorOverride) {
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                if (_externalizable || !_serializable || constructorOverride) {
                    Constructor constructor = null;
                    try {
                        constructor = _clazz.getDeclaredConstructor(EMPTY_CLASS_ARRAY);
                    } catch (final NoSuchMethodException nsme) {
                    }
                    if (_externalizable && (constructor == null || !Modifier.isPublic(constructor.getModifiers()))) {
                        throw new ConversionException("Externalizable class " + _clazz.getName()
                                + " requires a public no-argument constructor");
                    } else if (constructor == null) {
                        throw new ConversionException("Class " + _clazz.getName()
                                + " requires a no-argument constructor");
                    }
                    constructor.setAccessible(true);
                    _newInstanceConstructor = constructor;
                } else {
                    _newInstanceMethod = null;

                    try {
                        _newInstanceMethod = ObjectStreamClass.class.getDeclaredMethod(GET_14_NEW_INSTANCE_METHOD_NAME,
                                GET_14_NEW_INSTANCE_METHOD_TYPES);
                        _newInstanceArguments = EMPTY_OBJECT_ARRAY;
                    } catch (final NoSuchMethodException nsme) {
                        Class nonSerializableSuperclass = _clazz;
                        while (nonSerializableSuperclass != null
                                && Serializable.class.isAssignableFrom(nonSerializableSuperclass)) {
                            nonSerializableSuperclass = nonSerializableSuperclass.getSuperclass();
                        }
                        try {
                            _newInstanceMethod = ObjectInputStream.class.getDeclaredMethod(
                                    GET_13_NEW_INSTANCE_METHOD_NAME, GET_13_NEW_INSTANCE_METHOD_TYPES);
                            _newInstanceArguments = new Class[] { _clazz, nonSerializableSuperclass, };
                        } catch (final NoSuchMethodException nsme2) {
                        }
                    }
                    if (_newInstanceMethod != null) {
                        _newInstanceMethod.setAccessible(true);
                    } else {
                        throw new UnsupportedOperationException("Unable to find serialization constructor "
                                + "method for class " + _clazz.getName());
                    }
                }
                return null;
            }
        });
    }

    /**
     * Return the <code>class</code> that this <code>ObjectCoder</code> serves.
     *
     * @return The <code>class</code>
     */
    public Class getClientClass() {
        return _clazz;
    }

    private static Accessor lookupAccessor(final Class clazz, final String name) {
        String baseName;

        final char ch = name.charAt(0);
        if (Character.isLetter(ch) && Character.isLowerCase(ch)) {
            baseName = new Character(Character.toUpperCase(ch)) + name.substring(1);
        } else {
            baseName = name;
        }

        Method getMethod = null;
        Method setMethod = null;
        final Method[] methods = clazz.getMethods();
        //
        // First search for an appropriate accessor. Pattern is like a bean:
        // getFoo or isFoo; isFoo is a valid accessor only if its return
        // type is boolean.
        //
        for (int index = 0; index < methods.length; index++) {
            final Method m = methods[index];
            final String n = m.getName();

            if (n.startsWith("get") && n.regionMatches(3, baseName, 0, baseName.length())
                    && m.getParameterTypes().length == 0 && m.getReturnType() != Void.class
                    && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())
                    && !Modifier.isAbstract(m.getModifiers())) {
                getMethod = m;
            }

            else if (n.startsWith("is") && n.regionMatches(2, baseName, 0, baseName.length()) && getMethod == null
                    && m.getReturnType() == boolean.class && m.getParameterTypes().length == 1
                    && Modifier.isPublic(m.getModifiers()) && !Modifier.isStatic(m.getModifiers())
                    && !Modifier.isAbstract(m.getModifiers())) {
                getMethod = m;
            }
        }
        //
        // Now look for an appropriate setter. We want one that takes the
        // same type or more general type than the getter, which is why we
        // search for this after resolving the getter. We will choose the
        // most general setter that matches the getter's type.
        //
        if (getMethod != null) {
            for (int index = 0; index < methods.length; index++) {
                final Method m = methods[index];
                final String n = m.getName();

                if (n.startsWith("set") && n.regionMatches(3, baseName, 0, baseName.length())
                        && m.getParameterTypes().length == 1 && Modifier.isPublic(m.getModifiers())
                        && !Modifier.isStatic(m.getModifiers()) && !Modifier.isAbstract(m.getModifiers())) {
                    final Class c = m.getParameterTypes()[0];
                    if (m.getReturnType() == Void.TYPE && c == getMethod.getReturnType()) {
                        setMethod = m;
                    }
                }
            }
        }

        if (getMethod != null && setMethod != null) {
            return new PropertyAccessor(getMethod, setMethod);
        }

        Class c = clazz;
        while (c != null) {
            final Field field = lookupField(name, c);
            if (field != null && !Modifier.isTransient(field.getModifiers())
                    && !Modifier.isStatic(field.getModifiers())) {
                return accessorInstance(field);
            }
            c = c.getSuperclass();
        }

        throw new IllegalArgumentException("Class " + clazz.getName() + " has no accessible field or property named "
                + name);
    }

    private static Field lookupField(final String name, final Class clazz) {
        try {
            return clazz.getDeclaredField(name);
        } catch (final NoSuchFieldException nsfe) {
            return null;
        }
    }

    private static Accessor accessorInstance(final Field field) {
        Accessor accessor = null;
        if (field == null) {
            accessor = new NoFieldAccessor();
        } else if (field.getType().isPrimitive()) {
            if (field.getType() == boolean.class) {
                accessor = new BooleanFieldAccessor();
            } else if (field.getType() == byte.class) {
                accessor = new ByteFieldAccessor();
            } else if (field.getType() == short.class) {
                accessor = new ShortFieldAccessor();
            } else if (field.getType() == char.class) {
                accessor = new CharFieldAccessor();
            } else if (field.getType() == int.class) {
                accessor = new IntFieldAccessor();
            } else if (field.getType() == long.class) {
                accessor = new LongFieldAccessor();
            } else if (field.getType() == float.class) {
                accessor = new FloatFieldAccessor();
            } else if (field.getType() == double.class) {
                accessor = new DoubleFieldAccessor();
            } else {
                accessor = new ObjectFieldAccessor();
            }
        } else {
            accessor = new ObjectFieldAccessor();
        }
        accessor._field = field;
        return accessor;
    }

    static abstract class Accessor {
        Field _field;

        @Override
        public String toString() {
            return "Accessor[" + _field.getName() + "]";
        }

        void fromKey(final Object object, final Key key) throws Exception {
            final Object arg = key.decode();
            _field.set(object, arg);
        }

        void toKey(final Object object, final Key key) throws Exception {
            final Object arg = _field.get(object);
            key.append(arg);
        }

        abstract void fromValue(Object object, Value value) throws Exception;

        abstract void toValue(Object object, Value value) throws Exception;

        protected void cantModifyFinalField() {
            throw new ConversionException("Can not modify final field " + _field.getName());
        }
    }

    private static class PropertyAccessor extends Accessor {
        Method _getMethod;
        Method _setMethod;

        private PropertyAccessor(final Method getMethod, final Method setMethod) {
            _getMethod = getMethod;
            _setMethod = setMethod;
        }

        @Override
        public String toString() {
            return "Accessor[" + _getMethod.getName() + "/" + _setMethod.getName() + "]";
        }

        @Override
        void fromKey(final Object object, final Key key) throws Exception {
            final Object arg = key.decode();
            _setMethod.invoke(object, new Object[] { arg });
        }

        @Override
        void toKey(final Object object, final Key key) throws Exception {
            Object arg = null;
            arg = _getMethod.invoke(object, EMPTY_OBJECT_ARRAY);
            key.append(arg);
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            final Object arg = value.get(null, null);
            _setMethod.invoke(object, new Object[] { arg });
        }

        @Override
        void toValue(final Object object, final Value value) throws Exception {
            final Object arg = _getMethod.invoke(object, EMPTY_OBJECT_ARRAY);
            value.put(arg);
        }
    }

    private static class ObjectFieldAccessor extends Accessor {
        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            final Object arg = value.get(null, null);
            _field.set(object, arg);
        }

        @Override
        void toValue(final Object object, final Value value) throws Exception {
            final Object arg = _field.get(object);
            value.put(arg);
        }
    }

    private final static class BooleanFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getBoolean(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setBoolean(object, value.getBoolean());
        }
    }

    private final static class ByteFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getByte(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setByte(object, value.getByte());
        }
    }

    private final static class ShortFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getShort(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setShort(object, value.getShort());
        }
    }

    private final static class CharFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getChar(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setChar(object, value.getChar());
        }
    }

    private final static class IntFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getInt(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setInt(object, value.getInt());
        }
    }

    private final static class LongFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getLong(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setLong(object, value.getLong());
        }
    }

    private final static class FloatFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getFloat(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setFloat(object, value.getFloat());
        }
    }

    private final static class DoubleFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) throws Exception {
            value.put(_field.getDouble(object));
        }

        @Override
        void fromValue(final Object object, final Value value) throws Exception {
            _field.setDouble(object, value.getDouble());
        }
    }

    private final static class NoFieldAccessor extends Accessor {
        @Override
        void toValue(final Object object, final Value value) {
        }

        @Override
        void fromValue(final Object object, final Value value) {
        }
    }

    /**
     * <p>
     * A component of a <code>DefaultValueCoder</code> that reads and writes
     * data values to and from properties or fields of a client object.
     * </p>
     * <p>
     * Instances of this class implement <code>CoderContext</code> and therefore
     * may be supplied to the {@link Key#append(Object, CoderContext)} and
     * {@link Key#decode(Object, CoderContext)} methods.
     *
     */
    public static class Builder implements CoderContext {
        private final static long serialVersionUID = 1;
        private final String _name;
        private final Accessor[] _accessors;
        private final String[] _accessorNames;

        Builder(final String name, final String[] accessorNames, final Class clazz) {
            _name = name;
            _accessorNames = new String[accessorNames.length];
            System.arraycopy(accessorNames, 0, _accessorNames, 0, accessorNames.length);
            _accessors = new Accessor[accessorNames.length];

            for (int index = 0; index < accessorNames.length; index++) {
                final String accessorName = accessorNames[index];
                final Accessor accessor = lookupAccessor(clazz, accessorName);
                _accessors[index] = accessor;
            }
            makeAccessorsAccessible(this);
        }

        Builder(final String name, final Field[] fields, final Class clazz) {
            _name = name;
            _accessorNames = new String[fields.length];
            _accessors = new Accessor[fields.length];

            for (int index = 0; index < fields.length; index++) {
                _accessorNames[index] = fields[index].getName();
                _accessors[index] = accessorInstance(fields[index]);
            }
            makeAccessorsAccessible(this);
        }

        @Override
        public String toString() {
            final StringBuilder sb = new StringBuilder(_name);
            sb.append("[");
            for (int index = 0; index < _accessorNames.length; index++) {
                if (index > 0)
                    sb.append(",");
                sb.append(_accessorNames[index]);
                if (_accessors[index] instanceof PropertyAccessor)
                    sb.append("()");
            }
            sb.append("]");
            return sb.toString();
        }

        public String getName() {
            return _name;
        }

        public int getSize() {
            return _accessorNames.length;
        }

        Accessor getAccessor(final int index) {
            return _accessors[index];
        }

        public String getAccessorName(final int index) {
            return _accessorNames[index];
        }
    }

    private void lookupSerializationMethods() {
        if (_serializable) {
            _readResolveMethod = lookupInheritableMethod("readResolve", EMPTY_CLASS_ARRAY, Object.class);

            _writeReplaceMethod = lookupInheritableMethod("writeReplace", EMPTY_CLASS_ARRAY, Object.class);

            if (!_externalizable) {
                _readObjectMethod = lookupPrivateMethod("readObject", OIS_CLASS_ARRAY, void.class);

                _writeObjectMethod = lookupPrivateMethod("writeObject", OOS_CLASS_ARRAY, void.class);
            }
        }
    }

    private Method lookupPrivateMethod(final String name, final Class[] arguments, final Class returnType) {
        Method method = null;
        try {
            method = _clazz.getDeclaredMethod(name, arguments);
        } catch (final Exception e) {
        }
        if (method != null) {
            final int modifiers = method.getModifiers();
            if (method.getReturnType() == returnType && !Modifier.isStatic(modifiers)
                    && !Modifier.isAbstract(modifiers) && Modifier.isPrivate(modifiers)) {
                method.setAccessible(true);
                return method;
            }
        }
        return null;
    }

    private Method lookupInheritableMethod(final String name, final Class[] arguments, final Class returnType) {
        Class cl = _clazz;

        boolean privateOk = true;
        boolean packageOk = true;

        for (;;) {
            Method method = null;
            try {
                method = _clazz.getDeclaredMethod(name, arguments);
            } catch (final Exception e) {
            }
            if (method != null) {
                final int modifiers = method.getModifiers();
                if (method.getReturnType() == returnType
                        && !Modifier.isStatic(modifiers)
                        && !Modifier.isAbstract(modifiers)
                        && (Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers) || privateOk
                                && Modifier.isPrivate(modifiers) || packageOk && !Modifier.isPrivate(modifiers))) {
                    method.setAccessible(true);
                    return method;
                }
            }
            final Class scl = cl.getSuperclass();

            if (scl == null || scl == Object.class) {
                return null;
            }

            if (!packageEquals(cl, scl))
                packageOk = false;
            privateOk = false;
            cl = scl;
        }
    }

    /**
     * Return true if classes are defined in the same runtime package, false
     * otherwise. (From ObjectStreamClass)
     */
    private static boolean packageEquals(final Class cl1, final Class cl2) {
        return cl1.getClassLoader() == cl2.getClassLoader() && getPackageName(cl1).equals(getPackageName(cl2));
    }

    /**
     * Return package name of given class. (From ObjectStreamClass)
     */
    private static String getPackageName(final Class cl) {
        String s = cl.getName();
        int i = s.lastIndexOf('[');
        if (i >= 0) {
            s = s.substring(i + 2);
        }
        i = s.lastIndexOf('.');
        return (i >= 0) ? s.substring(0, i) : "";
    }

    private static void makeAccessorsAccessible(final Builder builder) {
        AccessController.doPrivileged(new PrivilegedAction() {
            @Override
            public Object run() {
                final ArrayList list = new ArrayList();

                final Accessor[] accessors = builder._accessors;

                for (int index = 0; index < accessors.length; index++) {
                    final Accessor a = accessors[index];

                    if (a instanceof PropertyAccessor) {
                        final PropertyAccessor pa = (PropertyAccessor) a;
                        if (pa._setMethod != null) {
                            list.add(pa._setMethod);
                        }
                        if (pa._getMethod != null) {
                            list.add(pa._getMethod);
                        }
                    } else {
                        list.add(a._field);
                    }
                }

                //
                // Make this call with the accumulated Fields, Methods and
                // Constructor
                // because it's faster: the code only needs to check privilege
                // once.
                //
                if (list.size() > 0) {
                    AccessibleObject.setAccessible(
                            (AccessibleObject[]) list.toArray(new AccessibleObject[list.size()]), true);
                }
                return null;
            }
        });
    }

    /**
     * <p>
     * Encodes the supplied <code>Object</code> into the supplied
     * <code>Value</code>. This method will be called only if this
     * <code>ValueCoder</code> has been registered with the current
     * {@link CoderManager} to encode objects having the class of the supplied
     * object.
     * </p>
     * <p>
     * Upon completion of this method, the backing byte array of the
     * <code>Value</code> and its size should be updated to reflect the
     * serialized object. Use the methods {@link Value#getEncodedBytes},
     * {@link Value#getEncodedSize} and {@link Value#setEncodedSize} to
     * manipulate the byte array directly. More commonly, the implementation of
     * this method will simply call the appropriate <code>put</code> methods to
     * write the interior field values into the <code>Value</code> object.
     * </p>
     *
     * @param value
     *            The <code>Value</code> to which the interior data of the
     *            supplied <code>Object</code> should be encoded
     * @param object
     *            The object value to encode. This parameter will never be
     *            <code>null</code> because Persistit encodes nulls with a
     *            built-in encoding.
     * @param context
     *            An arbitrary object that can optionally be supplied by the
     *            application to convey an application-specific context for the
     *            operation. (See {@link CoderContext}.) The default value is
     *            <code>null</code>.
     */
    @Override
    public void put(final Value value, final Object object, final CoderContext context) throws ConversionException {
        if (_superClassValueRenderer != null) {
            _superClassValueRenderer.put(value, object, context);
        }
        if (object instanceof Externalizable) {
            try {
                ((Externalizable) object).writeExternal(value.getObjectOutputStream());
            } catch (final Exception e) {
                throw new ConversionException("Invoking writeExternal for " + _clazz, e);
            }
        } else if (_writeObjectMethod != null) {
            invokeMethod(value, _writeObjectMethod, object, new Object[] { value.getObjectOutputStream() }, true);
        } else {
            putDefaultFields(value, object);
        }
    }

    /**
     * Invoke the object's writeReplace method, if there is one.
     *
     * @param value
     *            The <code>Value</code> into which the object is being
     *            serialized
     *
     * @param object
     *            The object being serialized
     *
     * @return The replacement determined by the object's
     *         <code>writeReplace</code>, if present, or otherwise the object
     *         itself.
     */
    Object writeReplace(final Value value, final Object object) {
        if (_writeReplaceMethod != null) {
            return invokeMethod(value, _writeReplaceMethod, object, EMPTY_OBJECT_ARRAY, false);
        } else {
            return object;
        }
    }

    private Object invokeMethod(final Value value, final Method method, final Object object, final Object[] args,
            final boolean setStackFields) throws ConversionException {
        final DefaultValueCoder saveCoder = value.getCurrentCoder();
        final Object saveObject = value.getCurrentObject();
        value.setCurrentCoder(setStackFields ? this : null);
        value.setCurrentObject(setStackFields ? object : null);
        try {
            final Object result = method.invoke(object, args);
            return result;
        } catch (final Exception e) {
            throw new ConversionException("Invoking " + method + " for " + _clazz, e);
        } finally {
            value.setCurrentCoder(saveCoder);
            value.setCurrentObject(saveObject);
        }
    }

    private Object invokeMethod(final Method method, final Object object, final Object[] args,
            final boolean setStackFields) throws ConversionException {
        try {
            final Object result = method.invoke(object, args);
            return result;
        } catch (final Exception e) {
            throw new ConversionException("Invoking " + method + " for " + _clazz, e);
        }
    }

    /**
     * Construct a new instance of the client class using internal, non-public
     * API methods of the Java platform. This method is package-private to
     * discourage inappropriate use.
     */
    Object newInstance() {
        try {
            if (_newInstanceConstructor != null) {
                return _newInstanceConstructor.newInstance(EMPTY_OBJECT_ARRAY);
            }
            if (_newInstanceMethod != null) {
                return _newInstanceMethod.invoke(_classDescriptor, _newInstanceArguments);
            } else {
                return _clazz.newInstance();
            }
        } catch (final Exception e) {
            throw new ConversionException("Instantiating " + _clazz.getName(), e);

        }
    }

    /**
     * Writes the fields of the supplied object to the supplied
     * <code>Value</code>. This method is called directly by {@link #put} unless
     * the class defines a <code>writeObject</code> method for custom
     * serialization. This method is also called indirectly by the
     * <code>defaultWriteObject</code> method of the
     * <code>ObjectOutputStream</code> passed to <code>writeObject</code> is
     * called. The collection and ordering of fields to be written is determined
     * by the Java Serialization Specification.
     *
     * @param value
     *            The <code>Value</code> into which fields should be put
     * @param object
     *            The object whose fields are to be serialized
     *
     * @throws ConversionException
     */
    public void putDefaultFields(final Value value, final Object object) throws ConversionException {
        Accessor accessor = null;
        try {
            final Accessor[] accessors = _valueBuilder._accessors;
            for (int index = 0; index < accessors.length; index++) {
                accessor = accessors[index];
                accessors[index].toValue(object, value);
            }
        } catch (final Exception e) {
            throw new ConversionException("Encoding " + accessor.toString() + " for " + _clazz, e);
        }
    }

    /**
     * <p>
     * Creates an instance of the supplied class, populates its state by
     * decoding the supplied <code>Value</code>, and returns it. This method
     * will be called only if this <code>ValueCoder</code> has been registered
     * with the current {@link CoderManager} to encode objects having supplied
     * <code>Class</code> value. Persistit will never call this method to decode
     * a value that was <code>null</code> when written because null values are
     * handled by built-in encoding logic.
     * </p>
     *
     * @param value
     *            The <code>Value</code> from which interior fields of the
     *            object are to be retrieved
     *
     * @param clazz
     *            The class of the object to be returned.
     *
     * @param context
     *            An arbitrary object that can optionally be supplied by the
     *            application to convey an application-specific context for the
     *            operation. (See {@link CoderContext}.) The default value is
     *            <code>null</code>.
     *
     * @return An <code>Object</code> having the same class as the suppled
     *         <code>clazz</code> parameter.
     *
     * @throws ConversionException
     */
    @Override
    public Object get(final Value value, final Class clazz, final CoderContext context) throws ConversionException {
        if (clazz != _clazz)
            throw new ClassCastException("Client class " + _clazz.getName() + " does not match requested class "
                    + clazz.getName());

        final Object instance = newInstance();
        value.registerEncodedObject(instance);
        render(value, instance, clazz, context);
        return readResolve(value, instance);
    }

    Object readResolve(final Value value, Object instance) {
        if (_readResolveMethod != null) {
            instance = invokeMethod(value, _readResolveMethod, instance, EMPTY_OBJECT_ARRAY, false);
        }
        return instance;
    }

    Object readResolve(Object instance) {
        if (_readResolveMethod != null) {
            instance = invokeMethod(_readResolveMethod, instance, EMPTY_OBJECT_ARRAY, false);
        }
        return instance;
    }

    /**
     * <p>
     * Populates the state of the supplied (mutable) target <code>Object</code>
     * by decoding the supplied <code>Value</code>. This method will be called
     * only if this <code>ValueRenderer</code> has been registered with the
     * current {@link CoderManager} to encode objects having the supplied
     * <code>Class</code> value. Persistit will never call this method to decode
     * a value that was <code>null</code> when written because null values are
     * handled by built-in encoding logic.
     * </p>
     *
     * @param value
     *            The <code>Value</code> from which interior fields of the
     *            object are to be retrieved
     *
     * @param target
     *            The object into which the decoded value is to be written
     *
     * @param clazz
     *            The class of the object that was originally encoded into
     *            Value.
     *
     * @param context
     *            An arbitrary object that can optionally be supplied by the
     *            application to convey an application-specific context for the
     *            operation. (See {@link CoderContext}.) The default value is
     *            <code>null</code>.
     *
     * @throws ConversionException
     */
    @Override
    public void render(final Value value, final Object target, final Class clazz, final CoderContext context)
            throws ConversionException {
        if (target == null) {
            throw new IllegalArgumentException("Target object must not be null");
        }
        if (_superClassValueRenderer != null) {
            _superClassValueRenderer.render(value, target, clazz.getSuperclass(), context);
        }

        if (target instanceof Externalizable) {
            try {
                ((Externalizable) target).readExternal(value.getObjectInputStream());
            } catch (final Exception e) {
                throw new ConversionException("Invoking readExternal for " + _clazz, e);
            }
        } else if (_readObjectMethod != null) {
            invokeMethod(value, _readObjectMethod, target, new Object[] { value.getObjectInputStream() }, true);
        } else {
            renderDefaultFields(value, target);
        }
    }

    /**
     * Reads the default fields - i.e., the fields identified as non-transient
     * non-static fields through introspection. This method is called by the
     * specialized ObjectInputStream used by the {@link Value} object.
     *
     * @param value
     *            The Value from which to read field data
     * @param target
     *            The object whose fields will be set
     *
     * @throws ConversionException
     */
    void renderDefaultFields(final Value value, final Object target) throws ConversionException {
        Accessor accessor = null;
        try {
            final Accessor[] accessors = _valueBuilder._accessors;
            for (int index = 0; index < accessors.length; index++) {
                accessor = accessors[index];
                accessor.fromValue(target, value);
            }
        } catch (final Exception e) {
            throw new ConversionException("Decoding " + accessor.toString() + " for " + _clazz, e);
        }
    }

    /**
     * Return the <code>Builder</code> that copies data values between a
     * <code>Value</code> and a client object.
     *
     * @return The Builder
     */
    public Builder getValueBuilder() {
        return _valueBuilder;
    }

    /**
     * Return a String description of this <code>DefaultValueCoder</code>. The
     * String includes the client class name, and the property or field names
     * identifying the properties and/or fields this coder accesses and
     * modifies.
     *
     * @return A String description.
     */
    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        sb.append("DefaultValueCoder(");
        sb.append(_clazz.getName());
        sb.append(",");
        sb.append(getValueBuilder().toString());
        sb.append(")");
        return sb.toString();
    }
}
TOP

Related Classes of com.persistit.DefaultValueCoder$ShortFieldAccessor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.