public static T coerce()

in brooklyn-server/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java [137:273]


    public static <T> T coerce(Object value, TypeToken<T> targetTypeToken) {
        if (value==null) return null;
        Class<? super T> targetType = targetTypeToken.getRawType();

        //recursive coercion of parameterized collections and map entries
        if (targetTypeToken.getType() instanceof ParameterizedType) {
            if (value instanceof Collection && Collection.class.isAssignableFrom(targetType)) {
                Type[] arguments = ((ParameterizedType) targetTypeToken.getType()).getActualTypeArguments();
                if (arguments.length != 1) {
                    throw new IllegalStateException("Unexpected number of parameters in collection type: " + arguments);
                }
                Collection coerced = Lists.newLinkedList();
                TypeToken<?> listEntryType = TypeToken.of(arguments[0]);
                for (Object entry : (Iterable<?>) value) {
                    coerced.add(coerce(entry, listEntryType));
                }
                if (Set.class.isAssignableFrom(targetType)) {
                    return (T) Sets.newLinkedHashSet(coerced);
                } else {
                    return (T) Lists.newArrayList(coerced);
                }
            } else if (value instanceof Map && Map.class.isAssignableFrom(targetType)) {
                Type[] arguments = ((ParameterizedType) targetTypeToken.getType()).getActualTypeArguments();
                if (arguments.length != 2) {
                    throw new IllegalStateException("Unexpected number of parameters in map type: " + arguments);
                }
                Map coerced = Maps.newLinkedHashMap();
                TypeToken<?> mapKeyType = TypeToken.of(arguments[0]);
                TypeToken<?> mapValueType = TypeToken.of(arguments[1]);
                for (Map.Entry entry : ((Map<?,?>) value).entrySet()) {
                    coerced.put(coerce(entry.getKey(), mapKeyType),  coerce(entry.getValue(), mapValueType));
                }
                return (T) Maps.newLinkedHashMap(coerced);
            }
        }

        if (targetType.isInstance(value)) return (T) value;

        // TODO use registry first?

        //deal with primitive->primitive casting
        if (isPrimitiveOrBoxer(targetType) && isPrimitiveOrBoxer(value.getClass())) {
            // Don't just rely on Java to do its normal casting later; if caller writes
            // long `l = coerce(new Integer(1), Long.class)` then letting java do its casting will fail,
            // because an Integer will not automatically be unboxed and cast to a long
            return castPrimitive(value, (Class<T>)targetType);
        }

        //deal with string->primitive
        if (value instanceof String && isPrimitiveOrBoxer(targetType)) {
            return stringToPrimitive((String)value, (Class<T>)targetType);
        }

        //deal with primitive->string
        if (isPrimitiveOrBoxer(value.getClass()) && targetType.equals(String.class)) {
            return (T) value.toString();
        }

        //look for value.asType where Type is castable to targetType
        String targetTypeSimpleName = getVerySimpleName(targetType);
        if (targetTypeSimpleName!=null && targetTypeSimpleName.length()>0) {
            for (Method m: value.getClass().getMethods()) {
                if (m.getName().startsWith("as") && m.getParameterTypes().length==0 &&
                        targetType.isAssignableFrom(m.getReturnType()) ) {
                    if (m.getName().equals("as"+getVerySimpleName(m.getReturnType()))) {
                        try {
                            return (T) m.invoke(value);
                        } catch (Exception e) {
                            throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): "+m.getName()+" adapting failed, "+e);
                        }
                    }
                }
            }
        }
        
        //now look for static TargetType.fromType(Type t) where value instanceof Type  
        for (Method m: targetType.getMethods()) {
            if (((m.getModifiers()&Modifier.STATIC)==Modifier.STATIC) && 
                    m.getName().startsWith("from") && m.getParameterTypes().length==1 &&
                    m.getParameterTypes()[0].isInstance(value)) {
                if (m.getName().equals("from"+getVerySimpleName(m.getParameterTypes()[0]))) {
                    try {
                        return (T) m.invoke(null, value);
                    } catch (Exception e) {
                        throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): "+m.getName()+" adapting failed, "+e);
                    }
                }
            }
        }
        
       //ENHANCEMENT could look in type hierarchy of both types for a conversion method...
        
        //primitives get run through again boxed up
        Class boxedT = UNBOXED_TO_BOXED_TYPES.get(targetType);
        Class boxedVT = UNBOXED_TO_BOXED_TYPES.get(value.getClass());
        if (boxedT!=null || boxedVT!=null) {
            try {
                if (boxedT==null) boxedT=targetType;
                Object boxedV;
                if (boxedVT==null) { boxedV = value; }
                else { boxedV = boxedVT.getConstructor(value.getClass()).newInstance(value); }
                return (T) coerce(boxedV, boxedT);
            } catch (Exception e) {
                throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): unboxing failed, "+e);
            }
        }

        //for enums call valueOf with the string representation of the value
        if (targetType.isEnum()) {
            T result = (T) stringToEnum((Class<Enum>) targetType, null).apply(String.valueOf(value));
            if (result != null) return result;
        }

        //now look in registry
        synchronized (TypeCoercions.class) {
            Map<Class, Function> adapters = registry.row(targetType);
            for (Map.Entry<Class, Function> entry : adapters.entrySet()) {
                if (entry.getKey().isInstance(value)) {
                    T result = (T) entry.getValue().apply(value);
                    
                    // Check if need to unwrap again (e.g. if want List<Integer> and are given a String "1,2,3"
                    // then we'll have so far converted to List.of("1", "2", "3"). Call recursively.
                    // First check that value has changed, to avoid stack overflow!
                    if (!Objects.equal(value, result) && targetTypeToken.getType() instanceof ParameterizedType) {
                        // Could duplicate check for `result instanceof Collection` etc; but recursive call
                        // will be fine as if that doesn't match we'll safely reach `targetType.isInstance(value)`
                        // and just return the result.
                        return coerce(result, targetTypeToken);
                    }
                    return result;
                }
            }
        }

        //not found
        throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): no adapter known");
    }