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.util.ArrayList;
020import java.util.Collections;
021import java.util.List;
022import java.util.StringJoiner;
023
024import org.springframework.asm.MethodVisitor;
025import org.springframework.expression.EvaluationException;
026import org.springframework.expression.TypedValue;
027import org.springframework.expression.spel.CodeFlow;
028import org.springframework.expression.spel.ExpressionState;
029import org.springframework.expression.spel.SpelNode;
030import org.springframework.lang.Nullable;
031import org.springframework.util.Assert;
032
033/**
034 * Represent a list in an expression, e.g. '{1,2,3}'
035 *
036 * @author Andy Clement
037 * @since 3.0.4
038 */
039public class InlineList extends SpelNodeImpl {
040
041        // If the list is purely literals, it is a constant value and can be computed and cached
042        @Nullable
043        private TypedValue constant;  // TODO must be immutable list
044
045
046        public InlineList(int startPos, int endPos, SpelNodeImpl... args) {
047                super(startPos, endPos, args);
048                checkIfConstant();
049        }
050
051
052        /**
053         * If all the components of the list are constants, or lists that themselves contain constants, then a constant list
054         * can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage
055         * created.
056         */
057        private void checkIfConstant() {
058                boolean isConstant = true;
059                for (int c = 0, max = getChildCount(); c < max; c++) {
060                        SpelNode child = getChild(c);
061                        if (!(child instanceof Literal)) {
062                                if (child instanceof InlineList) {
063                                        InlineList inlineList = (InlineList) child;
064                                        if (!inlineList.isConstant()) {
065                                                isConstant = false;
066                                        }
067                                }
068                                else {
069                                        isConstant = false;
070                                }
071                        }
072                }
073                if (isConstant) {
074                        List<Object> constantList = new ArrayList<>();
075                        int childcount = getChildCount();
076                        for (int c = 0; c < childcount; c++) {
077                                SpelNode child = getChild(c);
078                                if ((child instanceof Literal)) {
079                                        constantList.add(((Literal) child).getLiteralValue().getValue());
080                                }
081                                else if (child instanceof InlineList) {
082                                        constantList.add(((InlineList) child).getConstantValue());
083                                }
084                        }
085                        this.constant = new TypedValue(Collections.unmodifiableList(constantList));
086                }
087        }
088
089        @Override
090        public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
091                if (this.constant != null) {
092                        return this.constant;
093                }
094                else {
095                        int childCount = getChildCount();
096                        List<Object> returnValue = new ArrayList<>(childCount);
097                        for (int c = 0; c < childCount; c++) {
098                                returnValue.add(getChild(c).getValue(expressionState));
099                        }
100                        return new TypedValue(returnValue);
101                }
102        }
103
104        @Override
105        public String toStringAST() {
106                StringJoiner sj = new StringJoiner(",", "{", "}");
107                // String ast matches input string, not the 'toString()' of the resultant collection, which would use []
108                int count = getChildCount();
109                for (int c = 0; c < count; c++) {
110                        sj.add(getChild(c).toStringAST());
111                }
112                return sj.toString();
113        }
114
115        /**
116         * Return whether this list is a constant value.
117         */
118        public boolean isConstant() {
119                return (this.constant != null);
120        }
121
122        @SuppressWarnings("unchecked")
123        @Nullable
124        public List<Object> getConstantValue() {
125                Assert.state(this.constant != null, "No constant");
126                return (List<Object>) this.constant.getValue();
127        }
128
129        @Override
130        public boolean isCompilable() {
131                return isConstant();
132        }
133
134        @Override
135        public void generateCode(MethodVisitor mv, CodeFlow codeflow) {
136                final String constantFieldName = "inlineList$" + codeflow.nextFieldId();
137                final String className = codeflow.getClassName();
138
139                codeflow.registerNewField((cw, cflow) ->
140                                cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, constantFieldName, "Ljava/util/List;", null, null));
141
142                codeflow.registerNewClinit((mVisitor, cflow) ->
143                                generateClinitCode(className, constantFieldName, mVisitor, cflow, false));
144
145                mv.visitFieldInsn(GETSTATIC, className, constantFieldName, "Ljava/util/List;");
146                codeflow.pushDescriptor("Ljava/util/List");
147        }
148
149        void generateClinitCode(String clazzname, String constantFieldName, MethodVisitor mv, CodeFlow codeflow, boolean nested) {
150                mv.visitTypeInsn(NEW, "java/util/ArrayList");
151                mv.visitInsn(DUP);
152                mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "()V", false);
153                if (!nested) {
154                        mv.visitFieldInsn(PUTSTATIC, clazzname, constantFieldName, "Ljava/util/List;");
155                }
156                int childCount = getChildCount();
157                for (int c = 0; c < childCount; c++) {
158                        if (!nested) {
159                                mv.visitFieldInsn(GETSTATIC, clazzname, constantFieldName, "Ljava/util/List;");
160                        }
161                        else {
162                                mv.visitInsn(DUP);
163                        }
164                        // The children might be further lists if they are not constants. In this
165                        // situation do not call back into generateCode() because it will register another clinit adder.
166                        // Instead, directly build the list here:
167                        if (this.children[c] instanceof InlineList) {
168                                ((InlineList)this.children[c]).generateClinitCode(clazzname, constantFieldName, mv, codeflow, true);
169                        }
170                        else {
171                                this.children[c].generateCode(mv, codeflow);
172                                String lastDesc = codeflow.lastDescriptor();
173                                if (CodeFlow.isPrimitive(lastDesc)) {
174                                        CodeFlow.insertBoxIfNecessary(mv, lastDesc.charAt(0));
175                                }
176                        }
177                        mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
178                        mv.visitInsn(POP);
179                }
180        }
181
182}