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}