001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.expression.spel.support;
018
019import java.lang.reflect.Method;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.core.convert.TypeDescriptor;
026import org.springframework.expression.BeanResolver;
027import org.springframework.expression.ConstructorResolver;
028import org.springframework.expression.EvaluationContext;
029import org.springframework.expression.MethodFilter;
030import org.springframework.expression.MethodResolver;
031import org.springframework.expression.OperatorOverloader;
032import org.springframework.expression.PropertyAccessor;
033import org.springframework.expression.TypeComparator;
034import org.springframework.expression.TypeConverter;
035import org.springframework.expression.TypeLocator;
036import org.springframework.expression.TypedValue;
037import org.springframework.lang.Nullable;
038import org.springframework.util.Assert;
039
040/**
041 * A powerful and highly configurable {@link EvaluationContext} implementation.
042 * This context uses standard implementations of all applicable strategies,
043 * based on reflection to resolve properties, methods and fields.
044 *
045 * <p>For a simpler builder-style context variant for data-binding purposes,
046 * consider using {@link SimpleEvaluationContext} instead which allows for
047 * opting into several SpEL features as needed by specific evaluation cases.
048 *
049 * @author Andy Clement
050 * @author Juergen Hoeller
051 * @author Sam Brannen
052 * @since 3.0
053 * @see SimpleEvaluationContext
054 * @see ReflectivePropertyAccessor
055 * @see ReflectiveConstructorResolver
056 * @see ReflectiveMethodResolver
057 * @see StandardTypeLocator
058 * @see StandardTypeConverter
059 * @see StandardTypeComparator
060 * @see StandardOperatorOverloader
061 */
062public class StandardEvaluationContext implements EvaluationContext {
063
064        private TypedValue rootObject;
065
066        @Nullable
067        private volatile List<PropertyAccessor> propertyAccessors;
068
069        @Nullable
070        private volatile List<ConstructorResolver> constructorResolvers;
071
072        @Nullable
073        private volatile List<MethodResolver> methodResolvers;
074
075        @Nullable
076        private volatile ReflectiveMethodResolver reflectiveMethodResolver;
077
078        @Nullable
079        private BeanResolver beanResolver;
080
081        @Nullable
082        private TypeLocator typeLocator;
083
084        @Nullable
085        private TypeConverter typeConverter;
086
087        private TypeComparator typeComparator = new StandardTypeComparator();
088
089        private OperatorOverloader operatorOverloader = new StandardOperatorOverloader();
090
091        private final Map<String, Object> variables = new ConcurrentHashMap<>();
092
093
094        /**
095         * Create a {@code StandardEvaluationContext} with a null root object.
096         */
097        public StandardEvaluationContext() {
098                this.rootObject = TypedValue.NULL;
099        }
100
101        /**
102         * Create a {@code StandardEvaluationContext} with the given root object.
103         * @param rootObject the root object to use
104         * @see #setRootObject
105         */
106        public StandardEvaluationContext(@Nullable Object rootObject) {
107                this.rootObject = new TypedValue(rootObject);
108        }
109
110
111        public void setRootObject(@Nullable Object rootObject, TypeDescriptor typeDescriptor) {
112                this.rootObject = new TypedValue(rootObject, typeDescriptor);
113        }
114
115        public void setRootObject(@Nullable Object rootObject) {
116                this.rootObject = (rootObject != null ? new TypedValue(rootObject) : TypedValue.NULL);
117        }
118
119        @Override
120        public TypedValue getRootObject() {
121                return this.rootObject;
122        }
123
124        public void setPropertyAccessors(List<PropertyAccessor> propertyAccessors) {
125                this.propertyAccessors = propertyAccessors;
126        }
127
128        @Override
129        public List<PropertyAccessor> getPropertyAccessors() {
130                return initPropertyAccessors();
131        }
132
133        public void addPropertyAccessor(PropertyAccessor accessor) {
134                addBeforeDefault(initPropertyAccessors(), accessor);
135        }
136
137        public boolean removePropertyAccessor(PropertyAccessor accessor) {
138                return initPropertyAccessors().remove(accessor);
139        }
140
141        public void setConstructorResolvers(List<ConstructorResolver> constructorResolvers) {
142                this.constructorResolvers = constructorResolvers;
143        }
144
145        @Override
146        public List<ConstructorResolver> getConstructorResolvers() {
147                return initConstructorResolvers();
148        }
149
150        public void addConstructorResolver(ConstructorResolver resolver) {
151                addBeforeDefault(initConstructorResolvers(), resolver);
152        }
153
154        public boolean removeConstructorResolver(ConstructorResolver resolver) {
155                return initConstructorResolvers().remove(resolver);
156        }
157
158        public void setMethodResolvers(List<MethodResolver> methodResolvers) {
159                this.methodResolvers = methodResolvers;
160        }
161
162        @Override
163        public List<MethodResolver> getMethodResolvers() {
164                return initMethodResolvers();
165        }
166
167        public void addMethodResolver(MethodResolver resolver) {
168                addBeforeDefault(initMethodResolvers(), resolver);
169        }
170
171        public boolean removeMethodResolver(MethodResolver methodResolver) {
172                return initMethodResolvers().remove(methodResolver);
173        }
174
175        public void setBeanResolver(BeanResolver beanResolver) {
176                this.beanResolver = beanResolver;
177        }
178
179        @Override
180        @Nullable
181        public BeanResolver getBeanResolver() {
182                return this.beanResolver;
183        }
184
185        public void setTypeLocator(TypeLocator typeLocator) {
186                Assert.notNull(typeLocator, "TypeLocator must not be null");
187                this.typeLocator = typeLocator;
188        }
189
190        @Override
191        public TypeLocator getTypeLocator() {
192                if (this.typeLocator == null) {
193                        this.typeLocator = new StandardTypeLocator();
194                }
195                return this.typeLocator;
196        }
197
198        public void setTypeConverter(TypeConverter typeConverter) {
199                Assert.notNull(typeConverter, "TypeConverter must not be null");
200                this.typeConverter = typeConverter;
201        }
202
203        @Override
204        public TypeConverter getTypeConverter() {
205                if (this.typeConverter == null) {
206                        this.typeConverter = new StandardTypeConverter();
207                }
208                return this.typeConverter;
209        }
210
211        public void setTypeComparator(TypeComparator typeComparator) {
212                Assert.notNull(typeComparator, "TypeComparator must not be null");
213                this.typeComparator = typeComparator;
214        }
215
216        @Override
217        public TypeComparator getTypeComparator() {
218                return this.typeComparator;
219        }
220
221        public void setOperatorOverloader(OperatorOverloader operatorOverloader) {
222                Assert.notNull(operatorOverloader, "OperatorOverloader must not be null");
223                this.operatorOverloader = operatorOverloader;
224        }
225
226        @Override
227        public OperatorOverloader getOperatorOverloader() {
228                return this.operatorOverloader;
229        }
230
231        @Override
232        public void setVariable(@Nullable String name, @Nullable Object value) {
233                // For backwards compatibility, we ignore null names here...
234                // And since ConcurrentHashMap cannot store null values, we simply take null
235                // as a remove from the Map (with the same result from lookupVariable below).
236                if (name != null) {
237                        if (value != null) {
238                                this.variables.put(name, value);
239                        }
240                        else {
241                                this.variables.remove(name);
242                        }
243                }
244        }
245
246        public void setVariables(Map<String, Object> variables) {
247                variables.forEach(this::setVariable);
248        }
249
250        public void registerFunction(String name, Method method) {
251                this.variables.put(name, method);
252        }
253
254        @Override
255        @Nullable
256        public Object lookupVariable(String name) {
257                return this.variables.get(name);
258        }
259
260        /**
261         * Register a {@code MethodFilter} which will be called during method resolution
262         * for the specified type.
263         * <p>The {@code MethodFilter} may remove methods and/or sort the methods which
264         * will then be used by SpEL as the candidates to look through for a match.
265         * @param type the type for which the filter should be called
266         * @param filter a {@code MethodFilter}, or {@code null} to unregister a filter for the type
267         * @throws IllegalStateException if the {@link ReflectiveMethodResolver} is not in use
268         */
269        public void registerMethodFilter(Class<?> type, MethodFilter filter) throws IllegalStateException {
270                initMethodResolvers();
271                ReflectiveMethodResolver resolver = this.reflectiveMethodResolver;
272                if (resolver == null) {
273                        throw new IllegalStateException(
274                                        "Method filter cannot be set as the reflective method resolver is not in use");
275                }
276                resolver.registerMethodFilter(type, filter);
277        }
278
279
280        private List<PropertyAccessor> initPropertyAccessors() {
281                List<PropertyAccessor> accessors = this.propertyAccessors;
282                if (accessors == null) {
283                        accessors = new ArrayList<>(5);
284                        accessors.add(new ReflectivePropertyAccessor());
285                        this.propertyAccessors = accessors;
286                }
287                return accessors;
288        }
289
290        private List<ConstructorResolver> initConstructorResolvers() {
291                List<ConstructorResolver> resolvers = this.constructorResolvers;
292                if (resolvers == null) {
293                        resolvers = new ArrayList<>(1);
294                        resolvers.add(new ReflectiveConstructorResolver());
295                        this.constructorResolvers = resolvers;
296                }
297                return resolvers;
298        }
299
300        private List<MethodResolver> initMethodResolvers() {
301                List<MethodResolver> resolvers = this.methodResolvers;
302                if (resolvers == null) {
303                        resolvers = new ArrayList<>(1);
304                        this.reflectiveMethodResolver = new ReflectiveMethodResolver();
305                        resolvers.add(this.reflectiveMethodResolver);
306                        this.methodResolvers = resolvers;
307                }
308                return resolvers;
309        }
310
311        private static <T> void addBeforeDefault(List<T> resolvers, T resolver) {
312                resolvers.add(resolvers.size() - 1, resolver);
313        }
314
315}