001    package org.apache.commons.ognl.enhance;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one
005     * or more contributor license agreements.  See the NOTICE file
006     * distributed with this work for additional information
007     * regarding copyright ownership.  The ASF licenses this file
008     * to you under the Apache License, Version 2.0 (the
009     * "License"); you may not use this file except in compliance
010     * with the License.  You may obtain a copy of the License at
011     *
012     *   http://www.apache.org/licenses/LICENSE-2.0
013     *
014     * Unless required by applicable law or agreed to in writing,
015     * software distributed under the License is distributed on an
016     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017     * KIND, either express or implied.  See the License for the
018     * specific language governing permissions and limitations
019     * under the License.
020     */
021    
022    import javassist.CannotCompileException;
023    import javassist.ClassPool;
024    import javassist.CtClass;
025    import javassist.CtField;
026    import javassist.CtMethod;
027    import javassist.CtNewConstructor;
028    import javassist.CtNewMethod;
029    import javassist.LoaderClassPath;
030    import javassist.NotFoundException;
031    import org.apache.commons.ognl.ASTAnd;
032    import org.apache.commons.ognl.ASTChain;
033    import org.apache.commons.ognl.ASTConst;
034    import org.apache.commons.ognl.ASTCtor;
035    import org.apache.commons.ognl.ASTList;
036    import org.apache.commons.ognl.ASTMethod;
037    import org.apache.commons.ognl.ASTOr;
038    import org.apache.commons.ognl.ASTProperty;
039    import org.apache.commons.ognl.ASTRootVarRef;
040    import org.apache.commons.ognl.ASTStaticField;
041    import org.apache.commons.ognl.ASTStaticMethod;
042    import org.apache.commons.ognl.ASTThisVarRef;
043    import org.apache.commons.ognl.ASTVarRef;
044    import org.apache.commons.ognl.ClassResolver;
045    import org.apache.commons.ognl.ExpressionNode;
046    import org.apache.commons.ognl.Node;
047    import org.apache.commons.ognl.OgnlContext;
048    import org.apache.commons.ognl.OgnlRuntime;
049    
050    import java.lang.reflect.Method;
051    import java.lang.reflect.Modifier;
052    import java.util.Collection;
053    import java.util.HashMap;
054    import java.util.Iterator;
055    import java.util.List;
056    import java.util.Map;
057    import java.util.Set;
058    
059    import static java.lang.String.format;
060    
061    /**
062     * Responsible for managing/providing functionality related to compiling generated java source expressions via bytecode
063     * enhancements for a given ognl expression.
064     */
065    public class ExpressionCompiler
066        implements OgnlExpressionCompiler
067    {
068    
069        /**
070         * Key used to store any java source string casting statements in the {@link OgnlContext} during class compilation.
071         */
072        public static final String PRE_CAST = "_preCast";
073    
074        /**
075         * {@link ClassLoader} instances.
076         */
077        protected Map<ClassResolver, EnhancedClassLoader> loaders = new HashMap<ClassResolver, EnhancedClassLoader>();
078    
079        /**
080         * Javassist class definition poool.
081         */
082        protected ClassPool pool;
083    
084        protected int classCounter = 0;
085    
086        /**
087         * Used by {@link #castExpression(org.apache.commons.ognl.OgnlContext, org.apache.commons.ognl.Node, String)} to
088         * store the cast java source string in to the current {@link org.apache.commons.ognl.OgnlContext}. This will either
089         * add to the existing string present if it already exists or create a new instance and store it using the static
090         * key of {@link #PRE_CAST}.
091         * 
092         * @param context The current execution context.
093         * @param cast The java source string to store in to the context.
094         */
095        public static void addCastString( OgnlContext context, String cast )
096        {
097            String value = (String) context.get( PRE_CAST );
098    
099            if ( value != null )
100            {
101                value = cast + value;
102            }
103            else
104            {
105                value = cast;
106            }
107    
108            context.put( PRE_CAST, value );
109        }
110    
111        /**
112         * Returns the appropriate casting expression (minus parens) for the specified class type.
113         * <p/>
114         * For instance, if given an {@link Integer} object the string <code>"java.lang.Integer"</code> would be returned.
115         * For an array of primitive ints <code>"int[]"</code> and so on..
116         * </p>
117         * 
118         * @param type The class to cast a string expression for.
119         * @return The converted raw string version of the class name.
120         */
121        public static String getCastString( Class<?> type )
122        {
123            if ( type == null )
124            {
125                return null;
126            }
127    
128            return type.isArray() ? type.getComponentType().getName() + "[]" : type.getName();
129        }
130    
131        /**
132         * Convenience method called by many different property/method resolving AST types to get a root expression
133         * resolving string for the given node. The callers are mostly ignorant and rely on this method to properly
134         * determine if the expression should be cast at all and take the appropriate actions if it should.
135         * 
136         * @param expression The node to check and generate a root expression to if necessary.
137         * @param root The root object for this execution.
138         * @param context The current execution context.
139         * @return Either an empty string or a root path java source string compatible with javassist compilations from the
140         *         root object up to the specified {@link Node}.
141         */
142        public static String getRootExpression( Node expression, Object root, OgnlContext context )
143        {
144            String rootExpr = "";
145    
146            if ( !shouldCast( expression ) )
147            {
148                return rootExpr;
149            }
150    
151            if ( ( !ASTList.class.isInstance( expression ) && !ASTVarRef.class.isInstance( expression )
152                && !ASTStaticMethod.class.isInstance( expression ) && !ASTStaticField.class.isInstance( expression )
153                && !ASTConst.class.isInstance( expression ) && !ExpressionNode.class.isInstance( expression )
154                && !ASTCtor.class.isInstance( expression ) && !ASTStaticMethod.class.isInstance( expression )
155                && root != null ) || ( root != null && ASTRootVarRef.class.isInstance( expression ) ) )
156            {
157    
158                Class<?> castClass = OgnlRuntime.getCompiler( context ).getRootExpressionClass( expression, context );
159    
160                if ( castClass.isArray() || ASTRootVarRef.class.isInstance( expression ) || ASTThisVarRef.class.isInstance(
161                    expression ) )
162                {
163                    rootExpr = "((" + getCastString( castClass ) + ")$2)";
164    
165                    if ( ASTProperty.class.isInstance( expression ) && !( (ASTProperty) expression ).isIndexedAccess() )
166                    {
167                        rootExpr += ".";
168                    }
169                }
170                else if ( ( ASTProperty.class.isInstance( expression ) && ( (ASTProperty) expression ).isIndexedAccess() )
171                    || ASTChain.class.isInstance( expression ) )
172                {
173                    rootExpr = "((" + getCastString( castClass ) + ")$2)";
174                }
175                else
176                {
177                    rootExpr = "((" + getCastString( castClass ) + ")$2).";
178                }
179            }
180    
181            return rootExpr;
182        }
183    
184        /**
185         * Used by {@link #getRootExpression(org.apache.commons.ognl.Node, Object, org.apache.commons.ognl.OgnlContext)} to
186         * determine if the expression needs to be cast at all.
187         *
188         * @param expression The node to check against.
189         * @return Yes if the node type should be cast - false otherwise.
190         */
191        public static boolean shouldCast( Node expression )
192        {
193            if ( ASTChain.class.isInstance( expression ) )
194            {
195                Node child = expression.jjtGetChild( 0 );
196                if ( ASTConst.class.isInstance( child ) || ASTStaticMethod.class.isInstance( child )
197                    || ASTStaticField.class.isInstance( child ) || ( ASTVarRef.class.isInstance( child )
198                    && !ASTRootVarRef.class.isInstance( child ) ) )
199                {
200                    return false;
201                }
202            }
203    
204            return !ASTConst.class.isInstance( expression );
205        }
206    
207        /**
208         * {@inheritDoc}
209         */
210        public String castExpression( OgnlContext context, Node expression, String body )
211        {
212            //TODO: ok - so this looks really f-ed up ...and it is ..eh if you can do it better I'm all for it :)
213    
214            if ( context.getCurrentAccessor() == null || context.getPreviousType() == null
215                || context.getCurrentAccessor().isAssignableFrom( context.getPreviousType() ) || (
216                context.getCurrentType() != null && context.getCurrentObject() != null
217                    && context.getCurrentType().isAssignableFrom( context.getCurrentObject().getClass() )
218                    && context.getCurrentAccessor().isAssignableFrom( context.getPreviousType() ) ) || body == null
219                || body.trim().length() < 1 || ( context.getCurrentType() != null && context.getCurrentType().isArray() && (
220                context.getPreviousType() == null || context.getPreviousType() != Object.class ) )
221                || ASTOr.class.isInstance( expression ) || ASTAnd.class.isInstance( expression )
222                || ASTRootVarRef.class.isInstance( expression ) || context.getCurrentAccessor() == Class.class || (
223                context.get( ExpressionCompiler.PRE_CAST ) != null && ( (String) context.get(
224                    ExpressionCompiler.PRE_CAST ) ).startsWith( "new" ) ) || ASTStaticField.class.isInstance( expression )
225                || ASTStaticMethod.class.isInstance( expression ) || ( OrderedReturn.class.isInstance( expression )
226                && ( (OrderedReturn) expression ).getLastExpression() != null ) )
227            {
228                return body;
229            }
230    
231            /*
232             * System.out.println("castExpression() with expression " + expression + " expr class: " + expression.getClass()
233             * + " currentType is: " + context.getCurrentType() + " previousType: " + context.getPreviousType() +
234             * "\n current Accessor: " + context.getCurrentAccessor() + " previous Accessor: " +
235             * context.getPreviousAccessor() + " current object " + context.getCurrentObject());
236             */
237    
238            ExpressionCompiler.addCastString( context,
239                                              "((" + ExpressionCompiler.getCastString( context.getCurrentAccessor() )
240                                                  + ")" );
241    
242            return ")" + body;
243        }
244    
245        /**
246         * {@inheritDoc}
247         */
248        public String getClassName( Class<?> clazz )
249        {
250            if ( "java.util.AbstractList$Itr".equals( clazz.getName() ) )
251            {
252                return Iterator.class.getName();
253            }
254    
255            if ( Modifier.isPublic( clazz.getModifiers() ) && clazz.isInterface() )
256            {
257                return clazz.getName();
258            }
259    
260            Class<?>[] interfaces = clazz.getInterfaces();
261    
262            for ( Class<?> intface : interfaces )
263            {
264                if ( intface.getName().indexOf( "util.List" ) > 0 )
265                {
266                    return intface.getName();
267                }
268                else if ( intface.getName().indexOf( "Iterator" ) > 0 )
269                {
270                    return intface.getName();
271                }
272            }
273    
274            if ( clazz.getSuperclass() != null && clazz.getSuperclass().getInterfaces().length > 0 )
275            {
276                return getClassName( clazz.getSuperclass() );
277            }
278    
279            return clazz.getName();
280        }
281    
282        /**
283         * {@inheritDoc}
284         */
285        public Class<?> getSuperOrInterfaceClass( Method m, Class<?> clazz )
286        {
287            if ( clazz.getInterfaces() != null && clazz.getInterfaces().length > 0 )
288            {
289                Class<?>[] intfs = clazz.getInterfaces();
290                Class<?> intClass;
291    
292                for ( Class<?> intf : intfs )
293                {
294                    intClass = getSuperOrInterfaceClass( m, intf );
295    
296                    if ( intClass != null )
297                    {
298                        return intClass;
299                    }
300    
301                    if ( Modifier.isPublic( intf.getModifiers() ) && containsMethod( m, intf ) )
302                    {
303                        return intf;
304                    }
305                }
306            }
307    
308            if ( clazz.getSuperclass() != null )
309            {
310                Class<?> superClass = getSuperOrInterfaceClass( m, clazz.getSuperclass() );
311    
312                if ( superClass != null )
313                {
314                    return superClass;
315                }
316            }
317    
318            if ( Modifier.isPublic( clazz.getModifiers() ) && containsMethod( m, clazz ) )
319            {
320                return clazz;
321            }
322    
323            return null;
324        }
325    
326        /**
327         * Helper utility method used by compiler to help resolve class->method mappings during method calls to
328         * {@link OgnlExpressionCompiler#getSuperOrInterfaceClass(java.lang.reflect.Method, Class)}.
329         * 
330         * @param m The method to check for existance of.
331         * @param clazz The class to check for the existance of a matching method definition to the method passed in.
332         * @return True if the class contains the specified method, false otherwise.
333         */
334        public boolean containsMethod( Method m, Class<?> clazz )
335        {
336            Method[] methods = clazz.getMethods();
337    
338            if ( methods == null )
339            {
340                return false;
341            }
342    
343            for ( Method method : methods )
344            {
345                if ( method.getName().equals( m.getName() ) && method.getReturnType() == m.getReturnType() )
346                {
347                    Class<?>[] parms = m.getParameterTypes();
348                    if ( parms == null )
349                    {
350                        continue;
351                    }
352    
353                    Class<?>[] mparms = method.getParameterTypes();
354                    if ( mparms == null || mparms.length != parms.length )
355                    {
356                        continue;
357                    }
358    
359                    boolean parmsMatch = true;
360                    for ( int p = 0; p < parms.length; p++ )
361                    {
362                        if ( parms[p] != mparms[p] )
363                        {
364                            parmsMatch = false;
365                            break;
366                        }
367                    }
368    
369                    if ( !parmsMatch )
370                    {
371                        continue;
372                    }
373    
374                    Class<?>[] exceptions = m.getExceptionTypes();
375                    if ( exceptions == null )
376                    {
377                        continue;
378                    }
379    
380                    Class<?>[] mexceptions = method.getExceptionTypes();
381                    if ( mexceptions == null || mexceptions.length != exceptions.length )
382                    {
383                        continue;
384                    }
385    
386                    boolean exceptionsMatch = true;
387                    for ( int e = 0; e < exceptions.length; e++ )
388                    {
389                        if ( exceptions[e] != mexceptions[e] )
390                        {
391                            exceptionsMatch = false;
392                            break;
393                        }
394                    }
395    
396                    if ( !exceptionsMatch )
397                    {
398                        continue;
399                    }
400    
401                    return true;
402                }
403            }
404    
405            return false;
406        }
407    
408        /**
409         * {@inheritDoc}
410         */
411        public Class<?> getInterfaceClass( Class<?> clazz )
412        {
413            if ( "java.util.AbstractList$Itr".equals( clazz.getName() ) )
414            {
415                return Iterator.class;
416            }
417    
418            if ( Modifier.isPublic( clazz.getModifiers() ) && clazz.isInterface() || clazz.isPrimitive() )
419            {
420                return clazz;
421            }
422    
423            Class<?>[] intf = clazz.getInterfaces();
424    
425            for ( Class<?> anIntf : intf )
426            {
427                if ( List.class.isAssignableFrom( anIntf ) )
428                {
429                    return List.class;
430                }
431                else if ( Iterator.class.isAssignableFrom( anIntf ) )
432                {
433                    return Iterator.class;
434                }
435                else if ( Map.class.isAssignableFrom( anIntf ) )
436                {
437                    return Map.class;
438                }
439                else if ( Set.class.isAssignableFrom( anIntf ) )
440                {
441                    return Set.class;
442                }
443                else if ( Collection.class.isAssignableFrom( anIntf ) )
444                {
445                    return Collection.class;
446                }
447            }
448    
449            if ( clazz.getSuperclass() != null && clazz.getSuperclass().getInterfaces().length > 0 )
450            {
451                return getInterfaceClass( clazz.getSuperclass() );
452            }
453    
454            return clazz;
455        }
456    
457        /**
458         * {@inheritDoc}
459         */
460        public Class<?> getRootExpressionClass( Node rootNode, OgnlContext context )
461        {
462            if ( context.getRoot() == null )
463            {
464                return null;
465            }
466    
467            Class<?> ret = context.getRoot().getClass();
468    
469            if ( context.getFirstAccessor() != null && context.getFirstAccessor().isInstance( context.getRoot() ) )
470            {
471                ret = context.getFirstAccessor();
472            }
473    
474            return ret;
475        }
476    
477        /**
478         * {@inheritDoc}
479         */
480        public void compileExpression( OgnlContext context, Node expression, Object root )
481            throws Exception
482        {
483            // System.out.println("Compiling expr class " + expression.getClass().getName() + " and root " + root);
484    
485            if ( expression.getAccessor() != null )
486            {
487                return;
488            }
489    
490            String getBody, setBody;
491    
492            EnhancedClassLoader loader = getClassLoader( context );
493            ClassPool classPool = getClassPool( context, loader );
494    
495            CtClass newClass = classPool.makeClass(
496                expression.getClass().getName() + expression.hashCode() + classCounter++ + "Accessor" );
497            newClass.addInterface( getCtClass( ExpressionAccessor.class ) );
498    
499            CtClass ognlClass = getCtClass( OgnlContext.class );
500            CtClass objClass = getCtClass( Object.class );
501    
502            CtMethod valueGetter = new CtMethod( objClass, "get", new CtClass[] { ognlClass, objClass }, newClass );
503            CtMethod valueSetter =
504                new CtMethod( CtClass.voidType, "set", new CtClass[] { ognlClass, objClass, objClass }, newClass );
505    
506            CtField nodeMember = null; // will only be set if uncompilable exception is thrown
507    
508            CtClass nodeClass = getCtClass( Node.class );
509            CtMethod setExpression = null;
510    
511            try
512            {
513    
514                getBody = generateGetter( context, newClass, objClass, classPool, valueGetter, expression, root );
515    
516            }
517            catch ( UnsupportedCompilationException uc )
518            {
519                // uc.printStackTrace();
520    
521                nodeMember = new CtField( nodeClass, "_node", newClass );
522                newClass.addField( nodeMember );
523    
524                getBody = generateOgnlGetter( newClass, valueGetter, nodeMember );
525    
526                setExpression = CtNewMethod.setter( "setExpression", nodeMember );
527                newClass.addMethod( setExpression );
528            }
529    
530            try
531            {
532    
533                setBody = generateSetter( context, newClass, objClass, classPool, valueSetter, expression, root );
534    
535            }
536            catch ( UnsupportedCompilationException uc )
537            {
538    
539                // uc.printStackTrace();
540    
541                if ( nodeMember == null )
542                {
543                    nodeMember = new CtField( nodeClass, "_node", newClass );
544                    newClass.addField( nodeMember );
545                }
546    
547                setBody = generateOgnlSetter( newClass, valueSetter, nodeMember );
548    
549                if ( setExpression == null )
550                {
551                    setExpression = CtNewMethod.setter( "setExpression", nodeMember );
552                    newClass.addMethod( setExpression );
553                }
554            }
555    
556            try
557            {
558                newClass.addConstructor( CtNewConstructor.defaultConstructor( newClass ) );
559    
560                Class<?> clazz = classPool.toClass( newClass );
561                newClass.detach();
562    
563                expression.setAccessor( (ExpressionAccessor) clazz.newInstance() );
564    
565                // need to set expression on node if the field was just defined.
566    
567                if ( nodeMember != null )
568                {
569                    expression.getAccessor().setExpression( expression );
570                }
571    
572            }
573            catch ( Throwable t )
574            {
575                // t.printStackTrace();
576    
577                throw new RuntimeException( "Error compiling expression on object " + root + " with expression node "
578                    + expression + " getter body: " + getBody + " setter body: " + setBody, t );
579            }
580    
581        }
582    
583        protected String generateGetter( OgnlContext context, CtClass newClass, CtClass objClass, ClassPool classPool,
584                                         CtMethod valueGetter, Node expression, Object root )
585            throws Exception
586        {
587            String pre = "";
588            String post = "";
589            String body;
590    
591            context.setRoot( root );
592    
593            // the ExpressionAccessor API has to reference the generic Object class for get/set operations, so this sets up
594            // that known
595            // type beforehand
596    
597            context.remove( PRE_CAST );
598    
599            // Recursively generate the java source code representation of the top level expression
600    
601            String getterCode = expression.toGetSourceString( context, root );
602    
603            if ( getterCode == null || getterCode.trim().length() <= 0
604                && !ASTVarRef.class.isAssignableFrom( expression.getClass() ) )
605            {
606                getterCode = "null";
607            }
608    
609            String castExpression = (String) context.get( PRE_CAST );
610    
611            if ( context.getCurrentType() == null || context.getCurrentType().isPrimitive()
612                || Character.class.isAssignableFrom( context.getCurrentType() )
613                || Object.class == context.getCurrentType() )
614            {
615                pre = pre + " ($w) (";
616                post = post + ")";
617            }
618    
619            String rootExpr = !"null".equals( getterCode ) ? getRootExpression( expression, root, context ) : "";
620    
621            String noRoot = (String) context.remove( "_noRoot" );
622            if ( noRoot != null )
623            {
624                rootExpr = "";
625            }
626    
627            createLocalReferences( context, classPool, newClass, objClass, valueGetter.getParameterTypes() );
628    
629            if ( OrderedReturn.class.isInstance( expression )
630                && ( (OrderedReturn) expression ).getLastExpression() != null )
631            {
632                body = "{ " + ( ASTMethod.class.isInstance( expression ) || ASTChain.class.isInstance( expression )
633                    ? rootExpr
634                    : "" ) + ( castExpression != null ? castExpression : "" )
635                    + ( (OrderedReturn) expression ).getCoreExpression() + " return " + pre
636                    + ( (OrderedReturn) expression ).getLastExpression() + post + ";}";
637    
638            }
639            else
640            {
641    
642                body =
643                    "{  return " + pre + ( castExpression != null ? castExpression : "" ) + rootExpr + getterCode + post
644                        + ";}";
645            }
646    
647            body = body.replaceAll( "\\.\\.", "." );
648    
649            // System.out.println("Getter Body: ===================================\n" + body);
650            valueGetter.setBody( body );
651            newClass.addMethod( valueGetter );
652    
653            return body;
654        }
655    
656        /**
657         * {@inheritDoc}
658         */
659        public String createLocalReference( OgnlContext context, String expression, Class<?> type )
660        {
661            String referenceName = "ref" + context.incrementLocalReferenceCounter();
662            context.addLocalReference( referenceName, new LocalReferenceImpl( referenceName, expression, type ) );
663    
664            String castString = "";
665            if ( !type.isPrimitive() )
666            {
667                castString = "(" + ExpressionCompiler.getCastString( type ) + ") ";
668            }
669    
670            return castString + referenceName + "($$)";
671        }
672    
673        void createLocalReferences( OgnlContext context, ClassPool classPool, CtClass clazz, CtClass unused,
674                                    CtClass[] params )
675            throws NotFoundException, CannotCompileException
676        {
677            Map<String, LocalReference> referenceMap = context.getLocalReferences();
678            if ( referenceMap == null || referenceMap.isEmpty() )
679            {
680                return;
681            }
682    
683            Iterator<LocalReference> it = referenceMap.values().iterator();
684            while( it.hasNext() )
685            {
686                LocalReference ref = it.next();
687                String widener = ref.getType().isPrimitive() ? " " : " ($w) ";
688    
689                String body = format( "{ return %s %s; }", widener, ref.getExpression() ).replaceAll( "\\.\\.", "." );
690    
691                // System.out.println("adding method " + ref.getName() + " with body:\n" + body + " and return type: " +
692                // ref.getType());
693    
694                CtMethod method =
695                    new CtMethod( classPool.get( getCastString( ref.getType() ) ), ref.getName(), params, clazz );
696                method.setBody( body );
697    
698                clazz.addMethod( method );
699                it.remove();
700            }
701        }
702    
703        protected String generateSetter( OgnlContext context, CtClass newClass, CtClass objClass, ClassPool classPool,
704                                         CtMethod valueSetter, Node expression, Object root )
705            throws Exception
706        {
707            if ( ExpressionNode.class.isInstance( expression ) || ASTConst.class.isInstance( expression ) )
708            {
709                throw new UnsupportedCompilationException( "Can't compile expression/constant setters." );
710            }
711    
712            context.setRoot( root );
713            context.remove( PRE_CAST );
714    
715            String body;
716    
717            String setterCode = expression.toSetSourceString( context, root );
718            String castExpression = (String) context.get( PRE_CAST );
719    
720            if ( setterCode == null || setterCode.trim().length() < 1 )
721            {
722                throw new UnsupportedCompilationException( "Can't compile null setter body." );
723            }
724    
725            if ( root == null )
726            {
727                throw new UnsupportedCompilationException( "Can't compile setters with a null root object." );
728            }
729    
730            String pre = getRootExpression( expression, root, context );
731    
732            String noRoot = (String) context.remove( "_noRoot" );
733            if ( noRoot != null )
734            {
735                pre = "";
736            }
737    
738            createLocalReferences( context, classPool, newClass, objClass, valueSetter.getParameterTypes() );
739    
740            body = "{" + ( castExpression != null ? castExpression : "" ) + pre + setterCode + ";}";
741    
742            body = body.replaceAll( "\\.\\.", "." );
743    
744            // System.out.println("Setter Body: ===================================\n" + body);
745    
746            valueSetter.setBody( body );
747            newClass.addMethod( valueSetter );
748    
749            return body;
750        }
751    
752        /**
753         * Fail safe getter creation when normal compilation fails.
754         * 
755         * @param clazz The javassist class the new method should be attached to.
756         * @param valueGetter The method definition the generated code will be contained within.
757         * @param node The root expression node.
758         * @return The generated source string for this method, the method will still be added via the javassist API either
759         *         way so this is really a convenience for exception reporting / debugging.
760         * @throws Exception If a javassist error occurs.
761         */
762        protected String generateOgnlGetter( CtClass clazz, CtMethod valueGetter, CtField node )
763            throws Exception
764        {
765            String body = "return " + node.getName() + ".getValue($1, $2);";
766    
767            valueGetter.setBody( body );
768            clazz.addMethod( valueGetter );
769    
770            return body;
771        }
772    
773        /**
774         * Fail safe setter creation when normal compilation fails.
775         * 
776         * @param clazz The javassist class the new method should be attached to.
777         * @param valueSetter The method definition the generated code will be contained within.
778         * @param node The root expression node.
779         * @return The generated source string for this method, the method will still be added via the javassist API either
780         *         way so this is really a convenience for exception reporting / debugging.
781         * @throws Exception If a javassist error occurs.
782         */
783        protected String generateOgnlSetter( CtClass clazz, CtMethod valueSetter, CtField node )
784            throws Exception
785        {
786            String body = node.getName() + ".setValue($1, $2, $3);";
787    
788            valueSetter.setBody( body );
789            clazz.addMethod( valueSetter );
790    
791            return body;
792        }
793    
794        /**
795         * Creates a {@link ClassLoader} instance compatible with the javassist classloader and normal OGNL class resolving
796         * semantics.
797         * 
798         * @param context The current execution context.
799         * @return The created {@link ClassLoader} instance.
800         */
801        protected EnhancedClassLoader getClassLoader( OgnlContext context )
802        {
803            EnhancedClassLoader ret = loaders.get( context.getClassResolver() );
804    
805            if ( ret != null )
806            {
807                return ret;
808            }
809    
810            ClassLoader classLoader = new ContextClassLoader( OgnlContext.class.getClassLoader(), context );
811    
812            ret = new EnhancedClassLoader( classLoader );
813            loaders.put( context.getClassResolver(), ret );
814    
815            return ret;
816        }
817    
818        /**
819         * Loads a new class definition via javassist for the specified class.
820         * 
821         * @param searchClass The class to load.
822         * @return The javassist class equivalent.
823         * @throws javassist.NotFoundException When the class definition can't be found.
824         */
825        protected CtClass getCtClass( Class<?> searchClass )
826            throws NotFoundException
827        {
828            return pool.get( searchClass.getName() );
829        }
830    
831        /**
832         * Gets either a new or existing {@link ClassPool} for use in compiling javassist classes. A new class path object
833         * is inserted in to the returned {@link ClassPool} using the passed in <code>loader</code> instance if a new pool
834         * needs to be created.
835         * 
836         * @param context The current execution context.
837         * @param loader The {@link ClassLoader} instance to use - as returned by
838         *            {@link #getClassLoader(org.apache.commons.ognl.OgnlContext)}.
839         * @return The existing or new {@link ClassPool} instance.
840         */
841        protected ClassPool getClassPool( OgnlContext context, EnhancedClassLoader loader )
842        {
843            if ( pool != null )
844            {
845                return pool;
846            }
847    
848            pool = ClassPool.getDefault();
849            pool.insertClassPath( new LoaderClassPath( loader.getParent() ) );
850    
851            return pool;
852        }
853    }