All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.hasor.db.ognl.enhance.ExpressionCompiler Maven / Gradle / Ivy

The newest version!
package ognl.enhance;

import javassist.*;
import ognl.*;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;


/**
 * Responsible for managing/providing functionality related to compiling generated java source
 * expressions via bytecode enhancements for a given ognl expression.
 */
public class ExpressionCompiler implements OgnlExpressionCompiler {

    /**
     * Key used to store any java source string casting statements in the {@link OgnlContext} during
     * class compilation.
     */
    public static final String PRE_CAST = "_preCast";

    /**
     * {@link ClassLoader} instances.
     */
    protected Map _loaders = new HashMap();

    /**
     * Javassist class definition poool.
     */
    protected ClassPool _pool;

    protected int _classCounter = 0;

    /**
     * Default constructor, does nothing.
     */
    public ExpressionCompiler()
    {
    }

    /**
     * Used by {@link #castExpression(ognl.OgnlContext, ognl.Node, String)} to store the cast java
     * source string in to the current {@link OgnlContext}. This will either add to the existing
     * string present if it already exists or create a new instance and store it using the static key
     * of {@link #PRE_CAST}.
     *
     * @param context
     *          The current execution context.
     * @param cast
     *          The java source string to store in to the context.
     */
    public static void addCastString(OgnlContext context, String cast)
    {
        String value = (String) context.get(PRE_CAST);

        if (value != null)
            value = cast + value;
        else
            value = cast;

        context.put(PRE_CAST, value);
    }


