| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.felix.ipojo.manipulation; |
| |
| import org.objectweb.asm.*; |
| import org.objectweb.asm.commons.GeneratorAdapter; |
| import org.objectweb.asm.tree.LocalVariableNode; |
| |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Adapts a inner class in order to allow accessing outer class fields. |
| * A manipulated inner class has access to the managed field of the outer class. |
| * |
| * Only non-static inner classes are manipulated, others are not. |
| * |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| public class InnerClassAdapter extends ClassVisitor implements Opcodes { |
| |
| /** |
| * The manipulator having manipulated the outer class. |
| * We add method descriptions to this manipulator. |
| */ |
| private final Manipulator m_manipulator; |
| |
| /** |
| * The name of the inner class. This name is only define in the outer class. |
| */ |
| private final String m_name; |
| |
| /** |
| * The ismple name of the class. |
| */ |
| private final String m_simpleName; |
| |
| /** |
| * Implementation class name. |
| */ |
| private String m_outer; |
| /** |
| * List of fields of the implementation class. |
| */ |
| private Set<String> m_fields; |
| |
| /** |
| * Creates the inner class adapter. |
| * |
| * @param name the inner class name (internal name) |
| * @param visitor parent class visitor |
| * @param outerClassName outer class (implementation class) |
| * @param manipulator the manipulator having manipulated the outer class. |
| */ |
| public InnerClassAdapter(String name, ClassVisitor visitor, String outerClassName, |
| Manipulator manipulator) { |
| super(Opcodes.ASM5, visitor); |
| m_name = name; |
| m_simpleName = m_name.substring(m_name.indexOf("$") + 1); |
| m_outer = outerClassName; |
| m_manipulator = manipulator; |
| m_fields = manipulator.getFields().keySet(); |
| } |
| |
| /** |
| * Visits a method. |
| * This methods create a code visitor manipulating outer class field accesses. |
| * |
| * @param access method visibility |
| * @param name method name |
| * @param desc method descriptor |
| * @param signature method signature |
| * @param exceptions list of exceptions thrown by the method |
| * @return a code adapter manipulating field accesses |
| * @see org.objectweb.asm.ClassVisitor#visitMethod(int, java.lang.String, java.lang.String, java.lang.String, |
| * java.lang.String[]) |
| */ |
| public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { |
| // Do nothing on static methods, should not happen in non-static inner classes. |
| if ((access & ACC_STATIC) == ACC_STATIC) { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| // Do nothing on native methods |
| if ((access & ACC_NATIVE) == ACC_NATIVE) { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| |
| |
| // Do not re-manipulate. |
| if (! m_manipulator.isAlreadyManipulated()) { |
| |
| if (name.equals("<init>")) { |
| // We change the field access from the constructor, but we don't generate the wrapper. |
| MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); |
| return new MethodCodeAdapter(mv, m_outer, access, name, desc, m_fields); |
| } |
| |
| // For all non constructor methods |
| |
| MethodDescriptor md = getMethodDescriptor(name, desc); |
| if (md == null) { |
| generateMethodWrapper(access, name, desc, signature, exceptions, null, null, |
| null); |
| } else { |
| generateMethodWrapper(access, name, desc, signature, exceptions, |
| md.getArgumentLocalVariables(), |
| md.getAnnotations(), md.getParameterAnnotations()); |
| } |
| |
| // The new name is the method name prefixed by the PREFIX. |
| MethodVisitor mv = super.visitMethod(ACC_PRIVATE, ClassManipulator.PREFIX + name, desc, signature, |
| exceptions); |
| return new MethodCodeAdapter(mv, m_outer, ACC_PRIVATE, ClassManipulator.PREFIX + name, desc, m_fields); |
| } else { |
| return super.visitMethod(access, name, desc, signature, exceptions); |
| } |
| } |
| |
| private String getMethodFlagName(String name, String desc) { |
| return ClassManipulator.METHOD_FLAG_PREFIX + getMethodId(name, desc); |
| } |
| |
| private String getMethodId(String name, String desc) { |
| StringBuilder id = new StringBuilder(m_simpleName); |
| id.append("___"); // Separator |
| id.append(name); |
| |
| Type[] args = Type.getArgumentTypes(desc); |
| for (Type type : args) { |
| String arg = type.getClassName(); |
| if (arg.endsWith("[]")) { |
| // We have to replace all [] |
| String acc = ""; |
| while (arg.endsWith("[]")) { |
| arg = arg.substring(0, arg.length() - 2); |
| acc += "__"; |
| } |
| id.append("$").append(arg.replace('.', '_')).append(acc); |
| } else { |
| id.append("$").append(arg.replace('.', '_')); |
| } |
| } |
| return id.toString(); |
| } |
| |
| /** |
| * Generate the method header of a POJO method. |
| * This method header encapsulate the POJO method call to |
| * signal entry exit and error to the container. |
| * |
| * The instance manager and flag are accessed using method calls. |
| * @param access : access flag. |
| * @param name : method name. |
| * @param desc : method descriptor. |
| * @param signature : method signature. |
| * @param exceptions : declared exceptions. |
| * @param localVariables : the local variable nodes. |
| * @param annotations : the annotations to move to this method. |
| * @param paramAnnotations : the parameter annotations to move to this method. |
| */ |
| private void generateMethodWrapper(int access, String name, String desc, String signature, String[] exceptions, |
| List<LocalVariableNode> localVariables, List<ClassChecker.AnnotationDescriptor> annotations, |
| Map<Integer, List<ClassChecker.AnnotationDescriptor>> paramAnnotations) { |
| GeneratorAdapter mv = new GeneratorAdapter(cv.visitMethod(access, name, desc, signature, exceptions), access, name, desc); |
| |
| // If we have variables, we wraps the code within labels. The `lifetime` of the variables are bound to those |
| // two variables. |
| boolean hasArgumentLabels = localVariables != null && !localVariables.isEmpty(); |
| Label start = null; |
| if (hasArgumentLabels) { |
| start = new Label(); |
| mv.visitLabel(start); |
| } |
| |
| mv.visitCode(); |
| |
| Type returnType = Type.getReturnType(desc); |
| |
| // Compute result and exception stack location |
| int result = -1; |
| int exception; |
| |
| //int arguments = mv.newLocal(Type.getType((new Object[0]).getClass())); |
| |
| if (returnType.getSort() != Type.VOID) { |
| // The method returns something |
| result = mv.newLocal(returnType); |
| exception = mv.newLocal(Type.getType(Throwable.class)); |
| } else { |
| exception = mv.newLocal(Type.getType(Throwable.class)); |
| } |
| |
| Label l0 = new Label(); |
| Label l1 = new Label(); |
| Label l2 = new Label(); |
| |
| mv.visitTryCatchBlock(l0, l1, l2, "java/lang/Throwable"); |
| |
| // Access the flag from the outer class |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, m_name, "this$0", "L" + m_outer + ";"); |
| mv.visitFieldInsn(GETFIELD, m_outer, getMethodFlagName(name, desc), "Z"); |
| mv.visitJumpInsn(IFNE, l0); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.loadArgs(); |
| mv.visitMethodInsn(INVOKESPECIAL, m_name, ClassManipulator.PREFIX + name, desc, false); |
| mv.visitInsn(returnType.getOpcode(IRETURN)); |
| |
| // end of the non intercepted method invocation. |
| |
| mv.visitLabel(l0); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, m_name, "this$0", "L" + m_outer + ";"); |
| mv.visitFieldInsn(GETFIELD, m_outer, ClassManipulator.IM_FIELD, "Lorg/apache/felix/ipojo/InstanceManager;"); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitLdcInsn(getMethodId(name, desc)); |
| mv.loadArgArray(); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "org/apache/felix/ipojo/InstanceManager", ClassManipulator.ENTRY, |
| "(Ljava/lang/Object;Ljava/lang/String;[Ljava/lang/Object;)V", false); |
| |
| mv.visitVarInsn(ALOAD, 0); |
| |
| // Do not allow argument modification : just reload arguments. |
| mv.loadArgs(); |
| mv.visitMethodInsn(INVOKESPECIAL, m_name, ClassManipulator.PREFIX + name, desc, false); |
| |
| if (returnType.getSort() != Type.VOID) { |
| mv.visitVarInsn(returnType.getOpcode(ISTORE), result); |
| } |
| |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, m_name, "this$0", "L" + m_outer + ";"); |
| mv.visitFieldInsn(GETFIELD, m_outer, ClassManipulator.IM_FIELD, "Lorg/apache/felix/ipojo/InstanceManager;"); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitLdcInsn(getMethodId(name, desc)); |
| if (returnType.getSort() != Type.VOID) { |
| mv.visitVarInsn(returnType.getOpcode(ILOAD), result); |
| mv.box(returnType); |
| } else { |
| mv.visitInsn(ACONST_NULL); |
| } |
| mv.visitMethodInsn(INVOKEVIRTUAL, "org/apache/felix/ipojo/InstanceManager", |
| ClassManipulator.EXIT, "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Object;)V", false); |
| |
| mv.visitLabel(l1); |
| Label l7 = new Label(); |
| mv.visitJumpInsn(GOTO, l7); |
| mv.visitLabel(l2); |
| |
| mv.visitVarInsn(ASTORE, exception); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitFieldInsn(GETFIELD, m_name, "this$0", "L" + m_outer + ";"); |
| mv.visitFieldInsn(GETFIELD, m_outer, ClassManipulator.IM_FIELD, "Lorg/apache/felix/ipojo/InstanceManager;"); |
| mv.visitVarInsn(ALOAD, 0); |
| mv.visitLdcInsn(getMethodId(name, desc)); |
| mv.visitVarInsn(ALOAD, exception); |
| mv.visitMethodInsn(INVOKEVIRTUAL, "org/apache/felix/ipojo/InstanceManager", ClassManipulator.ERROR, |
| "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/Throwable;)V", false); |
| mv.visitVarInsn(ALOAD, exception); |
| mv.visitInsn(ATHROW); |
| |
| mv.visitLabel(l7); |
| if (returnType.getSort() != Type.VOID) { |
| mv.visitVarInsn(returnType.getOpcode(ILOAD), result); |
| } |
| mv.visitInsn(returnType.getOpcode(IRETURN)); |
| |
| // If we had arguments, we mark the end of the lifetime. |
| Label end = null; |
| if (hasArgumentLabels) { |
| end = new Label(); |
| mv.visitLabel(end); |
| } |
| |
| // Move annotations |
| if (annotations != null) { |
| for (ClassChecker.AnnotationDescriptor ad : annotations) { |
| ad.visitAnnotation(mv); |
| } |
| } |
| |
| // Move parameter annotations |
| if (paramAnnotations != null && ! paramAnnotations.isEmpty()) { |
| for (Integer id : paramAnnotations.keySet()) { |
| List<ClassChecker.AnnotationDescriptor> ads = paramAnnotations.get(id); |
| for (ClassChecker.AnnotationDescriptor ad : ads) { |
| ad.visitParameterAnnotation(id, mv); |
| } |
| } |
| } |
| |
| // Write the arguments name. |
| if (hasArgumentLabels) { |
| for (LocalVariableNode var : localVariables) { |
| mv.visitLocalVariable(var.name, var.desc, var.signature, start, end, var.index); |
| } |
| } |
| |
| mv.visitMaxs(0, 0); |
| mv.visitEnd(); |
| } |
| |
| /** |
| * Gets the method descriptor for the specified name and descriptor. |
| * The method descriptor is looked inside the |
| * {@link ClassManipulator#m_visitedMethods} |
| * @param name the name of the method |
| * @param desc the descriptor of the method |
| * @return the method descriptor or <code>null</code> if not found. |
| */ |
| private MethodDescriptor getMethodDescriptor(String name, String desc) { |
| for (MethodDescriptor md : m_manipulator.getMethodsFromInnerClass(m_name)) { |
| if (md.getName().equals(name) && md.getDescriptor().equals(desc)) { |
| return md; |
| } |
| } |
| return null; |
| } |
| |
| } |