/** * * This file is a part of ZOOLA - an extensible BeanShell implementation. * Zoola is based on original BeanShell code created by Pat Niemeyer. * * Original BeanShell code is Copyright (C) 2000 Pat Niemeyer <pat@pat.net>. * * New portions are Copyright 2012 Rafal Lewczuk <rafal.lewczuk@jitlogic.com> * * This is free software. You can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. * * 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. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public License * along with ZOOLA. If not, see <http://www.gnu.org/licenses/>. * */ package bsh; import bsh.ast.*; import bsh.interpreter.BshEvaluatingVisitor; import java.io.*; import java.util.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public final class ClassGenerator { private static ClassGenerator cg; private static final String DEBUG_DIR = System.getProperty("bsh.debugClasses"); public static ClassGenerator getClassGenerator() { if (cg == null) { cg = new ClassGenerator(); } return cg; } /** * Parse the BSHBlock for the class definition and generate the class. */ public Class generateClass(String name, Modifiers modifiers, Class[] interfaces, Class superClass, BSHBlock block, boolean isInterface, CallStack callstack, Interpreter interpreter) throws EvalError { // Delegate to the static method return generateClassImpl(name, modifiers, interfaces, superClass, block, isInterface, new BshEvaluatingVisitor(callstack, interpreter)); } /** * Invoke a super.method() style superclass method on an object instance. * This is not a normal function of the Java reflection API and is * provided by generated class accessor methods. */ public Object invokeSuperclassMethod(BshClassManager bcm, Object instance, String methodName, Object[] args) throws UtilEvalError, ReflectError, InvocationTargetException { // Delegate to the static method return invokeSuperclassMethodImpl(bcm, instance, methodName, args); } /** * Change the parent of the class instance namespace. * This is currently used for inner class support. * Note: This method will likely be removed in the future. */ // This could be static public void setInstanceNameSpaceParent(Object instance, String className, NameSpace parent) { This ithis = ClassGeneratorUtil.getClassInstanceThis(instance, className); ithis.getNameSpace().setParent(parent); } /** * Parse the BSHBlock for for the class definition and generate the class * using ClassGenerator. */ public static Class generateClassImpl(String name, Modifiers modifiers, Class[] interfaces, Class superClass, BSHBlock block, boolean isInterface, BshEvaluatingVisitor visitor) throws EvalError { // Scripting classes currently requires accessibility // This can be eliminated with a bit more work. try { Capabilities.setAccessibility(true); } catch (Capabilities.Unavailable e) { throw new EvalError("Defining classes currently requires reflective Accessibility.", block, visitor.getCallstack()); } NameSpace enclosingNameSpace = visitor.getCallstack().top(); String packageName = enclosingNameSpace.getPackage(); String className = enclosingNameSpace.isClass ? (enclosingNameSpace.getName() + "$" + name) : name; String fqClassName = packageName == null ? className : packageName + "." + className; BshClassManager bcm = visitor.getInterpreter().getClassManager(); // Race condition here... bcm.definingClass(fqClassName); // Create the class static namespace NameSpace classStaticNameSpace = new NameSpace(enclosingNameSpace, className); classStaticNameSpace.isClass = true; visitor.getCallstack().push(classStaticNameSpace); // Evaluate any inner class class definitions in the block // effectively recursively call this method for contained classes first visitor.evalBlock(block, true/*override*/, ClassNodeFilter.CLASSCLASSES); // Generate the type for our class Variable[] variables = getDeclaredVariables(block, visitor, packageName); DelayedEvalBshMethod[] methods = getDeclaredMethods(block, visitor, packageName); ClassGeneratorUtil classGenerator = new ClassGeneratorUtil(modifiers, className, packageName, superClass, interfaces, variables, methods, classStaticNameSpace, isInterface); byte[] code = classGenerator.generateClass(); // if debug, write out the class file to debugClasses directory if (DEBUG_DIR != null) try { FileOutputStream out = new FileOutputStream(DEBUG_DIR + '/' + className + ".class"); out.write(code); out.close(); } catch (IOException e) { throw new IllegalStateException("cannot create file " + DEBUG_DIR + '/' + className + ".class", e); } // Define the new class in the classloader Class genClass = bcm.defineClass(fqClassName, code); // import the unq name into parent enclosingNameSpace.importClass(fqClassName.replace('$', '.')); try { classStaticNameSpace.setLocalVariable(ClassGeneratorUtil.BSHINIT, block, false/*strictJava*/); } catch (UtilEvalError e) { throw new InterpreterError("unable to init static: " + e); } // Give the static space its class static import // important to do this after all classes are defined classStaticNameSpace.setClassStatic(genClass); // evaluate the static portion of the block in the static space visitor.evalBlock(block, true/*override*/, ClassNodeFilter.CLASSSTATIC); visitor.getCallstack().pop(); if ( ! genClass.isInterface()) { // Set the static bsh This callback String bshStaticFieldName = ClassGeneratorUtil.BSHSTATIC + className; try { LHS lhs = Reflect.getLHSStaticField(genClass, bshStaticFieldName); lhs.assign(classStaticNameSpace.getThis(visitor.getInterpreter()), false/*strict*/); } catch (Exception e) { throw new InterpreterError("Error in class gen setup: " + e); } } bcm.doneDefiningClass(fqClassName); return genClass; } static Variable[] getDeclaredVariables(BSHBlock body, BshEvaluatingVisitor visitor, String defaultPackage) { List<Variable> vars = new ArrayList<Variable>(); for (int child = 0; child < body.jjtGetNumChildren(); child++) { SimpleNode node = (SimpleNode) body.jjtGetChild(child); if (node instanceof BSHTypedVariableDeclaration) { BSHTypedVariableDeclaration tvd = (BSHTypedVariableDeclaration) node; Modifiers modifiers = tvd.modifiers; String type = visitor.getTypeDescriptor(tvd.getTypeNode(), defaultPackage); BSHVariableDeclarator[] vardec = tvd.getDeclarators(); for (BSHVariableDeclarator aVardec : vardec) { String name = aVardec.name; try { Variable var = new Variable(name, type, null/*value*/, modifiers); vars.add(var); } catch (UtilEvalError e) { // value error shouldn't happen } } } } return vars.toArray(new Variable[vars.size()]); } static DelayedEvalBshMethod[] getDeclaredMethods(BSHBlock body, BshEvaluatingVisitor visitor, String defaultPackage) throws EvalError { List<DelayedEvalBshMethod> methods = new ArrayList<DelayedEvalBshMethod>(); for (int child = 0; child < body.jjtGetNumChildren(); child++) { SimpleNode node = (SimpleNode) body.jjtGetChild(child); if (node instanceof BSHMethodDeclaration) { BSHMethodDeclaration md = (BSHMethodDeclaration) node; md.insureNodesParsed(); Modifiers modifiers = md.modifiers; String name = md.name; String returnType = visitor.getReturnTypeDescriptor(md, defaultPackage); BSHReturnType returnTypeNode = visitor.getReturnTypeNode(md); BSHFormalParameters paramTypesNode = md.paramsNode; String[] paramTypes = visitor.getTypeDescriptors(paramTypesNode, defaultPackage); DelayedEvalBshMethod bm = new DelayedEvalBshMethod(name, returnType, returnTypeNode, md.paramsNode.getParamNames(), paramTypes, paramTypesNode, md.blockNode, null/*declaringNameSpace*/, modifiers, visitor); methods.add(bm); } } return methods.toArray(new DelayedEvalBshMethod[methods.size()]); } /** * A node filter that filters nodes for either a class body static * initializer or instance initializer. In the static case only static * members are passed, etc. */ static class ClassNodeFilter implements BSHBlock.NodeFilter { public static final int STATIC = 0, INSTANCE = 1, CLASSES = 2; public static ClassNodeFilter CLASSSTATIC = new ClassNodeFilter(STATIC); public static ClassNodeFilter CLASSINSTANCE = new ClassNodeFilter(INSTANCE); public static ClassNodeFilter CLASSCLASSES = new ClassNodeFilter(CLASSES); int context; private ClassNodeFilter(int context) { this.context = context; } public boolean isVisible(SimpleNode node) { if (context == CLASSES) return node instanceof BSHClassDeclaration; // Only show class decs in CLASSES if (node instanceof BSHClassDeclaration) return false; if (context == STATIC) return isStatic(node); if (context == INSTANCE) return !isStatic(node); // ALL return true; } boolean isStatic(SimpleNode node) { if (node instanceof BSHTypedVariableDeclaration) return ((BSHTypedVariableDeclaration) node).modifiers != null && ((BSHTypedVariableDeclaration) node).modifiers.hasModifier("static"); if (node instanceof BSHMethodDeclaration) return ((BSHMethodDeclaration) node).modifiers != null && ((BSHMethodDeclaration) node).modifiers.hasModifier("static"); // need to add static block here if (node instanceof BSHBlock) return false; return false; } } public static Object invokeSuperclassMethodImpl(BshClassManager bcm, Object instance, String methodName, Object[] args) throws UtilEvalError, ReflectError, InvocationTargetException { String superName = ClassGeneratorUtil.BSHSUPER + methodName; // look for the specially named super delegate method Class clas = instance.getClass(); Method superMethod = Reflect.resolveJavaMethod(bcm, clas, superName, Types.getTypes(args), false/*onlyStatic*/); if (superMethod != null) return Reflect.invokeMethod(superMethod, instance, args); // No super method, try to invoke regular method // could be a superfluous "super." which is legal. Class superClass = clas.getSuperclass(); superMethod = Reflect.resolveExpectedJavaMethod(bcm, superClass, instance, methodName, args, false/*onlyStatic*/); return Reflect.invokeMethod(superMethod, instance, args); } }