Skip to content
This repository has been archived by the owner on Nov 5, 2019. It is now read-only.

Commit

Permalink
Implement #52, hopefully fix #51
Browse files Browse the repository at this point in the history
  • Loading branch information
cowtowncoder committed Mar 4, 2015
1 parent e5ad888 commit 66ada41
Show file tree
Hide file tree
Showing 11 changed files with 236 additions and 60 deletions.
2 changes: 2 additions & 0 deletions release-notes/VERSION
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ Project: jackson-module-afterburner

2.6.0 (not yet released)

#53: Include checksum in generated class names (to resolve #52)

2.5.1 (not yet released)

#47: java.lang.VerifyError (Illegal type in constant pool ...)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import com.fasterxml.jackson.module.afterburner.util.ClassName;
import com.fasterxml.jackson.module.afterburner.util.DynamicPropertyAccessorBase;
import com.fasterxml.jackson.module.afterburner.util.MyClassLoader;

Expand Down Expand Up @@ -77,30 +78,34 @@ protected OptimizedValueInstantiator createSubclass(Constructor<?> ctor, Method
{
MyClassLoader loader = (_classLoader == null) ?
new MyClassLoader(_valueClass.getClassLoader(), true) : _classLoader;
String srcName = _valueClass.getName() + "$Creator4JacksonDeserializer";
final ClassName baseName = ClassName.constructFor(_valueClass, "$Creator4JacksonDeserializer");

// We need to know checksum even for lookups, so generate it first
final byte[] bytecode = generateOptimized(baseName, ctor, factory);
baseName.assignChecksum(bytecode);

Class<?> impl = null;
try {
impl = loader.loadClass(srcName);
impl = loader.loadClass(baseName.getDottedName());
} catch (ClassNotFoundException e) { }
if (impl == null) {
byte[] bytecode = generateOptimized(srcName, ctor, factory);
impl = loader.loadAndResolve(srcName, bytecode);
impl = loader.loadAndResolve(baseName, bytecode);
}
try {
return (OptimizedValueInstantiator) impl.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Failed to generate accessor class '"+srcName+"': "+e.getMessage(), e);
throw new IllegalStateException("Failed to generate accessor class '"+baseName+"': "+e.getMessage(), e);
}
}

protected byte[] generateOptimized(String srcName, Constructor<?> ctor, Method factory)
protected byte[] generateOptimized(ClassName baseName, Constructor<?> ctor, Method factory)
{
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String superClass = internalClassName(OptimizedValueInstantiator.class.getName());
String generatedClass = internalClassName(srcName);
final String tmpClassName = baseName.getSlashedTemplate();

cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, generatedClass, null, superClass, null);
cw.visitSource(srcName + ".java", null);
cw.visit(V1_5, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, tmpClassName, null, superClass, null);
cw.visitSource(baseName.getSourceFilename(), null);

// First: must define 2 constructors:
// (a) default constructor, for creating bogus instance (just calls default instance)
Expand Down Expand Up @@ -131,10 +136,10 @@ protected byte[] generateOptimized(String srcName, Constructor<?> ctor, Method f
mv = cw.visitMethod(ACC_PUBLIC, "with", "("
+stdValueInstDesc+")"+optimizedValueInstDesc, null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, generatedClass);
mv.visitTypeInsn(NEW, tmpClassName);
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKESPECIAL, generatedClass, "<init>", "("+stdValueInstDesc+")V", false);
mv.visitMethodInsn(INVOKESPECIAL, tmpClassName, "<init>", "("+stdValueInstDesc+")V", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.fasterxml.jackson.databind.deser.SettableBeanProperty;
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.module.afterburner.util.ClassName;
import com.fasterxml.jackson.module.afterburner.util.DynamicPropertyAccessorBase;
import com.fasterxml.jackson.module.afterburner.util.MyClassLoader;

Expand Down Expand Up @@ -97,31 +98,26 @@ public BeanPropertyMutator buildMutator(MyClassLoader classLoader)
classLoader = new MyClassLoader(beanClass.getClassLoader(), true);
}

String srcName = beanClass.getName() + "$Access4JacksonDeserializer";

String generatedClass = internalClassName(srcName);
Class<?> accessorClass = null;
try {
accessorClass = classLoader.loadClass(srcName);
} catch (ClassNotFoundException e) { }
if (accessorClass == null) {
accessorClass = generateMutatorClass(classLoader, srcName, generatedClass);
}
final ClassName baseName = ClassName.constructFor(beanClass, "$Access4JacksonDeserializer");
Class<?> accessorClass = generateMutatorClass(classLoader, baseName);
try {
return (BeanPropertyMutator) accessorClass.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Failed to generate accessor class '"+srcName+"': "+e.getMessage(), e);
throw new IllegalStateException("Failed to generate accessor class '"+accessorClass.getName()+"': "+e.getMessage(), e);
}
}

