/* * Distributed as part of mchange-commons-java 0.2.11 * * Copyright (C) 2015 Machinery For Change, Inc. * * Author: Steve Waldman <swaldman@mchange.com> * * This library is free software; you can redistribute it and/or modify * it under the terms of EITHER: * * 1) The GNU Lesser General Public License (LGPL), version 2.1, as * published by the Free Software Foundation * * OR * * 2) The Eclipse Public License (EPL), version 1.0 * * You may choose which license to accept if you wish to redistribute * or modify this work. You may offer derivatives of this work * under the license you have chosen, or you may provide the same * choice of license which you have been offered here. * * This software 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. * * You should have received copies of both LGPL v2.1 and EPL v1.0 * along with this software; see the files LICENSE-EPL and LICENSE-LGPL. * If not, the text of these licenses are currently available at * * LGPL v2.1: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * EPL v1.0: http://www.eclipse.org/org/documents/epl-v10.php * */ package com.mchange.v2.codegen.intfc; import java.io.*; import java.util.*; import java.lang.reflect.*; import com.mchange.v2.codegen.*; import com.mchange.v1.lang.ClassUtils; public class DelegatorGenerator { int class_modifiers = Modifier.PUBLIC | Modifier.ABSTRACT; int method_modifiers = Modifier.PUBLIC; int wrapping_ctor_modifiers = Modifier.PUBLIC; int default_ctor_modifiers = Modifier.PUBLIC; boolean wrapping_constructor = true; boolean default_constructor = true; boolean inner_getter = true; boolean inner_setter = true; Class superclass = null; Class[] extraInterfaces = null; // A rarely used feature, see below Method[] reflectiveDelegateMethods = null; //by default, none of this ReflectiveDelegationPolicy reflectiveDelegationPolicy = ReflectiveDelegationPolicy.USE_MAIN_DELEGATE_INTERFACE; final static Comparator classComp = new Comparator() { public int compare(Object a, Object b) { return ((Class) a).getName().compareTo(((Class) b).getName()); } }; public void setGenerateInnerSetter( boolean b ) { this.inner_setter = b; } public boolean isGenerateInnerSetter() { return inner_setter; } public void setGenerateInnerGetter( boolean b ) { this.inner_getter = b; } public boolean isGenerateInnerGetter() { return inner_getter; } public void setGenerateNoArgConstructor( boolean b ) { this.default_constructor = b; } public boolean isGenerateNoArgConstructor() { return default_constructor; } public void setGenerateWrappingConstructor( boolean b ) { this.wrapping_constructor = b; } public boolean isGenerateWrappingConstructor() { return wrapping_constructor; } public void setWrappingConstructorModifiers( int modifiers ) { this.wrapping_ctor_modifiers = modifiers; } public int getWrappingConstructorModifiers() { return wrapping_ctor_modifiers; } public void setNoArgConstructorModifiers( int modifiers ) { this.default_ctor_modifiers = modifiers; } public int getNoArgConstructorModifiers() { return default_ctor_modifiers; } public void setMethodModifiers( int modifiers ) { this.method_modifiers = modifiers; } public int getMethodModifiers() { return method_modifiers; } public void setClassModifiers( int modifiers ) { this.class_modifiers = modifiers; } public int getClassModifiers() { return class_modifiers; } public void setSuperclass( Class superclass ) { this.superclass = superclass; } public Class getSuperclass() { return superclass; } public void setExtraInterfaces( Class[] extraInterfaces ) { this.extraInterfaces = extraInterfaces; } public Class[] getExtraInterfaces() { return extraInterfaces; } public Method[] getReflectiveDelegateMethods() { return reflectiveDelegateMethods; } /** * Reflectively delegated methods are methods that are not declared in the interface at * build time, but that should reflectively be forwarded at runtime to the inner delegate. * This permits support of public methods not exposed via the interface, or support of * methods added to versions of the interface newer than the build version. * * Note that the declaring class of these methods is simply ignored. Methods will ve * delegated solely by name and parameter. */ public void setReflectiveDelegateMethods(Method[] reflectiveDelegateMethods) { this.reflectiveDelegateMethods = reflectiveDelegateMethods; } public ReflectiveDelegationPolicy getReflectiveDelegationPolicy() { return reflectiveDelegationPolicy; } /** * If ReflectiveDelegationPolicy.USE_MAIN_DELEGATE_INTERFACE, delegate via the same interface we are generating methods against. * (This is useful for supporting methods in versions of the interface with methods that don't appear in the version we are generating against.) * * If ReflectiveDelegationPolicy.USE_RUNTIME_CLASS, delegate via the runtime class of the delegate. (This is useful if * the methods come from multiple interfaces, or we want to be able to forward to methods of the delegate class not captured * by an interface. * * Otherwise, use the delegateClass set in the constructor of ReflectiveDelegationPolicy. * * Note that if the delegate class is not public or otherwise accessible to the generated proxy, IllegalAccessExceptions may ensue. */ public void setReflectiveDelegationPolicy(ReflectiveDelegationPolicy reflectiveDelegationPolicy) { this.reflectiveDelegationPolicy = reflectiveDelegationPolicy; } // public boolean isDelegateViaRuntimeClass() // { return delegate_via_runtime_class; } // /** // * If true, reflective delegate methods are reflected via the runtime Class of the delegate object, // * rather than via an interface. Nice because the runtime class hopefully supports all the reflective // * delegates. Not so nice because the runtime class may not be accessible, so reflection may fail // * with IllegalAccessExceptions. // */ // public void setDelegateRuntimeClass( boolean delegate_via_runtime_class ) // { this.delegate_via_runtime_class = delegate_via_runtime_class; } public void writeDelegator(Class intfcl, String genclass, Writer w) throws IOException { IndentedWriter iw = CodegenUtils.toIndentedWriter(w); String pkg = genclass.substring(0, genclass.lastIndexOf('.')); String sgc = CodegenUtils.fqcnLastElement( genclass ); String scn = (superclass != null ? ClassUtils.simpleClassName( superclass ) : null); String sin = ClassUtils.simpleClassName( intfcl ); String[] eins = null; if (extraInterfaces != null) { eins = new String[ extraInterfaces.length ]; for (int i = 0, len = extraInterfaces.length; i < len; ++i) eins[i] = ClassUtils.simpleClassName( extraInterfaces[i] ); } Set imports = new TreeSet( classComp ); Method[] methods = intfcl.getMethods(); //TODO: don't add array classes! //build import set if (! CodegenUtils.inSamePackage( intfcl.getName(), genclass ) ) imports.add( intfcl ); if (superclass != null && ! CodegenUtils.inSamePackage( superclass.getName(), genclass ) ) imports.add( superclass ); if (extraInterfaces != null) { for (int i = 0, len = extraInterfaces.length; i < len; ++i) { Class checkMe = extraInterfaces[i]; if (! CodegenUtils.inSamePackage( checkMe.getName(), genclass ) ) imports.add( checkMe ); } } ensureImports(genclass, imports, methods ); if ( reflectiveDelegateMethods != null ) ensureImports(genclass, imports, reflectiveDelegateMethods ); if ( reflectiveDelegationPolicy.delegateClass != null && !CodegenUtils.inSamePackage( reflectiveDelegationPolicy.delegateClass.getName(), genclass ) ) imports.add( reflectiveDelegationPolicy.delegateClass ); generateBannerComment( iw ); iw.println("package " + pkg + ';'); iw.println(); for (Iterator ii = imports.iterator(); ii.hasNext(); ) iw.println("import "+ ((Class) ii.next()).getName() + ';'); generateExtraImports( iw ); iw.println(); generateClassJavaDocComment( iw ); iw.print(CodegenUtils.getModifierString( class_modifiers ) + " class " + sgc); if (superclass != null) iw.print(" extends " + scn); iw.print(" implements " + sin); if (eins != null) for (int i = 0, len = eins.length; i < len; ++i) iw.print(", " + eins[i]); iw.println(); iw.println("{"); iw.upIndent(); iw.println("protected " + sin + " inner;"); iw.println(); if (reflectiveDelegateMethods != null) iw.println("protected Class __delegateClass = null;"); iw.println(); iw.println("private void __setInner( " + sin + " inner )"); iw.println("{"); iw.upIndent(); iw.println("this.inner = inner;"); if (reflectiveDelegateMethods != null) { String delegateClassExpr; if ( reflectiveDelegationPolicy == ReflectiveDelegationPolicy.USE_MAIN_DELEGATE_INTERFACE ) delegateClassExpr = sin + ".class"; else if ( reflectiveDelegationPolicy == ReflectiveDelegationPolicy.USE_RUNTIME_CLASS ) delegateClassExpr = "inner.getClass()"; else delegateClassExpr = ClassUtils.simpleClassName( reflectiveDelegationPolicy.delegateClass ) + ".class"; iw.println("this.__delegateClass = inner == null ? null : " + delegateClassExpr + ";"); } iw.downIndent(); iw.println("}"); iw.println(); if ( wrapping_constructor ) { //System.err.println("WRAPPING CTOR MODIFIERS: " + CodegenUtils.getModifierString( wrapping_ctor_modifiers ) + " (intval: " + wrapping_ctor_modifiers + ")"); iw.println(CodegenUtils.getModifierString( wrapping_ctor_modifiers ) + ' ' + sgc + '(' + sin + " inner)"); iw.println("{ __setInner( inner ); }"); } if (default_constructor) { iw.println(); iw.println(CodegenUtils.getModifierString( default_ctor_modifiers ) + ' ' + sgc + "()"); iw.println("{}"); } if (inner_setter) { iw.println(); iw.println( CodegenUtils.getModifierString( method_modifiers ) + " void setInner( " + sin + " inner )"); iw.println( "{ __setInner( inner ); }" ); } if (inner_getter) { iw.println(); iw.println( CodegenUtils.getModifierString( method_modifiers ) + ' ' + sin + " getInner()"); iw.println( "{ return inner; }" ); } iw.println(); for (int i = 0, len = methods.length; i < len; ++i) { Method method = methods[i]; if (i != 0) iw.println(); iw.println( CodegenUtils.methodSignature( method_modifiers, method, null ) ); iw.println("{"); iw.upIndent(); generatePreDelegateCode( intfcl, genclass, method, iw ); generateDelegateCode( intfcl, genclass, method, iw ); generatePostDelegateCode( intfcl, genclass, method, iw ); iw.downIndent(); iw.println("}"); } if ( reflectiveDelegateMethods != null ) { iw.println("// Methods not in core interface to be delegated via reflection"); for (int i = 0, len = reflectiveDelegateMethods.length; i < len; ++i) { Method method = reflectiveDelegateMethods[i]; if (i != 0) iw.println(); iw.println( CodegenUtils.methodSignature( method_modifiers, method, null ) ); iw.println("{"); iw.upIndent(); generatePreDelegateCode( intfcl, genclass, method, iw ); generateReflectiveDelegateCode( intfcl, genclass, method, iw ); generatePostDelegateCode( intfcl, genclass, method, iw ); iw.downIndent(); iw.println("}"); } } iw.println(); generateExtraDeclarations( intfcl, genclass, iw ); iw.downIndent(); iw.println("}"); } private void ensureImports(String genclass, Set imports, Method[] methods ) { for (int i = 0, len = methods.length; i < len; ++i) { Class[] args = methods[i].getParameterTypes(); for (int j = 0, jlen = args.length; j < jlen; ++j) { if (! CodegenUtils.inSamePackage( args[j].getName(), genclass ) ) imports.add( CodegenUtils.unarrayClass( args[j] ) ); } Class[] excClasses = methods[i].getExceptionTypes(); for (int j = 0, jlen = excClasses.length; j < jlen; ++j) { if (! CodegenUtils.inSamePackage( excClasses[j].getName(), genclass ) ) { //System.err.println("Adding exception type: " + excClasses[j]); imports.add( CodegenUtils.unarrayClass( excClasses[j] ) ); } } if (! CodegenUtils.inSamePackage( methods[i].getReturnType().getName(), genclass ) ) imports.add( CodegenUtils.unarrayClass( methods[i].getReturnType() ) ); } } protected void generateDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException { Class retType = method.getReturnType(); iw.println( (retType == void.class ? "" : "return " ) + "inner." + CodegenUtils.methodCall( method ) + ";" ); } protected void generateReflectiveDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException { Class retType = method.getReturnType(); String paramTypesArrayStr = CodegenUtils.reflectiveMethodParameterTypeArray( method ); String argArrayStr = CodegenUtils.reflectiveMethodObjectArray( method ); Class[] exceptionsArray = method.getExceptionTypes(); Set exceptionsSet = new HashSet(); exceptionsSet.addAll( Arrays.asList( exceptionsArray ) ); iw.println("try"); iw.println("{"); iw.upIndent(); iw.println("Method m = __delegateClass.getMethod(\042" + method.getName() + "\042, " + paramTypesArrayStr + ");"); iw.println( (retType == void.class ? "" : "return (" + ClassUtils.simpleClassName( retType ) + ") ") + "m.invoke( inner, " + argArrayStr + " );" ); iw.downIndent(); iw.println("}"); if (! exceptionsSet.contains( IllegalAccessException.class ) ) { iw.println("catch (IllegalAccessException iae)"); iw.println("{"); iw.upIndent(); iw.println( "throw new RuntimeException(\042A reflectively delegated method '" + method.getName() + "' cannot access the object to which the call is delegated\042, iae);" ); iw.downIndent(); iw.println("}"); } iw.println("catch (InvocationTargetException ite)"); iw.println("{"); iw.upIndent(); iw.println("Throwable cause = ite.getCause();"); iw.println("if (cause instanceof RuntimeException) throw (RuntimeException) cause;"); iw.println("if (cause instanceof Error) throw (Error) cause;"); int len = exceptionsArray.length; if (len > 0) { for (int i = 0; i < len; ++i) { String ecn = ClassUtils.simpleClassName( exceptionsArray[i] ); iw.println("if (cause instanceof " + ecn + ") throw (" + ecn + ") cause;"); } } iw.println( "throw new RuntimeException(\042Target of reflectively delegated method '" + method.getName() + "' threw an Exception.\042, cause);" ); iw.downIndent(); iw.println("}"); } protected void generateBannerComment( IndentedWriter iw ) throws IOException { iw.println("/*"); iw.println(" * This class generated by " + this.getClass().getName()); iw.println(" * " + new Date()); iw.println(" * DO NOT HAND EDIT!!!!"); iw.println(" */"); } protected void generateClassJavaDocComment( IndentedWriter iw ) throws IOException { iw.println("/**"); iw.println(" * This class was generated by " + this.getClass().getName() + "."); iw.println(" */"); } protected void generateExtraImports( IndentedWriter iw ) throws IOException {} protected void generatePreDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException {} protected void generatePostDelegateCode( Class intfcl, String genclass, Method method, IndentedWriter iw ) throws IOException {} protected void generateExtraDeclarations( Class intfcl, String genclass, IndentedWriter iw ) throws IOException {} }