blob: ffca9c8e7de048a7b110d2593c920eb585bd6d9b [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.composite.service.provides;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.felix.ipojo.Handler;
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;
/**
* Create the Proxy class.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class POJOWriter implements Opcodes {
//TODO : merge this class with another class only static methods.
/**
* Create a class.
* @param cw : class writer
* @param className : class name
* @param spec : implemented specification
*/
private static void createClass(ClassWriter cw, String className, String spec) {
// Create the class
cw.visit(V1_2, ACC_PUBLIC + ACC_SUPER, className, null, "java/lang/Object", new String[] { spec.replace('.', '/') });
}
/**
* Inject field in the current class.
* @param cw : class writer.
* @param fields : list of field to inject.
*/
private static void injectFields(ClassWriter cw, List fields) {
// Inject fields
for (int i = 0; i < fields.size(); i++) {
FieldMetadata field = (FieldMetadata) fields.get(i);
if (field.isUseful()) {
SpecificationMetadata spec = field.getSpecification();
String fieldName = field.getName();
String desc = "";
if (field.isAggregate()) {
desc = "[L" + spec.getName().replace('.', '/') + ";";
} else {
desc = "L" + spec.getName().replace('.', '/') + ";";
}
cw.visitField(Opcodes.ACC_PRIVATE, fieldName, desc, null, null);
}
}
}
/**
* Generates an empty constructor.
* this constructor just call the java.lang.Object constructor.
* @param cw class writer
*/
private static void generateConstructor(ClassWriter cw) {
// Inject a constructor <INIT>()V
MethodVisitor cst = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
cst.visitVarInsn(ALOAD, 0);
cst.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
cst.visitInsn(RETURN);
cst.visitMaxs(0, 0);
cst.visitEnd();
}
/**
* Return the proxy 'classname' for the contract 'contractname' by delegating on available service.
* @param clazz : Specification class
* @param className : The class name to create
* @param fields : the list of fields on which delegate
* @param methods : the list of method on which delegate
* @param handler : handler object used to access the logger
* @return byte[] : the build class
*/
public static byte[] dump(Class clazz, String className, List fields, List methods, Handler handler) {
Method[] itfmethods = clazz.getMethods();
// Create the class
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
className = className.replace('.', '/');
createClass(cw, className, clazz.getName());
// Inject fields inside the POJO
injectFields(cw, fields);
generateConstructor(cw);
for (int i = 0; i < itfmethods.length; ++i) {
Method method = itfmethods[i];
// Get the field for this method
// 1) find the MethodMetadata
FieldMetadata delegator = null; // field to delegate
MethodMetadata methodDelegator = null; // field to delegate
for (int j = 0; j < methods.size(); j++) {
MethodMetadata methodMeta = (MethodMetadata) methods.get(j);
if (methodMeta.equals(method)) {
delegator = methodMeta.getDelegation();
methodDelegator = methodMeta;
}
}
generateMethod(cw, className, methodDelegator, method, delegator, handler);
}
// End process
cw.visitEnd();
return cw.toByteArray();
}
/**
* Generate on method.
* @param cw : class writer
* @param className : the current class name
* @param method : the method to generate
* @param sign : method signature to generate
* @param delegator : the field on which delegate
* @param handler : the handler (used to acess the logger)
*/
private static void generateMethod(ClassWriter cw, String className, MethodMetadata method, Method sign, FieldMetadata delegator, Handler handler) {
String desc = Type.getMethodDescriptor(sign);
String name = sign.getName();
String[] exc = new String[sign.getExceptionTypes().length];
for (int i = 0; i < sign.getExceptionTypes().length; i++) {
exc[i] = Type.getType(sign.getExceptionTypes()[i]).getInternalName();
}
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, name, desc, null, exc);
if (delegator.isOptional()) {
if (!delegator.isAggregate()) {
generateOptionalCase(mv, delegator, className);
}
if (delegator.isAggregate() /*&& method.getPolicy() == MethodMetadata.ONE_POLICY*/) {
generateOptionalAggregateCase(mv, delegator, className);
}
}
if (delegator.isAggregate()) {
if (method.getPolicy() == MethodMetadata.ONE_POLICY) {
// Aggregate and One Policy
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "[L" + delegator.getSpecification().getName().replace('.', '/') + ";");
mv.visitInsn(ICONST_0); // Use the first one
mv.visitInsn(AALOAD);
loadArgs(mv, ACC_PUBLIC, Type.getArgumentTypes(desc));
// Invoke
mv.visitMethodInsn(INVOKEINTERFACE, delegator.getSpecification().getName().replace('.', '/'), name, desc);
// Return
mv.visitInsn(Type.getReturnType(desc).getOpcode(Opcodes.IRETURN));
} else { // All policy
if (Type.getReturnType(desc).getSort() != Type.VOID) {
handler.error("All policy cannot be used on method which does not return void");
}
Type[] args = Type.getArgumentTypes(desc);
int index = args.length + 1;
// Init
mv.visitInsn(ICONST_0);
mv.visitVarInsn(ISTORE, index);
Label l1b = new Label();
mv.visitLabel(l1b);
Label l2b = new Label();
mv.visitJumpInsn(GOTO, l2b);
// Loop
Label l3b = new Label();
mv.visitLabel(l3b);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "[L" + delegator.getSpecification().getName().replace('.', '/') + ";");
mv.visitVarInsn(ILOAD, index);
mv.visitInsn(AALOAD);
loadArgs(mv, ACC_PUBLIC, Type.getArgumentTypes(desc));
mv.visitMethodInsn(INVOKEINTERFACE, delegator.getSpecification().getName().replace('.', '/'), name, desc);
Label l4b = new Label();
mv.visitLabel(l4b);
mv.visitIincInsn(index, 1); // i++;
// Condition
mv.visitLabel(l2b);
mv.visitVarInsn(ILOAD, index);
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "[L" + delegator.getSpecification().getName().replace('.', '/') + ";");
mv.visitInsn(ARRAYLENGTH);
mv.visitJumpInsn(IF_ICMPLT, l3b);
Label l5b = new Label();
mv.visitLabel(l5b);
mv.visitInsn(RETURN);
}
} else {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "L" + delegator.getSpecification().getName().replace('.', '/') + ";");
loadArgs(mv, ACC_PUBLIC, Type.getArgumentTypes(desc));
// Invoke
if (delegator.getSpecification().isInterface()) {
mv.visitMethodInsn(INVOKEINTERFACE, delegator.getSpecification().getName().replace('.', '/'), name, desc);
} else {
mv.visitMethodInsn(INVOKEVIRTUAL, delegator.getSpecification().getName().replace('.', '/'), name, desc);
}
// Return
mv.visitInsn(Type.getReturnType(desc).getOpcode(IRETURN));
}
mv.visitMaxs(0, 0);
mv.visitEnd();
}
/**
* Generate Optional Case for aggregate field.
* @param mv : method visitor
* @param delegator : Field on which delegate
* @param className : current class name
*/
private static void generateOptionalAggregateCase(MethodVisitor mv, FieldMetadata delegator, String className) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "[L" + delegator.getSpecification().getName().replace('.', '/') + ";");
mv.visitInsn(ARRAYLENGTH);
Label l1a = new Label();
mv.visitJumpInsn(IFNE, l1a);
Label l2a = new Label();
mv.visitLabel(l2a);
mv.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
mv.visitInsn(DUP);
mv.visitLdcInsn("Operation not supported");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitLabel(l1a);
}
/**
* Generate Optional case for non aggregate fields.
*
* @param mv : the method visitor
* @param delegator : the field on which delegate.
* @param className : the name of the current class.
*/
private static void generateOptionalCase(MethodVisitor mv, FieldMetadata delegator, String className) {
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, className, delegator.getName(), "L" + delegator.getSpecification().getName().replace('.', '/') + ";");
mv.visitTypeInsn(INSTANCEOF, "org/apache/felix/ipojo/Nullable");
Label end = new Label();
mv.visitJumpInsn(IFEQ, end);
Label begin = new Label();
mv.visitLabel(begin);
mv.visitTypeInsn(NEW, "java/lang/UnsupportedOperationException");
mv.visitInsn(DUP);
mv.visitLdcInsn("Operation not supported");
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V");
mv.visitInsn(ATHROW);
mv.visitLabel(end);
}
/**
* Load on stack the method arguments.
* @param mv method visitor
* @param access access level of the method
* @param args argument types array
*/
private static void loadArgs(MethodVisitor mv, int access, Type[] args) {
int i = 0;
int j = args.length;
int k = getArgIndex(access, args, i);
for (int l = 0; l < j; l++) {
Type type = args[i + l];
mv.visitVarInsn(type.getOpcode(ILOAD), k);
k += type.getSize();
}
}
/**
* Gets the index of the argument 'i'.
* This method manages double-spaces.
* @param access method access (mostly public)
* @param args argument type array
* @param i wanted index
* @return the real index
*/
private static int getArgIndex(int access, Type[] args, int i) {
int j = (access & 8) != 0 ? 0 : 1;
for (int k = 0; k < i; k++) {
j += args[k].getSize();
}
return j;
}
}