blob: 345afeeb64b1538d85332bae7ce5811edab92bea [file] [log] [blame]
/*
* 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.handlers.dependency;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* Generates proxy class delegating operation invocations thanks to a
* a dependency.
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ProxyGenerator implements Opcodes {
/**
* The dependency name.
*/
private static final String DEPENDENCY = "m_dependency";
/**
* The dependency descriptor.
*/
private static final String DEPENDENCY_DESC = Type.getDescriptor(Dependency.class);
/**
* Dependency internal class name.
*/
private static final String DEPENDENCY_INTERNAL_NAME = "org/apache/felix/ipojo/handlers/dependency/Dependency";
/**
* Gets the internal names of the given class objects.
* @param classes the classes
* @return the array containing internal names of the given class array.
*/
private static String[] getInternalClassNames(Class[] classes) {
final String[] names = new String[classes.length];
for (int i = 0; i < names.length; i++) {
names[i] = Type.getInternalName(classes[i]);
}
return names;
}
/**
* Generates a proxy class.
* @param spec the proxied service specification
* @return the byte[] for the generated proxy class.
*/
public static byte[] dumpProxy(Class spec) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
String internalClassName = Type.getInternalName(spec); // Specification class internal name.
String[] itfs = new String[0];
String parent = "java/lang/Object";
if (spec.isInterface()) {
itfs = new String[] {internalClassName}; // Implemented interface.
} else {
parent = internalClassName;
}
String className = internalClassName + "$$Proxy"; // Unique name.
// Turn around the VM changes (FELIX-2716) about java.* classes.
if (className.startsWith("java/")) {
className = "$" + className;
}
Method[] methods = spec.getMethods(); // Method to delegate
cw.visit(Opcodes.V1_3, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, className, null, parent, itfs);
addDependencyField(cw);
// We try to call super() on the parent, however this should not be used as proxing does work only for interface.
generateConstructor(cw, className, parent);
// For each method, create the delegator code.
for (int i = 0; i < methods.length; i++) {
if ((methods[i].getModifiers() & (Modifier.STATIC | Modifier.FINAL)) == 0) {
generateDelegator(cw, methods[i], className, internalClassName);
}
}
cw.visitEnd();
return cw.toByteArray();
}
/**
* Generates a delegated method.
* @param cw the class writer
* @param method the method object to delegate
* @param className the generated class name
* @param itfName the internal specification class name
*/
private static void generateDelegator(ClassWriter cw, Method method,
String className, String itfName) {
String methodName = method.getName();
String desc = Type.getMethodDescriptor(method);
String[] exceptions = getInternalClassNames(method.getExceptionTypes());
int modifiers = method.getModifiers()
& ~(Modifier.ABSTRACT | Modifier.NATIVE | Modifier.SYNCHRONIZED);
Type[] types = Type.getArgumentTypes(method);
int freeRoom = 1;
for (int t = 0; t < types.length; t++) {
freeRoom = freeRoom + types[t].getSize();
}
MethodVisitor mv = cw.visitMethod(modifiers, methodName, desc, null,
exceptions);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, DEPENDENCY, DEPENDENCY_DESC); // The dependency is on the stack.
mv.visitMethodInsn(INVOKEVIRTUAL, DEPENDENCY_INTERNAL_NAME, "getService", // Call getService
"()Ljava/lang/Object;"); // The service object is on the stack.
int varSvc = freeRoom;
freeRoom = freeRoom + 1; // Object Reference.
mv.visitVarInsn(ASTORE, varSvc); // Store the service object.
Label notNull = new Label();
Label isNull = new Label();
mv.visitVarInsn(ALOAD, varSvc); // Load the service
mv.visitJumpInsn(IFNONNULL, notNull); // If not null go to not null
// Null branch - throw the exception
mv.visitLabel(isNull);
mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
mv.visitInsn(DUP);
mv.visitLdcInsn("No service available");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
// End of the null branch
// Not null, go one the execution
mv.visitLabel(notNull);
// Invoke the method on the service object.
mv.visitVarInsn(ALOAD, varSvc);
// Push argument on the stack.
int i = 1; // Arguments. (non static method)
for (int t = 0; t < types.length; t++) {
mv.visitVarInsn(types[t].getOpcode(ILOAD), i);
i = i + types[t].getSize();
}
// Invocation
mv.visitMethodInsn(INVOKEINTERFACE, itfName, methodName, desc);
// Return the result
Type returnType = Type.getReturnType(desc);
if (returnType.getSort() != Type.VOID) {
mv.visitInsn(returnType.getOpcode(IRETURN));
} else {
mv.visitInsn(RETURN);
}
// End of the method.
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generates the constructors. The constructor receives a dependency
* and set the {@link ProxyGenerator#DEPENDENCY} field.
* @param cw the class writer
* @param className the generated class name.
*/
private static void generateConstructor(ClassWriter cw, String className, String parent) {
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", '(' + DEPENDENCY_DESC + ")V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0); // Load this
mv.visitInsn(DUP); // Dup
mv.visitMethodInsn(INVOKESPECIAL, parent, "<init>", "()V"); // Call super
mv.visitVarInsn(ALOAD, 1); // Load the argument
mv.visitFieldInsn(PUTFIELD, className, DEPENDENCY, DEPENDENCY_DESC); // Assign the dependency field
mv.visitInsn(RETURN); // Return void
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Adds the dependency field {@link ProxyGenerator#DEPENDENCY}.
* @param cw the class writer
*/
private static void addDependencyField(ClassWriter cw) {
cw.visitField(Opcodes.ACC_FINAL, DEPENDENCY, DEPENDENCY_DESC, null, null);
cw.visitEnd();
}
}