net.sf.juffrou.reflect.JuffrouTypeConverterDelegate.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.juffrou.reflect.JuffrouTypeConverterDelegate.java

Source

/*
 * Copyright 2002-2012 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.sf.juffrou.reflect;

import java.beans.PropertyEditor;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.PropertyAccessor;
import org.springframework.beans.PropertyEditorRegistrySupport;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.CollectionFactory;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

/**
 * Internal helper class for converting property values to target types.
 * <p>
 * Works on a given {@link PropertyEditorRegistrySupport} instance. Used as a delegate by {@link BeanWrapperImpl} and
 * {@link SimpleTypeConverter}.
 * 
 * @author Juergen Hoeller
 * @author Rob Harrop
 * @author Dave Syer
 * @since 2.0
 * @see BeanWrapperImpl
 * @see SimpleTypeConverter
 */
class JuffrouTypeConverterDelegate {

    private static final Log logger = LogFactory.getLog(JuffrouTypeConverterDelegate.class);

    private final JuffrouSpringBeanWrapper propertyEditorRegistry;

    private final Object targetObject;

    /**
     * Create a new TypeConverterDelegate for the given editor registry.
     * 
     * @param propertyEditorRegistry
     *            the editor registry to use
     */
    public JuffrouTypeConverterDelegate(JuffrouSpringBeanWrapper propertyEditorRegistry) {
        this(propertyEditorRegistry, null);
    }

    /**
     * Create a new TypeConverterDelegate for the given editor registry and bean instance.
     * 
     * @param propertyEditorRegistry
     *            the editor registry to use
     * @param targetObject
     *            the target object to work on (as context that can be passed to editors)
     */
    public JuffrouTypeConverterDelegate(JuffrouSpringBeanWrapper propertyEditorRegistry, Object targetObject) {
        this.propertyEditorRegistry = propertyEditorRegistry;
        this.targetObject = targetObject;
    }