public Class<?> generateMutatorClass(MyClassLoader classLoader, String srcName, String generatedClass)
public Class<?> generateMutatorClass(MyClassLoader classLoader, ClassName baseName)
{
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String superClass = internalClassName(BeanPropertyMutator.class.getName());

final String tmpClassName = baseName.getSlashedTemplate();

// muchos important: level at least 1.5 to get generics!!!
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, generatedClass, null, superClass, null);
cw.visitSource(srcName + ".java", null);
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, tmpClassName,
null, superClass, null);
cw.visitSource(baseName.getSourceFilename(), null);

// add default (no-arg) constructor first
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
Expand Down Expand Up @@ -151,11 +147,11 @@ public Class<?> generateMutatorClass(MyClassLoader classLoader, String srcName,
internalClassName(SettableBeanProperty.class.getName()), superClass);
mv = cw.visitMethod(ACC_PUBLIC, "with", withSig, null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, generatedClass);
mv.visitTypeInsn(NEW, tmpClassName);
mv.visitInsn(DUP);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ILOAD, 2);
mv.visitMethodInsn(INVOKESPECIAL, generatedClass, "<init>", ctorSig, false);
mv.visitMethodInsn(INVOKESPECIAL, tmpClassName, "<init>", ctorSig, false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0); // don't care (real values: 1,1)
mv.visitEnd();
Expand Down Expand Up @@ -198,10 +194,16 @@ public Class<?> generateMutatorClass(MyClassLoader classLoader, String srcName,
}

cw.visitEnd();
byte[] byteCode = cw.toByteArray();
return classLoader.loadAndResolve(srcName, byteCode);
byte[] bytecode = cw.toByteArray();
baseName.assignChecksum(bytecode);
// already defined exactly as-is?
try {
return classLoader.loadClass(baseName.getDottedName());
} catch (ClassNotFoundException e) { }
// if not, load, resolve etc:
return classLoader.loadAndResolve(baseName, bytecode);
}

