/* * Copyright (C) 2003-2011 eXo Platform SAS. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.etk.orm.apt; import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; import org.etk.orm.plugins.instrument.MethodHandler; import org.etk.reflect.api.ClassTypeInfo; import org.etk.reflect.api.MethodInfo; import org.etk.reflect.api.TypeInfo; import org.etk.reflect.api.VoidTypeInfo; import org.etk.reflect.api.definition.ClassKind; import org.etk.reflect.api.introspection.MethodIntrospector; import org.etk.reflect.api.visit.HierarchyScope; /** * Generates the proxy type implementation that is loaded by Chromattic at runtime. * */ class ProxyTypeGenerator { /** . */ private final ClassTypeInfo type; ProxyTypeGenerator(ClassTypeInfo type) { this.type = type; } void build(StringBuilder code) { // String simpleClassName = type.getSimpleName() + "_ORM"; code.append("package ").append(type.getPackageName()).append(";\n"); code.append("import ").append(Invoker.class.getName()).append(";\n"); code.append("import ").append(Instrumented.class.getName()).append(";\n"); // code.append("public class "); code.append(simpleClassName); code.append(" extends "); if (type.getKind() == ClassKind.INTERFACE) { code.append(Object.class.getName()); code.append(" implements "); code.append(type.getSimpleName()); code.append(","); code.append(Instrumented.class.getSimpleName()); } else { code.append(type.getSimpleName()); code.append(" implements "); code.append(Instrumented.class.getSimpleName()); } code.append(" {\n"); //Generates the constructor appendContructor(code); //Generates method appendMethods(code); // code.append("}\n"); } private void appendContructor(StringBuilder code) { code.append("public final ").append(MethodHandler.class.getName()).append(" handler;\n"); code.append("public ").append(type.getSimpleName()).append("_ORM(").append(MethodHandler.class.getName()).append(" handler) {\n"); code.append("this.handler = handler;\n"); code.append("}\n"); } private void appendMethods(StringBuilder code) { // int id = 0; // Iterable<MethodInfo> methods = getMethodsToImplement(); for (MethodInfo method : methods) { String methodId = "method_" + id++; String methodName = method.getName(); List<TypeInfo> parameterTypes = method.getParameterTypes(); TypeInfo rti = method.getReturnType(); // String scope; switch (method.getAccess()) { case PACKAGE_PROTECTED: scope = ""; break; case PROTECTED: scope = "protected"; break; case PUBLIC: scope = "public"; break; default: throw new AssertionError(); } // code.append("private static final "). append(Invoker.class.getSimpleName()). append(" ").append(methodId).append(" = "). append(Invoker.class.getSimpleName()). append(".getDeclaredMethod("). append(method.getOwner().getName()). append(".class,"). append('"'). append(methodName). append('"'); for (TypeInfo parameterType : parameterTypes) { code.append(","); new TypeFormatter(type, FormatterStyle.LITERAL, code).format(parameterType); code.append(".class"); } code.append(");\n"); // code.append(scope).append(" final "); new TypeFormatter(type, FormatterStyle.RETURN_TYPE, code).format(rti); code.append(" ").append(methodName).append("("); // for (int i = 0; i < parameterTypes.size(); i++) { TypeInfo parameterType = parameterTypes.get(i); if (i > 0) { code.append(","); } new TypeFormatter(type, FormatterStyle.TYPE_PARAMETER, code).format(parameterType); code.append(" arg_").append(i); } code.append(")"); // Build throws clause LinkedHashSet<String> catched = new LinkedHashSet<String>(); boolean hasThrown = false; for (ClassTypeInfo thrownCTI : method.getThrownTypes()) { if (hasThrown) { code.append(", "); } else { code.append(" throws "); hasThrown = true; } code.append(thrownCTI.getName()); catched.add(thrownCTI.getName()); } // Complete catched throwables catched.add(RuntimeException.class.getName()); catched.add(Error.class.getName()); // code.append(" {\n"); // code.append("try {\n"); // switch (parameterTypes.size()) { case 0: if (rti instanceof VoidTypeInfo) { code.append("handler.invoke(this,").append(methodId).append(".getMethod());\n"); } else { code.append("return ("); new TypeFormatter(type, FormatterStyle.CAST, code).format(rti); code.append(")"); code.append("handler.invoke(this,").append(methodId).append(".getMethod());\n"); } break; case 1: if (rti instanceof VoidTypeInfo) { code.append("handler.invoke(this,").append(methodId).append(".getMethod(),(Object)arg_0);\n"); } else { code.append("return ("); new TypeFormatter(type, FormatterStyle.CAST, code).format(rti); code.append(")"); code.append("handler.invoke(this,").append(methodId).append(".getMethod(),(Object)arg_0);\n"); } break; default: code.append("Object[] args = new Object[]{"); for (int i = 0; i < parameterTypes.size(); i++) { if (i > 0) { code.append(","); } code.append("arg_").append(i); } code.append("};\n"); if (rti instanceof VoidTypeInfo) { code.append("handler.invoke(this,").append(methodId).append(".getMethod(),args);\n"); } else { code.append("return ("); new TypeFormatter(type, FormatterStyle.CAST, code).format(rti); code.append(")"); code.append("handler.invoke(this,").append(methodId).append(".getMethod(),args);\n"); } break; } // code.append("} catch(Throwable t) {\n"); for (String c : catched) { code.append("if (t instanceof ").append(c).append(") throw (").append(c).append(")t;\n"); } code.append("throw new java.lang.reflect.UndeclaredThrowableException(t);\n"); code.append("}\n"); // code.append("}\n"); } } private Iterable<MethodInfo> getMethodsToImplement() { List<MethodInfo> methods = new ArrayList<MethodInfo>(); MethodIntrospector introspector = new MethodIntrospector(HierarchyScope.ALL, true); for (MethodInfo method : introspector.getMethods(type)) { if (method.isAbstract()) { methods.add(method); } } return methods; } }