/*
* Copyright 2016-2017 the original author or authors.
*
* 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.springframework.data.mapping.model;
import static org.springframework.asm.Opcodes.*;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.asm.ClassWriter;
import org.springframework.asm.Label;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.asm.Type;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.SimpleAssociationHandler;
import org.springframework.data.mapping.SimplePropertyHandler;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.TypeInformation;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
/**
* A factory that can generate byte code to speed-up dynamic property access. Uses the {@link PersistentEntity}'s
* {@link PersistentProperty} to discover the access to properties. Properties are accessed either using method handles
* to overcome Java visibility issues or directly using field access/getter/setter calls.
*
* @author Mark Paluch
* @author Oliver Gierke
* @author Christoph Strobl
* @since 1.13
*/
public class ClassGeneratingPropertyAccessorFactory implements PersistentPropertyAccessorFactory {
private volatile Map<TypeInformation<?>, Class<PersistentPropertyAccessor>> propertyAccessorClasses = new HashMap<>(
32);
private volatile Map<Class<PersistentPropertyAccessor>, Constructor<?>> constructorMap = new HashMap<>(32);
/*
* (non-Javadoc)
* @see org.springframework.data.mapping.model.PersistentPropertyAccessorFactory#getPropertyAccessor(org.springframework.data.mapping.PersistentEntity, java.lang.Object)
*/
@Override
public PersistentPropertyAccessor getPropertyAccessor(PersistentEntity<?, ?> entity, Object bean) {
final Class<PersistentPropertyAccessor> propertyAccessorClass = propertyAccessorClasses.computeIfAbsent(
entity.getTypeInformation(), k -> potentiallyCreateAndRegisterPersistentPropertyAccessorClass(entity));
try {
return (PersistentPropertyAccessor) constructorMap
.computeIfAbsent(propertyAccessorClass, k -> propertyAccessorClass.getConstructors()[0]).newInstance(bean);
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Cannot create persistent property accessor for %s", entity), e);
}
}
/**
* Checks whether an accessor class can be generated.
*
* @param entity
* @return {@literal true} if the runtime is equal or greater to Java 1.7, property name hash codes are unique and the
* type has a class loader we can use to re-inject types.
* @see PersistentPropertyAccessorFactory#isSupported(PersistentEntity)
*/
@Override
public boolean isSupported(PersistentEntity<?, ?> entity) {
Assert.notNull(entity, "PersistentEntity must not be null!");
if (entity.getType().getClassLoader() == null || entity.getType().getPackage().getName().startsWith("java")) {
return false;
}
final Set<Integer> hashCodes = new HashSet<>();
final AtomicInteger propertyCount = new AtomicInteger();
entity.doWithProperties((SimplePropertyHandler) property -> {
hashCodes.add(property.getName().hashCode());
propertyCount.incrementAndGet();
});
entity.doWithAssociations((SimpleAssociationHandler) association -> {
if (association.getInverse() != null) {
hashCodes.add(association.getInverse().getName().hashCode());
propertyCount.incrementAndGet();
}
});
return hashCodes.size() == propertyCount.get();
}
/**
* @param entity must not be {@literal null}.
* @return
*/
private synchronized Class<PersistentPropertyAccessor> potentiallyCreateAndRegisterPersistentPropertyAccessorClass(
PersistentEntity<?, ?> entity) {
Map<TypeInformation<?>, Class<PersistentPropertyAccessor>> map = this.propertyAccessorClasses;
Class<PersistentPropertyAccessor> propertyAccessorClass = map.get(entity.getTypeInformation());
if (propertyAccessorClass != null) {
return propertyAccessorClass;
}
propertyAccessorClass = createAccessorClass(entity);
map = new HashMap<>(map);
map.put(entity.getTypeInformation(), propertyAccessorClass);
this.propertyAccessorClasses = map;
return propertyAccessorClass;
}
@SuppressWarnings("unchecked")
private Class<PersistentPropertyAccessor> createAccessorClass(PersistentEntity<?, ?> entity) {
try {
return (Class<PersistentPropertyAccessor>) PropertyAccessorClassGenerator.generateCustomAccessorClass(entity);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Generates {@link PersistentPropertyAccessor} classes to access properties of a {@link PersistentEntity}. This code
* uses {@code private final static} held method handles which perform about the speed of native method invocations
* for property access which is restricted due to Java rules (such as private fields/methods) or private inner
* classes. All other scoped members (package default, protected and public) are accessed via field or property access
* to bypass reflection overhead. That's only possible if the type and the member access is possible from another
* class within the same package and class loader. Mixed access (MethodHandle/getter/setter calls) is possible as
* well. Accessing properties using generated accessors imposes some constraints:
* <ul>
* <li>Runtime must be Java 7 or higher</li>
* <li>The generated accessor decides upon generation whether to use field or property access for particular
* properties. It's not possible to change the access method once the accessor class is generated.</li>
* <li>Property names and their {@link String#hashCode()} must be unique within a {@link PersistentEntity}.</li>
* </ul>
* These constraints apply to retain the performance gains, otherwise the generated code has to decide which method
* (field/property) has to be used. The {@link String#hashCode()} rule originates in dispatching of to the appropriate
* {@link java.lang.invoke.MethodHandle}. This is done by {@code LookupSwitch} which is a O(1) operation but requires
* a constant input. {@link String#hashCode()} may change but since we run in the same VM, no evil should happen.
*
* <pre>
* {
* @code
* public class PersonWithId_Accessor_zd4wnl implements PersistentPropertyAccessor {
* private final Object bean;
* private static final MethodHandle $id_fieldGetter;
* private static final MethodHandle $id_fieldSetter;
* // ...
* public PersonWithId_Accessor_zd4wnl(Object bean) {
* this.bean = bean;
* }
* static {
* Method getter;
* Method setter;
* MethodHandles.Lookup lookup = MethodHandles.lookup();
* Class class_1 = Class.forName("org.springframework.data.mapping.Person");
* Class class_2 = Class.forName("org.springframework.data.mapping.PersonWithId");
* Field field = class_2.getDeclaredField("id");
* field.setAccessible(true);
* $id_fieldGetter = lookup.unreflectGetter(field);
* $id_fieldSetter = lookup.unreflectSetter(field);
* // ...
* }
* public Object getBean() {
* return this.bean;
* }
* public void setProperty(PersistentProperty<?> property, Object value) {
* Object bean = this.bean;
* switch (property.getName().hashCode()) {
* case 3355:
* $id_fieldSetter.invoke(bean, value);
* return;
* // ...
* }
* throw new UnsupportedOperationException(
* String.format("No MethodHandle to set property %s", new Object[] { property }));
* }
* public Object getProperty(PersistentProperty<?> property){
* Object bean = this.bean;
* switch (property.getName().hashCode()) {
* case 3355:
* return id_fieldGetter..invoke(bean);
* case 3356:
* return bean.getField();
* // ...
* case 3357:
* return bean.field;
* // ...
* throw new UnsupportedOperationException(
* String.format("No MethodHandle to get property %s", new Object[] { property }));
* }
* }
* </pre>
*
* @author Mark Paluch
*/
static class PropertyAccessorClassGenerator {
private static final String INIT = "<init>";
private static final String CLINIT = "<clinit>";
private static final String TAG = "_Accessor_";
private static final String JAVA_LANG_OBJECT = "java/lang/Object";
private static final String JAVA_LANG_STRING = "java/lang/String";
private static final String JAVA_LANG_REFLECT_METHOD = "java/lang/reflect/Method";
private static final String JAVA_LANG_INVOKE_METHOD_HANDLE = "java/lang/invoke/MethodHandle";
private static final String JAVA_LANG_CLASS = "java/lang/Class";
private static final String JAVA_UTIL_OPTIONAL = "java/util/Optional";
private static final String BEAN_FIELD = "bean";
private static final String THIS_REF = "this";
private static final String PERSISTENT_PROPERTY = "org/springframework/data/mapping/PersistentProperty";
private static final String SET_ACCESSIBLE = "setAccessible";
private static final String JAVA_LANG_REFLECT_FIELD = "java/lang/reflect/Field";
private static final String JAVA_LANG_INVOKE_METHOD_HANDLES = "java/lang/invoke/MethodHandles";
private static final String JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP = "java/lang/invoke/MethodHandles$Lookup";
private static final String JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION = "java/lang/UnsupportedOperationException";
private static final String[] IMPLEMENTED_INTERFACES = new String[] {
Type.getInternalName(PersistentPropertyAccessor.class) };
/**
* Generate a new class for the given {@link PersistentEntity}.
*
* @param entity
* @return
*/
public static Class<?> generateCustomAccessorClass(PersistentEntity<?, ?> entity) {
String className = generateClassName(entity);
byte[] bytecode = generateBytecode(className.replace('.', '/'), entity);
Class<?> accessorClass = Evil.defineClass(className, bytecode, 0, bytecode.length, entity);
return accessorClass;
}
/**
* Generate a new class for the given {@link PersistentEntity}.
*
* @param internalClassName
* @param entity
* @return
*/
public static byte[] generateBytecode(String internalClassName, PersistentEntity<?, ?> entity) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cw.visit(Opcodes.V1_6, ACC_PUBLIC + ACC_SUPER, internalClassName, null, JAVA_LANG_OBJECT, IMPLEMENTED_INTERFACES);
List<PersistentProperty<?>> persistentProperties = getPersistentProperties(entity);
visitFields(entity, persistentProperties, cw);
visitDefaultConstructor(entity, internalClassName, cw);
visitStaticInitializer(entity, persistentProperties, internalClassName, cw);
visitBeanGetter(entity, internalClassName, cw);
visitSetProperty(entity, persistentProperties, internalClassName, cw);
visitGetProperty(entity, persistentProperties, internalClassName, cw);
cw.visitEnd();
return cw.toByteArray();
}
private static List<PersistentProperty<?>> getPersistentProperties(PersistentEntity<?, ?> entity) {
final List<PersistentProperty<?>> persistentProperties = new ArrayList<>();
entity.doWithAssociations((SimpleAssociationHandler) association -> {
if (association.getInverse() != null) {
persistentProperties.add(association.getInverse());
}
});
entity.doWithProperties((SimplePropertyHandler) property -> persistentProperties.add(property));
return persistentProperties;
}
/**
* Generates field declarations for private-visibility properties.
*
* <pre>
* {
* @code
* private final Object bean;
* private static final MethodHandle $id_fieldGetter;
* private static final MethodHandle $id_fieldSetter;
* // ...
* }
* </pre>
*
* @param entity
* @param persistentProperties
* @param cw
*/
private static void visitFields(PersistentEntity<?, ?> entity, List<PersistentProperty<?>> persistentProperties,
ClassWriter cw) {
cw.visitInnerClass(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, JAVA_LANG_INVOKE_METHOD_HANDLES, "Lookup",
ACC_PRIVATE + ACC_FINAL + ACC_STATIC);
boolean accessibleType = isAccessible(entity);
if (accessibleType) {
cw.visitField(ACC_PRIVATE + ACC_FINAL, BEAN_FIELD, referenceName(Type.getInternalName(entity.getType())), null,
null).visitEnd();
} else {
cw.visitField(ACC_PRIVATE + ACC_FINAL, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, null).visitEnd();
}
for (PersistentProperty<?> property : persistentProperties) {
property.getSetter().filter(it -> generateMethodHandle(entity, it)).ifPresent(it -> cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, setterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd());
property.getGetter().filter(it -> generateMethodHandle(entity, it)).ifPresent(it -> cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, getterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd());
property.getField().filter(it -> generateSetterMethodHandle(entity, it)).ifPresent(it -> {
cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldSetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd();
cw.visitField(ACC_PRIVATE + ACC_FINAL + ACC_STATIC, fieldGetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE), null, null).visitEnd();
});
}
}
/**
* Generates the default constructor.
*
* <pre>
* {
* @code
* public PersonWithId_Accessor_zd4wnl(PersonWithId bean) {
* this.bean = bean;
* }
* }
* </pre>
*
* @param entity
* @param internalClassName
* @param cw
*/
private static void visitDefaultConstructor(PersistentEntity<?, ?> entity, String internalClassName,
ClassWriter cw) {
// public EntityAccessor(Entity bean) or EntityAccessor(Object bean)
MethodVisitor mv;
boolean accessibleType = isAccessible(entity);
if (accessibleType) {
mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", referenceName(entity.getType())), null, null);
} else {
mv = cw.visitMethod(ACC_PUBLIC, INIT, String.format("(%s)V", referenceName(JAVA_LANG_OBJECT)), null, null);
}
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, INIT, "()V", false);
// Assert.notNull(bean)
mv.visitVarInsn(ALOAD, 1);
mv.visitLdcInsn("Bean must not be null!");
mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull",
String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_STRING)), false);
// this.bean = bean
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
if (accessibleType) {
mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType()));
} else {
mv.visitFieldInsn(PUTFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT));
}
mv.visitInsn(RETURN);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l3, 0);
if (accessibleType) {
mv.visitLocalVariable(BEAN_FIELD, referenceName(Type.getInternalName(entity.getType())), null, l0, l3, 1);
} else {
mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l3, 1);
}
mv.visitMaxs(2, 2);
}
/**
* Generates the static initializer block.
*
* <pre>
* @code
* static {
* Method getter;
* Method setter;
* MethodHandles.Lookup lookup = MethodHandles.lookup();
* Class class_1 = Class.forName("org.springframework.data.mapping.Person");
* Class class_2 = Class.forName("org.springframework.data.mapping.PersonWithId");
* Field field = class_2.getDeclaredField("id");
* field.setAccessible(true);
* $id_fieldGetter = lookup.unreflectGetter(field);
* $id_fieldSetter = lookup.unreflectSetter(field);
* // ...
* }
* </pre>
*
* @param entity
* @param persistentProperties
* @param internalClassName
* @param cw
*/
private static void visitStaticInitializer(PersistentEntity<?, ?> entity,
List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_STATIC, CLINIT, "()V", null, null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
mv.visitLabel(l0);
// lookup = MethodHandles.lookup()
mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_INVOKE_METHOD_HANDLES, "lookup",
String.format("()%s", referenceName(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP)), false);
mv.visitVarInsn(ASTORE, 0);
List<Class<?>> entityClasses = getPropertyDeclaratingClasses(persistentProperties);
for (Class<?> entityClass : entityClasses) {
mv.visitLdcInsn(entityClass.getName());
mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_CLASS, "forName",
String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS)), false);
mv.visitVarInsn(ASTORE, classVariableIndex4(entityClasses, entityClass));
}
for (PersistentProperty<?> property : persistentProperties) {
if (property.usePropertyAccess()) {
property.getGetter().filter(it -> generateMethodHandle(entity, it)).ifPresent(it -> visitPropertyGetterInitializer(property, mv, entityClasses, internalClassName));
property.getSetter().filter(it -> generateMethodHandle(entity, it)).ifPresent(it -> visitPropertySetterInitializer(property, mv, entityClasses, internalClassName));
}
property.getField().filter(it -> generateSetterMethodHandle(entity, it)).ifPresent(it -> visitFieldGetterSetterInitializer(property, mv, entityClasses, internalClassName));
}
mv.visitLabel(l1);
mv.visitInsn(RETURN);
mv.visitLocalVariable("lookup", referenceName(JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP), null, l0, l1, 0);
mv.visitLocalVariable("field", referenceName(JAVA_LANG_REFLECT_FIELD), null, l0, l1, 1);
mv.visitLocalVariable("setter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 2);
mv.visitLocalVariable("getter", referenceName(JAVA_LANG_REFLECT_METHOD), null, l0, l1, 3);
for (Class<?> entityClass : entityClasses) {
int index = classVariableIndex4(entityClasses, entityClass);
mv.visitLocalVariable(String.format("class_%d", index), referenceName(JAVA_LANG_CLASS), null, l0, l1, index);
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Retrieve all classes which are involved in property/getter/setter declarations as these elements may be
* distributed across the type hierarchy.
*
* @param persistentProperties
* @return
*/
private static List<Class<?>> getPropertyDeclaratingClasses(List<PersistentProperty<?>> persistentProperties) {
return persistentProperties.stream().flatMap(property -> {
return Optionals.toStream(property.getField(), property.getGetter(), property.getSetter())
.map(it -> it.getDeclaringClass());
}).collect(Collectors.collectingAndThen(Collectors.toSet(), it -> new ArrayList<>(it)));
}
/**
* Generate property getter initializer.
*
* @param property
* @param mv
* @param entityClasses
* @param internalClassName
*/
private static void visitPropertyGetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
List<Class<?>> entityClasses, String internalClassName) {
// getter = <entity>.class.getDeclaredMethod()
Optional<Method> getter = property.getGetter();
getter.ifPresent(it -> {
mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, it.getDeclaringClass()));
mv.visitLdcInsn(it.getName());
mv.visitInsn(ICONST_0);
mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredMethod", String.format("(%s[%s)%s",
referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS), referenceName(JAVA_LANG_REFLECT_METHOD)),
false);
mv.visitVarInsn(ASTORE, 3);
// getter.setAccessible(true)
mv.visitVarInsn(ALOAD, 3);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_METHOD, SET_ACCESSIBLE, "(Z)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 3);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflect", String.format("(%s)%s",
referenceName(JAVA_LANG_REFLECT_METHOD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
});
if (!getter.isPresent()) {
mv.visitInsn(ACONST_NULL);
}
mv.visitFieldInsn(PUTSTATIC, internalClassName, getterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
}
/**
* Generate property setter initializer.
*
* @param property
* @param mv
* @param entityClasses
* @param internalClassName
*/
private static void visitPropertySetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
List<Class<?>> entityClasses, String internalClassName) {
// setter = <entity>.class.getDeclaredMethod()
Optional<Method> setter = property.getSetter();
setter.ifPresent(it -> {
mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, it.getDeclaringClass()));
mv.visitLdcInsn(it.getName());
mv.visitInsn(ICONST_1);
mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_CLASS);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
Class<?> parameterType = it.getParameterTypes()[0];
if (parameterType.isPrimitive()) {
mv.visitFieldInsn(GETSTATIC, Type.getInternalName(autoboxType(it.getParameterTypes()[0])), "TYPE",
referenceName(JAVA_LANG_CLASS));
} else {
mv.visitLdcInsn(Type.getType(referenceName(parameterType)));
}
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredMethod", String.format("(%s[%s)%s",
referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_CLASS), referenceName(JAVA_LANG_REFLECT_METHOD)),
false);
mv.visitVarInsn(ASTORE, 2);
// setter.setAccessible(true)
mv.visitVarInsn(ALOAD, 2);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_METHOD, SET_ACCESSIBLE, "(Z)V", false);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflect", String.format("(%s)%s",
referenceName(JAVA_LANG_REFLECT_METHOD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
});
if (!setter.isPresent()) {
mv.visitInsn(ACONST_NULL);
}
mv.visitFieldInsn(PUTSTATIC, internalClassName, setterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
}
/**
* Generate field getter and setter initializers.
*
* @param property
* @param mv
* @param entityClasses
* @param internalClassName
*/
private static void visitFieldGetterSetterInitializer(PersistentProperty<?> property, MethodVisitor mv,
List<Class<?>> entityClasses, String internalClassName) {
// field = <entity>.class.getDeclaredField()
property.getField().ifPresent(it -> {
mv.visitVarInsn(ALOAD, classVariableIndex4(entityClasses, it.getDeclaringClass()));
mv.visitLdcInsn(it.getName());
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_CLASS, "getDeclaredField",
String.format("(%s)%s", referenceName(JAVA_LANG_STRING), referenceName(JAVA_LANG_REFLECT_FIELD)), false);
mv.visitVarInsn(ASTORE, 1);
// field.setAccessible(true)
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(ICONST_1);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_REFLECT_FIELD, SET_ACCESSIBLE, "(Z)V", false);
// $fieldGetter = lookup.unreflectGetter(field)
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectGetter", String.format(
"(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldGetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
// $fieldSetter = lookup.unreflectSetter(field)
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLES_LOOKUP, "unreflectSetter", String.format(
"(%s)%s", referenceName(JAVA_LANG_REFLECT_FIELD), referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE)), false);
mv.visitFieldInsn(PUTSTATIC, internalClassName, fieldSetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
});
}
private static void visitBeanGetter(PersistentEntity<?, ?> entity, String internalClassName, ClassWriter cw) {
// public Object getBean()
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getBean", String.format("()%s", referenceName(JAVA_LANG_OBJECT)),
null, null);
mv.visitCode();
Label l0 = new Label();
// return this.bean
mv.visitLabel(l0);
mv.visitVarInsn(ALOAD, 0);
if (isAccessible(entity)) {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType()));
} else {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT));
}
mv.visitInsn(ARETURN);
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
/**
* Generate {@link PersistentPropertyAccessor#getProperty(PersistentProperty)} . *
*
* <pre>
* {
* @code
* public Optional<? extends Object> getProperty(PersistentProperty<?> property){
* Object bean = this.bean;
* switch (property.getName().hashCode()) {
* case 3355:
* return id_fieldGetter..invoke(bean);
* case 3356:
* return bean.getField();
* // ...
* case 3357:
* return bean.field;
* // ...
* }
* throw new UnsupportedOperationException(
* String.format("No MethodHandle to get property %s", new Object[] { property }));
* }
* }
* </pre>
*
* @param entity
* @param persistentProperties
* @param internalClassName
* @param cw
*/
private static void visitGetProperty(PersistentEntity<?, ?> entity,
List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getProperty",
"(Lorg/springframework/data/mapping/PersistentProperty;)Ljava/util/Optional;",
"(Lorg/springframework/data/mapping/PersistentProperty<*>;)Ljava/util/Optional<+Ljava/lang/Object;>;", null);
mv.visitCode();
Label l0 = new Label();
Label l1 = new Label();
mv.visitLabel(l0);
// Assert.notNull(property)
visitAssertNotNull(mv);
mv.visitVarInsn(ALOAD, 0);
if (isAccessible(entity)) {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType()));
} else {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT));
}
mv.visitVarInsn(ASTORE, 2);
visitGetPropertySwitch(entity, persistentProperties, internalClassName, mv);
mv.visitLabel(l1);
visitThrowUnsupportedOperationException(mv, "No accessor to get property %s");
mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
mv.visitLocalVariable("property", referenceName(PERSISTENT_PROPERTY),
"Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1);
if (isAccessible(entity)) {
mv.visitLocalVariable(BEAN_FIELD, referenceName(entity.getType()), null, l0, l1, 2);
} else {
mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l1, 2);
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate the {@code switch(hashcode) {label: }} block.
*
* @param entity
* @param persistentProperties
* @param internalClassName
* @param mv
*/
private static void visitGetPropertySwitch(PersistentEntity<?, ?> entity,
List<PersistentProperty<?>> persistentProperties, String internalClassName, MethodVisitor mv) {
Map<String, PropertyStackAddress> propertyStackMap = createPropertyStackMap(persistentProperties);
int[] hashes = new int[propertyStackMap.size()];
Label[] switchJumpLabels = new Label[propertyStackMap.size()];
List<PropertyStackAddress> stackmap = new ArrayList<>(propertyStackMap.values());
Collections.sort(stackmap);
for (int i = 0; i < stackmap.size(); i++) {
PropertyStackAddress propertyStackAddress = stackmap.get(i);
hashes[i] = propertyStackAddress.hash;
switchJumpLabels[i] = propertyStackAddress.label;
}
Label dfltLabel = new Label();
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, PERSISTENT_PROPERTY, "getName",
String.format("()%s", referenceName(JAVA_LANG_STRING)), true);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_STRING, "hashCode", "()I", false);
mv.visitLookupSwitchInsn(dfltLabel, hashes, switchJumpLabels);
for (PersistentProperty<?> property : persistentProperties) {
mv.visitLabel(propertyStackMap.get(property.getName()).label);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
if (property.getGetter().isPresent() || property.getField().isPresent()) {
visitGetProperty0(entity, property, mv, internalClassName);
} else {
mv.visitJumpInsn(GOTO, dfltLabel);
}
}
mv.visitLabel(dfltLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
/**
* Generate property read access using a {@link java.lang.invoke.MethodHandle}.
* {@link java.lang.invoke.MethodHandle#invoke(Object...)} have a {@code @PolymorphicSignature} so {@code invoke} is
* called as if the method had the expected signature and not array/varargs.
*
* @param entity
* @param property
* @param mv
* @param internalClassName
*/
private static void visitGetProperty0(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
MethodVisitor mv, String internalClassName) {
property.getGetter().filter(it -> property.usePropertyAccess()).map(it -> {
if (generateMethodHandle(entity, it)) {
// $getter.invoke(bean)
mv.visitFieldInsn(GETSTATIC, internalClassName, getterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
} else {
// bean.get...
mv.visitVarInsn(ALOAD, 2);
int invokeOpCode = INVOKEVIRTUAL;
Class<?> declaringClass = it.getDeclaringClass();
boolean interfaceDefinition = declaringClass.isInterface();
if (interfaceDefinition) {
invokeOpCode = INVOKEINTERFACE;
}
mv.visitMethodInsn(invokeOpCode, Type.getInternalName(declaringClass), it.getName(),
String.format("()%s", signatureTypeName(it.getReturnType())), interfaceDefinition);
autoboxIfNeeded(it.getReturnType(), autoboxType(it.getReturnType()), mv);
}
return null;
}).orElseGet(() -> {
property.getField().ifPresent(it -> {
if (generateMethodHandle(entity, it)) {
// $fieldGetter.invoke(bean)
mv.visitFieldInsn(GETSTATIC, internalClassName, fieldGetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
} else {
// bean.field
mv.visitVarInsn(ALOAD, 2);
mv.visitFieldInsn(GETFIELD, Type.getInternalName(it.getDeclaringClass()), it.getName(),
signatureTypeName(it.getType()));
autoboxIfNeeded(it.getType(), autoboxType(it.getType()), mv);
}
});
return null;
});
mv.visitMethodInsn(INVOKESTATIC, JAVA_UTIL_OPTIONAL, "ofNullable",
String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_UTIL_OPTIONAL)), false);
mv.visitInsn(ARETURN);
}
/**
* Generate the {@link PersistentPropertyAccessor#setProperty(PersistentProperty, Object)} method. *
*
* <pre>
* {
* @code
* public void setProperty(PersistentProperty<?> property, Optional<? extends Object> value) {
* Object bean = this.bean;
* switch (property.getName().hashCode()) {
* case 3355:
* $id_fieldSetter.invoke(bean, value);
* return;
* // ...
* }
* throw new UnsupportedOperationException(
* String.format("No MethodHandle to set property %s", new Object[] { property }));
* }
* }
* </pre>
*
* @param entity
* @param persistentProperties
* @param internalClassName
* @param cw
*/
private static void visitSetProperty(PersistentEntity<?, ?> entity,
List<PersistentProperty<?>> persistentProperties, String internalClassName, ClassWriter cw) {
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setProperty",
"(Lorg/springframework/data/mapping/PersistentProperty;Ljava/util/Optional;)V",
"(Lorg/springframework/data/mapping/PersistentProperty<*>;Ljava/util/Optional<+Ljava/lang/Object;>)V", null);
mv.visitCode();
Label l0 = new Label();
mv.visitLabel(l0);
visitAssertNotNull(mv);
visitUnwrapValue(mv);
mv.visitVarInsn(ALOAD, 0);
if (isAccessible(entity)) {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(entity.getType()));
} else {
mv.visitFieldInsn(GETFIELD, internalClassName, BEAN_FIELD, referenceName(JAVA_LANG_OBJECT));
}
mv.visitVarInsn(ASTORE, 3);
visitSetPropertySwitch(entity, persistentProperties, internalClassName, mv);
Label l1 = new Label();
mv.visitLabel(l1);
visitThrowUnsupportedOperationException(mv, "No accessor to set property %s");
mv.visitLocalVariable(THIS_REF, referenceName(internalClassName), null, l0, l1, 0);
mv.visitLocalVariable("property", "Lorg/springframework/data/mapping/PersistentProperty;",
"Lorg/springframework/data/mapping/PersistentProperty<*>;", l0, l1, 1);
mv.visitLocalVariable("optional", referenceName(JAVA_UTIL_OPTIONAL), "Ljava/util/Optional<+Ljava/lang/Object;>;",
l0, l1, 2);
if (isAccessible(entity)) {
mv.visitLocalVariable(BEAN_FIELD, referenceName(entity.getType()), null, l0, l1, 3);
} else {
mv.visitLocalVariable(BEAN_FIELD, referenceName(JAVA_LANG_OBJECT), null, l0, l1, 3);
}
mv.visitLocalVariable("value", referenceName(JAVA_LANG_OBJECT), null, l0, l1, 4);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
private static void visitUnwrapValue(MethodVisitor mv) {
mv.visitVarInsn(ALOAD, 2);
mv.visitInsn(ACONST_NULL);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_UTIL_OPTIONAL, "orElse",
String.format("(%s)%s", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
mv.visitVarInsn(ASTORE, 4);
}
/**
* Generate the {@code switch(hashcode) {label: }} block.
*
* @param entity
* @param persistentProperties
* @param internalClassName
* @param mv
*/
private static void visitSetPropertySwitch(PersistentEntity<?, ?> entity,
List<PersistentProperty<?>> persistentProperties, String internalClassName, MethodVisitor mv) {
Map<String, PropertyStackAddress> propertyStackMap = createPropertyStackMap(persistentProperties);
int[] hashes = new int[propertyStackMap.size()];
Label[] switchJumpLabels = new Label[propertyStackMap.size()];
List<PropertyStackAddress> stackmap = new ArrayList<>(propertyStackMap.values());
Collections.sort(stackmap);
for (int i = 0; i < stackmap.size(); i++) {
PropertyStackAddress propertyStackAddress = stackmap.get(i);
hashes[i] = propertyStackAddress.hash;
switchJumpLabels[i] = propertyStackAddress.label;
}
Label dfltLabel = new Label();
mv.visitVarInsn(ALOAD, 1);
mv.visitMethodInsn(INVOKEINTERFACE, PERSISTENT_PROPERTY, "getName",
String.format("()%s", referenceName(JAVA_LANG_STRING)), true);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_STRING, "hashCode", "()I", false);
mv.visitLookupSwitchInsn(dfltLabel, hashes, switchJumpLabels);
for (PersistentProperty<?> property : persistentProperties) {
mv.visitLabel(propertyStackMap.get(property.getName()).label);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
if (property.getSetter().isPresent() || property.getField().isPresent()) {
visitSetProperty0(entity, property, mv, internalClassName);
} else {
mv.visitJumpInsn(GOTO, dfltLabel);
}
}
mv.visitLabel(dfltLabel);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
/**
* Generate property write access using a {@link java.lang.invoke.MethodHandle}. NOTE:
* {@link java.lang.invoke.MethodHandle#invoke(Object...)} have a {@code @PolymorphicSignature} so {@code invoke} is
* called as if the method had the expected signature and not array/varargs.
*
* @param entity
* @param property
* @param mv
* @param internalClassName
*/
private static void visitSetProperty0(PersistentEntity<?, ?> entity, PersistentProperty<?> property,
MethodVisitor mv, String internalClassName) {
Optional<Method> setter = property.getSetter();
setter.filter(it -> property.usePropertyAccess()).map(it -> {
if (generateMethodHandle(entity, it)) {
// $setter.invoke(bean)
mv.visitFieldInsn(GETSTATIC, internalClassName, setterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
mv.visitVarInsn(ALOAD, 3);
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
} else {
// bean.set...(object)
mv.visitVarInsn(ALOAD, 3);
mv.visitVarInsn(ALOAD, 4);
Class<?> parameterType = it.getParameterTypes()[0];
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(parameterType)));
autoboxIfNeeded(autoboxType(parameterType), parameterType, mv);
int invokeOpCode = INVOKEVIRTUAL;
Class<?> declaringClass = it.getDeclaringClass();
boolean interfaceDefinition = declaringClass.isInterface();
if (interfaceDefinition) {
invokeOpCode = INVOKEINTERFACE;
}
mv.visitMethodInsn(invokeOpCode, Type.getInternalName(it.getDeclaringClass()), it.getName(),
String.format("(%s)V", signatureTypeName(parameterType)), interfaceDefinition);
}
return null;
}).orElseGet(() -> {
property.getField().ifPresent(it -> {
if (generateSetterMethodHandle(entity, it)) {
// $fieldSetter.invoke(bean, object)
mv.visitFieldInsn(GETSTATIC, internalClassName, fieldSetterName(property),
referenceName(JAVA_LANG_INVOKE_METHOD_HANDLE));
mv.visitVarInsn(ALOAD, 3);
mv.visitVarInsn(ALOAD, 4);
mv.visitMethodInsn(INVOKEVIRTUAL, JAVA_LANG_INVOKE_METHOD_HANDLE, "invoke",
String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_OBJECT)), false);
} else {
// bean.field
mv.visitVarInsn(ALOAD, 3);
mv.visitVarInsn(ALOAD, 4);
Class<?> fieldType = it.getType();
mv.visitTypeInsn(CHECKCAST, Type.getInternalName(autoboxType(fieldType)));
autoboxIfNeeded(autoboxType(fieldType), fieldType, mv);
mv.visitFieldInsn(PUTFIELD, Type.getInternalName(it.getDeclaringClass()), it.getName(),
signatureTypeName(fieldType));
}
});
return null;
});
mv.visitInsn(RETURN);
}
private static void visitAssertNotNull(MethodVisitor mv) {
// Assert.notNull(property)
mv.visitVarInsn(ALOAD, 1);
mv.visitLdcInsn("Property must not be null!");
mv.visitMethodInsn(INVOKESTATIC, "org/springframework/util/Assert", "notNull",
String.format("(%s%s)V", referenceName(JAVA_LANG_OBJECT), referenceName(JAVA_LANG_STRING)), false);
}
private static void visitThrowUnsupportedOperationException(MethodVisitor mv, String message) {
// throw new UnsupportedOperationException(msg)
mv.visitTypeInsn(NEW, JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION);
mv.visitInsn(DUP);
mv.visitLdcInsn(message);
mv.visitInsn(ICONST_1);
mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT);
mv.visitInsn(DUP);
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ALOAD, 1);
mv.visitInsn(AASTORE);
mv.visitMethodInsn(INVOKESTATIC, JAVA_LANG_STRING, "format",
"(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION, "<init>", "(Ljava/lang/String;)V",
false);
mv.visitInsn(ATHROW);
}
private static String fieldSetterName(PersistentProperty<?> property) {
return String.format("$%s_fieldSetter", property.getName());
}
private static String fieldGetterName(PersistentProperty<?> property) {
return String.format("$%s_fieldGetter", property.getName());
}
private static String setterName(PersistentProperty<?> property) {
return String.format("$%s_setter", property.getName());
}
private static String getterName(PersistentProperty<?> property) {
return String.format("$%s_getter", property.getName());
}
private static boolean isAccessible(PersistentEntity<?, ?> entity) {
return isAccessible(entity.getType());
}
private static boolean isAccessible(Class<?> theClass) {
return isAccessible(theClass.getModifiers());
}
private static boolean isAccessible(int modifiers) {
return !Modifier.isPrivate(modifiers);
}
private static boolean isDefault(int modifiers) {
if (Modifier.isPrivate(modifiers) || Modifier.isProtected(modifiers) || Modifier.isPublic(modifiers)) {
return false;
}
return true;
}
private static boolean generateSetterMethodHandle(PersistentEntity<?, ?> entity, Field field) {
return generateMethodHandle(entity, field) || Modifier.isFinal(field.getModifiers());
}
/**
* Check whether to generate {@link java.lang.invoke.MethodHandle} access. Checks visibility rules of the member and
* its declaring class. Use also {@link java.lang.invoke.MethodHandle} if visibility is protected/package-default
* and packages of the declaring types are different.
*
* @param entity
* @param member
* @return
*/
private static boolean generateMethodHandle(PersistentEntity<?, ?> entity, Member member) {
if (isAccessible(entity)) {
if (Modifier.isProtected(member.getModifiers()) || isDefault(member.getModifiers())) {
if (!member.getDeclaringClass().getPackage().equals(entity.getType().getPackage())) {
return true;
}
}
if (isAccessible(member.getDeclaringClass()) && isAccessible(member.getModifiers())) {
return false;
}
}
return true;
}
/**
* Retrieves the class variable index with an offset of {@code 4}.
*
* @param list
* @param item
* @return
*/
private static int classVariableIndex4(List<Class<?>> list, Class<?> item) {
return 4 + list.indexOf(item);
}
/**
* @param entity
* @return
*/
private static String generateClassName(PersistentEntity<?, ?> entity) {
return entity.getType().getName() + TAG + Integer.toString(entity.hashCode(), 36);
}
}
private static String referenceName(Class<?> type) {
if (type.isArray()) {
return Type.getInternalName(type);
}
return referenceName(Type.getInternalName(type));
}
private static String referenceName(String internalTypeName) {
return String.format("L%s;", internalTypeName);
}
private static Map<String, PropertyStackAddress> createPropertyStackMap(
List<PersistentProperty<?>> persistentProperties) {
Map<String, PropertyStackAddress> stackmap = new HashMap<>();
for (PersistentProperty<?> property : persistentProperties) {
stackmap.put(property.getName(), new PropertyStackAddress(new Label(), property.getName().hashCode()));
}
return stackmap;
}
/**
* Returns the appropriate autoboxing type.
*
* @param unboxed
* @return
*/
private static Class<?> autoboxType(Class<?> unboxed) {
if (unboxed.equals(Boolean.TYPE)) {
return Boolean.class;
}
if (unboxed.equals(Byte.TYPE)) {
return Byte.class;
}
if (unboxed.equals(Character.TYPE)) {
return Character.class;
}
if (unboxed.equals(Double.TYPE)) {
return Double.class;
}
if (unboxed.equals(Float.TYPE)) {
return Float.class;
}
if (unboxed.equals(Integer.TYPE)) {
return Integer.class;
}
if (unboxed.equals(Long.TYPE)) {
return Long.class;
}
if (unboxed.equals(Short.TYPE)) {
return Short.class;
}
if (unboxed.equals(Void.TYPE)) {
return Void.class;
}
return unboxed;
}
/**
* Auto-box/Auto-unbox primitives to object and vice versa.
*
* @param in the input type
* @param out the expected output type
* @param visitor
*/
private static void autoboxIfNeeded(Class<?> in, Class<?> out, MethodVisitor visitor) {
if (in.equals(Boolean.class) && out.equals(Boolean.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
}
if (in.equals(Boolean.TYPE) && out.equals(Boolean.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
}
if (in.equals(Byte.class) && out.equals(Byte.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
}
if (in.equals(Byte.TYPE) && out.equals(Byte.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
}
if (in.equals(Character.class) && out.equals(Character.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
}
if (in.equals(Character.TYPE) && out.equals(Character.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
}
if (in.equals(Double.class) && out.equals(Double.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
}
if (in.equals(Double.TYPE) && out.equals(Double.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
}
if (in.equals(Float.class) && out.equals(Float.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
}
if (in.equals(Float.TYPE) && out.equals(Float.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
}
if (in.equals(Integer.class) && out.equals(Integer.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
}
if (in.equals(Integer.TYPE) && out.equals(Integer.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
}
if (in.equals(Long.class) && out.equals(Long.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
}
if (in.equals(Long.TYPE) && out.equals(Long.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
}
if (in.equals(Short.class) && out.equals(Short.TYPE)) {
visitor.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
}
if (in.equals(Short.TYPE) && out.equals(Short.class)) {
visitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
}
}
/**
* Returns the signature type for a {@link Class} including primitives.
*
* @param type must not be {@literal null}.
* @return
*/
private static String signatureTypeName(Class<?> type) {
if (type.equals(Boolean.TYPE)) {
return "Z";
}
if (type.equals(Byte.TYPE)) {
return "B";
}
if (type.equals(Character.TYPE)) {
return "C";
}
if (type.equals(Double.TYPE)) {
return "D";
}
if (type.equals(Float.TYPE)) {
return "F";
}
if (type.equals(Integer.TYPE)) {
return "I";
}
if (type.equals(Long.TYPE)) {
return "J";
}
if (type.equals(Short.TYPE)) {
return "S";
}
if (type.equals(Void.TYPE)) {
return "V";
}
return referenceName(type);
}
/**
* Stack map address for a particular property.
*
* @author Mark Paluch
*/
@RequiredArgsConstructor
static class PropertyStackAddress implements Comparable<PropertyStackAddress> {
private final @NonNull Label label;
private final int hash;
/*
* (non-Javadoc)
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(PropertyStackAddress o) {
return (hash < o.hash) ? -1 : ((hash == o.hash) ? 0 : 1);
}
}
/**
* Yep, the name tells the truth. This little guy registers a class in the class loader of the
* {@link PersistentEntity} to allow protected and package-default access as protected/package-default members must be
* accessed from a class in the same class loader.
*
* @author Mark Paluch
*/
@UtilityClass
private static class Evil {
/**
* Define a Class in the {@link ClassLoader} of the {@link PersistentEntity} type.
*
* @param name
* @param bytes
* @param offset
* @param len
* @param persistentEntity
* @return
*/
@SuppressWarnings("rawtypes")
Class<?> defineClass(String name, byte[] bytes, int offset, int len, PersistentEntity<?, ?> persistentEntity) {
ClassLoader classLoader = persistentEntity.getType().getClassLoader();
Class<?> classLoaderClass = classLoader.getClass();
try {
Class<? extends PersistentEntity> persistentEntityClass = persistentEntity.getClass();
Method defineClass = ReflectionUtils.findMethod(classLoaderClass, "defineClass", String.class, byte[].class,
Integer.TYPE, Integer.TYPE, ProtectionDomain.class);
defineClass.setAccessible(true);
return (Class<?>) defineClass.invoke(classLoader, name, bytes, offset, len,
persistentEntityClass.getProtectionDomain());
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
}
}