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.lang.reflect.Array;
020
021import org.springframework.asm.MethodVisitor;
022import org.springframework.asm.Type;
023import org.springframework.expression.EvaluationException;
024import org.springframework.expression.TypedValue;
025import org.springframework.expression.spel.CodeFlow;
026import org.springframework.expression.spel.ExpressionState;
027import org.springframework.lang.Nullable;
028import org.springframework.util.Assert;
029
030/**
031 * Represents a reference to a type, for example
032 * {@code "T(String)" or "T(com.somewhere.Foo)"}.
033 *
034 * @author Andy Clement
035 */
036public class TypeReference extends SpelNodeImpl {
037
038        private final int dimensions;
039
040        @Nullable
041        private transient Class<?> type;
042
043
044        public TypeReference(int startPos, int endPos, SpelNodeImpl qualifiedId) {
045                this(startPos, endPos, qualifiedId, 0);
046        }
047
048        public TypeReference(int startPos, int endPos, SpelNodeImpl qualifiedId, int dims) {
049                super(startPos, endPos, qualifiedId);
050                this.dimensions = dims;
051        }
052
053
054        @Override
055        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
056                // TODO possible optimization here if we cache the discovered type reference, but can we do that?
057                String typeName = (String) this.children[0].getValueInternal(state).getValue();
058                Assert.state(typeName != null, "No type name");
059                if (!typeName.contains(".") && Character.isLowerCase(typeName.charAt(0))) {
060                        TypeCode tc = TypeCode.valueOf(typeName.toUpperCase());
061                        if (tc != TypeCode.OBJECT) {
062                                // It is a primitive type
063                                Class<?> clazz = makeArrayIfNecessary(tc.getType());
064                                this.exitTypeDescriptor = "Ljava/lang/Class";
065                                this.type = clazz;
066                                return new TypedValue(clazz);
067                        }
068                }
069                Class<?> clazz = state.findType(typeName);
070                clazz = makeArrayIfNecessary(clazz);
071                this.exitTypeDescriptor = "Ljava/lang/Class";
072                this.type = clazz;
073                return new TypedValue(clazz);
074        }
075
076        private Class<?> makeArrayIfNecessary(Class<?> clazz) {
077                if (this.dimensions != 0) {
078                        for (int i = 0; i < this.dimensions; i++) {
079                                Object array = Array.newInstance(clazz, 0);
080                                clazz = array.getClass();
081                        }
082                }
083                return clazz;
084        }
085
086        @Override
087        public String toStringAST() {
088                StringBuilder sb = new StringBuilder("T(");
089                sb.append(getChild(0).toStringAST());
090                for (int d = 0; d < this.dimensions; d++) {
091                        sb.append("[]");
092                }
093                sb.append(")");
094                return sb.toString();
095        }
096
097        @Override
098        public boolean isCompilable() {
099                return (this.exitTypeDescriptor != null);
100        }
101
102        @Override
103        public void generateCode(MethodVisitor mv, CodeFlow cf) {
104                // TODO Future optimization - if followed by a static method call, skip generating code here
105                Assert.state(this.type != null, "No type available");
106                if (this.type.isPrimitive()) {
107                        if (this.type == Boolean.TYPE) {
108                                mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", "TYPE", "Ljava/lang/Class;");
109                        }
110                        else if (this.type == Byte.TYPE) {
111                                mv.visitFieldInsn(GETSTATIC, "java/lang/Byte", "TYPE", "Ljava/lang/Class;");
112                        }
113                        else if (this.type == Character.TYPE) {
114                                mv.visitFieldInsn(GETSTATIC, "java/lang/Character", "TYPE", "Ljava/lang/Class;");
115                        }
116                        else if (this.type == Double.TYPE) {
117                                mv.visitFieldInsn(GETSTATIC, "java/lang/Double", "TYPE", "Ljava/lang/Class;");
118                        }
119                        else if (this.type == Float.TYPE) {
120                                mv.visitFieldInsn(GETSTATIC, "java/lang/Float", "TYPE", "Ljava/lang/Class;");
121                        }
122                        else if (this.type == Integer.TYPE) {
123                                mv.visitFieldInsn(GETSTATIC, "java/lang/Integer", "TYPE", "Ljava/lang/Class;");
124                        }
125                        else if (this.type == Long.TYPE) {
126                                mv.visitFieldInsn(GETSTATIC, "java/lang/Long", "TYPE", "Ljava/lang/Class;");
127                        }
128                        else if (this.type == Short.TYPE) {
129                                mv.visitFieldInsn(GETSTATIC, "java/lang/Short", "TYPE", "Ljava/lang/Class;");
130                        }
131                }
132                else {
133                        mv.visitLdcInsn(Type.getType(this.type));
134                }
135                cf.pushDescriptor(this.exitTypeDescriptor);
136        }
137
138}