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