Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.mapdb.SerializerPojo Maven / Gradle / Ivy
Go to download
MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap memory. It is a fast, scalable and easy to use embedded Java database.
/*
* Copyright (c) 2012 Jan Kotek
*
* 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 org.mapdb;
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
/**
* Serializer which handles POJO, object graphs etc.
*
* @author Jan Kotek
*/
public class SerializerPojo extends SerializerBase implements Serializable{
private static final Logger LOG = Logger.getLogger(SerializerPojo.class.getName());
static{
String ver = System.getProperty("java.version");
if(ver!=null && ver.toLowerCase().contains("jrockit")){
LOG.warning("POJO serialization might not work on JRockit JVM. See https://github.com/jankotek/mapdb/issues/572");
}
}
protected final Serializer classInfoSerializer = new Serializer() {
@Override
public void serialize(DataOutput out, ClassInfo ci) throws IOException {
out.writeUTF(ci.name);
out.writeBoolean(ci.isEnum);
out.writeBoolean(ci.useObjectStream);
if(ci.useObjectStream)
return; //no fields
DataIO.packInt(out, ci.fields.length);
for (FieldInfo fi : ci.fields) {
out.writeUTF(fi.name);
out.writeBoolean(fi.primitive);
out.writeUTF(fi.type);
}
}
@Override
public ClassInfo deserialize(DataInput in, int available) throws IOException{
String className = in.readUTF();
Class clazz = null;
boolean isEnum = in.readBoolean();
boolean isExternalizable = in.readBoolean();
int fieldsNum = isExternalizable? 0 : DataIO.unpackInt(in);
FieldInfo[] fields = new FieldInfo[fieldsNum];
for (int j = 0; j < fieldsNum; j++) {
String fieldName = in.readUTF();
boolean primitive = in.readBoolean();
String type = in.readUTF();
if(clazz == null)
clazz = classLoader.run(className);
fields[j] = new FieldInfo(fieldName,
type,
primitive?null:classLoader.run(type),
clazz);
}
return new ClassInfo(className, fields,isEnum,isExternalizable);
}
@Override
public boolean isTrusted() {
return true;
}
};
private static final long serialVersionUID = 3181417366609199703L;
protected static final Fun.Function1 DEFAULT_CLASS_LOADER = new Fun.Function1() {
@Override
public Class run(String className) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return classForName(className, loader);
}
};
protected static Class classForName(String className, ClassLoader loader) {
try {
return Class.forName(className, true, loader);
} catch (ClassNotFoundException e) {
throw new DBException.ClassNotFound(e);
}
}
protected final Engine engine;
protected final Fun.Function1 getNameForObject;
protected final Fun.Function1 getNamedObject;
protected final Fun.Function0 getClassInfos;
protected final Fun.Function1Int getClassInfo;
protected final Fun.Function1 notifyMissingClassInfo;
protected final Fun.Function1 classLoader;
public SerializerPojo(
Fun.Function1 getNameForObject,
Fun.Function1 getNamedObject,
Fun.Function1Int getClassInfo,
Fun.Function0 getClassInfos,
Fun.Function1 notifyMissingClassInfo,
Fun.Function1 classLoader,
Engine engine){
this.getNameForObject = getNameForObject;
this.getNamedObject = getNamedObject;
this.classLoader = classLoader!=null? classLoader : DEFAULT_CLASS_LOADER;
this.engine = engine;
this.getClassInfo = getClassInfo!=null?getClassInfo:new Fun.Function1Int() {
@Override public ClassInfo run(int a) {
return null;
}
};
this.getClassInfos = getClassInfos!=null?getClassInfos:new Fun.Function0() {
@Override
public ClassInfo[] run() {
return new ClassInfo[0];
}
};
this.notifyMissingClassInfo = notifyMissingClassInfo;
}
/**
* Stores info about single class stored in MapDB.
* Roughly corresponds to 'java.io.ObjectStreamClass'
*/
protected static final class ClassInfo {
//PERF optimize deserialization cost here.
protected final String name;
protected final FieldInfo[] fields;
protected final Map name2fieldInfo = new HashMap();
protected final Map name2fieldId = new HashMap();
protected ObjectStreamField[] objectStreamFields;
protected final boolean isEnum;
protected final boolean useObjectStream;
public ClassInfo(final String name, final FieldInfo[] fields, final boolean isEnum, final boolean isExternalizable) {
this.name = name;
this.isEnum = isEnum;
this.useObjectStream = isExternalizable;
this.fields = fields.clone();
//TODO constructing dictionary might be contraproductive, perhaps use linear scan for smaller sizes
for (int i=0;i typeClass;
// Class containing this field
protected final Class> clazz;
protected Field field;
// FieldInfo(String name, boolean primitive, String type, Class> clazz) {
// this(name, primitive, SerializerPojo.classForNameClassLoader(), type, clazz);
// }
//
// public FieldInfo(String name, boolean primitive, ClassLoader classLoader, String type, Class> clazz) {
// this(name, type, primitive ? null : classForName(classLoader, type), clazz);
// }
//
// public FieldInfo(ObjectStreamField sf, ClassLoader loader, Class> clazz) {
// this(sf.getName(), sf.isPrimitive(), loader, sf.getType().getName(), clazz);
// }
public FieldInfo(String name, String type, Class> typeClass, Class> clazz) {
this.name = name;
this.primitive = typeClass == null;
this.type = type;
this.clazz = clazz;
this.typeClass = typeClass;
//init field
Class> aClazz = clazz;
// iterate over class hierarchy, until root class
while (true) {
if(aClazz == Object.class) throw new RuntimeException("Could not set field value: "+name+" - "+clazz.toString());
// access field directly
try {
Field f = aClazz.getDeclaredField(name);
// security manager may not be happy about this
if (!f.isAccessible())
f.setAccessible(true);
field = f;
break;
} catch (NoSuchFieldException e) {
//field does not exists
}
// move to superclass
aClazz = aClazz.getSuperclass();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
FieldInfo fieldInfo = (FieldInfo) o;
if (primitive != fieldInfo.primitive) return false;
if (name != null ? !name.equals(fieldInfo.name) : fieldInfo.name != null) return false;
if (type != null ? !type.equals(fieldInfo.type) : fieldInfo.type != null) return false;
if (typeClass != null ? !typeClass.equals(fieldInfo.typeClass) : fieldInfo.typeClass != null) return false;
if (clazz != null ? !clazz.equals(fieldInfo.clazz) : fieldInfo.clazz != null) return false;
return !(field != null ? !field.equals(fieldInfo.field) : fieldInfo.field != null);
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (primitive ? 1 : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (typeClass != null ? typeClass.hashCode() : 0);
result = 31 * result + (clazz != null ? clazz.hashCode() : 0);
result = 31 * result + (field != null ? field.hashCode() : 0);
return result;
}
}
public ClassInfo makeClassInfo(String className){
Class clazz = classLoader.run(className);
final boolean advancedSer = usesAdvancedSerialization(clazz);
ObjectStreamField[] streamFields = advancedSer ? new ObjectStreamField[0] : makeFieldsForClass(clazz);
FieldInfo[] fields = new FieldInfo[streamFields.length];
for (int i = 0; i < fields.length; i++) {
ObjectStreamField sf = streamFields[i];
String type = sf.getType().getName();
fields[i] = new FieldInfo(
sf.getName(),
type,
sf.isPrimitive() ? null : classLoader.run(type),
clazz);
}
return new ClassInfo(clazz.getName(), fields, clazz.isEnum(), advancedSer);
}
protected static boolean usesAdvancedSerialization(Class> clazz) {
if(Externalizable.class.isAssignableFrom(clazz))
return true;
try {
if(clazz.getDeclaredMethod("readObject",ObjectInputStream.class)!=null)
return true;
} catch (NoSuchMethodException e) {
}
try {
if(clazz.getDeclaredMethod("writeObject",ObjectOutputStream.class)!=null)
return true;
} catch (NoSuchMethodException e) {
}
try {
if(clazz.getDeclaredMethod("writeReplace")!=null)
return true;
} catch (NoSuchMethodException e) {
}
try {
if(clazz.getDeclaredMethod("readResolve")!=null)
return true;
} catch (NoSuchMethodException e) {
}
Class su = clazz.getSuperclass();
if(su==Object.class || su==null)
return false;
return usesAdvancedSerialization(su);
}
protected static ObjectStreamField[] fieldsForClass(ClassInfo[] classes, Class> clazz) {
ObjectStreamField[] fields = null;
ClassInfo classInfo = null;
int classId = classToId(classes,clazz.getName());
if (classId != -1) {
classInfo = classes[classId];
fields = classInfo.getObjectStreamFields();
}
if (fields == null) {
fields = makeFieldsForClass(clazz);
}
return fields;
}
private static ObjectStreamField[] makeFieldsForClass(Class> clazz) {
ObjectStreamField[] fields;ObjectStreamClass streamClass = ObjectStreamClass.lookup(clazz);
FastArrayList fieldsList = new FastArrayList();
while (streamClass != null) {
for (ObjectStreamField f : streamClass.getFields()) {
fieldsList.add(f);
}
clazz = clazz.getSuperclass();
streamClass = ObjectStreamClass.lookup(clazz);
}
fields = new ObjectStreamField[fieldsList
.size];
System.arraycopy(fieldsList.data, 0, fields, 0, fields.length);
//TODO what is StreamField? perhaps performance optim?
// if(classInfo != null)
// classInfo.setObjectStreamFields(fields);
return fields;
}
public boolean isSerializable(Object o){
if(super.isSerializable(o))
return true;
return Serializable.class.isAssignableFrom(o.getClass());
}
protected void assertClassSerializable(ClassInfo[] classes, Class> clazz) throws NotSerializableException, InvalidClassException {
if(classToId(classes,clazz.getName())!=-1)
return;
if (!Serializable.class.isAssignableFrom(clazz))
throw new NotSerializableException(clazz.getName());
}
public Object getFieldValue(FieldInfo fieldInfo, Object object) {
if(fieldInfo.field==null){
throw new NoSuchFieldError(object.getClass() + "." + fieldInfo.name);
}
try {
return fieldInfo.field.get(object);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not get value from field", e);
}
}
public void setFieldValue(FieldInfo fieldInfo, Object object, Object value) {
if(fieldInfo.field==null)
throw new NoSuchFieldError(object.getClass() + "." + fieldInfo.name);
try{
fieldInfo.field.set(object, value);
} catch (IllegalAccessException e) {
throw new RuntimeException("Could not set field value: ",e);
}
}
public static int classToId(ClassInfo[] classes, String className) {
for(int i=0;i objectStack) throws IOException {
if(getNameForObject!=null){
//check for named objects
String name = getNameForObject.run(obj);
if(name!=null){
out.write(Header.NAMED);
out.writeUTF(name);
//TODO object stack here?
return;
}
}
out.write(Header.POJO);
ClassInfo[] classes = getClassInfos.run();
assertClassSerializable(classes,obj.getClass());
//write class header
int classId = classToId(classes,obj.getClass().getName());
if(classId==-1){
//unknown class, fallback into object OutputOutputStream
DataIO.packInt(out,-1);
ObjectOutputStream2 out2 = new ObjectOutputStream2((OutputStream) out, classes);
out2.writeObject(obj);
//and notify listeners about missing class
if(notifyMissingClassInfo!=null)
notifyMissingClassInfo.run(obj.getClass().getName());
return;
}
Class> clazz = obj.getClass();
if( !clazz.isEnum() && clazz.getSuperclass()!=null && clazz.getSuperclass().isEnum())
clazz = clazz.getSuperclass();
if(clazz != Object.class)
assertClassSerializable(classes,clazz);
//write class header
DataIO.packInt(out, classId);
ClassInfo classInfo = classes[classId];
if(classInfo.useObjectStream){
ObjectOutputStream2 out2 = new ObjectOutputStream2((OutputStream) out, classes);
out2.writeObject(obj);
return;
}
if(classInfo.isEnum) {
int ordinal = ((Enum>)obj).ordinal();
DataIO.packInt(out, ordinal);
}
ObjectStreamField[] fields = fieldsForClass(classes, clazz);
DataIO.packInt(out, fields.length);
for (ObjectStreamField f : fields) {
//write field ID
int fieldId = classInfo.getFieldId(f.getName());
if (fieldId == -1) {
throw new AssertionError("Missing field: "+f.getName());
//TODO class info is immutable in 2.0, so this old code can not be used
// //field does not exists in class definition stored in db,
// //probably new field was added so add field descriptor
// fieldId = classInfo.addFieldInfo(new FieldInfo(f, clazz));
// saveClassInfo();
}
DataIO.packInt(out, fieldId);
//and write value
Object fieldValue = getFieldValue(classInfo.fields[fieldId], obj);
serialize(out, fieldValue, objectStack);
}
}
@Override
protected Object deserializeUnknownHeader(DataInput in, int head, FastArrayList objectStack) throws IOException {
if(head == Header.NAMED){
String name = in.readUTF();
Object o = getNamedObject.run(name);
if(o==null)
throw new DBException.DataCorruption("Named object was not found: "+name);
objectStack.add(o);
return o;
}
if(head!= Header.POJO)
throw new DBException.DataCorruption("wrong header");
try {
int classId = DataIO.unpackInt(in);
ClassInfo classInfo = getClassInfo.run(classId);
//is unknown Class or uses specialized serialization
if (classId == -1 || classInfo.useObjectStream) {
//deserialize using object stream
ObjectInputStream2 in2 = new ObjectInputStream2(in, getClassInfos.run());
Object o = in2.readObject();
objectStack.add(o);
return o;
}
Class> clazz = classLoader.run(classInfo.name);
if (!Serializable.class.isAssignableFrom(clazz))
throw new NotSerializableException(clazz.getName());
Object o;
if (classInfo.isEnum) {
int ordinal = DataIO.unpackInt(in);
o = clazz.getEnumConstants()[ordinal];
} else {
o = createInstanceSkippinkConstructor(clazz);
}
objectStack.add(o);
int fieldCount = DataIO.unpackInt(in);
for (int i = 0; i < fieldCount; i++) {
int fieldId = DataIO.unpackInt(in);
FieldInfo f = classInfo.fields[fieldId];
Object fieldValue = deserialize(in, objectStack);
setFieldValue(f, o, fieldValue);
}
return o;
}catch(ClassNotFoundException e){
throw new DBException.ClassNotFound(e);
}
}
static protected Method sunConstructor = null;
static protected Object sunReflFac = null;
static protected Method androidConstructor = null;
static private Method androidConstructorGinger = null;
static private Method androidConstructorJelly = null;
static private Object constructorId;
static{
try{
Class> clazz = DEFAULT_CLASS_LOADER.run("sun.reflect.ReflectionFactory");
if(clazz!=null){
Method getReflectionFactory = clazz.getMethod("getReflectionFactory");
sunReflFac = getReflectionFactory.invoke(null);
sunConstructor = clazz.getMethod("newConstructorForSerialization",
java.lang.Class.class, java.lang.reflect.Constructor.class);
}
}catch(Exception e){
//ignore
}
if(sunConstructor == null)try{
//try android way
Method newInstance = ObjectInputStream.class.getDeclaredMethod("newInstance", Class.class, Class.class);
newInstance.setAccessible(true);
androidConstructor = newInstance;
}catch(Exception e){
//ignore
}
//this method was taken from
//http://dexmaker.googlecode.com/git-history/5a7820356e68a977711afc854d6cd71296c56391/src/mockito/java/com/google/dexmaker/mockito/UnsafeAllocator.java
//Copyright (C) 2012 The Android Open Source Project, licenced under Apache 2 license
if(sunConstructor == null && androidConstructor == null)try{
//try android post ginger way
Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
getConstructorId.setAccessible(true);
constructorId = getConstructorId.invoke(null, Object.class);
Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance", Class.class, getConstructorId.getReturnType());
newInstance.setAccessible(true);
androidConstructorGinger = newInstance;
}catch(Exception e){
//ignore
}
if(sunConstructor == null && androidConstructor == null && androidConstructorGinger == null)try{
//try android post 4.2 way
Method getConstructorId = ObjectStreamClass.class.getDeclaredMethod("getConstructorId", Class.class);
getConstructorId.setAccessible(true);
constructorId = getConstructorId.invoke(null, Object.class);
Method newInstance = ObjectStreamClass.class.getDeclaredMethod("newInstance", Class.class, long.class);
newInstance.setAccessible(true);
androidConstructorJelly = newInstance;
}catch(Exception e){
//ignore
}
}
protected static Map, Constructor>> class2constuctor = new ConcurrentHashMap, Constructor>>();
/**
*
* For pojo serialization we need to instantiate class without invoking its constructor.
* There are two ways to do it:
*
* Using proprietary API on Oracle JDK and OpenJDK
* sun.reflect.ReflectionFactory.getReflectionFactory().newConstructorForSerialization()
* more at http://www.javaspecialists.eu/archive/Issue175.html
*
* Using {@code ObjectInputStream.newInstance} on Android
* http://stackoverflow.com/a/3448384
*
* If non of these works we fallback into usual reflection which requires an no-arg constructor
*
*/
@SuppressWarnings("restriction")
protected T createInstanceSkippinkConstructor(Class clazz) {
try {
if (sunConstructor != null) {
//Sun specific way
Constructor> intConstr = class2constuctor.get(clazz);
if (intConstr == null) {
Constructor> objDef = Object.class.getDeclaredConstructor();
intConstr = (Constructor>) sunConstructor.invoke(sunReflFac, clazz, objDef);
class2constuctor.put(clazz, intConstr);
}
return (T) intConstr.newInstance();
} else if (androidConstructor != null) {
//android (harmony) specific way
return (T) androidConstructor.invoke(null, clazz, Object.class);
} else if (androidConstructorGinger != null) {
//android (post ginger) specific way
return (T) androidConstructorGinger.invoke(null, clazz, constructorId);
} else if (androidConstructorJelly != null) {
//android (post 4.2) specific way
return (T) androidConstructorJelly.invoke(null, clazz, constructorId);
} else {
//try usual generic stuff which does not skip constructor
Constructor> c = class2constuctor.get(clazz);
if (c == null) {
c = clazz.getConstructor();
if (!c.isAccessible()) c.setAccessible(true);
class2constuctor.put(clazz, c);
}
return (T) c.newInstance();
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
protected final class ObjectOutputStream2 extends ObjectOutputStream{
private final ClassInfo[] classes;
protected ObjectOutputStream2(OutputStream out, ClassInfo[] classes) throws IOException, SecurityException {
super(out);
this.classes = classes;
}
@Override
protected void writeClassDescriptor(ObjectStreamClass desc) throws IOException {
int classId = classToId(classes,desc.getName());
DataIO.packInt(this,classId);
if(classId==-1){
//unknown class, write its full name
this.writeUTF(desc.getName());
//and notify about unknown class
if(notifyMissingClassInfo!=null)
notifyMissingClassInfo.run(desc.getName());
}
}
}
protected final class ObjectInputStream2 extends ObjectInputStream{
private final ClassInfo[] classes;
// One-element cache to handle the common case where we immediately resolve a descriptor to its class.
// Unlike most ObjecTInputStream subclasses we actually have to look up the class to find the descriptor!
private ObjectStreamClass lastDescriptor;
private Class lastDescriptorClass;
protected ObjectInputStream2(DataInput in, ClassInfo[] classes) throws IOException, SecurityException {
super(new DataIO.DataInputToStream(in));
this.classes = classes;
}
@Override
protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
int classId = DataIO.unpackInt(this);
final Class clazz;
String className;
if(classId == -1){
//unknown class, so read its name
className = this.readUTF();
}else{
className = classes[classId].name;
}
clazz = classLoader.run(className);
final ObjectStreamClass descriptor = ObjectStreamClass.lookup(clazz);
lastDescriptor = descriptor;
lastDescriptorClass = clazz;
return descriptor;
}
@Override
protected Class> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (desc == lastDescriptor) return lastDescriptorClass;
Class> clazz = classLoader.run(desc.getName());
if (clazz != null)
return clazz;
return super.resolveClass(desc);
}
}
}