protected Maybe tryCoerceInternal2()

in utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java [123:258]


    protected <T> Maybe<T> tryCoerceInternal2(Object value, TypeToken<T> targetTypeToken, Class<T> targetType) {
        if (value==null) return Maybe.of((T)null);
        Maybe<T> result = null;
        List<Maybe<T>> errors = MutableList.of();

        //recursive coercion of parameterized collections and map entries
        targetType = TypeTokens.getRawType(targetTypeToken, targetType);
        if (targetTypeToken!=null && targetTypeToken.getType() instanceof ParameterizedType) {
            if (value instanceof Iterable && Iterable.class.isAssignableFrom(targetType)) {
                result = tryCoerceIterable(value, targetTypeToken, targetType);
            } else if (value.getClass().isArray() && Iterable.class.isAssignableFrom(targetType)) {
                result = tryCoerceArray(value, targetTypeToken, targetType);
            } else if (value instanceof Map && Map.class.isAssignableFrom(targetType)) {
                result = tryCoerceMap(value, targetTypeToken);
            }
            if (result!=null) {
                if (result.isPresent()) return result;
                // Previous to v1.0.0 we'd overlook errors in generics with warnings; now we bail
                TypeToken<T> targetTypeTokenF = targetTypeToken;
                RuntimeException e = Maybe.getException(result);
                return Maybe.absent(new AnyExceptionSupplier<>(ClassCoercionException.class,
                    () -> "Generic type mismatch coercing "+value.getClass().getName()+" to "+targetTypeTokenF+": "+Exceptions.collapseText(e), e));
            }
        }
        
        if (targetType.isInstance(value)) return Maybe.of( (T) value );

        targetTypeToken = TypeTokens.getTypeToken(targetTypeToken, targetType);
        for (TryCoercer coercer : genericCoercers) {
            result = coercer.tryCoerce(value, targetTypeToken);
            
            if (result!=null && result.isPresentAndNonNull()) {
                // 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.get()) && !Objects.equal(value.getClass(), result.get().getClass())
                        // previously did this just for generics but it's more useful than that, e.g. if was a WrappedValue
                        //&& targetTypeToken.getType() instanceof ParameterizedType
                        ) {
                    Maybe<T> resultM = tryCoerce(result.get(), targetTypeToken);
                    if (resultM!=null) {
                        if (resultM.isPresent()) return resultM;
                        // if couldn't coerce parameterized types then back out of this coercer
                        result = resultM;
                    }
                } else {
                    return result;
                }
            }
            
            if (result!=null) {
                if (result.isAbsent()) errors.add(result);
                else {
                    if (coercer instanceof TryCoercer.TryCoercerReturningNull) {
                        return result;
                    } else {
                        String c = getCoercerName(coercer);
                        log.warn("Coercer " + c + " returned wrapped null when coercing " + value);
                        errors.add(Maybe.absent("coercion returned null ("+c+")"));
                        // coercers the return null should implement 'TryCoercerReturningNull'
                    }
                }
            }
        }
        
        //ENHANCEMENT could look in type hierarchy of both types for a conversion method...
        
        //at this point, if either is primitive then run instead over boxed types
        Class<?> boxedT = Boxing.PRIMITIVE_TO_BOXED.get(targetType);
        Class<?> boxedVT = Boxing.PRIMITIVE_TO_BOXED.get(value.getClass());
        if (boxedT!=null || boxedVT!=null) {
            try {
                if (boxedT==null) boxedT=targetType;
                Object boxedV = boxedVT==null ? value : boxedVT.getConstructor(value.getClass()).newInstance(value);
                return tryCoerce(boxedV, (Class<T>)boxedT);
            } catch (Exception e) {
                return Maybe.absent(new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): unboxing failed", e));
            }
        }
        
        //now look in registry
        synchronized (registry) {
            Map<Class<?>, Function<?,?>> adapters = registry.row(targetType);
            for (Map.Entry<Class<?>, Function<?,?>> entry : adapters.entrySet()) {
                if (entry.getKey().isInstance(value)) {
                    try {
                        T resultT = ((Function<Object,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, resultT) && 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.
                            Maybe<T> resultM = tryCoerce(resultT, targetTypeToken);
                            if (resultM!=null) {
                                if (resultM.isPresent()) return resultM;
                                // if couldn't coerce parameterized types then back out of this coercer
                                // but remember the error if we were first
                                errors.add(resultM);
                            }
                        } else {
                            return Maybe.of(resultT);
                        }
                    } catch (Exception e) {
                        Exceptions.propagateIfFatal(e);
                        if (log.isDebugEnabled()) {
                            log.debug("When coercing, registry adapter "+entry+" gave error on "+value+" -> "+targetType+" "
                                + (errors.isEmpty() ? "(rethrowing)" : "(adding as secondary error as there is already another)")
                                + ": "+e, e);
                        }
                        if (e instanceof ClassCoercionException) {
                            errors.add(Maybe.absent(e));
                        } else {
                            errors.add(Maybe.absent(new ClassCoercionException("Cannot coerce type "+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): registered coercer failed", e)));
                        }
                        continue;
                    }
                }
            }
        }

        // not found
        if (!errors.isEmpty()) {
            if (errors.size()==1) return Iterables.getOnlyElement(errors);
            return Maybe.absent(Exceptions.create(errors.stream().map(Maybe.Absent::getException).collect(Collectors.toList())));
        }
        if (value instanceof Map) {
            if (((Map)value).containsKey("type")) {
                return Maybe.absent(new ClassCoercionException("Cannot coerce map containing {type: \""+((Map)value).get("type")+"\"} to "+targetTypeToken+": type not known or not supported here"));
            }
            return Maybe.absent(new ClassCoercionException("Cannot coerce map to "+targetTypeToken+" ("+value+"): no adapter known"));
        }
        return Maybe.absent(new ClassCoercionException("Cannot coerce type "+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): no adapter known"));
    }