Java tutorial
/******************************************************************************* * Copyright 2017 Albert Shun-Dat Chan * * 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.github.javalbert.reflection; import static java.util.Comparator.*; import static java.util.stream.Collectors.*; import static org.objectweb.asm.Opcodes.*; import java.beans.BeanInfo; import java.beans.IntrospectionException; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.WeakHashMap; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.text.WordUtils; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Type; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.javalbert.bytecode.utils.AsmUtils; public final class ClassAccessFactory<T> { @SuppressWarnings("rawtypes") private static final Map<Class, ClassAccess> CLASS_ACCESS_MAP = new WeakHashMap<>(); private static final Logger LOGGER = LoggerFactory.getLogger(ClassAccessFactory.class); private static final int MAX_METHOD_ACCESS_PARAMETER_COUNT = 22; private static final String MEMBER_TYPE_FIELD = "field"; private static final String MEMBER_TYPE_PROPERTY = "property"; public static <T> ClassAccess<T> get(Class<T> clazz) { if (ClassAccess.class.isAssignableFrom(clazz)) { throw new IllegalArgumentException("should not get class access recursively"); } try { return getInstance(clazz); } catch (Exception e) { LOGGER.error("Error occurred while trying to get ClassAccess for " + clazz, e); throw new RuntimeException(e); } } private static Class<?> createClassAccessClass(Class<?> clazz) throws ClassNotFoundException { String className = getClassNameOfClassAccessFor(clazz); try { return AccessClassLoader.get(clazz).loadClass(className); } catch (ClassNotFoundException ignored) { } new ClassAccessFactory<>(clazz).buildClassAccessClass(); return AccessClassLoader.get(clazz).loadClass(className); } private static String getClassNameOfClassAccessFor(Class<?> clazz) { return clazz.getName() + "$" + clazz.getSimpleName() + "ClassAccess"; // com.github.javalbert.reflection.test.Foo$FooClassAccess } @SuppressWarnings("unchecked") private static <T> ClassAccess<T> getInstance(Class<T> clazz) throws ClassNotFoundException, InstantiationException, IllegalAccessException { ClassAccess<T> classAccess = CLASS_ACCESS_MAP.get(clazz); if (classAccess == null) { synchronized (CLASS_ACCESS_MAP) { classAccess = CLASS_ACCESS_MAP.get(clazz); if (classAccess == null) { Class<?> classAccessClass = createClassAccessClass(clazz); classAccess = (ClassAccess<T>) classAccessClass.newInstance(); CLASS_ACCESS_MAP.put(clazz, classAccess); } } } return classAccess; } private static Label[] getTableSwitchLabelsForAccess(Label defaultCaseLabel, List<? extends MemberInfo> members) { return AsmUtils.getTableSwitchLabels(defaultCaseLabel, members.stream().mapToInt(m -> m.memberIndex).toArray()); } private static boolean isDescriptorDoubleOrLong(String descriptor) { return descriptor.equals(Type.DOUBLE_TYPE.getDescriptor()) || descriptor.equals(Type.LONG_TYPE.getDescriptor()); } private static Label[] newLabelArray(int size) { Label[] labels = new Label[size]; for (int i = 0; i < size; i++) { labels[i] = new Label(); } return labels; } private static void setAccessible(AccessibleObject object) { object.setAccessible(true); } private static boolean useTableSwitch(List<? extends MemberInfo> members) { return AsmUtils.useTableSwitch(members.stream().mapToInt(m -> m.memberIndex).toArray()); } private static class AccessInfo { private static AccessInfo forPrimitive(String memberType, Type type) { String camelCaseClassName = WordUtils.capitalize(type.getClassName()); String capitalizedMemberType = WordUtils.capitalize(memberType); return new AccessInfo(memberType, "get" + camelCaseClassName + capitalizedMemberType, "set" + camelCaseClassName + capitalizedMemberType, type.getClassName(), type.getDescriptor(), type.getOpcode(ILOAD), type.getOpcode(IRETURN)); } private static AccessInfo forPrimitiveWrapper(String memberType, Class<?> clazz) { Class<?> primitiveClass = ClassUtils.wrapperToPrimitive(clazz); String camelCaseClassName = WordUtils.capitalize(primitiveClass.getName()); String capitalizedMemberType = WordUtils.capitalize(memberType); return new AccessInfo(memberType, "getBoxed" + camelCaseClassName + capitalizedMemberType, "setBoxed" + camelCaseClassName + capitalizedMemberType, clazz.getName(), Type.getDescriptor(clazz), ALOAD, ARETURN); } private static AccessInfo forReferenceType(String memberType, Class<?> clazz) { String capitalizedMemberType = WordUtils.capitalize(memberType); return new AccessInfo(memberType, "get" + clazz.getSimpleName() + capitalizedMemberType, "set" + clazz.getSimpleName() + capitalizedMemberType, clazz.getName(), Type.getDescriptor(clazz), ALOAD, ARETURN); } private final String className; private final String descriptor; private final String getMethodName; private final int loadOpcode; private final String memberType; private final int returnOpcode; private final String setMethodName; private AccessInfo(String memberType, String getMethodName, String setMethodName, String className, String descriptor, int loadOpcode, int returnOpcode) { this.className = className; this.descriptor = descriptor; this.getMethodName = getMethodName; this.loadOpcode = loadOpcode; this.memberType = memberType; this.returnOpcode = returnOpcode; this.setMethodName = setMethodName; } } private static abstract class AssignableInfo extends MemberInfo implements Castable { protected final String internalName; protected final Class<?> type; @Override public String getDescriptor() { return descriptor; } @Override public String getInternalName() { return internalName; } @SuppressWarnings("rawtypes") @Override public Class getType() { return type; } private AssignableInfo(String name, int memberIndex, Class<?> type) { super(name, memberIndex, Type.getDescriptor(type)); this.internalName = Type.getInternalName(type); this.type = type; } } private static interface Castable { String getDescriptor(); String getInternalName(); @SuppressWarnings("rawtypes") Class getType(); } private static class FieldInfo extends AssignableInfo { private final int getFieldOpcode; private final int setFieldOpcode; private FieldInfo(Field field, int fieldIndex) { super(field.getName(), fieldIndex, field.getType()); getFieldOpcode = Modifier.isStatic(field.getModifiers()) ? GETSTATIC : GETFIELD; setFieldOpcode = Modifier.isStatic(field.getModifiers()) ? PUTSTATIC : PUTFIELD; } } private static abstract class MemberInfo { protected final String descriptor; protected final int memberIndex; protected final String name; private MemberInfo(String name, int memberIndex, String descriptor) { this.descriptor = descriptor; this.name = name; this.memberIndex = memberIndex; } } private static class MethodInfo extends MemberInfo { private final int invokeOpcode; private final Method method; private final int parameterCount; private final List<ParameterInfo> parameters; private MethodInfo(Method method, int index) { super(method.getName(), index, Type.getMethodDescriptor(method)); invokeOpcode = method.getDeclaringClass().isInterface() ? INVOKEINTERFACE : Modifier.isStatic(method.getModifiers()) ? INVOKESTATIC : INVOKEVIRTUAL; this.method = method; parameterCount = method.getParameterCount(); parameters = Collections.unmodifiableList( Arrays.stream(method.getParameterTypes()).map(ParameterInfo::new).collect(toList())); } } private static class MethodNameReturnIndex extends StringCaseReturnIndex { private final List<MethodInfo> methods; private MethodNameReturnIndex(List<MethodInfo> methods) { super(methods.get(0).name, methods.get(0).memberIndex); this.methods = methods; } } private static class ParameterInfo implements Castable { private final String descriptor; private final String internalName; private final Class<?> type; @Override public String getDescriptor() { return descriptor; } @Override public String getInternalName() { return internalName; } @SuppressWarnings("rawtypes") @Override public Class getType() { return type; } private ParameterInfo(Class<?> type) { descriptor = Type.getDescriptor(type); internalName = Type.getInternalName(type); this.type = type; } } private static class PropertyInfo extends AssignableInfo { private final String readMethodName; private final String writeMethodName; private PropertyInfo(PropertyDescriptor propertyDescriptor, int propertyIndex) { super(propertyDescriptor.getName(), propertyIndex, propertyDescriptor.getPropertyType()); readMethodName = Optional.ofNullable(propertyDescriptor.getReadMethod()).map(prop -> prop.getName()) .orElse(null); writeMethodName = Optional.ofNullable(propertyDescriptor.getWriteMethod()).map(prop -> prop.getName()) .orElse(null); } } private static class StringCaseReturnIndex { private static int compareIndex(StringCaseReturnIndex a, StringCaseReturnIndex b) { return Integer.compare(a.index, b.index); } private static int compareHashCode(StringCaseReturnIndex a, StringCaseReturnIndex b) { return Integer.compare(a.hashCode, b.hashCode); } protected final int hashCode; protected final int index; protected final String name; protected final Label returnIndexLabel = new Label(); protected StringCaseReturnIndex(String name, int index) { hashCode = name.hashCode(); this.index = index; this.name = name; } } private final List<PropertyInfo> accessorInfoList = new ArrayList<>(); private String classAccessInternalName; private String classAccessTypeDescriptor; private String classTypeDescriptor; private final Class<T> clazz; private final ClassWriter cw; private final List<FieldInfo> fieldInfoList = new ArrayList<>(); private String internalName; private final List<MethodInfo> methodInfoList = new ArrayList<>(); private final List<PropertyInfo> mutatorInfoList = new ArrayList<>(); private MethodVisitor mv; private final Map<Integer, List<MethodInfo>> paramCountMethodsMap = new HashMap<>(); private final List<PropertyInfo> propertyInfoList = new ArrayList<>(); private final Map<String, List<PropertyInfo>> typeToAccessorsMap = new HashMap<>(); private final Map<String, List<FieldInfo>> typeToFieldsMap = new HashMap<>(); private final Map<String, List<PropertyInfo>> typeToMutatorsMap = new HashMap<>(); private ClassAccessFactory(Class<T> clazz) { this.clazz = clazz; initializePropertyDescriptors(); initializeFields(); initializeMethods(); cw = new ClassWriter(0); } void buildClassAccessClass() { visitClass(); AsmUtils.visitDefaultConstructor(cw, classAccessTypeDescriptor); visitIndexMethod(MEMBER_TYPE_FIELD, getMemberIndexSwitchCases(fieldInfoList)); visitFieldAccessMethods(); visitIndexMethod(MEMBER_TYPE_PROPERTY, getMemberIndexSwitchCases(propertyInfoList)); visitPropertyAccessMethods(); visitMethodIndexMethod(); visitMethodAccessMethods(); cw.visitEnd(); AccessClassLoader.get(clazz).defineClass(getClassNameOfClassAccessFor(clazz), cw.toByteArray()); } private void addFieldInfo(FieldInfo fieldInfo) { String key = fieldInfo.type.getName(); List<FieldInfo> fieldsOfType = typeToFieldsMap.get(key); if (fieldsOfType == null) { fieldsOfType = new ArrayList<>(); typeToFieldsMap.put(key, fieldsOfType); } fieldsOfType.add(fieldInfo); fieldInfoList.add(fieldInfo); } private void addMethodInfo(MethodInfo methodInfo) { List<MethodInfo> methodsWithParamCount = paramCountMethodsMap.get(methodInfo.parameterCount); if (methodsWithParamCount == null) { methodsWithParamCount = new ArrayList<>(); paramCountMethodsMap.put(methodInfo.parameterCount, methodsWithParamCount); } methodsWithParamCount.add(methodInfo); methodInfoList.add(methodInfo); } private void addPropertyInfo(PropertyInfo propertyInfo) { String key = propertyInfo.type.getName(); if (propertyInfo.readMethodName != null) { List<PropertyInfo> accessorsOfType = typeToAccessorsMap.get(key); if (accessorsOfType == null) { accessorsOfType = new ArrayList<>(); typeToAccessorsMap.put(key, accessorsOfType); } accessorsOfType.add(propertyInfo); accessorInfoList.add(propertyInfo); } if (propertyInfo.writeMethodName != null) { List<PropertyInfo> mutatorsOfType = typeToMutatorsMap.get(key); if (mutatorsOfType == null) { mutatorsOfType = new ArrayList<>(); typeToMutatorsMap.put(key, mutatorsOfType); } mutatorsOfType.add(propertyInfo); mutatorInfoList.add(propertyInfo); } propertyInfoList.add(propertyInfo); } private void checkCast(Castable castable) { String internalNameOfCast = castable.getInternalName(); if (castable.getType().isPrimitive()) { Class<?> wrapperType = ClassUtils.primitiveToWrapper(castable.getType()); internalNameOfCast = Type.getInternalName(wrapperType); } mv.visitTypeInsn(CHECKCAST, internalNameOfCast); if (castable.getType().isPrimitive()) { mv.visitMethodInsn(INVOKEVIRTUAL, internalNameOfCast, castable.getType().getName() + "Value", "()" + castable.getDescriptor(), false); } } private List<StringCaseReturnIndex> getMemberIndexSwitchCases(List<? extends MemberInfo> memberInfoList) { List<StringCaseReturnIndex> memberIndexSwitchCases = new ArrayList<>(); for (int i = 0; i < memberInfoList.size(); i++) { memberIndexSwitchCases.add(new StringCaseReturnIndex(memberInfoList.get(i).name, i)); } return memberIndexSwitchCases; } private String getMethodAccessMethodDescriptor(int parameterCount) { return getMethodAccessMethodDescriptor(parameterCount, classTypeDescriptor); } private String getMethodAccessMethodDescriptor(int parameterCount, String typeDescriptor) { StringBuilder desc = new StringBuilder("(" + typeDescriptor + "I"); for (int i = 0; i < parameterCount; i++) { desc.append("Ljava/lang/Object;"); } return desc.append(")Ljava/lang/Object;").toString(); } private List<MethodNameReturnIndex> getMethodIndexSwitchCases() { List<MethodNameReturnIndex> methodIndices = new ArrayList<>(); Map<String, List<MethodInfo>> overloadedMap = new HashMap<>(); for (int i = 0; i < methodInfoList.size(); i++) { MethodInfo methodInfo = methodInfoList.get(i); List<MethodInfo> overloadedMethods = overloadedMap.get(methodInfo.name); if (overloadedMethods == null) { overloadedMethods = new ArrayList<>(); overloadedMap.put(methodInfo.name, overloadedMethods); overloadedMethods.add(methodInfo); methodIndices.add(new MethodNameReturnIndex(overloadedMethods)); } else { overloadedMethods.add(methodInfo); } } return methodIndices; } private void initializeFields() { List<Field> fields = Arrays.stream(clazz.getDeclaredFields()).sorted(comparing(Field::getName)) .collect(toList()); for (int i = 0; i < fields.size(); i++) { Field field = fields.get(i); setAccessible(field); addFieldInfo(new FieldInfo(field, i)); } } private void initializeMethods() { List<Method> methods = Arrays.stream(clazz.getDeclaredMethods()).sorted((a, b) -> { int compareMethodName = a.getName().compareTo(b.getName()); if (compareMethodName != 0) { return compareMethodName; } Class<?>[] aparams = a.getParameterTypes(); Class<?>[] bparams = b.getParameterTypes(); int len = Math.min(aparams.length, bparams.length); for (int i = 0; i < len; i++) { int compareParamType = aparams[i].getName().compareTo(bparams[i].getName()); if (compareParamType != 0) { return compareParamType; } } return Integer.compare(aparams.length, bparams.length); }).collect(toList()); for (int i = 0; i < methods.size(); i++) { Method method = methods.get(i); setAccessible(method); addMethodInfo(new MethodInfo(method, i)); } } private void initializePropertyDescriptors() { @SuppressWarnings("unchecked") List<PropertyDescriptor> propertyDescriptors = Collections.EMPTY_LIST; try { BeanInfo info = Introspector.getBeanInfo(clazz); propertyDescriptors = Collections.unmodifiableList(Arrays.stream(info.getPropertyDescriptors()) .filter(prop -> !prop.getName().equals("class")).collect(toList())); } catch (IntrospectionException e) { throw new RuntimeException(e); } for (int i = 0; i < propertyDescriptors.size(); i++) { addPropertyInfo(new PropertyInfo(propertyDescriptors.get(i), i)); } } private void visitAccessGetter(List<? extends AssignableInfo> memberInfoList, AccessInfo accessInfo) { mv = cw.visitMethod(ACC_PUBLIC, accessInfo.getMethodName, "(" + classTypeDescriptor + "I)" + accessInfo.descriptor, null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (memberInfoList == null || memberInfoList.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitAccessGetterLastPart(accessInfo.memberType, firstLabel); return; } boolean useTableSwitch = useTableSwitch(memberInfoList); Label[] labels = useTableSwitch ? getTableSwitchLabelsForAccess(defaultCaseLabel, memberInfoList) : newLabelArray(memberInfoList.size()); if (useTableSwitch) { mv.visitTableSwitchInsn(memberInfoList.get(0).memberIndex, memberInfoList.get(memberInfoList.size() - 1).memberIndex, defaultCaseLabel, labels); } else { mv.visitLookupSwitchInsn(defaultCaseLabel, memberInfoList.stream().mapToInt(f -> f.memberIndex).toArray(), labels); } for (int i = 0; i < memberInfoList.size(); i++) { MemberInfo memberInfo = memberInfoList.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); switch (accessInfo.memberType) { case MEMBER_TYPE_FIELD: mv.visitFieldInsn(((FieldInfo) memberInfo).getFieldOpcode, internalName, memberInfo.name, accessInfo.descriptor); break; case MEMBER_TYPE_PROPERTY: mv.visitMethodInsn(INVOKEVIRTUAL, internalName, ((PropertyInfo) memberInfo).readMethodName, "()" + accessInfo.descriptor, false); break; } mv.visitInsn(accessInfo.returnOpcode); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitAccessGetterLastPart(accessInfo.memberType, firstLabel); } private void visitAccessGetterBridge(AccessInfo accessInfo) { visitAccessGetterBridge(accessInfo.getMethodName, accessInfo.descriptor, accessInfo.returnOpcode); } private void visitAccessGetterBridge(String methodName, String returnTypeDescriptor, int returnOpcode) { mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, methodName, "(Ljava/lang/Object;I)" + returnTypeDescriptor, null, null); mv.visitCode(); mv.visitLabel(new Label()); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, internalName); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, classAccessInternalName, methodName, "(" + classTypeDescriptor + "I)" + returnTypeDescriptor, false); mv.visitInsn(returnOpcode); mv.visitMaxs(3, 3); mv.visitEnd(); } private void visitAccessGetterLastPart(String memberType, Label firstLabel) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No " + memberType + " with index: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("obj", classTypeDescriptor, null, firstLabel, lastLabel, 1); mv.visitLocalVariable(memberType + "Index", "I", null, firstLabel, lastLabel, 2); mv.visitMaxs(5, 3); mv.visitEnd(); } private void visitAccessSetter(List<? extends AssignableInfo> memberInfoList, AccessInfo accessInfo) { mv = cw.visitMethod(ACC_PUBLIC, accessInfo.setMethodName, "(" + classTypeDescriptor + "I" + accessInfo.descriptor + ")V", null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (memberInfoList == null || memberInfoList.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitAccessSetterLastPart(accessInfo.memberType, accessInfo.descriptor, firstLabel, null); return; } boolean useTableSwitch = useTableSwitch(memberInfoList); Label[] labels = useTableSwitch ? getTableSwitchLabelsForAccess(defaultCaseLabel, memberInfoList) : newLabelArray(memberInfoList.size()); if (useTableSwitch) { mv.visitTableSwitchInsn(memberInfoList.get(0).memberIndex, memberInfoList.get(memberInfoList.size() - 1).memberIndex, defaultCaseLabel, labels); } else { mv.visitLookupSwitchInsn(defaultCaseLabel, memberInfoList.stream().mapToInt(f -> f.memberIndex).toArray(), labels); } Label breakLabel = new Label(); for (int i = 0; i < memberInfoList.size(); i++) { MemberInfo memberInfo = memberInfoList.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(accessInfo.loadOpcode, 3); switch (accessInfo.memberType) { case MEMBER_TYPE_FIELD: mv.visitFieldInsn(((FieldInfo) memberInfo).setFieldOpcode, internalName, memberInfo.name, accessInfo.descriptor); break; case MEMBER_TYPE_PROPERTY: mv.visitMethodInsn(INVOKEVIRTUAL, internalName, ((PropertyInfo) memberInfo).writeMethodName, "(" + accessInfo.descriptor + ")V", false); break; } mv.visitLabel(new Label()); mv.visitJumpInsn(GOTO, breakLabel); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitAccessSetterLastPart(accessInfo.memberType, accessInfo.descriptor, firstLabel, breakLabel); } private void visitAccessSetterBridge(AccessInfo accessInfo) { visitAccessSetterBridge(accessInfo.setMethodName, accessInfo.loadOpcode, accessInfo.descriptor); } private void visitAccessSetterBridge(String methodName, int loadOpcode, String descriptor) { mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, methodName, "(Ljava/lang/Object;I" + descriptor + ")V", null, null); mv.visitCode(); mv.visitLabel(new Label()); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, internalName); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(loadOpcode, 3); mv.visitMethodInsn(INVOKEVIRTUAL, classAccessInternalName, methodName, "(" + classTypeDescriptor + "I" + descriptor + ")V", false); mv.visitInsn(RETURN); if (isDescriptorDoubleOrLong(descriptor)) { mv.visitMaxs(5, 5); } else { mv.visitMaxs(4, 4); } mv.visitEnd(); } private void visitAccessSetterLastPart(String memberType, String fieldDescriptor, Label firstLabel, Label breakLabel) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No " + memberType + " with index: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); if (breakLabel != null) { mv.visitLabel(breakLabel); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitInsn(RETURN); } Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("obj", classTypeDescriptor, null, firstLabel, lastLabel, 1); mv.visitLocalVariable(memberType + "Index", "I", null, firstLabel, lastLabel, 2); mv.visitLocalVariable("x", fieldDescriptor, null, firstLabel, lastLabel, 3); mv.visitMaxs(5, isDescriptorDoubleOrLong(fieldDescriptor) ? 5 : 4); mv.visitEnd(); } private void visitClass() { internalName = Type.getInternalName(clazz); // com/github/javalbert/reflection/test/Foo String classAccessSimpleName = clazz.getSimpleName() + "ClassAccess"; // FooClassAccess classAccessInternalName = internalName + "$" + classAccessSimpleName; // com/github/javalbert/reflection/test/Foo$FooClassAccess cw.visit(V1_8, ACC_PUBLIC + ACC_SUPER, classAccessInternalName, internalName, "sun/reflect/MagicAccessorImpl"/*CREDIT: https://github.com/dimzon/reflectasm/blob/master/src/com/esotericsoftware/reflectasm/ClassAccess.java*/, new String[] { Type.getInternalName(ClassAccess.class) }); cw.visitSource(clazz.getSimpleName() + ".java", null); cw.visitInnerClass(classAccessInternalName, internalName, classAccessSimpleName, ACC_PUBLIC + ACC_STATIC); // For the lamda function Class::getName in methodIndex() method // Appears not to be required // cw.visitInnerClass( // "java/lang/invoke/MethodHandles$Lookup", // "java/lang/invoke/MethodHandles", // "Lookup", // ACC_PUBLIC + ACC_FINAL + ACC_STATIC); classAccessTypeDescriptor = "L" + classAccessInternalName + ";"; classTypeDescriptor = "L" + internalName + ";"; } private void visitFieldAccessMethods() { List<AccessInfo> fieldAccessInfoList = Collections.unmodifiableList(Arrays.asList( // Primitive types // AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.BOOLEAN_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.BYTE_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.CHAR_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.DOUBLE_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.FLOAT_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.INT_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.LONG_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_FIELD, Type.SHORT_TYPE), // Primitive wrapper types // AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Boolean.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Byte.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Character.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Double.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Float.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Integer.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Long.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_FIELD, Short.class), // Common reference types // AccessInfo.forReferenceType(MEMBER_TYPE_FIELD, BigDecimal.class), AccessInfo.forReferenceType(MEMBER_TYPE_FIELD, Date.class), AccessInfo.forReferenceType(MEMBER_TYPE_FIELD, LocalDate.class), AccessInfo.forReferenceType(MEMBER_TYPE_FIELD, LocalDateTime.class), AccessInfo.forReferenceType(MEMBER_TYPE_FIELD, String.class))); for (int i = 0; i < fieldAccessInfoList.size(); i++) { AccessInfo fieldAccessInfo = fieldAccessInfoList.get(i); List<FieldInfo> fieldInfoList = typeToFieldsMap.get(fieldAccessInfo.className); visitAccessGetter(fieldInfoList, fieldAccessInfo); visitAccessGetterBridge(fieldAccessInfo); visitAccessSetter(fieldInfoList, fieldAccessInfo); visitAccessSetterBridge(fieldAccessInfo); } visitGeneralAccessGetter("getField", MEMBER_TYPE_FIELD, fieldInfoList); visitAccessGetterBridge("getField", "Ljava/lang/Object;", ARETURN); visitGeneralAccessSetter("setField", MEMBER_TYPE_FIELD, fieldInfoList); visitAccessSetterBridge("setField", ALOAD, "Ljava/lang/Object;"); } private void visitGeneralAccessGetter(String methodName, String memberType, List<? extends AssignableInfo> memberInfoList) { mv = cw.visitMethod(ACC_PUBLIC, methodName, "(" + classTypeDescriptor + "I)Ljava/lang/Object;", null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (memberInfoList == null || memberInfoList.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitAccessGetterLastPart(memberType, firstLabel); return; } Label[] labels = getTableSwitchLabelsForAccess(defaultCaseLabel, memberInfoList); // Always use a table switch because there are no gaps between member indices mv.visitTableSwitchInsn(memberInfoList.get(0).memberIndex, memberInfoList.get(memberInfoList.size() - 1).memberIndex, defaultCaseLabel, labels); for (int i = 0; i < memberInfoList.size(); i++) { AssignableInfo member = memberInfoList.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); switch (memberType) { case MEMBER_TYPE_FIELD: mv.visitFieldInsn(((FieldInfo) member).getFieldOpcode, internalName, member.name, member.descriptor); break; case MEMBER_TYPE_PROPERTY: mv.visitMethodInsn(INVOKEVIRTUAL, internalName, ((PropertyInfo) member).readMethodName, "()" + member.descriptor, false); break; } if (member.type.isPrimitive()) { Class<?> wrapperType = ClassUtils.primitiveToWrapper(member.type); mv.visitMethodInsn(INVOKESTATIC, Type.getInternalName(wrapperType), "valueOf", "(" + member.descriptor + ")" + Type.getDescriptor(wrapperType), false); } mv.visitInsn(ARETURN); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitAccessGetterLastPart(memberType, firstLabel); } private void visitGeneralAccessSetter(String methodName, String memberType, List<? extends AssignableInfo> memberInfoList) { mv = cw.visitMethod(ACC_PUBLIC, methodName, "(" + classTypeDescriptor + "ILjava/lang/Object;)V", null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (memberInfoList == null || memberInfoList.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitAccessSetterLastPart(memberType, "Ljava/lang/Object;", firstLabel, null); return; } Label[] labels = getTableSwitchLabelsForAccess(defaultCaseLabel, memberInfoList); // Always use a table switch because there are no gaps between member indices mv.visitTableSwitchInsn(memberInfoList.get(0).memberIndex, memberInfoList.get(memberInfoList.size() - 1).memberIndex, defaultCaseLabel, labels); Label breakLabel = new Label(); for (int i = 0; i < memberInfoList.size(); i++) { AssignableInfo member = memberInfoList.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); mv.visitVarInsn(ALOAD, 3); checkCast(member); switch (memberType) { case MEMBER_TYPE_FIELD: mv.visitFieldInsn(((FieldInfo) member).setFieldOpcode, internalName, member.name, member.descriptor); break; case MEMBER_TYPE_PROPERTY: mv.visitMethodInsn(INVOKEVIRTUAL, internalName, ((PropertyInfo) member).writeMethodName, "(" + member.descriptor + ")V", false); break; } mv.visitLabel(new Label()); mv.visitJumpInsn(GOTO, breakLabel); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitAccessSetterLastPart(memberType, "Ljava/lang/Object;", firstLabel, breakLabel); } private void visitIndexMethod(String categoryOfStringCase, List<StringCaseReturnIndex> stringCaseReturnIndices) { mv = cw.visitMethod(ACC_PUBLIC, categoryOfStringCase + "Index", "(Ljava/lang/String;)I", null, null); mv.visitCode(); final Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ALOAD, 1); mv.visitInsn(DUP); mv.visitVarInsn(ASTORE, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); final Label defaultCaseLabel = new Label(); if (stringCaseReturnIndices.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitIndexMethodLastPart(categoryOfStringCase, firstLabel); return; } Collections.sort(stringCaseReturnIndices, StringCaseReturnIndex::compareHashCode); int[] stringHashCodes = new int[stringCaseReturnIndices.size()]; Label[] caseLabels = new Label[stringCaseReturnIndices.size()]; for (int i = 0; i < stringCaseReturnIndices.size(); i++) { stringHashCodes[i] = stringCaseReturnIndices.get(i).hashCode; caseLabels[i] = new Label(); } mv.visitLookupSwitchInsn(defaultCaseLabel, stringHashCodes, caseLabels); for (int i = 0; i < stringCaseReturnIndices.size(); i++) { StringCaseReturnIndex stringCaseReturnIndex = stringCaseReturnIndices.get(i); mv.visitLabel(caseLabels[i]); if (i > 0) { mv.visitFrame(F_SAME, 0, null, 0, null); } else { mv.visitFrame(F_APPEND, 1, new Object[] { "java/lang/String" }, 0, null); } mv.visitVarInsn(ALOAD, 2); mv.visitLdcInsn(stringCaseReturnIndex.name); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); mv.visitJumpInsn(IFNE, stringCaseReturnIndex.returnIndexLabel); mv.visitJumpInsn(GOTO, defaultCaseLabel); } Collections.sort(stringCaseReturnIndices, StringCaseReturnIndex::compareIndex); for (int i = 0; i < stringCaseReturnIndices.size(); i++) { StringCaseReturnIndex stringCaseReturnIndex = stringCaseReturnIndices.get(i); mv.visitLabel(stringCaseReturnIndex.returnIndexLabel); mv.visitFrame(F_SAME, 0, null, 0, null); AsmUtils.visitZeroOperandInt(mv, stringCaseReturnIndex.index); mv.visitInsn(IRETURN); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitIndexMethodLastPart(categoryOfStringCase, firstLabel); } private void visitIndexMethodLastPart(String categoryOfStringCase, Label firstLabel) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No " + categoryOfStringCase + " with name: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("name", "Ljava/lang/String;", null, firstLabel, lastLabel, 1); mv.visitMaxs(5, 3); mv.visitEnd(); } private void visitMethodAccessBridge(int parameterCount) { mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "call", getMethodAccessMethodDescriptor(parameterCount, "Ljava/lang/Object;"), null, null); mv.visitCode(); mv.visitLabel(new Label()); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, internalName); mv.visitVarInsn(ILOAD, 2); for (int i = 0; i < parameterCount; i++) { mv.visitVarInsn(ALOAD, 3 + i); } mv.visitMethodInsn(INVOKEVIRTUAL, classAccessInternalName, "call", getMethodAccessMethodDescriptor(parameterCount), false); mv.visitInsn(ARETURN); mv.visitMaxs(3 + parameterCount, 3 + parameterCount); mv.visitEnd(); } private void visitMethodAccessInvokeBridge() { mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_VARARGS + ACC_SYNTHETIC, "invoke", "(Ljava/lang/Object;I[Ljava/lang/Object;)Ljava/lang/Object;", null, null); mv.visitCode(); mv.visitLabel(new Label()); mv.visitVarInsn(ALOAD, 0); mv.visitVarInsn(ALOAD, 1); mv.visitTypeInsn(CHECKCAST, internalName); mv.visitVarInsn(ILOAD, 2); mv.visitVarInsn(ALOAD, 3); mv.visitMethodInsn(INVOKEVIRTUAL, classAccessInternalName, "invoke", "(" + classTypeDescriptor + "I[Ljava/lang/Object;)Ljava/lang/Object;", false); mv.visitInsn(ARETURN); mv.visitMaxs(4, 4); mv.visitEnd(); } private void visitMethodAccessInvokeMethod() { mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "invoke", "(" + classTypeDescriptor + "I[Ljava/lang/Object;)Ljava/lang/Object;", null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (methodInfoList == null || methodInfoList.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitMethodAccessInvokeMethodLastPart(firstLabel); return; } Label[] labels = getTableSwitchLabelsForAccess(defaultCaseLabel, methodInfoList); // Always use a table switch because there are no gaps between method indices mv.visitTableSwitchInsn(methodInfoList.get(0).memberIndex, methodInfoList.get(methodInfoList.size() - 1).memberIndex, defaultCaseLabel, labels); for (int i = 0; i < methodInfoList.size(); i++) { MethodInfo methodInfo = methodInfoList.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); List<ParameterInfo> parameters = methodInfo.parameters; for (int j = 0; j < parameters.size(); j++) { ParameterInfo parameter = parameters.get(j); mv.visitVarInsn(ALOAD, 3); AsmUtils.visitZeroOperandInt(mv, j); mv.visitInsn(AALOAD); checkCast(parameter); } mv.visitMethodInsn(methodInfo.invokeOpcode, internalName, methodInfo.name, methodInfo.descriptor, true); if (methodInfo.method.getReturnType() == Void.TYPE) { mv.visitLabel(new Label()); mv.visitInsn(ACONST_NULL); } mv.visitInsn(ARETURN); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitMethodAccessInvokeMethodLastPart(firstLabel); } private void visitMethodAccessInvokeMethodLastPart(Label firstLabel) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No method with index: "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("obj", classTypeDescriptor, null, firstLabel, lastLabel, 1); mv.visitLocalVariable("methodIndex", "I", null, firstLabel, lastLabel, 2); mv.visitLocalVariable("args", "[Ljava/lang/Object;", null, firstLabel, lastLabel, 3); mv.visitMaxs(5, 4); mv.visitEnd(); } private void visitMethodAccessLastPart(Label firstLabel, int parameterCount) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No method with index "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitLabel(new Label()); mv.visitVarInsn(ILOAD, 2); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(I)Ljava/lang/StringBuilder;", false); mv.visitLdcInsn(" with " + parameterCount + " parameter(s)"); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitLabel(new Label()); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("obj", classTypeDescriptor, null, firstLabel, lastLabel, 1); mv.visitLocalVariable("methodIndex", "I", null, firstLabel, lastLabel, 2); for (int i = 0; i < parameterCount; i++) { mv.visitLocalVariable("arg" + i, "Ljava/lang/Object;", null, firstLabel, lastLabel, 3 + i); } mv.visitMaxs(5, 3 + parameterCount); mv.visitEnd(); } private void visitMethodAccessMethod(int parameterCount) { List<MethodInfo> methodsWithParamCount = paramCountMethodsMap.get(parameterCount); mv = cw.visitMethod(ACC_PUBLIC, "call", getMethodAccessMethodDescriptor(parameterCount), null, null); mv.visitCode(); Label firstLabel = new Label(); mv.visitLabel(firstLabel); mv.visitVarInsn(ILOAD, 2); Label defaultCaseLabel = new Label(); if (methodsWithParamCount == null || methodsWithParamCount.isEmpty()) { mv.visitInsn(POP); mv.visitLabel(defaultCaseLabel); visitMethodAccessLastPart(firstLabel, parameterCount); return; } boolean useTableSwitch = useTableSwitch(methodsWithParamCount); Label[] labels = useTableSwitch ? getTableSwitchLabelsForAccess(defaultCaseLabel, methodsWithParamCount) : newLabelArray(methodsWithParamCount.size()); if (useTableSwitch) { mv.visitTableSwitchInsn(methodsWithParamCount.get(0).memberIndex, methodsWithParamCount.get(methodsWithParamCount.size() - 1).memberIndex, defaultCaseLabel, labels); } else { mv.visitLookupSwitchInsn(defaultCaseLabel, methodsWithParamCount.stream().mapToInt(f -> f.memberIndex).toArray(), labels); } for (int i = 0; i < methodsWithParamCount.size(); i++) { MethodInfo methodInfo = methodsWithParamCount.get(i); mv.visitLabel(labels[i]); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 1); List<ParameterInfo> parameters = methodInfo.parameters; for (int j = 0; j < parameters.size(); j++) { ParameterInfo parameter = parameters.get(j); mv.visitVarInsn(ALOAD, 3 + j); checkCast(parameter); } mv.visitMethodInsn(methodInfo.invokeOpcode, internalName, methodInfo.name, methodInfo.descriptor, true); if (methodInfo.method.getReturnType() == Void.TYPE) { mv.visitLabel(new Label()); mv.visitInsn(ACONST_NULL); } mv.visitInsn(ARETURN); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_SAME, 0, null, 0, null); visitMethodAccessLastPart(firstLabel, parameterCount); } private void visitMethodAccessMethods() { visitMethodAccessInvokeMethod(); visitMethodAccessInvokeBridge(); for (int i = 0; i <= MAX_METHOD_ACCESS_PARAMETER_COUNT; i++) { visitMethodAccessMethod(i); visitMethodAccessBridge(i); } } private void visitMethodIndexMethod() { mv = cw.visitMethod(ACC_PUBLIC + ACC_VARARGS, "methodIndex", "(Ljava/lang/String;[Ljava/lang/Class;)I", "(Ljava/lang/String;[Ljava/lang/Class<*>;)I", null); mv.visitCode(); final Label firstLabel = new Label(); mv.visitLabel(firstLabel); List<MethodNameReturnIndex> methodIndices = getMethodIndexSwitchCases(); if (methodIndices.isEmpty()) { visitMethodIndexMethodLastPart(firstLabel, methodIndices); return; } mv.visitVarInsn(ALOAD, 1); mv.visitInsn(DUP); mv.visitVarInsn(ASTORE, 3); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I", false); final Label defaultCaseLabel = new Label(); Collections.sort(methodIndices, StringCaseReturnIndex::compareHashCode); int[] methodNameHashCodes = new int[methodIndices.size()]; Label[] caseLabels = new Label[methodIndices.size()]; for (int i = 0; i < methodIndices.size(); i++) { methodNameHashCodes[i] = methodIndices.get(i).hashCode; caseLabels[i] = new Label(); } mv.visitLookupSwitchInsn(defaultCaseLabel, methodNameHashCodes, caseLabels); for (int i = 0; i < methodIndices.size(); i++) { MethodNameReturnIndex methodIndex = methodIndices.get(i); mv.visitLabel(caseLabels[i]); if (i > 0) { mv.visitFrame(F_SAME, 0, null, 0, null); } else { mv.visitFrame(F_APPEND, 1, new Object[] { "java/lang/String" }, 0, null); } mv.visitVarInsn(ALOAD, 3); mv.visitLdcInsn(methodIndex.name); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z", false); mv.visitJumpInsn(IFNE, methodIndex.returnIndexLabel); mv.visitJumpInsn(GOTO, defaultCaseLabel); } Collections.sort(methodIndices, StringCaseReturnIndex::compareIndex); for (int i = 0; i < methodIndices.size(); i++) { visitMethodIndexReturnStatements(methodIndices.get(i), defaultCaseLabel); } mv.visitLabel(defaultCaseLabel); mv.visitFrame(F_CHOP, 1, null, 0, null); visitMethodIndexMethodLastPart(firstLabel, methodIndices); } private void visitMethodIndexMethodLastPart(Label firstLabel, List<MethodNameReturnIndex> methodIndices) { mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException"); mv.visitInsn(DUP); mv.visitTypeInsn(NEW, "java/lang/StringBuilder"); mv.visitInsn(DUP); mv.visitLdcInsn("No method called "); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "(Ljava/lang/String;)V", false); mv.visitVarInsn(ALOAD, 1); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitLabel(new Label()); mv.visitLdcInsn(" with parameters "); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false); mv.visitVarInsn(ALOAD, 2); mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "stream", "([Ljava/lang/Object;)Ljava/util/stream/Stream;", false); mv.visitLabel(new Label()); mv.visitInvokeDynamicInsn("apply", "()Ljava/util/function/Function;", new Handle(H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;", false), new Object[] { Type.getType("(Ljava/lang/Object;)Ljava/lang/Object;"), new Handle(H_INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;", false), Type.getType("(Ljava/lang/Class;)Ljava/lang/String;") }); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/stream/Stream", "map", "(Ljava/util/function/Function;)Ljava/util/stream/Stream;", true); mv.visitLabel(new Label()); mv.visitMethodInsn(INVOKESTATIC, "java/util/stream/Collectors", "toList", "()Ljava/util/stream/Collector;", false); mv.visitMethodInsn(INVOKEINTERFACE, "java/util/stream/Stream", "collect", "(Ljava/util/stream/Collector;)Ljava/lang/Object;", true); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/Object;)Ljava/lang/StringBuilder;", false); mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false); mv.visitLabel(new Label()); mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V", false); mv.visitInsn(ATHROW); Label lastLabel = new Label(); mv.visitLabel(lastLabel); mv.visitLocalVariable("this", classAccessTypeDescriptor, null, firstLabel, lastLabel, 0); mv.visitLocalVariable("name", "Ljava/lang/String;", null, firstLabel, lastLabel, 1); mv.visitLocalVariable("parameterTypes", "[Ljava/lang/Class;", null, firstLabel, lastLabel, 2); mv.visitMaxs(5, methodIndices.isEmpty() ? 3 : 4); mv.visitEnd(); } private void visitMethodIndexReturnStatements(MethodNameReturnIndex methodIndex, Label defaultCaseLabel) { Label jumpLabel = methodIndex.returnIndexLabel; List<MethodInfo> methods = methodIndex.methods; for (int i = 0; i < methods.size(); i++) { MethodInfo methodInfo = methods.get(i); Class<?>[] parameterTypes = methodInfo.method.getParameterTypes(); mv.visitLabel(jumpLabel); mv.visitFrame(F_SAME, 0, null, 0, null); mv.visitVarInsn(ALOAD, 2); AsmUtils.visitZeroOperandInt(mv, parameterTypes.length); mv.visitTypeInsn(ANEWARRAY, "java/lang/Class"); for (int j = 0; j < parameterTypes.length; j++) { mv.visitInsn(DUP); AsmUtils.visitZeroOperandInt(mv, j); Class<?> parameterType = parameterTypes[j]; if (parameterType.isPrimitive()) { mv.visitFieldInsn(GETSTATIC, Type.getInternalName(ClassUtils.primitiveToWrapper(parameterType)), "TYPE", "Ljava/lang/Class;"); } else { mv.visitLdcInsn(Type.getType(parameterType)); } mv.visitInsn(AASTORE); } mv.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "equals", "([Ljava/lang/Object;[Ljava/lang/Object;)Z", false); jumpLabel = i + 1 >= methods.size() ? defaultCaseLabel : new Label(); mv.visitJumpInsn(IFEQ, jumpLabel); mv.visitLabel(new Label()); AsmUtils.visitZeroOperandInt(mv, methodInfo.memberIndex); mv.visitInsn(IRETURN); } } private void visitPropertyAccessMethods() { List<AccessInfo> propertyAccessInfoList = Collections.unmodifiableList(Arrays.asList( // Primitive types // AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.BOOLEAN_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.BYTE_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.CHAR_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.DOUBLE_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.FLOAT_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.INT_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.LONG_TYPE), AccessInfo.forPrimitive(MEMBER_TYPE_PROPERTY, Type.SHORT_TYPE), // Primitive wrapper types // AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Boolean.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Byte.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Character.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Double.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Float.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Integer.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Long.class), AccessInfo.forPrimitiveWrapper(MEMBER_TYPE_PROPERTY, Short.class), // Common reference types // AccessInfo.forReferenceType(MEMBER_TYPE_PROPERTY, BigDecimal.class), AccessInfo.forReferenceType(MEMBER_TYPE_PROPERTY, Date.class), AccessInfo.forReferenceType(MEMBER_TYPE_PROPERTY, LocalDate.class), AccessInfo.forReferenceType(MEMBER_TYPE_PROPERTY, LocalDateTime.class), AccessInfo.forReferenceType(MEMBER_TYPE_PROPERTY, String.class))); for (int i = 0; i < propertyAccessInfoList.size(); i++) { AccessInfo propertyAccessInfo = propertyAccessInfoList.get(i); visitAccessGetter(typeToAccessorsMap.get(propertyAccessInfo.className), propertyAccessInfo); visitAccessGetterBridge(propertyAccessInfo); visitAccessSetter(typeToMutatorsMap.get(propertyAccessInfo.className), propertyAccessInfo); visitAccessSetterBridge(propertyAccessInfo); } visitGeneralAccessGetter("getProperty", MEMBER_TYPE_PROPERTY, accessorInfoList); visitAccessGetterBridge("getProperty", "Ljava/lang/Object;", ARETURN); visitGeneralAccessSetter("setProperty", MEMBER_TYPE_PROPERTY, mutatorInfoList); visitAccessSetterBridge("setProperty", ALOAD, "Ljava/lang/Object;"); } }