    /**
     * Convert the value to the specified required type.
     * 
     * @param newValue
     *            the proposed new value
     * @param requiredType
     *            the type we must convert to (or <code>null</code> if not known, for example in case of a collection
     *            element)
     * @param methodParam
     *            the method parameter that is the target of the conversion (may be <code>null</code>)
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException
     *             if type conversion failed
     */
    public <T> T convertIfNecessary(Object newValue, Class<T> requiredType, MethodParameter methodParam)
            throws IllegalArgumentException {

        return convertIfNecessary(null, null, newValue, requiredType,
                (methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
    }

    /**
     * Convert the value to the required type for the specified property.
     * 
     * @param propertyName
     *            name of the property
     * @param oldValue
     *            the previous value, if available (may be <code>null</code>)
     * @param newValue
     *            the proposed new value
     * @param requiredType
     *            the type we must convert to (or <code>null</code> if not known, for example in case of a collection
     *            element)
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException
     *             if type conversion failed
     */
    public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType)
            throws IllegalArgumentException {

        return convertIfNecessary(propertyName, oldValue, newValue, requiredType,
                TypeDescriptor.valueOf(requiredType));
    }

    /**
     * Convert the value to the required type (if necessary from a String), for the specified property.
     * 
     * @param propertyName
     *            name of the property
     * @param oldValue
     *            the previous value, if available (may be <code>null</code>)
     * @param newValue
     *            the proposed new value
     * @param requiredType
     *            the type we must convert to (or <code>null</code> if not known, for example in case of a collection
     *            element)
     * @param typeDescriptor
     *            the descriptor for the target property or field
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException
     *             if type conversion failed
     */
    @SuppressWarnings("unchecked")
    public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType,
            TypeDescriptor typeDescriptor) throws IllegalArgumentException {

        Object convertedValue = newValue;

        // Custom editor for this type?
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException firstAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && convertedValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            TypeDescriptor targetTypeDesc = typeDescriptor;
            if (conversionService.canConvert(sourceTypeDesc, targetTypeDesc)) {
                try {
                    return (T) conversionService.convert(convertedValue, sourceTypeDesc, targetTypeDesc);
                } catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    firstAttemptEx = ex;
                }
            }
        }

        // Value not of required type?
        if (editor != null
                || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            if (requiredType != null && Collection.class.isAssignableFrom(requiredType)
                    && convertedValue instanceof String) {
                TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
                if (elementType != null && Enum.class.isAssignableFrom(elementType.getType())) {
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
            }
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;

        if (requiredType != null) {
            // Try to apply some standard type conversion rules if appropriate.

            if (convertedValue != null) {
                if (requiredType.isArray()) {
                    // Array required -> apply appropriate conversion of elements.
                    if (convertedValue instanceof String
                            && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                    return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
                } else if (convertedValue instanceof Collection) {
                    // Convert elements to target type, if determined.
                    convertedValue = convertToTypedCollection((Collection) convertedValue, propertyName,
                            requiredType, typeDescriptor);
                    standardConversion = true;
                } else if (convertedValue instanceof Map) {
                    // Convert keys and values to respective target type, if determined.
                    convertedValue = convertToTypedMap((Map) convertedValue, propertyName, requiredType,
                            typeDescriptor);
                    standardConversion = true;
                }
                if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                    convertedValue = Array.get(convertedValue, 0);
                    standardConversion = true;
                }
                if (String.class.equals(requiredType)
                        && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                    // We can stringify any primitive value...
                    return (T) convertedValue.toString();
                } else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                    if (firstAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                        try {
                            Constructor strCtor = requiredType.getConstructor(String.class);
                            return (T) BeanUtils.instantiateClass(strCtor, convertedValue);
                        } catch (NoSuchMethodException ex) {
                            // proceed with field lookup
                            if (logger.isTraceEnabled()) {
                                logger.trace("No String constructor found on type [" + requiredType.getName() + "]",
                                        ex);
                            }
                        } catch (Exception ex) {
                            if (logger.isDebugEnabled()) {
                                logger.debug(
                                        "Construction via String failed for type [" + requiredType.getName() + "]",
                                        ex);
                            }
                        }
                    }
                    String trimmedValue = ((String) convertedValue).trim();
                    if (requiredType.isEnum() && "".equals(trimmedValue)) {
                        // It's an empty enum identifier: reset the enum value to null.
                        return null;
                    }
                    convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                    standardConversion = true;
                }
            }

            if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
                if (firstAttemptEx != null) {
                    throw firstAttemptEx;
                }
                // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type [").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("] to required type [").append(ClassUtils.getQualifiedName(requiredType)).append("]");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }
                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName())
                            .append("] returned inappropriate value of type [")
                            .append(ClassUtils.getDescriptiveType(convertedValue)).append("]");
                    throw new IllegalArgumentException(msg.toString());
                } else {
                    msg.append(": no matching editors or conversion strategy found");
                    throw new IllegalStateException(msg.toString());
                }
            }
        }

        if (firstAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null
                    && !Object.class.equals(requiredType)) {
                throw firstAttemptEx;
            }
            logger.debug("Original ConversionService attempt failed - ignored since "
                    + "PropertyEditor based conversion eventually succeeded", firstAttemptEx);
        }

        return (T) convertedValue;
    }

    private Object attemptToConvertStringToEnum(Class<?> requiredType, String trimmedValue,
            Object currentConvertedValue) {
        Object convertedValue = currentConvertedValue;

        if (Enum.class.equals(requiredType)) {
            // target type is declared as raw enum, treat the trimmed value as <enum.fqn>.FIELD_NAME
            int index = trimmedValue.lastIndexOf(".");
            if (index > -1) {
                String enumType = trimmedValue.substring(0, index);
                String fieldName = trimmedValue.substring(index + 1);
                ClassLoader loader = this.targetObject.getClass().getClassLoader();
                try {
                    Class<?> enumValueType = loader.loadClass(enumType);
                    Field enumField = enumValueType.getField(fieldName);
                    convertedValue = enumField.get(null);
                } catch (ClassNotFoundException ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Enum class [" + enumType + "] cannot be loaded from [" + loader + "]", ex);
                    }
                } catch (Throwable ex) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Field [" + fieldName + "] isn't an enum value for type [" + enumType + "]",
                                ex);
                    }
                }
            }
        }

        if (convertedValue == currentConvertedValue) {
            // Try field lookup as fallback: for JDK 1.5 enum or custom enum
            // with values defined as static fields. Resulting value still needs
            // to be checked, hence we don't return it right away.
            try {
                Field enumField = requiredType.getField(trimmedValue);
                convertedValue = enumField.get(null);
            } catch (Throwable ex) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Field [" + convertedValue + "] isn't an enum value", ex);

                }
            }
        }

        return convertedValue;
    }

    /**
     * Find a default editor for the given type.
     * 
     * @param requiredType
     *            the type to find an editor for
     * @return the corresponding editor, or <code>null</code> if none
     */
    private PropertyEditor findDefaultEditor(Class requiredType) {
        PropertyEditor editor = null;
        if (requiredType != null) {
            // No custom editor -> check BeanWrapperImpl's default editors.
            editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
            if (editor == null && !String.class.equals(requiredType)) {
                // No BeanWrapper default editor -> check standard JavaBean editor.
                editor = BeanUtils.findEditorByConvention(requiredType);
            }
        }
        return editor;
    }

    /**
     * Convert the value to the required type (if necessary from a String), using the given property editor.
     * 
     * @param oldValue
     *            the previous value, if available (may be <code>null</code>)
     * @param newValue
     *            the proposed new value
     * @param requiredType
     *            the type we must convert to (or <code>null</code> if not known, for example in case of a collection
     *            element)
     * @param editor
     *            the PropertyEditor to use
     * @return the new value, possibly the result of type conversion
     * @throws IllegalArgumentException
     *             if type conversion failed
     */
    private Object doConvertValue(Object oldValue, Object newValue, Class<?> requiredType, PropertyEditor editor) {
        Object convertedValue = newValue;
        boolean sharedEditor = false;

        if (editor != null) {
            sharedEditor = this.propertyEditorRegistry.isSharedEditor(editor);
        }

        if (editor != null && !(convertedValue instanceof String)) {
            // Not a String -> use PropertyEditor's setValue.
            // With standard PropertyEditors, this will return the very same object;
            // we just want to allow special PropertyEditors to override setValue
            // for type conversion from non-String values to the required type.
            try {
                Object newConvertedValue;
                if (sharedEditor) {
                    // Synchronized access to shared editor instance.
                    synchronized (editor) {
                        editor.setValue(convertedValue);
                        newConvertedValue = editor.getValue();
                    }
                } else {
                    // Unsynchronized access to non-shared editor instance.
                    editor.setValue(convertedValue);
                    newConvertedValue = editor.getValue();
                }
                if (newConvertedValue != convertedValue) {
                    convertedValue = newConvertedValue;
                    // Reset PropertyEditor: It already did a proper conversion.
                    // Don't use it again for a setAsText call.
                    editor = null;
                }
            } catch (Exception ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                            "PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call",
                            ex);
                }
                // Swallow and proceed.
            }
        }

        Object returnValue = convertedValue;

        if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
            // Convert String array to a comma-separated String.
            // Only applies if no PropertyEditor converted the String array before.
            // The CSV String will be passed into a PropertyEditor's setAsText method, if any.
            if (logger.isTraceEnabled()) {
                logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
            }
            convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
        }

        if (convertedValue instanceof String) {
            if (editor != null) {
                // Use PropertyEditor's setAsText in case of a String value.
                if (logger.isTraceEnabled()) {
                    logger.trace(
                            "Converting String to [" + requiredType + "] using property editor [" + editor + "]");
                }
                String newTextValue = (String) convertedValue;
                if (sharedEditor) {
                    // Synchronized access to shared editor instance.
                    synchronized (editor) {
                        return doConvertTextValue(oldValue, newTextValue, editor);
                    }
                } else {
                    // Unsynchronized access to non-shared editor instance.
                    return doConvertTextValue(oldValue, newTextValue, editor);
                }
            } else if (String.class.equals(requiredType)) {
                returnValue = convertedValue;
            }
        }

        return returnValue;
    }

    /**
     * Convert the given text value using the given property editor.
     * 
     * @param oldValue
     *            the previous value, if available (may be <code>null</code>)
     * @param newTextValue
     *            the proposed text value
     * @param editor
     *            the PropertyEditor to use
     * @return the converted value
     */
    private Object doConvertTextValue(Object oldValue, String newTextValue, PropertyEditor editor) {
        try {
            editor.setValue(oldValue);
        } catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call",
                        ex);
            }
            // Swallow and proceed.
        }
        editor.setAsText(newTextValue);
        return editor.getValue();
    }

    private Object convertToTypedArray(Object input, String propertyName, Class<?> componentType) {
        if (input instanceof Collection) {
            // Convert Collection elements to array elements.
            Collection coll = (Collection) input;
            Object result = Array.newInstance(componentType, coll.size());
            int i = 0;
            for (Iterator it = coll.iterator(); it.hasNext(); i++) {
                Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, i), null, it.next(),
                        componentType);
                Array.set(result, i, value);
            }
            return result;
        } else if (input.getClass().isArray()) {
            // Convert array elements, if necessary.
            if (componentType.equals(input.getClass().getComponentType())
                    && !this.propertyEditorRegistry.hasCustomEditorForElement(componentType, propertyName)) {
                return input;
            }
            int arrayLength = Array.getLength(input);
            Object result = Array.newInstance(componentType, arrayLength);
            for (int i = 0; i < arrayLength; i++) {
                Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, i), null,
                        Array.get(input, i), componentType);
                Array.set(result, i, value);
            }
            return result;
        } else {
            // A plain value: convert it to an array with a single component.
            Object result = Array.newInstance(componentType, 1);
            Object value = convertIfNecessary(buildIndexedPropertyName(propertyName, 0), null, input,
                    componentType);
            Array.set(result, 0, value);
            return result;
        }
    }

    @SuppressWarnings("unchecked")
    private Collection convertToTypedCollection(Collection original, String propertyName, Class requiredType,
            TypeDescriptor typeDescriptor) {

        if (!Collection.class.isAssignableFrom(requiredType)) {
            return original;
        }

        boolean approximable = CollectionFactory.isApproximableCollectionType(requiredType);
        if (!approximable && !canCreateCopy(requiredType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Custom Collection type [" + original.getClass().getName()
                        + "] does not allow for creating a copy - injecting original Collection as-is");
            }
            return original;
        }

        boolean originalAllowed = requiredType.isInstance(original);
        typeDescriptor = typeDescriptor.narrow(original);
        TypeDescriptor elementType = typeDescriptor.getElementTypeDescriptor();
        if (elementType == null && originalAllowed
                && !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
            return original;
        }

        Iterator it;
        try {
            it = original.iterator();
            if (it == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Collection of type [" + original.getClass().getName()
                            + "] returned null Iterator - injecting original Collection as-is");
                }
                return original;
            }
        } catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot access Collection of type [" + original.getClass().getName()
                        + "] - injecting original Collection as-is: " + ex);
            }
            return original;
        }

        Collection convertedCopy;
        try {
            if (approximable) {
                convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
            } else {
                convertedCopy = (Collection) requiredType.newInstance();
            }
        } catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot create copy of Collection type [" + original.getClass().getName()
                        + "] - injecting original Collection as-is: " + ex);
            }
            return original;
        }

        int i = 0;
        for (; it.hasNext(); i++) {
            Object element = it.next();
            String indexedPropertyName = buildIndexedPropertyName(propertyName, i);
            Object convertedElement = convertIfNecessary(indexedPropertyName, null, element,
                    (elementType != null ? elementType.getType() : null), elementType);
            try {
                convertedCopy.add(convertedElement);
            } catch (Throwable ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Collection type [" + original.getClass().getName()
                            + "] seems to be read-only - injecting original Collection as-is: " + ex);
                }
                return original;
            }
            originalAllowed = originalAllowed && (element == convertedElement);
        }
        return (originalAllowed ? original : convertedCopy);
    }

    @SuppressWarnings("unchecked")
    private Map convertToTypedMap(Map original, String propertyName, Class requiredType,
            TypeDescriptor typeDescriptor) {

        if (!Map.class.isAssignableFrom(requiredType)) {
            return original;
        }

        boolean approximable = CollectionFactory.isApproximableMapType(requiredType);
        if (!approximable && !canCreateCopy(requiredType)) {
            if (logger.isDebugEnabled()) {
                logger.debug("Custom Map type [" + original.getClass().getName()
                        + "] does not allow for creating a copy - injecting original Map as-is");
            }
            return original;
        }

        boolean originalAllowed = requiredType.isInstance(original);
        typeDescriptor = typeDescriptor.narrow(original);
        TypeDescriptor keyType = typeDescriptor.getMapKeyTypeDescriptor();
        TypeDescriptor valueType = typeDescriptor.getMapValueTypeDescriptor();
        if (keyType == null && valueType == null && originalAllowed
                && !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
            return original;
        }

        Iterator it;
        try {
            it = original.entrySet().iterator();
            if (it == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Map of type [" + original.getClass().getName()
                            + "] returned null Iterator - injecting original Map as-is");
                }
                return original;
            }
        } catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot access Map of type [" + original.getClass().getName()
                        + "] - injecting original Map as-is: " + ex);
            }
            return original;
        }

        Map convertedCopy;
        try {
            if (approximable) {
                convertedCopy = CollectionFactory.createApproximateMap(original, original.size());
            } else {
                convertedCopy = (Map) requiredType.newInstance();
            }
        } catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Cannot create copy of Map type [" + original.getClass().getName()
                        + "] - injecting original Map as-is: " + ex);
            }
            return original;
        }

        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry) it.next();
            Object key = entry.getKey();
            Object value = entry.getValue();
            String keyedPropertyName = buildKeyedPropertyName(propertyName, key);
            Object convertedKey = convertIfNecessary(keyedPropertyName, null, key,
                    (keyType != null ? keyType.getType() : null), keyType);
            Object convertedValue = convertIfNecessary(keyedPropertyName, null, value,
                    (valueType != null ? valueType.getType() : null), valueType);
            try {
                convertedCopy.put(convertedKey, convertedValue);
            } catch (Throwable ex) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Map type [" + original.getClass().getName()
                            + "] seems to be read-only - injecting original Map as-is: " + ex);
                }
                return original;
            }
            originalAllowed = originalAllowed && (key == convertedKey) && (value == convertedValue);
        }
        return (originalAllowed ? original : convertedCopy);
    }

    private String buildIndexedPropertyName(String propertyName, int index) {
        return (propertyName != null
                ? propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + index + PropertyAccessor.PROPERTY_KEY_SUFFIX
                : null);
    }

    private String buildKeyedPropertyName(String propertyName, Object key) {
        return (propertyName != null
                ? propertyName + PropertyAccessor.PROPERTY_KEY_PREFIX + key + PropertyAccessor.PROPERTY_KEY_SUFFIX
                : null);
    }

    private boolean canCreateCopy(Class requiredType) {
        return (!requiredType.isInterface() && !Modifier.isAbstract(requiredType.getModifiers())
                && Modifier.isPublic(requiredType.getModifiers()) && ClassUtils.hasConstructor(requiredType));
    }

}