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.context.expression;
018
019import java.util.Map;
020
021import org.springframework.core.DefaultParameterNameDiscoverer;
022import org.springframework.core.ParameterNameDiscoverer;
023import org.springframework.expression.Expression;
024import org.springframework.expression.spel.standard.SpelExpressionParser;
025import org.springframework.lang.Nullable;
026import org.springframework.util.Assert;
027import org.springframework.util.ObjectUtils;
028
029/**
030 * Shared utility class used to evaluate and cache SpEL expressions that
031 * are defined on {@link java.lang.reflect.AnnotatedElement}.
032 *
033 * @author Stephane Nicoll
034 * @since 4.2
035 * @see AnnotatedElementKey
036 */
037public abstract class CachedExpressionEvaluator {
038
039        private final SpelExpressionParser parser;
040
041        private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
042
043
044        /**
045         * Create a new instance with the specified {@link SpelExpressionParser}.
046         */
047        protected CachedExpressionEvaluator(SpelExpressionParser parser) {
048                Assert.notNull(parser, "SpelExpressionParser must not be null");
049                this.parser = parser;
050        }
051
052        /**
053         * Create a new instance with a default {@link SpelExpressionParser}.
054         */
055        protected CachedExpressionEvaluator() {
056                this(new SpelExpressionParser());
057        }
058
059
060        /**
061         * Return the {@link SpelExpressionParser} to use.
062         */
063        protected SpelExpressionParser getParser() {
064                return this.parser;
065        }
066
067        /**
068         * Return a shared parameter name discoverer which caches data internally.
069         * @since 4.3
070         */
071        protected ParameterNameDiscoverer getParameterNameDiscoverer() {
072                return this.parameterNameDiscoverer;
073        }
074
075
076        /**
077         * Return the {@link Expression} for the specified SpEL value
078         * <p>Parse the expression if it hasn't been already.
079         * @param cache the cache to use
080         * @param elementKey the element on which the expression is defined
081         * @param expression the expression to parse
082         */
083        protected Expression getExpression(Map<ExpressionKey, Expression> cache,
084                        AnnotatedElementKey elementKey, String expression) {
085
086                ExpressionKey expressionKey = createKey(elementKey, expression);
087                Expression expr = cache.get(expressionKey);
088                if (expr == null) {
089                        expr = getParser().parseExpression(expression);
090                        cache.put(expressionKey, expr);
091                }
092                return expr;
093        }
094
095        private ExpressionKey createKey(AnnotatedElementKey elementKey, String expression) {
096                return new ExpressionKey(elementKey, expression);
097        }
098
099
100        /**
101         * An expression key.
102         */
103        protected static class ExpressionKey implements Comparable<ExpressionKey> {
104
105                private final AnnotatedElementKey element;
106
107                private final String expression;
108
109                protected ExpressionKey(AnnotatedElementKey element, String expression) {
110                        Assert.notNull(element, "AnnotatedElementKey must not be null");
111                        Assert.notNull(expression, "Expression must not be null");
112                        this.element = element;
113                        this.expression = expression;
114                }
115
116                @Override
117                public boolean equals(@Nullable Object other) {
118                        if (this == other) {
119                                return true;
120                        }
121                        if (!(other instanceof ExpressionKey)) {
122                                return false;
123                        }
124                        ExpressionKey otherKey = (ExpressionKey) other;
125                        return (this.element.equals(otherKey.element) &&
126                                        ObjectUtils.nullSafeEquals(this.expression, otherKey.expression));
127                }
128
129                @Override
130                public int hashCode() {
131                        return this.element.hashCode() * 29 + this.expression.hashCode();
132                }
133
134                @Override
135                public String toString() {
136                        return this.element + " with expression \"" + this.expression + "\"";
137                }
138
139                @Override
140                public int compareTo(ExpressionKey other) {
141                        int result = this.element.toString().compareTo(other.element.toString());
142                        if (result == 0) {
143                                result = this.expression.compareTo(other.expression);
144                        }
145                        return result;
146                }
147        }
148
149}