001 /*
002 * $Id: CompilingPropertyAccessor.java 1103095 2011-05-14 13:18:29Z simonetripodi $
003 * Licensed to the Apache Software Foundation (ASF) under one
004 * or more contributor license agreements. See the NOTICE file
005 * distributed with this work for additional information
006 * regarding copyright ownership. The ASF licenses this file
007 * to you under the Apache License, Version 2.0 (the
008 * "License"); you may not use this file except in compliance
009 * with the License. You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing,
014 * software distributed under the License is distributed on an
015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
016 * KIND, either express or implied. See the License for the
017 * specific language governing permissions and limitations
018 * under the License.
019 */
020 package org.apache.commons.ognl.test;
021
022 import javassist.ClassPool;
023 import javassist.CtClass;
024 import javassist.CtMethod;
025 import javassist.LoaderClassPath;
026 import org.apache.commons.ognl.ObjectPropertyAccessor;
027 import org.apache.commons.ognl.OgnlContext;
028 import org.apache.commons.ognl.OgnlException;
029 import org.apache.commons.ognl.OgnlRuntime;
030 import org.apache.commons.ognl.enhance.ContextClassLoader;
031 import org.apache.commons.ognl.enhance.EnhancedClassLoader;
032 import org.apache.commons.ognl.test.util.NameFactory;
033
034 import java.lang.reflect.Method;
035 import java.lang.reflect.Modifier;
036 import java.util.HashMap;
037 import java.util.IdentityHashMap;
038 import java.util.Map;
039
040 /**
041 * Implementation of PropertyAccessor that uses Javassist to compile a property accessor specifically tailored to the
042 * property.
043 */
044 public class CompilingPropertyAccessor
045 extends ObjectPropertyAccessor
046 {
047
048 private static NameFactory NAME_FACTORY = new NameFactory( "ognl.PropertyAccessor", "v" );
049
050 private static Getter NotFoundGetter = new Getter()
051 {
052
053 public Object get( OgnlContext context, Object target, String propertyName )
054 {
055 return null;
056 }
057 };
058
059 private static Getter DefaultGetter = new Getter()
060 {
061
062 public Object get( OgnlContext context, Object target, String propertyName )
063 {
064 try
065 {
066 return OgnlRuntime.getMethodValue( context, target, propertyName, true );
067 }
068 catch ( Exception ex )
069 {
070 throw new RuntimeException( ex );
071 }
072 }
073 };
074
075 private static Map pools = new HashMap();
076
077 private static Map loaders = new HashMap();
078
079 private static java.util.IdentityHashMap PRIMITIVE_WRAPPER_CLASSES = new IdentityHashMap();
080
081 private java.util.IdentityHashMap seenGetMethods = new java.util.IdentityHashMap();
082
083 static
084 {
085 PRIMITIVE_WRAPPER_CLASSES.put( Boolean.TYPE, Boolean.class );
086 PRIMITIVE_WRAPPER_CLASSES.put( Boolean.class, Boolean.TYPE );
087 PRIMITIVE_WRAPPER_CLASSES.put( Byte.TYPE, Byte.class );
088 PRIMITIVE_WRAPPER_CLASSES.put( Byte.class, Byte.TYPE );
089 PRIMITIVE_WRAPPER_CLASSES.put( Character.TYPE, Character.class );
090 PRIMITIVE_WRAPPER_CLASSES.put( Character.class, Character.TYPE );
091 PRIMITIVE_WRAPPER_CLASSES.put( Short.TYPE, Short.class );
092 PRIMITIVE_WRAPPER_CLASSES.put( Short.class, Short.TYPE );
093 PRIMITIVE_WRAPPER_CLASSES.put( Integer.TYPE, Integer.class );
094 PRIMITIVE_WRAPPER_CLASSES.put( Integer.class, Integer.TYPE );
095 PRIMITIVE_WRAPPER_CLASSES.put( Long.TYPE, Long.class );
096 PRIMITIVE_WRAPPER_CLASSES.put( Long.class, Long.TYPE );
097 PRIMITIVE_WRAPPER_CLASSES.put( Float.TYPE, Float.class );
098 PRIMITIVE_WRAPPER_CLASSES.put( Float.class, Float.TYPE );
099 PRIMITIVE_WRAPPER_CLASSES.put( Double.TYPE, Double.class );
100 PRIMITIVE_WRAPPER_CLASSES.put( Double.class, Double.TYPE );
101 }
102
103 public static Class getPrimitiveWrapperClass( Class primitiveClass )
104 {
105 return (Class) PRIMITIVE_WRAPPER_CLASSES.get( primitiveClass );
106 }
107
108 public interface Getter
109 {
110 public Object get( OgnlContext context, Object target, String propertyName );
111 }
112
113 public static Getter generateGetter( OgnlContext context, String code )
114 throws OgnlException
115 {
116 String className = NAME_FACTORY.getNewClassName();
117
118 try
119 {
120 ClassPool pool = (ClassPool) pools.get( context.getClassResolver() );
121 EnhancedClassLoader loader = (EnhancedClassLoader) loaders.get( context.getClassResolver() );
122 CtClass newClass;
123 CtClass ognlContextClass;
124 CtClass objectClass;
125 CtClass stringClass;
126 CtMethod method;
127 byte[] byteCode;
128 Class compiledClass;
129
130 if ( ( pool == null ) || ( loader == null ) )
131 {
132 ClassLoader classLoader = new ContextClassLoader( OgnlContext.class.getClassLoader(), context );
133
134 pool = ClassPool.getDefault();
135 pool.insertClassPath( new LoaderClassPath( classLoader ) );
136 pools.put( context.getClassResolver(), pool );
137
138 loader = new EnhancedClassLoader( classLoader );
139 loaders.put( context.getClassResolver(), loader );
140 }
141
142 newClass = pool.makeClass( className );
143 ognlContextClass = pool.get( OgnlContext.class.getName() );
144 objectClass = pool.get( Object.class.getName() );
145 stringClass = pool.get( String.class.getName() );
146
147 newClass.addInterface( pool.get( Getter.class.getName() ) );
148 method =
149 new CtMethod( objectClass, "get", new CtClass[] { ognlContextClass, objectClass, stringClass },
150 newClass );
151 method.setBody( "{" + code + "}" );
152 newClass.addMethod( method );
153 byteCode = newClass.toBytecode();
154 compiledClass = loader.defineClass( className, byteCode );
155 return (Getter) compiledClass.newInstance();
156 }
157 catch ( Throwable ex )
158 {
159 throw new OgnlException( "Cannot create class", ex );
160 }
161 }
162
163 private Getter getGetter( OgnlContext context, Object target, String propertyName )
164 throws OgnlException
165 {
166 Getter result;
167 Class targetClass = target.getClass();
168 Map propertyMap;
169
170 if ( ( propertyMap = (Map) seenGetMethods.get( targetClass ) ) == null )
171 {
172 propertyMap = new HashMap( 101 );
173 seenGetMethods.put( targetClass, propertyMap );
174 }
175 if ( ( result = (Getter) propertyMap.get( propertyName ) ) == null )
176 {
177 try
178 {
179 Method method = OgnlRuntime.getGetMethod( context, targetClass, propertyName );
180
181 if ( method != null )
182 {
183 if ( Modifier.isPublic( method.getModifiers() ) )
184 {
185 if ( method.getReturnType().isPrimitive() )
186 {
187 propertyMap.put( propertyName,
188 result =
189 generateGetter( context,
190 "java.lang.Object\t\tresult;\n"
191 + targetClass.getName()
192 + "\t"
193 + "t0 = ("
194 + targetClass.getName()
195 + ")$2;\n"
196 + "\n"
197 + "try {\n"
198 + " result = new "
199 + getPrimitiveWrapperClass( method.getReturnType() ).getName()
200 + "(t0."
201 + method.getName()
202 + "());\n"
203 + "} catch (java.lang.Exception ex) {\n"
204 + " throw new java.lang.RuntimeException(ex);\n"
205 + "}\n" + "return result;" ) );
206 }
207 else
208 {
209 propertyMap.put( propertyName,
210 result =
211 generateGetter( context,
212 "java.lang.Object\t\tresult;\n"
213 + targetClass.getName()
214 + "\t"
215 + "t0 = ("
216 + targetClass.getName()
217 + ")$2;\n"
218 + "\n"
219 + "try {\n"
220 + " result = t0."
221 + method.getName()
222 + "();\n"
223 + "} catch (java.lang.Exception ex) {\n"
224 + " throw new java.lang.RuntimeException(ex);\n"
225 + "}\n" + "return result;" ) );
226 }
227 }
228 else
229 {
230 propertyMap.put( propertyName, result = DefaultGetter );
231 }
232 }
233 else
234 {
235 propertyMap.put( propertyName, result = NotFoundGetter );
236 }
237 }
238 catch ( Exception ex )
239 {
240 throw new OgnlException( "getting getter", ex );
241 }
242 }
243 return result;
244 }
245
246 /**
247 * Returns OgnlRuntime.NotFound if the property does not exist.
248 */
249 public Object getPossibleProperty( Map context, Object target, String name )
250 throws OgnlException
251 {
252 Object result;
253 OgnlContext ognlContext = (OgnlContext) context;
254
255 if ( context.get( "_compile" ) != null )
256 {
257 Getter getter = getGetter( ognlContext, target, name );
258
259 if ( getter != NotFoundGetter )
260 {
261 result = getter.get( ognlContext, target, name );
262 }
263 else
264 {
265 try
266 {
267 result = OgnlRuntime.getFieldValue( ognlContext, target, name, true );
268 }
269 catch ( Exception ex )
270 {
271 throw new OgnlException( name, ex );
272 }
273 }
274 }
275 else
276 {
277 result = super.getPossibleProperty( context, target, name );
278 }
279 return result;
280 }
281 }