View Javadoc
1   /*
2    * Copyright 2002-2014 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.springframework.expression.spel.ast;
18  
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.springframework.asm.ClassWriter;
24  import org.springframework.asm.MethodVisitor;
25  import org.springframework.expression.EvaluationException;
26  import org.springframework.expression.TypedValue;
27  import org.springframework.expression.spel.CodeFlow;
28  import org.springframework.expression.spel.ExpressionState;
29  import org.springframework.expression.spel.SpelNode;
30  
31  /**
32   * Represent a list in an expression, e.g. '{1,2,3}'
33   *
34   * @author Andy Clement
35   * @since 3.0.4
36   */
37  public class InlineList extends SpelNodeImpl {
38  
39  	// If the list is purely literals, it is a constant value and can be computed and cached
40  	private TypedValue constant = null;  // TODO must be immutable list
41  
42  
43  	public InlineList(int pos, SpelNodeImpl... args) {
44  		super(pos, args);
45  		checkIfConstant();
46  	}
47  
48  
49  	/**
50  	 * If all the components of the list are constants, or lists that themselves contain constants, then a constant list
51  	 * can be built to represent this node. This will speed up later getValue calls and reduce the amount of garbage
52  	 * created.
53  	 */
54  	private void checkIfConstant() {
55  		boolean isConstant = true;
56  		for (int c = 0, max = getChildCount(); c < max; c++) {
57  			SpelNode child = getChild(c);
58  			if (!(child instanceof Literal)) {
59  				if (child instanceof InlineList) {
60  					InlineList inlineList = (InlineList) child;
61  					if (!inlineList.isConstant()) {
62  						isConstant = false;
63  					}
64  				}
65  				else {
66  					isConstant = false;
67  				}
68  			}
69  		}
70  		if (isConstant) {
71  			List<Object> constantList = new ArrayList<Object>();
72  			int childcount = getChildCount();
73  			for (int c = 0; c < childcount; c++) {
74  				SpelNode child = getChild(c);
75  				if ((child instanceof Literal)) {
76  					constantList.add(((Literal) child).getLiteralValue().getValue());
77  				}
78  				else if (child instanceof InlineList) {
79  					constantList.add(((InlineList) child).getConstantValue());
80  				}
81  			}
82  			this.constant = new TypedValue(Collections.unmodifiableList(constantList));
83  		}
84  	}
85  
86  	@Override
87  	public TypedValue getValueInternal(ExpressionState expressionState) throws EvaluationException {
88  		if (this.constant != null) {
89  			return this.constant;
90  		}
91  		else {
92  			List<Object> returnValue = new ArrayList<Object>();
93  			int childCount = getChildCount();
94  			for (int c = 0; c < childCount; c++) {
95  				returnValue.add(getChild(c).getValue(expressionState));
96  			}
97  			return new TypedValue(returnValue);
98  		}
99  	}
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 clazzname = 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(clazzname,constantFieldName, mv,codeflow,false);
147 			}
148 		});
149 		
150 		mv.visitFieldInsn(GETSTATIC, clazzname, 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 }