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    }