| /******************************************************************************* |
| * Copyright (c) 2008, 2014 Stuart McCulloch |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * Stuart McCulloch - initial API and implementation |
| *******************************************************************************/ |
| |
| package org.eclipse.sisu.peaberry.internal; |
| |
| import static java.lang.reflect.Modifier.ABSTRACT; |
| import static java.lang.reflect.Modifier.FINAL; |
| import static java.lang.reflect.Modifier.NATIVE; |
| import static java.lang.reflect.Modifier.PRIVATE; |
| import static java.lang.reflect.Modifier.PUBLIC; |
| import static java.lang.reflect.Modifier.STATIC; |
| import static java.lang.reflect.Modifier.SYNCHRONIZED; |
| import static java.util.Collections.addAll; |
| import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS; |
| import static org.objectweb.asm.Opcodes.ACONST_NULL; |
| import static org.objectweb.asm.Opcodes.ALOAD; |
| import static org.objectweb.asm.Opcodes.ASTORE; |
| import static org.objectweb.asm.Opcodes.ATHROW; |
| import static org.objectweb.asm.Opcodes.CHECKCAST; |
| import static org.objectweb.asm.Opcodes.DUP; |
| import static org.objectweb.asm.Opcodes.GETFIELD; |
| import static org.objectweb.asm.Opcodes.IFNONNULL; |
| import static org.objectweb.asm.Opcodes.ILOAD; |
| import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; |
| import static org.objectweb.asm.Opcodes.INVOKESPECIAL; |
| import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; |
| import static org.objectweb.asm.Opcodes.IRETURN; |
| import static org.objectweb.asm.Opcodes.ISTORE; |
| import static org.objectweb.asm.Opcodes.NEW; |
| import static org.objectweb.asm.Opcodes.PUTFIELD; |
| import static org.objectweb.asm.Opcodes.RETURN; |
| import static org.objectweb.asm.Opcodes.V1_5; |
| import static org.objectweb.asm.Type.VOID; |
| import static org.objectweb.asm.Type.getArgumentTypes; |
| import static org.objectweb.asm.Type.getDescriptor; |
| import static org.objectweb.asm.Type.getInternalName; |
| import static org.objectweb.asm.Type.getMethodDescriptor; |
| import static org.objectweb.asm.Type.getReturnType; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| import org.eclipse.sisu.peaberry.Import; |
| import org.eclipse.sisu.peaberry.ServiceUnavailableException; |
| import org.objectweb.asm.ClassWriter; |
| import org.objectweb.asm.Label; |
| import org.objectweb.asm.MethodVisitor; |
| import org.objectweb.asm.Type; |
| |
| /** |
| * Around-advice glue, specifically optimized for the {@link Import} concept. |
| * |
| * @author mcculls@gmail.com (Stuart McCulloch) |
| */ |
| final class ImportGlue { |
| |
| // instances not allowed |
| private ImportGlue() {} |
| |
| private static final String UNAVAILABLE_NAME = getInternalName(ServiceUnavailableException.class); |
| private static final String EXCEPTION_NAME = getInternalName(Exception.class); |
| |
| private static final String IMPORT_NAME = getInternalName(Import.class); |
| private static final String OBJECT_NAME = getInternalName(Object.class); |
| |
| private static final String VOID_DESC = "()V"; |
| |
| private static final String IMPORT_DESC = getDescriptor(Import.class); |
| private static final String OBJECT_DESC = getDescriptor(Object.class); |
| |
| private static final Method[] OBJECT_METHODS = Object.class.getMethods(); |
| |
| private static final String PROXY_SUFFIX = "$pbryglu"; |
| private static final String PROXY_HANDLE = "__pbry__"; |
| |
| static String getProxyName(final String clazzName) { |
| final StringBuilder tmpName = new StringBuilder(); |
| |
| // support proxy of java.* interfaces by changing the package space |
| if (clazzName.startsWith("java.") || clazzName.startsWith("java/")) { |
| tmpName.append('$'); |
| } |
| |
| return tmpName.append(clazzName).append(PROXY_SUFFIX).toString(); |
| } |
| |
| static String getClazzName(final String proxyName) { |
| final int head = '$' == proxyName.charAt(0) ? 1 : 0; |
| final int tail = proxyName.lastIndexOf(PROXY_SUFFIX); |
| |
| if (tail > 0) { |
| return proxyName.substring(head, tail); |
| } |
| |
| return proxyName; |
| } |
| |
| static byte[] generateProxy(final Class<?> clazz) { |
| |
| final String clazzName = getInternalName(clazz); |
| final String proxyName = getProxyName(clazzName); |
| |
| final String superName; |
| final String[] interfaceNames; |
| |
| if (clazz.isInterface()) { |
| superName = OBJECT_NAME; |
| interfaceNames = new String[]{clazzName}; |
| } else { |
| superName = clazzName; |
| interfaceNames = getInternalNames(clazz.getInterfaces()); |
| } |
| |
| final ClassWriter cw = new ClassWriter(COMPUTE_MAXS); |
| |
| cw.visit(V1_5, PUBLIC | FINAL, proxyName, null, superName, interfaceNames); |
| |
| // single Import<T> constructor |
| init(cw, superName, proxyName); |
| |
| // for the moment only proxy the public API... |
| final Method[] publicAPI = clazz.getMethods(); |
| final List<Method> methods = new ArrayList<Method>(publicAPI.length + OBJECT_METHODS.length); |
| addAll(methods, publicAPI); |
| |
| if (clazz.isInterface()) { |
| // patch in any missing Object methods... |
| for (final Method m : OBJECT_METHODS) { |
| if (missingMethod(publicAPI, m)) { |
| methods.add(m); |
| } |
| } |
| } |
| |
| for (final Method m : methods) { |
| // we cannot proxy any static or final methods |
| if ((m.getModifiers() & (STATIC | FINAL)) == 0) { |
| wrap(cw, proxyName, m); |
| } |
| } |
| |
| cw.visitEnd(); |
| |
| return cw.toByteArray(); |
| } |
| |
| private static void init(final ClassWriter cw, final String superName, final String proxyName) { |
| cw.visitField(PRIVATE | FINAL, PROXY_HANDLE, IMPORT_DESC, null, null).visitEnd(); |
| |
| final MethodVisitor v = cw.visitMethod(PUBLIC, "<init>", '(' + IMPORT_DESC + ")V", null, null); |
| |
| v.visitCode(); |
| |
| // store Import<T> handle |
| v.visitVarInsn(ALOAD, 0); |
| v.visitInsn(DUP); |
| v.visitVarInsn(ALOAD, 1); |
| v.visitFieldInsn(PUTFIELD, proxyName, PROXY_HANDLE, IMPORT_DESC); |
| v.visitMethodInsn(INVOKESPECIAL, superName, "<init>", VOID_DESC); |
| v.visitInsn(RETURN); |
| |
| v.visitMaxs(0, 0); |
| v.visitEnd(); |
| } |
| |
| @SuppressWarnings("PMD.ExcessiveMethodLength") |
| private static void wrap(final ClassWriter cw, final String proxyName, final Method method) { |
| |
| final String methodName = method.getName(); |
| |
| final String descriptor = getMethodDescriptor(method); |
| final String[] exceptions = getInternalNames(method.getExceptionTypes()); |
| |
| // simple delegating proxy, so don't need synchronization on wrapper method |
| final int modifiers = method.getModifiers() & ~(ABSTRACT | NATIVE | SYNCHRONIZED); |
| |
| final MethodVisitor v = cw.visitMethod(modifiers, methodName, descriptor, null, exceptions); |
| |
| final Label start = new Label(); |
| final Label invoke = new Label(); |
| final Label end = new Label(); |
| |
| final Label ungetR = new Label(); |
| final Label finalR = new Label(); |
| final Label catchX = new Label(); |
| final Label ungetX = new Label(); |
| final Label finalX = new Label(); |
| |
| v.visitCode(); |
| |
| // support try{ get(); } finally { unget(); } model |
| v.visitTryCatchBlock(start, ungetR, catchX, null); |
| v.visitTryCatchBlock(ungetR, finalR, finalR, EXCEPTION_NAME); |
| v.visitTryCatchBlock(ungetX, finalX, finalX, EXCEPTION_NAME); |
| |
| // store handle as "this" |
| v.visitVarInsn(ALOAD, 0); |
| v.visitFieldInsn(GETFIELD, proxyName, PROXY_HANDLE, IMPORT_DESC); |
| v.visitInsn(DUP); |
| v.visitVarInsn(ASTORE, 0); |
| |
| v.visitLabel(start); |
| |
| // dereference handle to get actual service instance |
| v.visitMethodInsn(INVOKEINTERFACE, IMPORT_NAME, "get", "()" + OBJECT_DESC); |
| v.visitInsn(DUP); |
| |
| // null => ServiceUnavailableException |
| v.visitJumpInsn(IFNONNULL, invoke); |
| v.visitTypeInsn(NEW, UNAVAILABLE_NAME); |
| v.visitInsn(DUP); |
| v.visitMethodInsn(INVOKESPECIAL, UNAVAILABLE_NAME, "<init>", "()V"); |
| v.visitInsn(ATHROW); |
| |
| v.visitLabel(invoke); |
| |
| final Class<?> clazz = method.getDeclaringClass(); |
| final String subjectName = getInternalName(clazz); |
| |
| if (!clazz.isInterface()) { |
| v.visitTypeInsn(CHECKCAST, subjectName); |
| } |
| |
| int i = 1; |
| for (final Type t : getArgumentTypes(method)) { |
| v.visitVarInsn(t.getOpcode(ILOAD), i); |
| i = i + t.getSize(); |
| } |
| |
| // delegate to real method |
| if (clazz.isInterface()) { |
| v.visitMethodInsn(INVOKEINTERFACE, subjectName, methodName, descriptor); |
| } else { |
| v.visitMethodInsn(INVOKEVIRTUAL, subjectName, methodName, descriptor); |
| } |
| |
| final Type returnType = getReturnType(method); |
| |
| if (VOID != returnType.getSort()) { |
| v.visitVarInsn(returnType.getOpcode(ISTORE), 1); |
| } |
| |
| v.visitLabel(ungetR); |
| |
| // unget on return |
| v.visitVarInsn(ALOAD, 0); |
| v.visitMethodInsn(INVOKEINTERFACE, IMPORT_NAME, "unget", VOID_DESC); |
| v.visitInsn(ACONST_NULL); |
| |
| v.visitLabel(finalR); |
| |
| if (VOID != returnType.getSort()) { |
| v.visitVarInsn(returnType.getOpcode(ILOAD), 1); |
| } |
| |
| v.visitInsn(returnType.getOpcode(IRETURN)); |
| |
| // cache initial exception |
| v.visitLabel(catchX); |
| v.visitVarInsn(ASTORE, 1); |
| |
| v.visitLabel(ungetX); |
| |
| // unget on exception |
| v.visitVarInsn(ALOAD, 0); |
| v.visitMethodInsn(INVOKEINTERFACE, IMPORT_NAME, "unget", VOID_DESC); |
| v.visitInsn(ACONST_NULL); |
| |
| v.visitLabel(finalX); |
| |
| // restore initial exception |
| v.visitVarInsn(ALOAD, 1); |
| v.visitInsn(ATHROW); |
| |
| v.visitLabel(end); |
| v.visitMaxs(0, 0); |
| v.visitEnd(); |
| } |
| |
| private static String[] getInternalNames(final Class<?>... clazzes) { |
| final String[] names = new String[clazzes.length]; |
| for (int i = 0; i < names.length; i++) { |
| names[i] = getInternalName(clazzes[i]); |
| } |
| return names; |
| } |
| |
| private static boolean missingMethod(final Method[] methods, final Method method) { |
| final String sig = Arrays.toString(method.getParameterTypes()); |
| final String name = method.getName(); |
| |
| for (final Method m : methods) { |
| // just filter on method name and signature, ignore the declaring class... |
| if (name.equals(m.getName()) && sig.equals(Arrays.toString(m.getParameterTypes()))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |