/**
* Copyright 2005-2012 Akiban Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.HandleCache;
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, HandleCache {
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;
private volatile int _handle;
/**
* <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();
}
@Override
public synchronized void setHandle(final int handle) {
if (_handle != 0 && _handle != handle) {
throw new IllegalStateException("Attempt to change handle from " + _handle + " to " + handle);
}
_handle = handle;
}
@Override
public int getHandle() {
return _handle;
}
}