001/*
002 * Copyright 2002-2019 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.ast;
018
019import java.lang.reflect.Constructor;
020import java.lang.reflect.Member;
021import java.lang.reflect.Method;
022
023import org.springframework.asm.MethodVisitor;
024import org.springframework.asm.Opcodes;
025import org.springframework.expression.EvaluationException;
026import org.springframework.expression.TypedValue;
027import org.springframework.expression.common.ExpressionUtils;
028import org.springframework.expression.spel.CodeFlow;
029import org.springframework.expression.spel.ExpressionState;
030import org.springframework.expression.spel.SpelEvaluationException;
031import org.springframework.expression.spel.SpelMessage;
032import org.springframework.expression.spel.SpelNode;
033import org.springframework.lang.Nullable;
034import org.springframework.util.Assert;
035import org.springframework.util.ObjectUtils;
036
037/**
038 * The common supertype of all AST nodes in a parsed Spring Expression Language
039 * format expression.
040 *
041 * @author Andy Clement
042 * @author Juergen Hoeller
043 * @since 3.0
044 */
045public abstract class SpelNodeImpl implements SpelNode, Opcodes {
046
047        private static final SpelNodeImpl[] NO_CHILDREN = new SpelNodeImpl[0];
048
049
050        private final int startPos;
051
052        private final int endPos;
053
054        protected SpelNodeImpl[] children = SpelNodeImpl.NO_CHILDREN;
055
056        @Nullable
057        private SpelNodeImpl parent;
058
059        /**
060         * Indicates the type descriptor for the result of this expression node.
061         * This is set as soon as it is known. For a literal node it is known immediately.
062         * For a property access or method invocation it is known after one evaluation of
063         * that node.
064         * <p>The descriptor is like the bytecode form but is slightly easier to work with.
065         * It does not include the trailing semicolon (for non array reference types).
066         * Some examples: Ljava/lang/String, I, [I
067     */
068        @Nullable
069        protected volatile String exitTypeDescriptor;
070
071
072        public SpelNodeImpl(int startPos, int endPos, SpelNodeImpl... operands) {
073                this.startPos = startPos;
074                this.endPos = endPos;
075                if (!ObjectUtils.isEmpty(operands)) {
076                        this.children = operands;
077                        for (SpelNodeImpl operand : operands) {
078                                Assert.notNull(operand, "Operand must not be null");
079                                operand.parent = this;
080                        }
081                }
082        }
083
084
085        /**
086     * Return {@code true} if the next child is one of the specified classes.
087     */
088        protected boolean nextChildIs(Class<?>... classes) {
089                if (this.parent != null) {
090                        SpelNodeImpl[] peers = this.parent.children;
091                        for (int i = 0, max = peers.length; i < max; i++) {
092                                if (this == peers[i]) {
093                                        if (i + 1 >= max) {
094                                                return false;
095                                        }
096                                        Class<?> peerClass = peers[i + 1].getClass();
097                                        for (Class<?> desiredClass : classes) {
098                                                if (peerClass == desiredClass) {
099                                                        return true;
100                                                }
101                                        }
102                                        return false;
103                                }
104                        }
105                }
106                return false;
107        }
108
109        @Override
110        @Nullable
111        public final Object getValue(ExpressionState expressionState) throws EvaluationException {
112                return getValueInternal(expressionState).getValue();
113        }
114
115        @Override
116        public final TypedValue getTypedValue(ExpressionState expressionState) throws EvaluationException {
117                return getValueInternal(expressionState);
118        }
119
120        // by default Ast nodes are not writable
121        @Override
122        public boolean isWritable(ExpressionState expressionState) throws EvaluationException {
123                return false;
124        }
125
126        @Override
127        public void setValue(ExpressionState expressionState, @Nullable Object newValue) throws EvaluationException {
128                throw new SpelEvaluationException(getStartPosition(), SpelMessage.SETVALUE_NOT_SUPPORTED, getClass());
129        }
130
131        @Override
132        public SpelNode getChild(int index) {
133                return this.children[index];
134        }
135
136        @Override
137        public int getChildCount() {
138                return this.children.length;
139        }
140
141        @Override
142        @Nullable
143        public Class<?> getObjectClass(@Nullable Object obj) {
144                if (obj == null) {
145                        return null;
146                }
147                return (obj instanceof Class ? ((Class<?>) obj) : obj.getClass());
148        }
149
150        @Override
151        public int getStartPosition() {
152                return this.startPos;
153        }
154
155        @Override
156        public int getEndPosition() {
157                return this.endPos;
158        }
159
160        /**
161         * Check whether a node can be compiled to bytecode. The reasoning in each node may
162         * be different but will typically involve checking whether the exit type descriptor
163         * of the node is known and any relevant child nodes are compilable.
164         * @return {@code true} if this node can be compiled to bytecode
165         */
166        public boolean isCompilable() {
167                return false;
168        }
169
170        /**
171         * Generate the bytecode for this node into the supplied visitor. Context info about
172         * the current expression being compiled is available in the codeflow object, e.g.
173         * including information about the type of the object currently on the stack.
174         * @param mv the ASM MethodVisitor into which code should be generated
175         * @param cf a context object with info about what is on the stack
176         */
177        public void generateCode(MethodVisitor mv, CodeFlow cf) {
178                throw new IllegalStateException(getClass().getName() +" has no generateCode(..) method");
179        }
180
181        @Nullable
182        public String getExitDescriptor() {
183                return this.exitTypeDescriptor;
184        }
185
186        @Nullable
187        protected final <T> T getValue(ExpressionState state, Class<T> desiredReturnType) throws EvaluationException {
188                return ExpressionUtils.convertTypedValue(state.getEvaluationContext(), getValueInternal(state), desiredReturnType);
189        }
190
191        protected ValueRef getValueRef(ExpressionState state) throws EvaluationException {
192                throw new SpelEvaluationException(getStartPosition(), SpelMessage.NOT_ASSIGNABLE, toStringAST());
193        }
194
195        public abstract TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException;
196
197
198        /**
199         * Generate code that handles building the argument values for the specified method.
200         * This method will take account of whether the invoked method is a varargs method
201         * and if it is then the argument values will be appropriately packaged into an array.
202         * @param mv the method visitor where code should be generated
203         * @param cf the current codeflow
204         * @param member the method or constructor for which arguments are being setup
205         * @param arguments the expression nodes for the expression supplied argument values
206         */
207        protected static void generateCodeForArguments(MethodVisitor mv, CodeFlow cf, Member member, SpelNodeImpl[] arguments) {
208                String[] paramDescriptors = null;
209                boolean isVarargs = false;
210                if (member instanceof Constructor) {
211                        Constructor<?> ctor = (Constructor<?>) member;
212                        paramDescriptors = CodeFlow.toDescriptors(ctor.getParameterTypes());
213                        isVarargs = ctor.isVarArgs();
214                }
215                else { // Method
216                        Method method = (Method)member;
217                        paramDescriptors = CodeFlow.toDescriptors(method.getParameterTypes());
218                        isVarargs = method.isVarArgs();
219                }
220                if (isVarargs) {
221                        // The final parameter may or may not need packaging into an array, or nothing may
222                        // have been passed to satisfy the varargs and so something needs to be built.
223                        int p = 0; // Current supplied argument being processed
224                        int childCount = arguments.length;
225
226                        // Fulfill all the parameter requirements except the last one
227                        for (p = 0; p < paramDescriptors.length - 1; p++) {
228                                generateCodeForArgument(mv, cf, arguments[p], paramDescriptors[p]);
229                        }
230
231                        SpelNodeImpl lastChild = (childCount == 0 ? null : arguments[childCount - 1]);
232                        String arrayType = paramDescriptors[paramDescriptors.length - 1];
233                        // Determine if the final passed argument is already suitably packaged in array
234                        // form to be passed to the method
235                        if (lastChild != null && arrayType.equals(lastChild.getExitDescriptor())) {
236                                generateCodeForArgument(mv, cf, lastChild, paramDescriptors[p]);
237                        }
238                        else {
239                                arrayType = arrayType.substring(1); // trim the leading '[', may leave other '['
240                                // build array big enough to hold remaining arguments
241                                CodeFlow.insertNewArrayCode(mv, childCount - p, arrayType);
242                                // Package up the remaining arguments into the array
243                                int arrayindex = 0;
244                                while (p < childCount) {
245                                        SpelNodeImpl child = arguments[p];
246                                        mv.visitInsn(DUP);
247                                        CodeFlow.insertOptimalLoad(mv, arrayindex++);
248                                        generateCodeForArgument(mv, cf, child, arrayType);
249                                        CodeFlow.insertArrayStore(mv, arrayType);
250                                        p++;
251                                }
252                        }
253                }
254                else {
255                        for (int i = 0; i < paramDescriptors.length;i++) {
256                                generateCodeForArgument(mv, cf, arguments[i], paramDescriptors[i]);
257                        }
258                }
259        }
260
261        /**
262         * Ask an argument to generate its bytecode and then follow it up
263         * with any boxing/unboxing/checkcasting to ensure it matches the expected parameter descriptor.
264         */
265        protected static void generateCodeForArgument(MethodVisitor mv, CodeFlow cf, SpelNodeImpl argument, String paramDesc) {
266                cf.enterCompilationScope();
267                argument.generateCode(mv, cf);
268                String lastDesc = cf.lastDescriptor();
269                Assert.state(lastDesc != null, "No last descriptor");
270                boolean primitiveOnStack = CodeFlow.isPrimitive(lastDesc);
271                // Check if need to box it for the method reference?
272                if (primitiveOnStack && paramDesc.charAt(0) == 'L') {
273                        CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
274                }
275                else if (paramDesc.length() == 1 && !primitiveOnStack) {
276                        CodeFlow.insertUnboxInsns(mv, paramDesc.charAt(0), lastDesc);
277                }
278                else if (!paramDesc.equals(lastDesc)) {
279                        // This would be unnecessary in the case of subtyping (e.g. method takes Number but Integer passed in)
280                        CodeFlow.insertCheckCast(mv, paramDesc);
281                }
282                cf.exitCompilationScope();
283        }
284
285}