/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.brooklyn.util.core.flags;
import groovy.lang.Closure;
import groovy.time.TimeDuration;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import org.apache.brooklyn.api.entity.Entity;
import org.apache.brooklyn.api.sensor.AttributeSensor;
import org.apache.brooklyn.api.sensor.Sensor;
import org.apache.brooklyn.core.entity.factory.ClosureEntityFactory;
import org.apache.brooklyn.core.entity.factory.ConfigurableEntityFactory;
import org.apache.brooklyn.core.entity.factory.ConfigurableEntityFactoryFromEntityFactory;
import org.apache.brooklyn.core.internal.BrooklynInitialization;
import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
import org.apache.brooklyn.core.sensor.Sensors;
import org.apache.brooklyn.util.JavaGroovyEquivalents;
import org.apache.brooklyn.util.collections.MutableSet;
import org.apache.brooklyn.util.collections.QuorumCheck;
import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.guava.Maybe;
import org.apache.brooklyn.util.javalang.Enums;
import org.apache.brooklyn.util.net.Cidr;
import org.apache.brooklyn.util.net.Networking;
import org.apache.brooklyn.util.net.UserAndHostAndPort;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;
import org.apache.brooklyn.util.yaml.Yamls;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.CaseFormat;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Table;
import com.google.common.net.HostAndPort;
import com.google.common.primitives.Primitives;
import com.google.common.reflect.TypeToken;
@SuppressWarnings("rawtypes")
public class TypeCoercions {
private static final Logger log = LoggerFactory.getLogger(TypeCoercions.class);
private TypeCoercions() {}
/** Store the coercion {@link Function functions} in a {@link Table table}. */
@GuardedBy("TypeCoercions.class")
private static Table<Class, Class, Function> registry = HashBasedTable.create();
/**
* Attempts to coerce {@code value} to {@code targetType}.
* <p>
* Maintains a registry of adapter functions for type pairs in a {@link Table} which
* is searched after checking various strategies, including the following:
* <ul>
* <li>{@code value.asTargetType()}
* <li>{@code TargetType.fromType(value)} (if {@code value instanceof Type})
* <li>{@code value.targetTypeValue()} (handy for primitives)
* <li>{@code TargetType.valueOf(value)} (for enums)
* </ul>
* <p>
* A default set of adapters will handle most common Java-type coercions
* as well as <code>String</code> coercion to:
* <ul>
* <li> {@link Set}, {@link List}, {@link Map} and similar -- parses as YAML
* <li> {@link Date} -- parses using {@link Time#parseDate(String)}
* <li> {@link Duration} -- parses using {@link Duration#parse(String)}
* </ul>
*/
public static <T> T coerce(Object value, Class<T> targetType) {
return coerce(value, TypeToken.of(targetType));
}
/** @see #coerce(Object, Class); allows a null value in the contents of the Maybe */
public static <T> Maybe<T> tryCoerce(Object value, TypeToken<T> targetTypeToken) {
try {
return Maybe.ofAllowingNull( coerce(value, targetTypeToken) );
} catch (Throwable t) {
Exceptions.propagateIfFatal(t);
return Maybe.absent(t);
}
}
/** @see #coerce(Object, Class) */
@SuppressWarnings({ "unchecked" })
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");
}
/**
* Returns a function that does a type coercion to the given type. For example,
* {@code TypeCoercions.function(Double.class)} will return a function that will
* coerce its input value to a {@link Double} (or throw a {@link ClassCoercionException}
* if that is not possible).
*/
public static <T> Function<Object, T> function(final Class<T> type) {
return new CoerceFunction<T>(type);
}
private static class CoerceFunction<T> implements Function<Object, T> {
private final Class<T> type;
public CoerceFunction(Class<T> type) {
this.type = type;
}
@Override
public T apply(Object input) {
return coerce(input, type);
}
}
/**
* Type coercion {@link Function function} for {@link Enum enums}.
* <p>
* Tries to convert the string to {@link CaseFormat#UPPER_UNDERSCORE} first,
* handling all of the different {@link CaseFormat format} possibilites. Failing
* that, it tries a case-insensitive comparison with the valid enum values.
* <p>
* Returns {@code defaultValue} if the string cannot be converted.
*
* @see TypeCoercions#coerce(Object, Class)
* @see Enum#valueOf(Class, String)
*/
public static <E extends Enum<E>> Function<String, E> stringToEnum(final Class<E> type, @Nullable final E defaultValue) {
return new StringToEnumFunction<E>(type, defaultValue);
}
private static class StringToEnumFunction<E extends Enum<E>> implements Function<String, E> {
private final Class<E> type;
private final E defaultValue;
public StringToEnumFunction(Class<E> type, @Nullable E defaultValue) {
this.type = type;
this.defaultValue = defaultValue;
}
@Override
public E apply(String input) {
Preconditions.checkNotNull(input, "input");
List<String> options = ImmutableList.of(
input,
CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, input),
CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, input),
CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, input),
CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, input));
for (String value : options) {
try {
return Enum.valueOf(type, value);
} catch (IllegalArgumentException iae) {
continue;
}
}
Maybe<E> result = Enums.valueOfIgnoreCase(type, input);
return (result.isPresent()) ? result.get() : defaultValue;
}
}
/**
* Sometimes need to explicitly cast primitives, rather than relying on Java casting.
* For example, when using generics then type-erasure means it doesn't actually cast,
* which causes tests to fail with 0 != 0.0
*/
@SuppressWarnings("unchecked")
public static <T> T castPrimitive(Object value, Class<T> targetType) {
if (value==null) return null;
assert isPrimitiveOrBoxer(targetType) : "targetType="+targetType;
assert isPrimitiveOrBoxer(value.getClass()) : "value="+targetType+"; valueType="+value.getClass();
Class<?> sourceWrapType = Primitives.wrap(value.getClass());
Class<?> targetWrapType = Primitives.wrap(targetType);
// optimization, for when already correct type
if (sourceWrapType == targetWrapType) {
return (T) value;
}
if (targetWrapType == Boolean.class) {
// only char can be mapped to boolean
// (we could say 0=false, nonzero=true, but there is no compelling use case so better
// to encourage users to write as boolean)
if (sourceWrapType == Character.class)
return (T) stringToPrimitive(value.toString(), targetType);
throw new ClassCoercionException("Cannot cast "+sourceWrapType+" ("+value+") to "+targetType);
} else if (sourceWrapType == Boolean.class) {
// boolean can't cast to anything else
throw new ClassCoercionException("Cannot cast "+sourceWrapType+" ("+value+") to "+targetType);
}
// for whole-numbers (where casting to long won't lose anything)...
long v = 0;
boolean islong = true;
if (sourceWrapType == Character.class) {
v = (long) ((Character)value).charValue();
} else if (sourceWrapType == Byte.class) {
v = (long) ((Byte)value).byteValue();
} else if (sourceWrapType == Short.class) {
v = (long) ((Short)value).shortValue();
} else if (sourceWrapType == Integer.class) {
v = (long) ((Integer)value).intValue();
} else if (sourceWrapType == Long.class) {
v = ((Long)value).longValue();
} else {
islong = false;
}
if (islong) {
if (targetWrapType == Character.class) return (T) Character.valueOf((char)v);
if (targetWrapType == Byte.class) return (T) Byte.valueOf((byte)v);
if (targetWrapType == Short.class) return (T) Short.valueOf((short)v);
if (targetWrapType == Integer.class) return (T) Integer.valueOf((int)v);
if (targetWrapType == Long.class) return (T) Long.valueOf((long)v);
if (targetWrapType == Float.class) return (T) Float.valueOf((float)v);
if (targetWrapType == Double.class) return (T) Double.valueOf((double)v);
throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
}
// for real-numbers (cast to double)...
double d = 0;
boolean isdouble = true;
if (sourceWrapType == Float.class) {
d = (double) ((Float)value).floatValue();
} else if (sourceWrapType == Double.class) {
d = (double) ((Double)value).doubleValue();
} else {
isdouble = false;
}
if (isdouble) {
if (targetWrapType == Character.class) return (T) Character.valueOf((char)d);
if (targetWrapType == Byte.class) return (T) Byte.valueOf((byte)d);
if (targetWrapType == Short.class) return (T) Short.valueOf((short)d);
if (targetWrapType == Integer.class) return (T) Integer.valueOf((int)d);
if (targetWrapType == Long.class) return (T) Long.valueOf((long)d);
if (targetWrapType == Float.class) return (T) Float.valueOf((float)d);
if (targetWrapType == Double.class) return (T) Double.valueOf((double)d);
throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
} else {
throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
}
}
public static boolean isPrimitiveOrBoxer(Class<?> type) {
return Primitives.allPrimitiveTypes().contains(type) || Primitives.allWrapperTypes().contains(type);
}
@SuppressWarnings("unchecked")
public static <T> T stringToPrimitive(String value, Class<T> targetType) {
assert Primitives.allPrimitiveTypes().contains(targetType) || Primitives.allWrapperTypes().contains(targetType) : "targetType="+targetType;
// If char, then need to do explicit conversion
if (targetType == Character.class || targetType == char.class) {
if (value.length() == 1) {
return (T) (Character) value.charAt(0);
} else if (value.length() != 1) {
throw new ClassCoercionException("Cannot coerce type String to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
}
}
value = value.trim();
// For boolean we could use valueOf, but that returns false whereas we'd rather throw errors on bad values
if (targetType == Boolean.class || targetType == boolean.class) {
if ("true".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
if ("false".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
if ("yes".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
if ("no".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
if ("t".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
if ("f".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
if ("y".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
if ("n".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
throw new ClassCoercionException("Cannot coerce type String to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
}
// Otherwise can use valueOf reflectively
Class<?> wrappedType;
if (Primitives.allPrimitiveTypes().contains(targetType)) {
wrappedType = Primitives.wrap(targetType);
} else {
wrappedType = targetType;
}
try {
return (T) wrappedType.getMethod("valueOf", String.class).invoke(null, value);
} catch (Exception e) {
ClassCoercionException tothrow = new ClassCoercionException("Cannot coerce "+JavaStringEscapes.wrapJavaString(value)+" to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
tothrow.initCause(e);
throw tothrow;
}
}
/** returns the simple class name, and for any inner class the portion after the $ */
public static String getVerySimpleName(Class c) {
String s = c.getSimpleName();
if (s.indexOf('$')>=0)
s = s.substring(s.lastIndexOf('$')+1);
return s;
}
public static final Map<Class,Class> BOXED_TO_UNBOXED_TYPES = ImmutableMap.<Class,Class>builder().
put(Integer.class, Integer.TYPE).
put(Long.class, Long.TYPE).
put(Boolean.class, Boolean.TYPE).
put(Byte.class, Byte.TYPE).
put(Double.class, Double.TYPE).
put(Float.class, Float.TYPE).
put(Character.class, Character.TYPE).
put(Short.class, Short.TYPE).
build();
public static final Map<Class,Class> UNBOXED_TO_BOXED_TYPES = ImmutableMap.<Class,Class>builder().
put(Integer.TYPE, Integer.class).
put(Long.TYPE, Long.class).
put(Boolean.TYPE, Boolean.class).
put(Byte.TYPE, Byte.class).
put(Double.TYPE, Double.class).
put(Float.TYPE, Float.class).
put(Character.TYPE, Character.class).
put(Short.TYPE, Short.class).
build();
/** for automatic conversion */
public static Object getMatchingConstructor(Class target, Object ...arguments) {
Constructor[] cc = target.getConstructors();
for (Constructor c: cc) {
if (c.getParameterTypes().length != arguments.length)
continue;
boolean matches = true;
Class[] tt = c.getParameterTypes();
for (int i=0; i<tt.length; i++) {
if (arguments[i]!=null && !tt[i].isInstance(arguments[i])) {
matches=false;
break;
}
}
if (matches)
return c;
}
return null;
}
/** Registers an adapter for use with type coercion. Returns any old adapter. */
public synchronized static <A,B> Function registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
return registry.put(targetType, sourceType, fn);
}
static { BrooklynInitialization.initTypeCoercionStandardAdapters(); }
public static void initStandardAdapters() {
registerAdapter(CharSequence.class, String.class, new Function<CharSequence,String>() {
@Override
public String apply(CharSequence input) {
return input.toString();
}
});
registerAdapter(byte[].class, String.class, new Function<byte[],String>() {
@Override
public String apply(byte[] input) {
return new String(input);
}
});
registerAdapter(Collection.class, Set.class, new Function<Collection,Set>() {
@SuppressWarnings("unchecked")
@Override
public Set apply(Collection input) {
return Sets.newLinkedHashSet(input);
}
});
registerAdapter(Collection.class, List.class, new Function<Collection,List>() {
@SuppressWarnings("unchecked")
@Override
public List apply(Collection input) {
return Lists.newArrayList(input);
}
});
registerAdapter(String.class, InetAddress.class, new Function<String,InetAddress>() {
@Override
public InetAddress apply(String input) {
return Networking.getInetAddressWithFixedName(input);
}
});
registerAdapter(String.class, HostAndPort.class, new Function<String,HostAndPort>() {
@Override
public HostAndPort apply(String input) {
return HostAndPort.fromString(input);
}
});
registerAdapter(String.class, UserAndHostAndPort.class, new Function<String,UserAndHostAndPort>() {
@Override
public UserAndHostAndPort apply(String input) {
return UserAndHostAndPort.fromString(input);
}
});
registerAdapter(String.class, Cidr.class, new Function<String,Cidr>() {
@Override
public Cidr apply(String input) {
return new Cidr(input);
}
});
registerAdapter(String.class, URL.class, new Function<String,URL>() {
@Override
public URL apply(String input) {
try {
return new URL(input);
} catch (Exception e) {
throw Exceptions.propagate(e);
}
}
});
registerAdapter(URL.class, String.class, new Function<URL,String>() {
@Override
public String apply(URL input) {
return input.toString();
}
});
registerAdapter(String.class, URI.class, new Function<String,URI>() {
@Override
public URI apply(String input) {
return URI.create(input);
}
});
registerAdapter(URI.class, String.class, new Function<URI,String>() {
@Override
public String apply(URI input) {
return input.toString();
}
});
registerAdapter(Closure.class, ConfigurableEntityFactory.class, new Function<Closure,ConfigurableEntityFactory>() {
@SuppressWarnings("unchecked")
@Override
public ConfigurableEntityFactory apply(Closure input) {
return new ClosureEntityFactory(input);
}
});
@SuppressWarnings({"unused", "deprecation"})
Function<?,?> ignoredVarHereToAllowSuppressDeprecationWarning1 = registerAdapter(org.apache.brooklyn.core.entity.factory.EntityFactory.class, ConfigurableEntityFactory.class, new Function<org.apache.brooklyn.core.entity.factory.EntityFactory,ConfigurableEntityFactory>() {
@SuppressWarnings("unchecked")
@Override
public ConfigurableEntityFactory apply(org.apache.brooklyn.core.entity.factory.EntityFactory input) {
if (input instanceof ConfigurableEntityFactory) return (ConfigurableEntityFactory)input;
return new ConfigurableEntityFactoryFromEntityFactory(input);
}
});
@SuppressWarnings({"unused", "deprecation"})
Function<?,?> ignoredVarHereToAllowSuppressDeprecationWarning2 = registerAdapter(Closure.class, org.apache.brooklyn.core.entity.factory.EntityFactory.class, new Function<Closure,org.apache.brooklyn.core.entity.factory.EntityFactory>() {
@SuppressWarnings("unchecked")
@Override
public org.apache.brooklyn.core.entity.factory.EntityFactory apply(Closure input) {
return new ClosureEntityFactory(input);
}
});
registerAdapter(Closure.class, Predicate.class, new Function<Closure,Predicate>() {
@Override
public Predicate<?> apply(final Closure closure) {
return new Predicate<Object>() {
@Override public boolean apply(Object input) {
return (Boolean) closure.call(input);
}
};
}
});
registerAdapter(Closure.class, Function.class, new Function<Closure,Function>() {
@Override
public Function apply(final Closure closure) {
return new Function() {
@Override public Object apply(Object input) {
return closure.call(input);
}
};
}
});
registerAdapter(Object.class, Duration.class, new Function<Object,Duration>() {
@Override
public Duration apply(final Object input) {
return org.apache.brooklyn.util.time.Duration.of(input);
}
});
registerAdapter(Object.class, TimeDuration.class, new Function<Object,TimeDuration>() {
@SuppressWarnings("deprecation")
@Override
public TimeDuration apply(final Object input) {
log.warn("deprecated automatic coercion of Object to TimeDuration (set breakpoint in TypeCoercions to inspect, convert to Duration)");
return JavaGroovyEquivalents.toTimeDuration(input);
}
});
registerAdapter(TimeDuration.class, Long.class, new Function<TimeDuration,Long>() {
@Override
public Long apply(final TimeDuration input) {
log.warn("deprecated automatic coercion of TimeDuration to Long (set breakpoint in TypeCoercions to inspect, use Duration instead of Long!)");
return input.toMilliseconds();
}
});
registerAdapter(Integer.class, AtomicLong.class, new Function<Integer,AtomicLong>() {
@Override public AtomicLong apply(final Integer input) {
return new AtomicLong(input);
}
});
registerAdapter(Long.class, AtomicLong.class, new Function<Long,AtomicLong>() {
@Override public AtomicLong apply(final Long input) {
return new AtomicLong(input);
}
});
registerAdapter(String.class, AtomicLong.class, new Function<String,AtomicLong>() {
@Override public AtomicLong apply(final String input) {
return new AtomicLong(Long.parseLong(input.trim()));
}
});
registerAdapter(Integer.class, AtomicInteger.class, new Function<Integer,AtomicInteger>() {
@Override public AtomicInteger apply(final Integer input) {
return new AtomicInteger(input);
}
});
registerAdapter(String.class, AtomicInteger.class, new Function<String,AtomicInteger>() {
@Override public AtomicInteger apply(final String input) {
return new AtomicInteger(Integer.parseInt(input.trim()));
}
});
/** This always returns a {@link Double}, cast as a {@link Number};
* however primitives and boxers get exact typing due to call in #stringToPrimitive */
registerAdapter(String.class, Number.class, new Function<String,Number>() {
@Override
public Number apply(String input) {
return Double.valueOf(input);
}
});
registerAdapter(BigDecimal.class, Double.class, new Function<BigDecimal,Double>() {
@Override
public Double apply(BigDecimal input) {
return input.doubleValue();
}
});
registerAdapter(BigInteger.class, Long.class, new Function<BigInteger,Long>() {
@Override
public Long apply(BigInteger input) {
return input.longValue();
}
});
registerAdapter(BigInteger.class, Integer.class, new Function<BigInteger,Integer>() {
@Override
public Integer apply(BigInteger input) {
return input.intValue();
}
});
registerAdapter(String.class, BigDecimal.class, new Function<String,BigDecimal>() {
@Override
public BigDecimal apply(String input) {
return new BigDecimal(input);
}
});
registerAdapter(Double.class, BigDecimal.class, new Function<Double,BigDecimal>() {
@Override
public BigDecimal apply(Double input) {
return BigDecimal.valueOf(input);
}
});
registerAdapter(String.class, BigInteger.class, new Function<String,BigInteger>() {
@Override
public BigInteger apply(String input) {
return new BigInteger(input);
}
});
registerAdapter(Long.class, BigInteger.class, new Function<Long,BigInteger>() {
@Override
public BigInteger apply(Long input) {
return BigInteger.valueOf(input);
}
});
registerAdapter(Integer.class, BigInteger.class, new Function<Integer,BigInteger>() {
@Override
public BigInteger apply(Integer input) {
return BigInteger.valueOf(input);
}
});
registerAdapter(String.class, Date.class, new Function<String,Date>() {
@Override
public Date apply(final String input) {
return Time.parseDate(input);
}
});
registerAdapter(String.class, Class.class, new Function<String,Class>() {
@Override
public Class apply(final String input) {
try {
return Class.forName(input);
} catch (ClassNotFoundException e) {
throw Exceptions.propagate(e);
}
}
});
registerAdapter(String.class, AttributeSensor.class, new Function<String,AttributeSensor>() {
@Override
public AttributeSensor apply(final String input) {
Entity entity = BrooklynTaskTags.getContextEntity(Tasks.current());
if (entity!=null) {
Sensor<?> result = entity.getEntityType().getSensor(input);
if (result instanceof AttributeSensor)
return (AttributeSensor) result;
}
return Sensors.newSensor(Object.class, input);
}
});
registerAdapter(String.class, Sensor.class, new Function<String,Sensor>() {
@Override
public AttributeSensor apply(final String input) {
Entity entity = BrooklynTaskTags.getContextEntity(Tasks.current());
if (entity!=null) {
Sensor<?> result = entity.getEntityType().getSensor(input);
if (result != null)
return (AttributeSensor) result;
}
return Sensors.newSensor(Object.class, input);
}
});
registerAdapter(String.class, List.class, new Function<String,List>() {
@Override
public List<String> apply(final String input) {
return JavaStringEscapes.unwrapJsonishListIfPossible(input);
}
});
registerAdapter(String.class, Set.class, new Function<String,Set>() {
@Override
public Set<String> apply(final String input) {
return MutableSet.copyOf(JavaStringEscapes.unwrapJsonishListIfPossible(input)).asUnmodifiable();
}
});
registerAdapter(String.class, QuorumCheck.class, new Function<String,QuorumCheck>() {
@Override
public QuorumCheck apply(final String input) {
return QuorumChecks.of(input);
}
});
registerAdapter(Iterable.class, String[].class, new Function<Iterable, String[]>() {
@Nullable
@Override
public String[] apply(@Nullable Iterable list) {
if (list == null) return null;
String[] result = new String[Iterables.size(list)];
int count = 0;
for (Object element : list) {
result[count++] = coerce(element, String.class);
}
return result;
}
});
registerAdapter(Iterable.class, Integer[].class, new Function<Iterable, Integer[]>() {
@Nullable
@Override
public Integer[] apply(@Nullable Iterable list) {
if (list == null) return null;
Integer[] result = new Integer[Iterables.size(list)];
int count = 0;
for (Object element : list) {
result[count++] = coerce(element, Integer.class);
}
return result;
}
});
registerAdapter(Iterable.class, int[].class, new Function<Iterable, int[]>() {
@Nullable
@Override
public int[] apply(@Nullable Iterable list) {
if (list == null) return null;
int[] result = new int[Iterables.size(list)];
int count = 0;
for (Object element : list) {
result[count++] = coerce(element, int.class);
}
return result;
}
});
registerAdapter(String.class, Map.class, new Function<String,Map>() {
@Override
public Map apply(final String input) {
Exception error = null;
// first try wrapping in braces if needed
if (!input.trim().startsWith("{")) {
try {
return apply("{ "+input+" }");
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
// prefer this error
error = e;
// fall back to parsing without braces, e.g. if it's multiline
}
}
try {
return Yamls.getAs( Yamls.parseAll(input), Map.class );
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
if (error!=null && input.indexOf('\n')==-1) {
// prefer the original error if it wasn't braced and wasn't multiline
e = error;
}
throw new IllegalArgumentException("Cannot parse string as map with flexible YAML parsing; "+
(e instanceof ClassCastException ? "yaml treats it as a string" :
(e instanceof IllegalArgumentException && Strings.isNonEmpty(e.getMessage())) ? e.getMessage() :
""+e) );
}
// NB: previously we supported this also, when we did json above;
// yaml support is better as it supports quotes (and better than json because it allows dropping quotes)
// snake-yaml, our parser, also accepts key=value -- although i'm not sure this is strictly yaml compliant;
// our tests will catch it if snake behaviour changes, and we can reinstate this
// (but note it doesn't do quotes; see http://code.google.com/p/guava-libraries/issues/detail?id=412 for that):
// return ImmutableMap.copyOf(Splitter.on(",").trimResults().omitEmptyStrings().withKeyValueSeparator("=").split(input));
}
});
}
}