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");
}