001    package org.apache.commons.ognl;
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 org.apache.commons.ognl.enhance.LocalReference;
023    
024    import java.util.Collection;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.LinkedHashMap;
028    import java.util.Map;
029    import java.util.Set;
030    import java.util.Stack;
031    
032    /**
033     * This class defines the execution context for an OGNL expression
034     * 
035     * @author Luke Blanshard (blanshlu@netscape.net)
036     * @author Drew Davidson (drew@ognl.org)
037     */
038    public class OgnlContext
039        implements Map<String, Object>
040    {
041    
042        public static final String CONTEXT_CONTEXT_KEY = "context";
043    
044        public static final String ROOT_CONTEXT_KEY = "root";
045    
046        public static final String THIS_CONTEXT_KEY = "this";
047    
048        public static final String TRACE_EVALUATIONS_CONTEXT_KEY = "_traceEvaluations";
049    
050        public static final String LAST_EVALUATION_CONTEXT_KEY = "_lastEvaluation";
051    
052        public static final String KEEP_LAST_EVALUATION_CONTEXT_KEY = "_keepLastEvaluation";
053    
054        public static final String CLASS_RESOLVER_CONTEXT_KEY = "_classResolver";
055    
056        public static final String TYPE_CONVERTER_CONTEXT_KEY = "_typeConverter";
057    
058        public static final String MEMBER_ACCESS_CONTEXT_KEY = "_memberAccess";
059    
060        private static final String PROPERTY_KEY_PREFIX = "ognl";
061    
062        private static boolean defaultTraceEvaluations = false;
063    
064        private static boolean defaultKeepLastEvaluation = false;
065    
066        public static final DefaultClassResolver DEFAULT_CLASS_RESOLVER = new DefaultClassResolver();
067    
068        public static final TypeConverter DEFAULT_TYPE_CONVERTER = new DefaultTypeConverter();
069    
070        public static final MemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess( false );
071    
072        private static final Set<String> RESERVED_KEYS = new HashSet<String>( 11 );
073    
074        private Object root;
075    
076        private Object currentObject;
077    
078        private Node currentNode;
079    
080        private boolean traceEvaluations = defaultTraceEvaluations;
081    
082        private Evaluation rootEvaluation;
083    
084        private Evaluation currentEvaluation;
085    
086        private Evaluation lastEvaluation;
087    
088        private boolean keepLastEvaluation = defaultKeepLastEvaluation;
089    
090        private Map<String, Object> values = new HashMap<String, Object>( 23 );
091    
092        private ClassResolver classResolver = DEFAULT_CLASS_RESOLVER;
093    
094        private TypeConverter typeConverter = DEFAULT_TYPE_CONVERTER;
095    
096        private MemberAccess memberAccess = DEFAULT_MEMBER_ACCESS;
097    
098        static
099        {
100            String s;
101    
102            RESERVED_KEYS.add( CONTEXT_CONTEXT_KEY );
103            RESERVED_KEYS.add( ROOT_CONTEXT_KEY );
104            RESERVED_KEYS.add( THIS_CONTEXT_KEY );
105            RESERVED_KEYS.add( TRACE_EVALUATIONS_CONTEXT_KEY );
106            RESERVED_KEYS.add( LAST_EVALUATION_CONTEXT_KEY );
107            RESERVED_KEYS.add( KEEP_LAST_EVALUATION_CONTEXT_KEY );
108            RESERVED_KEYS.add( CLASS_RESOLVER_CONTEXT_KEY );
109            RESERVED_KEYS.add( TYPE_CONVERTER_CONTEXT_KEY );
110            RESERVED_KEYS.add( MEMBER_ACCESS_CONTEXT_KEY );
111    
112            try
113            {
114                s = System.getProperty( PROPERTY_KEY_PREFIX + ".traceEvaluations" );
115                if ( s != null )
116                {
117                    defaultTraceEvaluations = Boolean.valueOf( s.trim() );
118                }
119                s = System.getProperty( PROPERTY_KEY_PREFIX + ".keepLastEvaluation" );
120                if ( s != null )
121                {
122                    defaultKeepLastEvaluation = Boolean.valueOf( s.trim() );
123                }
124            }
125            catch ( SecurityException ex )
126            {
127                // restricted access environment, just keep defaults
128            }
129        }
130    
131        private Stack<Class<?>> typeStack = new Stack<Class<?>>();
132    
133        private Stack<Class<?>> accessorStack = new Stack<Class<?>>();
134    
135        private int localReferenceCounter = 0;
136    
137        private Map<String, LocalReference> localReferenceMap = null;
138    
139        /**
140         * Constructs a new OgnlContext with the default class resolver, type converter and member access.
141         */
142        public OgnlContext()
143        {
144        }
145    
146        /**
147         * Constructs a new OgnlContext with the given class resolver, type converter and member access. If any of these
148         * parameters is null the default will be used.
149         */
150        public OgnlContext( ClassResolver classResolver, TypeConverter typeConverter, MemberAccess memberAccess )
151        {
152            this();
153            if ( classResolver != null )
154            {
155                this.classResolver = classResolver;
156            }
157            if ( typeConverter != null )
158            {
159                this.typeConverter = typeConverter;
160            }
161            if ( memberAccess != null )
162            {
163                this.memberAccess = memberAccess;
164            }
165        }
166    
167        public OgnlContext( Map<String, Object> values )
168        {
169            super();
170            this.values = values;
171        }
172    
173        public OgnlContext( ClassResolver classResolver, TypeConverter typeConverter, MemberAccess memberAccess,
174                            Map<String, Object> values )
175        {
176            this( classResolver, typeConverter, memberAccess );
177            this.values = values;
178        }
179    
180        public void setValues( Map<String, Object> value )
181        {
182            values.putAll( value );
183        }
184    
185        public Map<String, Object> getValues()
186        {
187            return values;
188        }
189    
190        public void setClassResolver( ClassResolver value )
191        {
192            if ( value == null )
193            {
194                throw new IllegalArgumentException( "cannot set ClassResolver to null" );
195            }
196            classResolver = value;
197        }
198    
199        public ClassResolver getClassResolver()
200        {
201            return classResolver;
202        }
203    
204        public void setTypeConverter( TypeConverter value )
205        {
206            if ( value == null )
207            {
208                throw new IllegalArgumentException( "cannot set TypeConverter to null" );
209            }
210            typeConverter = value;
211        }
212    
213        public TypeConverter getTypeConverter()
214        {
215            return typeConverter;
216        }
217    
218        public void setMemberAccess( MemberAccess value )
219        {
220            if ( value == null )
221            {
222                throw new IllegalArgumentException( "cannot set MemberAccess to null" );
223            }
224            memberAccess = value;
225        }
226    
227        public MemberAccess getMemberAccess()
228        {
229            return memberAccess;
230        }
231    
232        public void setRoot( Object value )
233        {
234            root = value;
235            accessorStack.clear();
236            typeStack.clear();
237            currentObject = value;
238    
239            if ( currentObject != null )
240            {
241                setCurrentType( currentObject.getClass() );
242            }
243        }
244    
245        public Object getRoot()
246        {
247            return root;
248        }
249    
250        public boolean getTraceEvaluations()
251        {
252            return traceEvaluations;
253        }
254    
255        public void setTraceEvaluations( boolean value )
256        {
257            traceEvaluations = value;
258        }
259    
260        public Evaluation getLastEvaluation()
261        {
262            return lastEvaluation;
263        }
264    
265        public void setLastEvaluation( Evaluation value )
266        {
267            lastEvaluation = value;
268        }
269    
270        /**
271         * This method can be called when the last evaluation has been used and can be returned for reuse in the free pool
272         * maintained by the runtime. This is not a necessary step, but is useful for keeping memory usage down. This will
273         * recycle the last evaluation and then set the last evaluation to null.
274         */
275        public void recycleLastEvaluation()
276        {
277            OgnlRuntime.getEvaluationPool().recycleAll( lastEvaluation );
278            lastEvaluation = null;
279        }
280    
281        /**
282         * Returns true if the last evaluation that was done on this context is retained and available through
283         * <code>getLastEvaluation()</code>. The default is true.
284         */
285        public boolean getKeepLastEvaluation()
286        {
287            return keepLastEvaluation;
288        }
289    
290        /**
291         * Sets whether the last evaluation that was done on this context is retained and available through
292         * <code>getLastEvaluation()</code>. The default is true.
293         */
294        public void setKeepLastEvaluation( boolean value )
295        {
296            keepLastEvaluation = value;
297        }
298    
299        public void setCurrentObject( Object value )
300        {
301            currentObject = value;
302        }
303    
304        public Object getCurrentObject()
305        {
306            return currentObject;
307        }
308    
309        public void setCurrentAccessor( Class<?> type )
310        {
311            accessorStack.add( type );
312        }
313    
314        public Class<?> getCurrentAccessor()
315        {
316            if ( accessorStack.isEmpty() )
317            {
318                return null;
319            }
320    
321            return accessorStack.peek();
322        }
323    
324        public Class<?> getPreviousAccessor()
325        {
326            if ( accessorStack.isEmpty() )
327            {
328                return null;
329            }
330    
331            if ( accessorStack.size() > 1 )
332            {
333                return accessorStack.get( accessorStack.size() - 2 );
334            }
335    
336            return null;
337        }
338    
339        public Class<?> getFirstAccessor()
340        {
341            if ( accessorStack.isEmpty() )
342            {
343                return null;
344            }
345    
346            return accessorStack.get( 0 );
347        }
348    
349        /**
350         * Gets the current class type being evaluated on the stack, as set by {@link #setCurrentType(Class)}.
351         * 
352         * @return The current object type, may be null.
353         */
354        public Class<?> getCurrentType()
355        {
356            if ( typeStack.isEmpty() )
357            {
358                return null;
359            }
360    
361            return typeStack.peek();
362        }
363    
364        public void setCurrentType( Class<?> type )
365        {
366            typeStack.add( type );
367        }
368    
369        /**
370         * Represents the last known object type on the evaluation stack, will be the value of the last known
371         * {@link #getCurrentType()}.
372         * 
373         * @return The previous type of object on the stack, may be null.
374         */
375        public Class<?> getPreviousType()
376        {
377            if ( typeStack.isEmpty() )
378            {
379                return null;
380            }
381    
382            if ( typeStack.size() > 1 )
383            {
384                return typeStack.get( typeStack.size() - 2 );
385            }
386    
387            return null;
388        }
389    
390        public void setPreviousType( Class<?> type )
391        {
392            if ( typeStack.isEmpty() || typeStack.size() < 2 )
393            {
394                return;
395            }
396    
397            typeStack.set( typeStack.size() - 2, type );
398        }
399    
400        public Class<?> getFirstType()
401        {
402            if ( typeStack.isEmpty() )
403            {
404                return null;
405            }
406    
407            return typeStack.get( 0 );
408        }
409    
410        public void setCurrentNode( Node value )
411        {
412            currentNode = value;
413        }
414    
415        public Node getCurrentNode()
416        {
417            return currentNode;
418        }
419    
420        /**
421         * Gets the current Evaluation from the top of the stack. This is the Evaluation that is in process of evaluating.
422         */
423        public Evaluation getCurrentEvaluation()
424        {
425            return currentEvaluation;
426        }
427    
428        public void setCurrentEvaluation( Evaluation value )
429        {
430            currentEvaluation = value;
431        }
432    
433        /**
434         * Gets the root of the evaluation stack. This Evaluation contains the node representing the root expression and the
435         * source is the root source object.
436         */
437        public Evaluation getRootEvaluation()
438        {
439            return rootEvaluation;
440        }
441    
442        public void setRootEvaluation( Evaluation value )
443        {
444            rootEvaluation = value;
445        }
446    
447        /**
448         * Returns the Evaluation at the relative index given. This should be zero or a negative number as a relative
449         * reference back up the evaluation stack. Therefore getEvaluation(0) returns the current Evaluation.
450         */
451        public Evaluation getEvaluation( int relativeIndex )
452        {
453            Evaluation result = null;
454    
455            if ( relativeIndex <= 0 )
456            {
457                result = currentEvaluation;
458                while ( ( ++relativeIndex < 0 ) && ( result != null ) )
459                {
460                    result = result.getParent();
461                }
462            }
463            return result;
464        }
465    
466        /**
467         * Pushes a new Evaluation onto the stack. This is done before a node evaluates. When evaluation is complete it
468         * should be popped from the stack via <code>popEvaluation()</code>.
469         */
470        public void pushEvaluation( Evaluation value )
471        {
472            if ( currentEvaluation != null )
473            {
474                currentEvaluation.addChild( value );
475            }
476            else
477            {
478                setRootEvaluation( value );
479            }
480            setCurrentEvaluation( value );
481        }
482    
483        /**
484         * Pops the current Evaluation off of the top of the stack. This is done after a node has completed its evaluation.
485         */
486        public Evaluation popEvaluation()
487        {
488            Evaluation result;
489    
490            result = currentEvaluation;
491            setCurrentEvaluation( result.getParent() );
492            if ( currentEvaluation == null )
493            {
494                setLastEvaluation( getKeepLastEvaluation() ? result : null );
495                setRootEvaluation( null );
496                setCurrentNode( null );
497            }
498            return result;
499        }
500    
501        public int incrementLocalReferenceCounter()
502        {
503            return ++localReferenceCounter;
504        }
505    
506        public void addLocalReference( String key, LocalReference reference )
507        {
508            if ( localReferenceMap == null )
509            {
510                localReferenceMap = new LinkedHashMap<String, LocalReference>();
511            }
512    
513            localReferenceMap.put( key, reference );
514        }
515    
516        public Map<String, LocalReference> getLocalReferences()
517        {
518            return localReferenceMap;
519        }
520    
521        /* ================= Map interface ================= */
522        public int size()
523        {
524            return values.size();
525        }
526    
527        public boolean isEmpty()
528        {
529            return values.isEmpty();
530        }
531    
532        public boolean containsKey( Object key )
533        {
534            return values.containsKey( key );
535        }
536    
537        public boolean containsValue( Object value )
538        {
539            return values.containsValue( value );
540        }
541    
542        public Object get( Object key )
543        {
544            Object result = null;
545    
546            // FIXME: complexity is O(n)
547            if ( RESERVED_KEYS.contains( key ) )
548            {
549                if ( THIS_CONTEXT_KEY.equals( key ) )
550                {
551                    result = getCurrentObject();
552                }
553                else if ( ROOT_CONTEXT_KEY.equals( key ) )
554                {
555                    result = getRoot();
556                }
557                else if ( CONTEXT_CONTEXT_KEY.equals( key ) )
558                {
559                    result = this;
560                }
561                else if ( TRACE_EVALUATIONS_CONTEXT_KEY.equals( key ) )
562                {
563                    result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE;
564                }
565                else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
566                {
567                    result = getLastEvaluation();
568                }
569                else if ( KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
570                {
571                    result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE;
572                }
573                else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) )
574                {
575                    result = getClassResolver();
576                }
577                else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) )
578                {
579                    result = getTypeConverter();
580                }
581                else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) )
582                {
583                    result = getMemberAccess();
584                }
585            }
586            else
587            {
588                result = values.get( key );
589            }
590            return result;
591        }
592    
593        public Object put( String key, Object value )
594        {
595            Object result = null;
596    
597            // FIXME: complexity is O(n)
598            if ( RESERVED_KEYS.contains( key ) )
599            {
600                if ( CONTEXT_CONTEXT_KEY.equals( key ) )
601                {
602                    throw new IllegalArgumentException( "can't change " + CONTEXT_CONTEXT_KEY + " in context" );
603                }
604    
605                if ( THIS_CONTEXT_KEY.equals( key ) )
606                {
607                    result = getCurrentObject();
608                    setCurrentObject( value );
609                }
610                else if ( ROOT_CONTEXT_KEY.equals( key ) )
611                {
612                    result = getRoot();
613                    setRoot( value );
614                }
615                else if ( TRACE_EVALUATIONS_CONTEXT_KEY.equals( key ) )
616                {
617                    result = getTraceEvaluations() ? Boolean.TRUE : Boolean.FALSE;
618                    setTraceEvaluations( OgnlOps.booleanValue( value ) );
619                }
620                else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
621                {
622                    result = getLastEvaluation();
623                    lastEvaluation = (Evaluation) value;
624                }
625                else if ( KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
626                {
627                    result = getKeepLastEvaluation() ? Boolean.TRUE : Boolean.FALSE;
628                    setKeepLastEvaluation( OgnlOps.booleanValue( value ) );
629                }
630                else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) )
631                {
632                    result = getClassResolver();
633                    setClassResolver( (ClassResolver) value );
634                }
635                else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) )
636                {
637                    result = getTypeConverter();
638                    setTypeConverter( (TypeConverter) value );
639                }
640                else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) )
641                {
642                    result = getMemberAccess();
643                    setMemberAccess( (MemberAccess) value );
644                }
645            }
646            else
647            {
648                result = values.put( key, value );
649            }
650    
651            return result;
652        }
653    
654        public Object remove( Object key )
655        {
656            Object result = null;
657    
658            // FIXME: complexity is O(n)
659            if ( RESERVED_KEYS.contains( key ) )
660            {
661                if ( CONTEXT_CONTEXT_KEY.equals( key ) || TRACE_EVALUATIONS_CONTEXT_KEY.equals( key )
662                    || KEEP_LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
663                {
664                    throw new IllegalArgumentException( "can't remove " + key + " from context" );
665                }
666    
667                if ( THIS_CONTEXT_KEY.equals( key ) )
668                {
669                    result = getCurrentObject();
670                    setCurrentObject( null );
671                }
672                else if ( ROOT_CONTEXT_KEY.equals( key ) )
673                {
674                    result = getRoot();
675                    setRoot( null );
676                }
677                else if ( LAST_EVALUATION_CONTEXT_KEY.equals( key ) )
678                {
679                    result = lastEvaluation;
680                    setLastEvaluation( null );
681                }
682                else if ( CLASS_RESOLVER_CONTEXT_KEY.equals( key ) )
683                {
684                    result = getClassResolver();
685                    setClassResolver( null );
686                }
687                else if ( TYPE_CONVERTER_CONTEXT_KEY.equals( key ) )
688                {
689                    result = getTypeConverter();
690                    setTypeConverter( null );
691                }
692                else if ( MEMBER_ACCESS_CONTEXT_KEY.equals( key ) )
693                {
694                    result = getMemberAccess();
695                    setMemberAccess( null );
696                }
697            }
698            else
699            {
700                result = values.remove( key );
701            }
702            return result;
703        }
704    
705        public void putAll( Map<? extends String, ?> t )
706        {
707            for ( Entry<? extends String, ?> entry : t.entrySet() )
708            {
709                put( entry.getKey(), entry.getValue() );
710            }
711        }
712    
713        public void clear()
714        {
715            values.clear();
716            typeStack.clear();
717            accessorStack.clear();
718    
719            localReferenceCounter = 0;
720            if ( localReferenceMap != null )
721            {
722                localReferenceMap.clear();
723            }
724    
725            setRoot( null );
726            setCurrentObject( null );
727            setRootEvaluation( null );
728            setCurrentEvaluation( null );
729            setLastEvaluation( null );
730            setCurrentNode( null );
731            setClassResolver( DEFAULT_CLASS_RESOLVER );
732            setTypeConverter( DEFAULT_TYPE_CONVERTER );
733            setMemberAccess( DEFAULT_MEMBER_ACCESS );
734        }
735    
736        public Set<String> keySet()
737        {
738            /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
739            return values.keySet();
740        }
741    
742        public Collection<Object> values()
743        {
744            /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
745            return values.values();
746        }
747    
748        public Set<Entry<String, Object>> entrySet()
749        {
750            /* Should root, currentObject, classResolver, typeConverter & memberAccess be included here? */
751            return values.entrySet();
752        }
753    
754        @Override
755        public boolean equals( Object o )
756        {
757            return values.equals( o );
758        }
759    
760        @Override
761        public int hashCode()
762        {
763            return values.hashCode();
764        }
765    }