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}