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