/*
/**********************************************************
/* Code generation; method-based getters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import com.fasterxml.jackson.databind.introspect.AnnotatedField;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.module.afterburner.util.ClassName;
import com.fasterxml.jackson.module.afterburner.util.DynamicPropertyAccessorBase;
import com.fasterxml.jackson.module.afterburner.util.MyClassLoader;

Expand Down Expand Up @@ -92,34 +93,27 @@ public BeanPropertyAccessor findAccessor(MyClassLoader classLoader)
if (classLoader == null) {
classLoader = new MyClassLoader(beanClass.getClassLoader(), true);
}

String srcName = beanClass.getName() + "$Access4JacksonSerializer";

String generatedClass = internalClassName(srcName);
Class<?> accessorClass = null;
try {
accessorClass = classLoader.loadClass(srcName);
} catch (ClassNotFoundException e) { }
if (accessorClass == null) {
accessorClass = generateAccessorClass(classLoader, srcName, generatedClass);
}
final ClassName baseName = ClassName.constructFor(beanClass, "$Access4JacksonDeserializer");
Class<?> accessorClass = generateAccessorClass(classLoader, baseName);
try {
return (BeanPropertyAccessor) accessorClass.newInstance();
} catch (Exception e) {
throw new IllegalStateException("Failed to generate accessor class '"+srcName+"': "+e.getMessage(), e);
throw new IllegalStateException("Failed to generate accessor class '"+accessorClass.getName()+"': "+e.getMessage(), e);
}
}

public Class<?> generateAccessorClass(MyClassLoader classLoader,
String srcName, String generatedClass)
public Class<?> generateAccessorClass(MyClassLoader classLoader, ClassName baseName)
{
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String superClass = internalClassName(BeanPropertyAccessor.class.getName());

final String tmpClassName = baseName.getSlashedTemplate();

// muchos important: level at least 1.5 to get generics!!!
// also: since we require JDK 1.6 anyway, use that starting with Jackson 2.5
cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, generatedClass, null, superClass, null);
cw.visitSource(srcName + ".java", null);

cw.visit(V1_6, ACC_PUBLIC + ACC_SUPER + ACC_FINAL, tmpClassName,
null, superClass, null);
cw.visitSource(baseName.getSourceFilename(), null);

// add default (no-arg) constructor:
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
Expand Down Expand Up @@ -166,8 +160,15 @@ public Class<?> generateAccessorClass(MyClassLoader classLoader,
}

cw.visitEnd();
byte[] byteCode = cw.toByteArray();
return classLoader.loadAndResolve(srcName, byteCode);
byte[] bytecode = cw.toByteArray();
baseName.assignChecksum(bytecode);

// Did we already generate this?
try {
return classLoader.loadClass(baseName.getDottedName());
} catch (ClassNotFoundException e) { }
// if not, load and resolve:
return classLoader.loadAndResolve(baseName, bytecode);
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,16 @@ public List<BeanPropertyWriter> changeProperties(SerializationConfig config,
}

// if we had a match, need to materialize
BeanPropertyAccessor acc = collector.findAccessor(_classLoader);
BeanPropertyAccessor acc = null;

// and then link accessors to bean property writers:
ListIterator<BeanPropertyWriter> it = beanProperties.listIterator();
while (it.hasNext()) {
BeanPropertyWriter bpw = it.next();
if (bpw instanceof OptimizedBeanPropertyWriter<?>) {
if (acc == null) {
acc = collector.findAccessor(_classLoader);
}
it.set(((OptimizedBeanPropertyWriter<?>) bpw).withAccessor(acc));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.fasterxml.jackson.module.afterburner.util;

import java.util.zip.Adler32;

/**
* Accessing various permutations of dotted/slashed representations gets
* tiresome after a while, so here's an abstraction for hiding complexities,
* and for performing lazy transformations as necessary.
*/
public class ClassName
{
public final static String TEMPLATE_SUFFIX = actualClassName("", 0L);

// Basenames with no checksum suffix
protected final String _dottedBase;

protected String _slashedBase;

protected String _dottedName, _slashedName;

protected long _checksum;

private ClassName(String dottedBase) {
_dottedBase = dottedBase;
}

public static ClassName constructFor(Class<?> baseClass, String suffix) {
return new ClassName(baseClass.getName() + suffix);
}

public void assignChecksum(byte[] data) {
long l = adler32(data);
if (_checksum != 0L) {
throw new IllegalStateException("Trying to re-assign checksum as 0x"+Long.toHexString(l)
+" (had 0x"+Long.toHexString(_checksum)+")");
}
// Need to mask unlikely checksum of 0
if (l == 0L) {
l = 1;
}
_checksum = l;
}

public String getDottedTemplate() {
return _dottedBase + TEMPLATE_SUFFIX;
}

public String getSlashedTemplate() {
return getSlashedBase() + TEMPLATE_SUFFIX;
}

public String getDottedName() {
if (_dottedName == null) {
if (_checksum == 0) {
throw new IllegalStateException("No checksum assigned yet");
}
_dottedName = String.format("%s%08x", getDottedBase(), (int) _checksum);
}
return _dottedName;
}

public String getSlashedName() {
if (_slashedName == null) {
if (_checksum == 0) {
throw new IllegalStateException("No checksum assigned yet");
}
_slashedName = String.format("%s%08x", getSlashedBase(), (int) _checksum);
}
return _slashedName;
}

public String getSourceFilename() {
return getSlashedBase() + ".java";
}

public String getDottedBase() {
return _dottedBase;
}

public String getSlashedBase() {
if (_slashedBase == null) {
_slashedBase = dotsToSlashes(_dottedBase);
}
return _slashedBase;
}

@Override
public String toString() {
return getDottedName();
}

private static String actualClassName(String base, long checksum) {
return String.format("%s%08x", base, (int) checksum);
}

protected static String dotsToSlashes(String className) {
return className.replace(".", "/");
}

protected static long adler32(byte[] data)
{
Adler32 adler = new Adler32();
adler.update(data);
return adler.getValue();
}
}
Loading

0 comments on commit 66ada41

Please sign in to comment.