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