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;
020import java.lang.reflect.Constructor;
021import java.lang.reflect.InvocationTargetException;
022import java.lang.reflect.Modifier;
023import java.util.ArrayList;
024import java.util.List;
025
026import org.springframework.asm.MethodVisitor;
027import org.springframework.core.convert.TypeDescriptor;
028import org.springframework.expression.AccessException;
029import org.springframework.expression.ConstructorExecutor;
030import org.springframework.expression.ConstructorResolver;
031import org.springframework.expression.EvaluationContext;
032import org.springframework.expression.EvaluationException;
033import org.springframework.expression.TypeConverter;
034import org.springframework.expression.TypedValue;
035import org.springframework.expression.common.ExpressionUtils;
036import org.springframework.expression.spel.CodeFlow;
037import org.springframework.expression.spel.ExpressionState;
038import org.springframework.expression.spel.SpelEvaluationException;
039import org.springframework.expression.spel.SpelMessage;
040import org.springframework.expression.spel.SpelNode;
041import org.springframework.expression.spel.support.ReflectiveConstructorExecutor;
042import org.springframework.lang.Nullable;
043import org.springframework.util.Assert;
044
045/**
046 * Represents the invocation of a constructor. Either a constructor on a regular type or
047 * construction of an array. When an array is constructed, an initializer can be specified.
048 *
049 * <p>Examples:<br>
050 * new String('hello world')<br>
051 * new int[]{1,2,3,4}<br>
052 * new int[3] new int[3]{1,2,3}
053 *
054 * @author Andy Clement
055 * @author Juergen Hoeller
056 * @since 3.0
057 */
058public class ConstructorReference extends SpelNodeImpl {
059
060        private boolean isArrayConstructor = false;
061
062        @Nullable
063        private SpelNodeImpl[] dimensions;
064
065        // TODO is this caching safe - passing the expression around will mean this executor is also being passed around
066        /** The cached executor that may be reused on subsequent evaluations. */
067        @Nullable
068        private volatile ConstructorExecutor cachedExecutor;
069
070
071        /**
072         * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
073         * call
074         */
075        public ConstructorReference(int startPos, int endPos, SpelNodeImpl... arguments) {
076                super(startPos, endPos, arguments);
077                this.isArrayConstructor = false;
078        }
079
080        /**
081         * Create a constructor reference. The first argument is the type, the rest are the parameters to the constructor
082         * call
083         */
084        public ConstructorReference(int startPos, int endPos, SpelNodeImpl[] dimensions, SpelNodeImpl... arguments) {
085                super(startPos, endPos, arguments);
086                this.isArrayConstructor = true;
087                this.dimensions = dimensions;
088        }
089
090
091        /**
092         * Implements getValue() - delegating to the code for building an array or a simple type.
093         */
094        @Override
095        public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
096                if (this.isArrayConstructor) {
097                        return createArray(state);
098                }
099                else {
100                        return createNewInstance(state);
101                }
102        }
103
104        /**
105         * Create a new ordinary object and return it.
106         * @param state the expression state within which this expression is being evaluated
107         * @return the new object
108         * @throws EvaluationException if there is a problem creating the object
109         */
110        private TypedValue createNewInstance(ExpressionState state) throws EvaluationException {
111                Object[] arguments = new Object[getChildCount() - 1];
112                List<TypeDescriptor> argumentTypes = new ArrayList<>(getChildCount() - 1);
113                for (int i = 0; i < arguments.length; i++) {
114                        TypedValue childValue = this.children[i + 1].getValueInternal(state);
115                        Object value = childValue.getValue();
116                        arguments[i] = value;
117                        argumentTypes.add(TypeDescriptor.forObject(value));
118                }
119
120                ConstructorExecutor executorToUse = this.cachedExecutor;
121                if (executorToUse != null) {
122                        try {
123                                return executorToUse.execute(state.getEvaluationContext(), arguments);
124                        }
125                        catch (AccessException ex) {
126                                // Two reasons this can occur:
127                                // 1. the method invoked actually threw a real exception
128                                // 2. the method invoked was not passed the arguments it expected and has become 'stale'
129
130                                // In the first case we should not retry, in the second case we should see if there is a
131                                // better suited method.
132
133                                // To determine which situation it is, the AccessException will contain a cause.
134                                // If the cause is an InvocationTargetException, a user exception was thrown inside the constructor.
135                                // Otherwise the constructor could not be invoked.
136                                if (ex.getCause() instanceof InvocationTargetException) {
137                                        // User exception was the root cause - exit now
138                                        Throwable rootCause = ex.getCause().getCause();
139                                        if (rootCause instanceof RuntimeException) {
140                                                throw (RuntimeException) rootCause;
141                                        }
142                                        else {
143                                                String typeName = (String) this.children[0].getValueInternal(state).getValue();
144                                                throw new SpelEvaluationException(getStartPosition(), rootCause,
145                                                                SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName,
146                                                                FormatHelper.formatMethodForMessage("", argumentTypes));
147                                        }
148                                }
149
150                                // At this point we know it wasn't a user problem so worth a retry if a better candidate can be found
151                                this.cachedExecutor = null;
152                        }
153                }
154
155                // Either there was no accessor or it no longer exists
156                String typeName = (String) this.children[0].getValueInternal(state).getValue();
157                Assert.state(typeName != null, "No type name");
158                executorToUse = findExecutorForConstructor(typeName, argumentTypes, state);
159                try {
160                        this.cachedExecutor = executorToUse;
161                        if (executorToUse instanceof ReflectiveConstructorExecutor) {
162                                this.exitTypeDescriptor = CodeFlow.toDescriptor(
163                                                ((ReflectiveConstructorExecutor) executorToUse).getConstructor().getDeclaringClass());
164
165                        }
166                        return executorToUse.execute(state.getEvaluationContext(), arguments);
167                }
168                catch (AccessException ex) {
169                        throw new SpelEvaluationException(getStartPosition(), ex,
170                                        SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName,
171                                        FormatHelper.formatMethodForMessage("", argumentTypes));
172                }
173        }
174
175        /**
176         * Go through the list of registered constructor resolvers and see if any can find a
177         * constructor that takes the specified set of arguments.
178         * @param typeName the type trying to be constructed
179         * @param argumentTypes the types of the arguments supplied that the constructor must take
180         * @param state the current state of the expression
181         * @return a reusable ConstructorExecutor that can be invoked to run the constructor or null
182         * @throws SpelEvaluationException if there is a problem locating the constructor
183         */
184        private ConstructorExecutor findExecutorForConstructor(String typeName,
185                        List<TypeDescriptor> argumentTypes, ExpressionState state) throws SpelEvaluationException {
186
187                EvaluationContext evalContext = state.getEvaluationContext();
188                List<ConstructorResolver> ctorResolvers = evalContext.getConstructorResolvers();
189                for (ConstructorResolver ctorResolver : ctorResolvers) {
190                        try {
191                                ConstructorExecutor ce = ctorResolver.resolve(state.getEvaluationContext(), typeName, argumentTypes);
192                                if (ce != null) {
193                                        return ce;
194                                }
195                        }
196                        catch (AccessException ex) {
197                                throw new SpelEvaluationException(getStartPosition(), ex,
198                                                SpelMessage.CONSTRUCTOR_INVOCATION_PROBLEM, typeName,
199                                                FormatHelper.formatMethodForMessage("", argumentTypes));
200                        }
201                }
202                throw new SpelEvaluationException(getStartPosition(), SpelMessage.CONSTRUCTOR_NOT_FOUND, typeName,
203                                FormatHelper.formatMethodForMessage("", argumentTypes));
204        }
205
206        @Override
207        public String toStringAST() {
208                StringBuilder sb = new StringBuilder("new ");
209                int index = 0;
210                sb.append(getChild(index++).toStringAST());
211                sb.append("(");
212                for (int i = index; i < getChildCount(); i++) {
213                        if (i > index) {
214                                sb.append(",");
215                        }
216                        sb.append(getChild(i).toStringAST());
217                }
218                sb.append(")");
219                return sb.toString();
220        }
221
222        /**
223         * Create an array and return it.
224         * @param state the expression state within which this expression is being evaluated
225         * @return the new array
226         * @throws EvaluationException if there is a problem creating the array
227         */
228        private TypedValue createArray(ExpressionState state) throws EvaluationException {
229                // First child gives us the array type which will either be a primitive or reference type
230                Object intendedArrayType = getChild(0).getValue(state);
231                if (!(intendedArrayType instanceof String)) {
232                        throw new SpelEvaluationException(getChild(0).getStartPosition(),
233                                        SpelMessage.TYPE_NAME_EXPECTED_FOR_ARRAY_CONSTRUCTION,
234                                        FormatHelper.formatClassNameForMessage(
235                                                        intendedArrayType != null ? intendedArrayType.getClass() : null));
236                }
237                String type = (String) intendedArrayType;
238                Class<?> componentType;
239                TypeCode arrayTypeCode = TypeCode.forName(type);
240                if (arrayTypeCode == TypeCode.OBJECT) {
241                        componentType = state.findType(type);
242                }
243                else {
244                        componentType = arrayTypeCode.getType();
245                }
246                Object newArray;
247                if (!hasInitializer()) {
248                        // Confirm all dimensions were specified (for example [3][][5] is missing the 2nd dimension)
249                        if (this.dimensions != null) {
250                                for (SpelNodeImpl dimension : this.dimensions) {
251                                        if (dimension == null) {
252                                                throw new SpelEvaluationException(getStartPosition(), SpelMessage.MISSING_ARRAY_DIMENSION);
253                                        }
254                                }
255                        }
256                        TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
257
258                        // Shortcut for 1 dimensional
259                        if (this.dimensions.length == 1) {
260                                TypedValue o = this.dimensions[0].getTypedValue(state);
261                                int arraySize = ExpressionUtils.toInt(typeConverter, o);
262                                newArray = Array.newInstance(componentType, arraySize);
263                        }
264                        else {
265                                // Multi-dimensional - hold onto your hat!
266                                int[] dims = new int[this.dimensions.length];
267                                for (int d = 0; d < this.dimensions.length; d++) {
268                                        TypedValue o = this.dimensions[d].getTypedValue(state);
269                                        dims[d] = ExpressionUtils.toInt(typeConverter, o);
270                                }
271                                newArray = Array.newInstance(componentType, dims);
272                        }
273                }
274                else {
275                        // There is an initializer
276                        if (this.dimensions == null || this.dimensions.length > 1) {
277                                // There is an initializer but this is a multi-dimensional array (e.g. new int[][]{{1,2},{3,4}}) - this
278                                // is not currently supported
279                                throw new SpelEvaluationException(getStartPosition(),
280                                                SpelMessage.MULTIDIM_ARRAY_INITIALIZER_NOT_SUPPORTED);
281                        }
282                        TypeConverter typeConverter = state.getEvaluationContext().getTypeConverter();
283                        InlineList initializer = (InlineList) getChild(1);
284                        // If a dimension was specified, check it matches the initializer length
285                        if (this.dimensions[0] != null) {
286                                TypedValue dValue = this.dimensions[0].getTypedValue(state);
287                                int i = ExpressionUtils.toInt(typeConverter, dValue);
288                                if (i != initializer.getChildCount()) {
289                                        throw new SpelEvaluationException(getStartPosition(), SpelMessage.INITIALIZER_LENGTH_INCORRECT);
290                                }
291                        }
292                        // Build the array and populate it
293                        int arraySize = initializer.getChildCount();
294                        newArray = Array.newInstance(componentType, arraySize);
295                        if (arrayTypeCode == TypeCode.OBJECT) {
296                                populateReferenceTypeArray(state, newArray, typeConverter, initializer, componentType);
297                        }
298                        else if (arrayTypeCode == TypeCode.BOOLEAN) {
299                                populateBooleanArray(state, newArray, typeConverter, initializer);
300                        }
301                        else if (arrayTypeCode == TypeCode.BYTE) {
302                                populateByteArray(state, newArray, typeConverter, initializer);
303                        }
304                        else if (arrayTypeCode == TypeCode.CHAR) {
305                                populateCharArray(state, newArray, typeConverter, initializer);
306                        }
307                        else if (arrayTypeCode == TypeCode.DOUBLE) {
308                                populateDoubleArray(state, newArray, typeConverter, initializer);
309                        }
310                        else if (arrayTypeCode == TypeCode.FLOAT) {
311                                populateFloatArray(state, newArray, typeConverter, initializer);
312                        }
313                        else if (arrayTypeCode == TypeCode.INT) {
314                                populateIntArray(state, newArray, typeConverter, initializer);
315                        }
316                        else if (arrayTypeCode == TypeCode.LONG) {
317                                populateLongArray(state, newArray, typeConverter, initializer);
318                        }
319                        else if (arrayTypeCode == TypeCode.SHORT) {
320                                populateShortArray(state, newArray, typeConverter, initializer);
321                        }
322                        else {
323                                throw new IllegalStateException(arrayTypeCode.name());
324                        }
325                }
326                return new TypedValue(newArray);
327        }
328
329        private void populateReferenceTypeArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
330                        InlineList initializer, Class<?> componentType) {
331
332                TypeDescriptor toTypeDescriptor = TypeDescriptor.valueOf(componentType);
333                Object[] newObjectArray = (Object[]) newArray;
334                for (int i = 0; i < newObjectArray.length; i++) {
335                        SpelNode elementNode = initializer.getChild(i);
336                        Object arrayEntry = elementNode.getValue(state);
337                        newObjectArray[i] = typeConverter.convertValue(arrayEntry,
338                                        TypeDescriptor.forObject(arrayEntry), toTypeDescriptor);
339                }
340        }
341
342        private void populateByteArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
343                        InlineList initializer) {
344
345                byte[] newByteArray = (byte[]) newArray;
346                for (int i = 0; i < newByteArray.length; i++) {
347                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
348                        newByteArray[i] = ExpressionUtils.toByte(typeConverter, typedValue);
349                }
350        }
351
352        private void populateFloatArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
353                        InlineList initializer) {
354
355                float[] newFloatArray = (float[]) newArray;
356                for (int i = 0; i < newFloatArray.length; i++) {
357                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
358                        newFloatArray[i] = ExpressionUtils.toFloat(typeConverter, typedValue);
359                }
360        }
361
362        private void populateDoubleArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
363                        InlineList initializer) {
364
365                double[] newDoubleArray = (double[]) newArray;
366                for (int i = 0; i < newDoubleArray.length; i++) {
367                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
368                        newDoubleArray[i] = ExpressionUtils.toDouble(typeConverter, typedValue);
369                }
370        }
371
372        private void populateShortArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
373                        InlineList initializer) {
374
375                short[] newShortArray = (short[]) newArray;
376                for (int i = 0; i < newShortArray.length; i++) {
377                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
378                        newShortArray[i] = ExpressionUtils.toShort(typeConverter, typedValue);
379                }
380        }
381
382        private void populateLongArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
383                        InlineList initializer) {
384
385                long[] newLongArray = (long[]) newArray;
386                for (int i = 0; i < newLongArray.length; i++) {
387                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
388                        newLongArray[i] = ExpressionUtils.toLong(typeConverter, typedValue);
389                }
390        }
391
392        private void populateCharArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
393                        InlineList initializer) {
394
395                char[] newCharArray = (char[]) newArray;
396                for (int i = 0; i < newCharArray.length; i++) {
397                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
398                        newCharArray[i] = ExpressionUtils.toChar(typeConverter, typedValue);
399                }
400        }
401
402        private void populateBooleanArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
403                        InlineList initializer) {
404
405                boolean[] newBooleanArray = (boolean[]) newArray;
406                for (int i = 0; i < newBooleanArray.length; i++) {
407                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
408                        newBooleanArray[i] = ExpressionUtils.toBoolean(typeConverter, typedValue);
409                }
410        }
411
412        private void populateIntArray(ExpressionState state, Object newArray, TypeConverter typeConverter,
413                        InlineList initializer) {
414
415                int[] newIntArray = (int[]) newArray;
416                for (int i = 0; i < newIntArray.length; i++) {
417                        TypedValue typedValue = initializer.getChild(i).getTypedValue(state);
418                        newIntArray[i] = ExpressionUtils.toInt(typeConverter, typedValue);
419                }
420        }
421
422        private boolean hasInitializer() {
423                return (getChildCount() > 1);
424        }
425
426        @Override
427        public boolean isCompilable() {
428                if (!(this.cachedExecutor instanceof ReflectiveConstructorExecutor) ||
429                        this.exitTypeDescriptor == null) {
430                        return false;
431                }
432
433                if (getChildCount() > 1) {
434                        for (int c = 1, max = getChildCount();c < max; c++) {
435                                if (!this.children[c].isCompilable()) {
436                                        return false;
437                                }
438                        }
439                }
440
441                ReflectiveConstructorExecutor executor = (ReflectiveConstructorExecutor) this.cachedExecutor;
442                if (executor == null) {
443                        return false;
444                }
445                Constructor<?> constructor = executor.getConstructor();
446                return (Modifier.isPublic(constructor.getModifiers()) &&
447                                Modifier.isPublic(constructor.getDeclaringClass().getModifiers()));
448        }
449
450        @Override
451        public void generateCode(MethodVisitor mv, CodeFlow cf) {
452                ReflectiveConstructorExecutor executor = ((ReflectiveConstructorExecutor) this.cachedExecutor);
453                Assert.state(executor != null, "No cached executor");
454
455                Constructor<?> constructor = executor.getConstructor();
456                String classDesc = constructor.getDeclaringClass().getName().replace('.', '/');
457                mv.visitTypeInsn(NEW, classDesc);
458                mv.visitInsn(DUP);
459
460                // children[0] is the type of the constructor, don't want to include that in argument processing
461                SpelNodeImpl[] arguments = new SpelNodeImpl[this.children.length - 1];
462                System.arraycopy(this.children, 1, arguments, 0, this.children.length - 1);
463                generateCodeForArguments(mv, cf, constructor, arguments);
464                mv.visitMethodInsn(INVOKESPECIAL, classDesc, "<init>", CodeFlow.createSignatureDescriptor(constructor), false);
465                cf.pushDescriptor(this.exitTypeDescriptor);
466        }
467
468}