blob: 2b78a60ef00bfe9508854ab22a07c4fbf1d29485 [file] [log] [blame]
/*
* $Id$
* 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.commons.ognl.test;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import org.apache.commons.ognl.ObjectPropertyAccessor;
import org.apache.commons.ognl.OgnlContext;
import org.apache.commons.ognl.OgnlException;
import org.apache.commons.ognl.OgnlRuntime;
import org.apache.commons.ognl.enhance.ContextClassLoader;
import org.apache.commons.ognl.enhance.EnhancedClassLoader;
import org.apache.commons.ognl.test.util.NameFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
/**
* Implementation of PropertyAccessor that uses Javassist to compile a property accessor specifically tailored to the
* property.
*/
public class CompilingPropertyAccessor
extends ObjectPropertyAccessor
{
private static final NameFactory NAME_FACTORY = new NameFactory( "ognl.PropertyAccessor", "v" );
private static final Getter NOT_FOUND_GETTER = (context, target, propertyName) -> null;
private static final Getter DEFAULT_GETTER = (context, target, propertyName) -> {
try
{
return OgnlRuntime.getMethodValue( context, target, propertyName, true );
}
catch ( Exception ex )
{
throw new RuntimeException( ex );
}
};
private static final Map POOLS = new HashMap();
private static final Map LOADERS = new HashMap();
private static final java.util.IdentityHashMap PRIMITIVE_WRAPPER_CLASSES = new IdentityHashMap();
private final java.util.IdentityHashMap seenGetMethods = new java.util.IdentityHashMap();
static
{
PRIMITIVE_WRAPPER_CLASSES.put( Boolean.TYPE, Boolean.class );
PRIMITIVE_WRAPPER_CLASSES.put( Boolean.class, Boolean.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Byte.TYPE, Byte.class );
PRIMITIVE_WRAPPER_CLASSES.put( Byte.class, Byte.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Character.TYPE, Character.class );
PRIMITIVE_WRAPPER_CLASSES.put( Character.class, Character.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Short.TYPE, Short.class );
PRIMITIVE_WRAPPER_CLASSES.put( Short.class, Short.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Integer.TYPE, Integer.class );
PRIMITIVE_WRAPPER_CLASSES.put( Integer.class, Integer.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Long.TYPE, Long.class );
PRIMITIVE_WRAPPER_CLASSES.put( Long.class, Long.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Float.TYPE, Float.class );
PRIMITIVE_WRAPPER_CLASSES.put( Float.class, Float.TYPE );
PRIMITIVE_WRAPPER_CLASSES.put( Double.TYPE, Double.class );
PRIMITIVE_WRAPPER_CLASSES.put( Double.class, Double.TYPE );
}
public static Class getPrimitiveWrapperClass( Class primitiveClass )
{
return (Class) PRIMITIVE_WRAPPER_CLASSES.get( primitiveClass );
}
public interface Getter
{
Object get( OgnlContext context, Object target, String propertyName );
}
public static Getter generateGetter( OgnlContext context, String code )
throws OgnlException
{
String className = NAME_FACTORY.getNewClassName();
try
{
ClassPool pool = (ClassPool) POOLS.get( context.getClassResolver() );
EnhancedClassLoader loader = (EnhancedClassLoader) LOADERS.get( context.getClassResolver() );
CtClass newClass;
CtClass ognlContextClass;
CtClass objectClass;
CtClass stringClass;
CtMethod method;
byte[] byteCode;
Class compiledClass;
if ( ( pool == null ) || ( loader == null ) )
{
ClassLoader classLoader = new ContextClassLoader( OgnlContext.class.getClassLoader(), context );
pool = ClassPool.getDefault();
pool.insertClassPath( new LoaderClassPath( classLoader ) );
POOLS.put( context.getClassResolver(), pool );
loader = new EnhancedClassLoader( classLoader );
LOADERS.put( context.getClassResolver(), loader );
}
newClass = pool.makeClass( className );
ognlContextClass = pool.get( OgnlContext.class.getName() );
objectClass = pool.get( Object.class.getName() );
stringClass = pool.get( String.class.getName() );
newClass.addInterface( pool.get( Getter.class.getName() ) );
method =
new CtMethod( objectClass, "get", new CtClass[] { ognlContextClass, objectClass, stringClass },
newClass );
method.setBody( "{" + code + "}" );
newClass.addMethod( method );
byteCode = newClass.toBytecode();
compiledClass = loader.defineClass( className, byteCode );
return (Getter) compiledClass.newInstance();
}
catch ( Throwable ex )
{
throw new OgnlException( "Cannot create class", ex );
}
}
private Getter getGetter( OgnlContext context, Object target, String propertyName )
throws OgnlException
{
Getter result;
Class targetClass = target.getClass();
Map propertyMap;
if ( ( propertyMap = (Map) seenGetMethods.get( targetClass ) ) == null )
{
propertyMap = new HashMap( 101 );
seenGetMethods.put( targetClass, propertyMap );
}
if ( ( result = (Getter) propertyMap.get( propertyName ) ) == null )
{
try
{
Method method = OgnlRuntime.getGetMethod( context, targetClass, propertyName );
if ( method != null )
{
if ( Modifier.isPublic( method.getModifiers() ) )
{
if ( method.getReturnType().isPrimitive() )
{
propertyMap.put( propertyName,
result =
generateGetter( context,
"java.lang.Object\t\tresult;\n"
+ targetClass.getName()
+ "\t"
+ "t0 = ("
+ targetClass.getName()
+ ")$2;\n"
+ "\n"
+ "try {\n"
+ " result = new "
+ getPrimitiveWrapperClass( method.getReturnType() ).getName()
+ "(t0."
+ method.getName()
+ "());\n"
+ "} catch (java.lang.Exception ex) {\n"
+ " throw new java.lang.RuntimeException(ex);\n"
+ "}\n" + "return result;" ) );
}
else
{
propertyMap.put( propertyName,
result =
generateGetter( context,
"java.lang.Object\t\tresult;\n"
+ targetClass.getName()
+ "\t"
+ "t0 = ("
+ targetClass.getName()
+ ")$2;\n"
+ "\n"
+ "try {\n"
+ " result = t0."
+ method.getName()
+ "();\n"
+ "} catch (java.lang.Exception ex) {\n"
+ " throw new java.lang.RuntimeException(ex);\n"
+ "}\n" + "return result;" ) );
}
}
else
{
propertyMap.put( propertyName, result = DEFAULT_GETTER );
}
}
else
{
propertyMap.put( propertyName, result = NOT_FOUND_GETTER );
}
}
catch ( Exception ex )
{
throw new OgnlException( "getting getter", ex );
}
}
return result;
}
/**
* Returns OgnlRuntime.NotFound if the property does not exist.
*/
public Object getPossibleProperty( Map context, Object target, String name )
throws OgnlException
{
Object result;
OgnlContext ognlContext = (OgnlContext) context;
if ( context.get( "_compile" ) != null )
{
Getter getter = getGetter( ognlContext, target, name );
if ( getter != NOT_FOUND_GETTER )
{
result = getter.get( ognlContext, target, name );
}
else
{
try
{
result = OgnlRuntime.getFieldValue( ognlContext, target, name, true );
}
catch ( Exception ex )
{
throw new OgnlException( name, ex );
}
}
}
else
{
result = super.getPossibleProperty( context, target, name );
}
return result;
}
}