    /**
     * Returns the appropriate casting expression (minus parens) for the specified class type.
     *
     * 

* For instance, if given an {@link Integer} object the string "java.lang.Integer" * would be returned. For an array of primitive ints "int[]" and so on.. *

* * @param type The class to cast a string expression for. * @return The converted raw string version of the class name. */ public static String getCastString(Class type) { if (type == null) return null; return type.isArray() ? type.getComponentType().getName() + "[]" : type.getName(); } /** * Convenience method called by many different property/method resolving AST types to get a root expression * resolving string for the given node. The callers are mostly ignorant and rely on this method to properly * determine if the expression should be cast at all and take the appropriate actions if it should. * * @param expression * The node to check and generate a root expression to if necessary. * @param root * The root object for this execution. * @param context * The current execution context. * @return Either an empty string or a root path java source string compatible with javassist compilations * from the root object up to the specified {@link Node}. */ public static String getRootExpression(Node expression, Object root, OgnlContext context) { String rootExpr = ""; if (!shouldCast(expression)) return rootExpr; if ((!ASTList.class.isInstance(expression) && !ASTVarRef.class.isInstance(expression) && !ASTStaticMethod.class.isInstance(expression) && !ASTStaticField.class.isInstance(expression) && !ASTConst.class.isInstance(expression) && !ExpressionNode.class.isInstance(expression) && !ASTCtor.class.isInstance(expression) && !ASTStaticMethod.class.isInstance(expression) && root != null) || (root != null && ASTRootVarRef.class.isInstance(expression))) { Class castClass = OgnlRuntime.getCompiler().getRootExpressionClass(expression, context); if (castClass.isArray() || ASTRootVarRef.class.isInstance(expression) || ASTThisVarRef.class.isInstance(expression)) { rootExpr = "((" + getCastString(castClass) + ")$2)"; if (ASTProperty.class.isInstance(expression) && !((ASTProperty) expression).isIndexedAccess()) rootExpr += "."; } else if ((ASTProperty.class.isInstance(expression) && ((ASTProperty) expression).isIndexedAccess()) || ASTChain.class.isInstance(expression)) { rootExpr = "((" + getCastString(castClass) + ")$2)"; } else { rootExpr = "((" + getCastString(castClass) + ")$2)."; } } return rootExpr; } /** * Used by {@link #getRootExpression(ognl.Node, Object, ognl.OgnlContext)} to determine if the expression * needs to be cast at all. * * @param expression * The node to check against. * @return Yes if the node type should be cast - false otherwise. */ public static boolean shouldCast(Node expression) { if (ASTChain.class.isInstance(expression)) { Node child = expression.jjtGetChild(0); if (ASTConst.class.isInstance(child) || ASTStaticMethod.class.isInstance(child) || ASTStaticField.class.isInstance(child) || (ASTVarRef.class.isInstance(child) && !ASTRootVarRef.class.isInstance(child))) return false; } return !ASTConst.class.isInstance(expression); } public String castExpression(OgnlContext context, Node expression, String body) { // ok - so this looks really f-ed up ...and it is ..eh if you can do it better I'm all for it :) if (context.getCurrentAccessor() == null || context.getPreviousType() == null || context.getCurrentAccessor().isAssignableFrom(context.getPreviousType()) || (context.getCurrentType() != null && context.getCurrentObject() != null && context.getCurrentType().isAssignableFrom(context.getCurrentObject().getClass()) && context.getCurrentAccessor().isAssignableFrom(context.getPreviousType())) || body == null || body.trim().length() < 1 || (context.getCurrentType() != null && context.getCurrentType().isArray() && (context.getPreviousType() == null || context.getPreviousType() != Object.class)) || ASTOr.class.isInstance(expression) || ASTAnd.class.isInstance(expression) || ASTRootVarRef.class.isInstance(expression) || context.getCurrentAccessor() == Class.class || (context.get(ExpressionCompiler.PRE_CAST) != null && ((String) context.get(ExpressionCompiler.PRE_CAST)).startsWith("new")) || ASTStaticField.class.isInstance(expression) || ASTStaticMethod.class.isInstance(expression) || (OrderedReturn.class.isInstance(expression) && ((OrderedReturn) expression).getLastExpression() != null)) return body; /* System.out.println("castExpression() with expression " + expression + " expr class: " + expression.getClass() + " currentType is: " + context.getCurrentType() + " previousType: " + context.getPreviousType() + "\n current Accessor: " + context.getCurrentAccessor() + " previous Accessor: " + context.getPreviousAccessor() + " current object " + context.getCurrentObject());*/ ExpressionCompiler.addCastString(context, "((" + ExpressionCompiler.getCastString(context.getCurrentAccessor()) + ")"); return ")" + body; } public String getClassName(Class clazz) { if (clazz.getName().equals("java.util.AbstractList$Itr")) return Iterator.class.getName(); if (Modifier.isPublic(clazz.getModifiers()) && clazz.isInterface()) return clazz.getName(); return _getClassName(clazz, clazz.getInterfaces()); } private String _getClassName(Class clazz, Class[] intf) { for (int i = 0; i < intf.length; i++) { if (intf[i].getName().indexOf("util.List") > 0) return intf[i].getName(); else if (intf[i].getName().indexOf("Iterator") > 0) return intf[i].getName(); } final Class superclazz = clazz.getSuperclass(); if (superclazz != null) { final Class[] superclazzIntf = superclazz.getInterfaces(); if (superclazzIntf.length > 0) return _getClassName(superclazz, superclazzIntf); } return clazz.getName(); } public Class getSuperOrInterfaceClass(Method m, Class clazz) { Class[] intfs = clazz.getInterfaces(); if (intfs != null && intfs.length > 0) { Class intClass; for (int i = 0; i < intfs.length; i++) { intClass = getSuperOrInterfaceClass(m, intfs[i]); if (intClass != null) return intClass; if (Modifier.isPublic(intfs[i].getModifiers()) && containsMethod(m, intfs[i])) return intfs[i]; } } if (clazz.getSuperclass() != null) { Class superClass = getSuperOrInterfaceClass(m, clazz.getSuperclass()); if (superClass != null) return superClass; } if (Modifier.isPublic(clazz.getModifiers()) && containsMethod(m, clazz)) return clazz; return null; } /** * Helper utility method used by compiler to help resolve class->method mappings * during method calls to {@link OgnlExpressionCompiler#getSuperOrInterfaceClass(java.lang.reflect.Method, Class)}. * * @param m * The method to check for existance of. * @param clazz * The class to check for the existance of a matching method definition to the method passed in. * @return True if the class contains the specified method, false otherwise. */ public boolean containsMethod(Method m, Class clazz) { Method[] methods = clazz.getMethods(); if (methods == null) return false; for (int i = 0; i < methods.length; i++) { if (methods[i].getName().equals(m.getName()) && methods[i].getReturnType() == m.getReturnType()) { Class[] parms = m.getParameterTypes(); if (parms == null) continue; Class[] mparms = methods[i].getParameterTypes(); if (mparms == null || mparms.length != parms.length) continue; boolean parmsMatch = true; for (int p = 0; p < parms.length; p++) { if (parms[p] != mparms[p]) { parmsMatch = false; break; } } if (!parmsMatch) continue; Class[] exceptions = m.getExceptionTypes(); if (exceptions == null) continue; Class[] mexceptions = methods[i].getExceptionTypes(); if (mexceptions == null || mexceptions.length != exceptions.length) continue; boolean exceptionsMatch = true; for (int e = 0; e < exceptions.length; e++) { if (exceptions[e] != mexceptions[e]) { exceptionsMatch = false; break; } } if (!exceptionsMatch) continue; return true; } } return false; } public Class getInterfaceClass(Class clazz) { if (clazz.getName().equals("java.util.AbstractList$Itr")) return Iterator.class; if (Modifier.isPublic(clazz.getModifiers()) && clazz.isInterface() || clazz.isPrimitive()) return clazz; return _getInterfaceClass(clazz, clazz.getInterfaces()); } private Class _getInterfaceClass(Class clazz, Class[] intf) { for (int i = 0; i < intf.length; i++) { if (List.class.isAssignableFrom(intf[i])) return List.class; else if (Iterator.class.isAssignableFrom(intf[i])) return Iterator.class; else if (Map.class.isAssignableFrom(intf[i])) return Map.class; else if (Set.class.isAssignableFrom(intf[i])) return Set.class; else if (Collection.class.isAssignableFrom(intf[i])) return Collection.class; } final Class superclazz = clazz.getSuperclass(); if (superclazz != null) { final Class[] superclazzIntf = superclazz.getInterfaces(); if (superclazzIntf.length > 0) return _getInterfaceClass(superclazz, superclazzIntf); } return clazz; } public Class getRootExpressionClass(Node rootNode, OgnlContext context) { if (context.getRoot() == null) return null; Class ret = context.getRoot().getClass(); if (context.getFirstAccessor() != null && context.getFirstAccessor().isInstance(context.getRoot())) { ret = context.getFirstAccessor(); } return ret; } /* (non-Javadoc) * @see ognl.enhance.OgnlExpressionCompiler#compileExpression(ognl.OgnlContext, ognl.Node, java.lang.Object) */ public void compileExpression(OgnlContext context, Node expression, Object root) throws Exception { // System.out.println("Compiling expr class " + expression.getClass().getName() + " and root " + root); if (expression.getAccessor() != null) return; String getBody, setBody; EnhancedClassLoader loader = getClassLoader(context); ClassPool pool = getClassPool(context, loader); CtClass newClass = pool.makeClass(expression.getClass().getName() + expression.hashCode() + _classCounter++ + "Accessor"); newClass.addInterface(getCtClass(ExpressionAccessor.class)); CtClass ognlClass = getCtClass(OgnlContext.class); CtClass objClass = getCtClass(Object.class); CtMethod valueGetter = new CtMethod(objClass, "get", new CtClass[]{ognlClass, objClass}, newClass); CtMethod valueSetter = new CtMethod(CtClass.voidType, "set", new CtClass[]{ognlClass, objClass, objClass}, newClass); CtField nodeMember = null; // will only be set if uncompilable exception is thrown CtClass nodeClass = getCtClass(Node.class); CtMethod setExpression = null; try { getBody = generateGetter(context, newClass, objClass, pool, valueGetter, expression, root); } catch (UnsupportedCompilationException uc) { //uc.printStackTrace(); nodeMember = new CtField(nodeClass, "_node", newClass); newClass.addField(nodeMember); getBody = generateOgnlGetter(newClass, valueGetter, nodeMember); if (setExpression == null) { setExpression = CtNewMethod.setter("setExpression", nodeMember); newClass.addMethod(setExpression); } } try { setBody = generateSetter(context, newClass, objClass, pool, valueSetter, expression, root); } catch (UnsupportedCompilationException uc) { //uc.printStackTrace(); if (nodeMember == null) { nodeMember = new CtField(nodeClass, "_node", newClass); newClass.addField(nodeMember); } setBody = generateOgnlSetter(newClass, valueSetter, nodeMember); if (setExpression == null) { setExpression = CtNewMethod.setter("setExpression", nodeMember); newClass.addMethod(setExpression); } } try { newClass.addConstructor(CtNewConstructor.defaultConstructor(newClass)); Class clazz = pool.toClass(newClass); newClass.detach(); expression.setAccessor((ExpressionAccessor) clazz.newInstance()); // need to set expression on node if the field was just defined. if (nodeMember != null) { expression.getAccessor().setExpression(expression); } } catch (Throwable t) { //t.printStackTrace(); throw new RuntimeException("Error compiling expression on object " + root + " with expression node " + expression + " getter body: " + getBody + " setter body: " + setBody, t); } } protected String generateGetter(OgnlContext context, CtClass newClass, CtClass objClass, ClassPool pool, CtMethod valueGetter, Node expression, Object root) throws Exception { String pre = ""; String post = ""; String body; context.setRoot(root); // the ExpressionAccessor API has to reference the generic Object class for get/set operations, so this sets up that known // type beforehand context.remove(PRE_CAST); // Recursively generate the java source code representation of the top level expression String getterCode = expression.toGetSourceString(context, root); if (getterCode == null || getterCode.trim().length() <= 0 && !ASTVarRef.class.isAssignableFrom(expression.getClass())) getterCode = "null"; String castExpression = (String) context.get(PRE_CAST); if (context.getCurrentType() == null || context.getCurrentType().isPrimitive() || Character.class.isAssignableFrom(context.getCurrentType()) || Object.class == context.getCurrentType()) { pre = pre + " ($w) ("; post = post + ")"; } String rootExpr = !getterCode.equals("null") ? getRootExpression(expression, root, context) : ""; String noRoot = (String) context.remove("_noRoot"); if (noRoot != null) rootExpr = ""; createLocalReferences(context, pool, newClass, objClass, valueGetter.getParameterTypes()); if (OrderedReturn.class.isInstance(expression) && ((OrderedReturn) expression).getLastExpression() != null) { body = "{ " + (ASTMethod.class.isInstance(expression) || ASTChain.class.isInstance(expression) ? rootExpr : "") + (castExpression != null ? castExpression : "") + ((OrderedReturn) expression).getCoreExpression() + " return " + pre + ((OrderedReturn) expression).getLastExpression() + post + ";}"; } else { body = "{ return " + pre + (castExpression != null ? castExpression : "") + rootExpr + getterCode + post + ";}"; } if (body.indexOf("..") >= 0) body = body.replaceAll("\\.\\.", "."); // System.out.println("Getter Body: ===================================\n" + body); valueGetter.setBody(body); newClass.addMethod(valueGetter); return body; } public String createLocalReference(OgnlContext context, String expression, Class type) { String referenceName = "ref" + context.incrementLocalReferenceCounter(); context.addLocalReference(referenceName, new LocalReferenceImpl(referenceName, expression, type)); String castString = ""; if (!type.isPrimitive()) castString = "(" + ExpressionCompiler.getCastString(type) + ") "; return castString + referenceName + "($$)"; } void createLocalReferences(OgnlContext context, ClassPool pool, CtClass clazz, CtClass objClass, CtClass[] params) throws CannotCompileException, NotFoundException { Map referenceMap = context.getLocalReferences(); if (referenceMap == null || referenceMap.size() < 1) return; Iterator it = referenceMap.values().iterator(); while (it.hasNext()) { LocalReference ref = (LocalReference) it.next(); String widener = ref.getType().isPrimitive() ? " " : " ($w) "; String body = "{"; body += " return " + widener + ref.getExpression() + ";"; body += "}"; if (body.indexOf("..") >= 0) body = body.replaceAll("\\.\\.", "."); // System.out.println("adding method " + ref.getName() + " with body:\n" + body + " and return type: " + ref.getType()); CtMethod method = new CtMethod(pool.get(getCastString(ref.getType())), ref.getName(), params, clazz); method.setBody(body); clazz.addMethod(method); it.remove(); } } protected String generateSetter(OgnlContext context, CtClass newClass, CtClass objClass, ClassPool pool, CtMethod valueSetter, Node expression, Object root) throws Exception { if (ExpressionNode.class.isInstance(expression) || ASTConst.class.isInstance(expression)) throw new UnsupportedCompilationException("Can't compile expression/constant setters."); context.setRoot(root); context.remove(PRE_CAST); String body; String setterCode = expression.toSetSourceString(context, root); String castExpression = (String) context.get(PRE_CAST); if (setterCode == null || setterCode.trim().length() < 1) throw new UnsupportedCompilationException("Can't compile null setter body."); if (root == null) throw new UnsupportedCompilationException("Can't compile setters with a null root object."); String pre = getRootExpression(expression, root, context); String noRoot = (String) context.remove("_noRoot"); if (noRoot != null) pre = ""; createLocalReferences(context, pool, newClass, objClass, valueSetter.getParameterTypes()); body = "{" + (castExpression != null ? castExpression : "") + pre + setterCode + ";}"; if (body.indexOf("..") >= 0) body = body.replaceAll("\\.\\.", "."); // System.out.println("Setter Body: ===================================\n" + body); valueSetter.setBody(body); newClass.addMethod(valueSetter); return body; } /** * Fail safe getter creation when normal compilation fails. * * @param clazz * The javassist class the new method should be attached to. * @param valueGetter * The method definition the generated code will be contained within. * @param node * The root expression node. * @return The generated source string for this method, the method will still be * added via the javassist API either way so this is really a convenience * for exception reporting / debugging. * @throws Exception * If a javassist error occurs. */ protected String generateOgnlGetter(CtClass clazz, CtMethod valueGetter, CtField node) throws Exception { String body = "return " + node.getName() + ".getValue($1, $2);"; valueGetter.setBody(body); clazz.addMethod(valueGetter); return body; } /** * Fail safe setter creation when normal compilation fails. * * @param clazz * The javassist class the new method should be attached to. * @param valueSetter * The method definition the generated code will be contained within. * @param node * The root expression node. * @return The generated source string for this method, the method will still be * added via the javassist API either way so this is really a convenience * for exception reporting / debugging. * @throws Exception * If a javassist error occurs. */ protected String generateOgnlSetter(CtClass clazz, CtMethod valueSetter, CtField node) throws Exception { String body = node.getName() + ".setValue($1, $2, $3);"; valueSetter.setBody(body); clazz.addMethod(valueSetter); return body; } /** * Creates a {@link ClassLoader} instance compatible with the javassist classloader and normal * OGNL class resolving semantics. * * @param context * The current execution context. * * @return The created {@link ClassLoader} instance. */ protected EnhancedClassLoader getClassLoader(OgnlContext context) { EnhancedClassLoader ret = (EnhancedClassLoader) _loaders.get(context.getClassResolver()); if (ret != null) return ret; ClassLoader classLoader = new ContextClassLoader(OgnlContext.class.getClassLoader(), context); ret = new EnhancedClassLoader(classLoader); _loaders.put(context.getClassResolver(), ret); return ret; } /** * Loads a new class definition via javassist for the specified class. * * @param searchClass * The class to load. * @return The javassist class equivalent. * * @throws NotFoundException When the class definition can't be found. */ protected CtClass getCtClass(Class searchClass) throws NotFoundException { return _pool.get(searchClass.getName()); } /** * Gets either a new or existing {@link ClassPool} for use in compiling javassist * classes. A new class path object is inserted in to the returned {@link ClassPool} using * the passed in loader instance if a new pool needs to be created. * * @param context * The current execution context. * @param loader * The {@link ClassLoader} instance to use - as returned by {@link #getClassLoader(ognl.OgnlContext)}. * @return The existing or new {@link ClassPool} instance. */ protected ClassPool getClassPool(OgnlContext context, EnhancedClassLoader loader) { if (_pool != null) return _pool; _pool = ClassPool.getDefault(); _pool.insertClassPath(new LoaderClassPath(loader.getParent())); return _pool; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy