/******************************************************************************* * 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; } }