You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/07/07 09:18:39 UTC
[05/10] brooklyn-server git commit: refactor TypeCoercions so that
most is in utils with an interface
refactor TypeCoercions so that most is in utils with an interface
with the Brooklyn TypeCoercions referring to and extending that
Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/ec4da197
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/ec4da197
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/ec4da197
Branch: refs/heads/master
Commit: ec4da197d5caf3ac9bb63ab94924718aae83c615
Parents: 23ab1c5
Author: Alex Heneveld <al...@cloudsoftcorp.com>
Authored: Fri Jun 24 11:02:52 2016 +0100
Committer: Alex Heneveld <al...@cloudsoftcorp.com>
Committed: Fri Jun 24 23:02:48 2016 +0100
----------------------------------------------------------------------
.../spi/dsl/methods/BrooklynDslCommon.java | 2 +-
.../util/core/flags/ClassCoercionException.java | 41 -
.../brooklyn/util/core/flags/TypeCoercions.java | 908 +++----------------
.../util/core/internal/TypeCoercionsTest.java | 11 +-
.../rest/util/DefaultExceptionMapper.java | 2 +-
.../javalang/coerce/ClassCoercionException.java | 48 +
.../util/javalang/coerce/CoerceFunctionals.java | 41 +
.../coerce/CommonAdaptorTypeCoercions.java | 380 ++++++++
.../util/javalang/coerce/EnumTypeCoercions.java | 104 +++
.../coerce/PrimitiveStringTypeCoercions.java | 208 +++++
.../util/javalang/coerce/TypeCoercer.java | 31 +
.../javalang/coerce/TypeCoercerExtensible.java | 296 ++++++
.../util/javalang/coerce/TypeCoercionsTest.java | 379 ++++++++
13 files changed, 1625 insertions(+), 826 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
----------------------------------------------------------------------
diff --git a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
index b88829b..309785d 100644
--- a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
+++ b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/BrooklynDslCommon.java
@@ -46,13 +46,13 @@ import org.apache.brooklyn.core.mgmt.persist.DeserializingClassRenamesProvider;
import org.apache.brooklyn.core.sensor.DependentConfiguration;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
-import org.apache.brooklyn.util.core.flags.ClassCoercionException;
import org.apache.brooklyn.util.core.flags.FlagUtils;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
import org.apache.brooklyn.util.core.task.DeferredSupplier;
import org.apache.brooklyn.util.core.task.Tasks;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.javalang.coerce.ClassCoercionException;
import org.apache.brooklyn.util.text.StringEscapes.JavaStringEscapes;
import org.apache.brooklyn.util.text.Strings;
import org.apache.commons.beanutils.BeanUtils;
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/core/src/main/java/org/apache/brooklyn/util/core/flags/ClassCoercionException.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/ClassCoercionException.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/ClassCoercionException.java
deleted file mode 100644
index cea7484..0000000
--- a/core/src/main/java/org/apache/brooklyn/util/core/flags/ClassCoercionException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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;
-
-/**
- * Thrown to indicate that {@link TypeCoercions} could not cast an object from one
- * class to another.
- */
-public class ClassCoercionException extends ClassCastException {
- private static final long serialVersionUID = -4616045237993172497L;
-
- public ClassCoercionException() {
- super();
- }
-
- /**
- * Constructs a <code>ClassCoercionException</code> with the specified
- * detail message.
- *
- * @param s the detail message.
- */
- public ClassCoercionException(String s) {
- super(s);
- }
-}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
index 65f85d6..0169827 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
@@ -22,26 +22,9 @@ 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.Arrays;
-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;
@@ -53,443 +36,177 @@ 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.javalang.Boxing;
import org.apache.brooklyn.util.javalang.JavaClassNames;
-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.StringEscapes.JavaStringEscapes;
-import org.apache.brooklyn.util.text.Strings;
-import org.apache.brooklyn.util.time.Duration;
-import org.apache.brooklyn.util.time.Time;
-import org.apache.brooklyn.util.yaml.Yamls;
+import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.javalang.coerce.CommonAdaptorTypeCoercions;
+import org.apache.brooklyn.util.javalang.coerce.EnumTypeCoercions;
+import org.apache.brooklyn.util.javalang.coerce.PrimitiveStringTypeCoercions;
+import org.apache.brooklyn.util.javalang.coerce.TypeCoercer;
+import org.apache.brooklyn.util.javalang.coerce.TypeCoercerExtensible;
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")
+/** Static class providing a shared {@link TypeCoercer} for all of Brooklyn */
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));
+ static TypeCoercerExtensible coercer;
+ static {
+ coercer = TypeCoercerExtensible.newEmpty();
+ BrooklynInitialization.initTypeCoercionStandardAdapters();
+ }
+
+ public static void initStandardAdapters() {
+ new CommonAdaptorTypeCoercions(coercer).registerAllAdapters();
+ registerDeprecatedBrooklynAdapters();
+ registerBrooklynAdapters();
+ registerGroovyAdapters();
}
+
+ public static <T> T coerce(Object input, Class<T> type) { return coercer.coerce(input, type); }
+ public static <T> T coerce(Object input, TypeToken<T> type) { return coercer.coerce(input, type); }
+ public static <T> Maybe<T> tryCoerce(Object input, Class<T> type) { return coercer.tryCoerce(input, type); }
+ public static <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) { return coercer.tryCoerce(input, type); }
- /** @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);
- }
+ public static <A,B> Function<? super A,B> registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
+ return coercer.registerAdapter(sourceType, targetType, fn);
}
- /** @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();
+ public static <T> Function<Object, T> function(final Class<T> type) {
+ return coercer.function(type);
+ }
- //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);
+ @SuppressWarnings({"unused", "deprecation", "unchecked", "rawtypes"})
+ public static void registerDeprecatedBrooklynAdapters() {
+ registerAdapter(Closure.class, ConfigurableEntityFactory.class, new Function<Closure,ConfigurableEntityFactory>() {
+ @Override
+ public ConfigurableEntityFactory apply(Closure input) {
+ return new ClosureEntityFactory(input);
}
- }
-
- 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);
- }
- }
+ });
+ Function<?,?> ignoredVarHereToAllowSuppressDeprecationWarning1 = registerAdapter(org.apache.brooklyn.core.entity.factory.EntityFactory.class, ConfigurableEntityFactory.class, new Function<org.apache.brooklyn.core.entity.factory.EntityFactory,ConfigurableEntityFactory>() {
+ @Override
+ public ConfigurableEntityFactory apply(org.apache.brooklyn.core.entity.factory.EntityFactory input) {
+ if (input instanceof ConfigurableEntityFactory) return (ConfigurableEntityFactory)input;
+ return new ConfigurableEntityFactoryFromEntityFactory(input);
+ }
+ });
+ Function<?,?> ignoredVarHereToAllowSuppressDeprecationWarning2 = registerAdapter(Closure.class, org.apache.brooklyn.core.entity.factory.EntityFactory.class, new Function<Closure,org.apache.brooklyn.core.entity.factory.EntityFactory>() {
+ @Override
+ public org.apache.brooklyn.core.entity.factory.EntityFactory apply(Closure input) {
+ return new ClosureEntityFactory(input);
+ }
+ });
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static void registerBrooklynAdapters() {
+ 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);
}
- }
-
- //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);
- }
+ });
+ 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);
}
- }
-
- //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);
+ });
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static void registerGroovyAdapters() {
+ 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);
+ }
+ };
}
- }
-
- //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);
+ });
+ 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);
}
- return result;
- }
+ };
}
- }
-
- //not found
- if (targetType.isEnum()) {
- try {
- throw new ClassCoercionException("Invalid value '"+value+"' for "+JavaClassNames.simpleClassName(targetType)+"; expected one of "+
- Arrays.asList((Object[])targetType.getMethod("values").invoke(null)));
- } catch (ClassCoercionException e) {
- throw e;
- } catch (Exception e) {
- Exceptions.propagateIfFatal(e);
- // fall back to below
+ });
+ 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);
}
- }
- throw new ClassCoercionException("Cannot coerce type "+value.getClass().getCanonicalName()+" 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);
+ });
+ 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();
+ }
+ });
}
-
- 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);
- }
- }
+ // ---- legacy compatibility
- /**
- * 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)
- */
+ /** @deprecated since 0.10.0 see method in {@link EnumTypeCoercions} */ @Deprecated
public static <E extends Enum<E>> Function<String, E> stringToEnum(final Class<E> type, @Nullable final E defaultValue) {
- return new StringToEnumFunction<E>(type, defaultValue);
+ return EnumTypeCoercions.stringToEnum(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")
+ /** @deprecated since 0.10.0 see method in {@link PrimitiveStringTypeCoercions} */ @Deprecated
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);
- }
+ return PrimitiveStringTypeCoercions.castPrimitive(value, targetType);
}
+ /** @deprecated since 0.10.0 see method in {@link PrimitiveStringTypeCoercions} */ @Deprecated
public static boolean isPrimitiveOrBoxer(Class<?> type) {
- return Primitives.allPrimitiveTypes().contains(type) || Primitives.allWrapperTypes().contains(type);
+ return PrimitiveStringTypeCoercions.isPrimitiveOrBoxer(type);
}
-
- @SuppressWarnings("unchecked")
+
+ /** @deprecated since 0.10.0 see method in {@link PrimitiveStringTypeCoercions} */ @Deprecated
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;
- }
+ return PrimitiveStringTypeCoercions.stringToPrimitive(value, targetType);
}
- /** returns the simple class name, and for any inner class the portion after the $ */
+ /** @deprecated since 0.10.0 see {@link JavaClassNames#verySimpleClassName(Class)} */ @Deprecated
+ @SuppressWarnings("rawtypes")
public static String getVerySimpleName(Class c) {
- String s = c.getSimpleName();
- if (s.indexOf('$')>=0)
- s = s.substring(s.lastIndexOf('$')+1);
- return s;
+ return JavaClassNames.verySimpleClassName(c);
}
+
+ /** @deprecated since 0.10.0 see {@link Boxing#PRIMITIVE_TO_BOXED} and its <code>inverse()</code> method */
+ @SuppressWarnings("rawtypes")
public static final Map<Class,Class> BOXED_TO_UNBOXED_TYPES = ImmutableMap.<Class,Class>builder().
put(Integer.class, Integer.TYPE).
put(Long.class, Long.TYPE).
@@ -500,6 +217,8 @@ public class TypeCoercions {
put(Character.class, Character.TYPE).
put(Short.class, Short.TYPE).
build();
+ /** @deprecated since 0.10.0 see {@link Boxing#PRIMITIVE_TO_BOXED} */ @Deprecated
+ @SuppressWarnings("rawtypes")
public static final Map<Class,Class> UNBOXED_TO_BOXED_TYPES = ImmutableMap.<Class,Class>builder().
put(Integer.TYPE, Integer.class).
put(Long.TYPE, Long.class).
@@ -511,7 +230,10 @@ public class TypeCoercions {
put(Short.TYPE, Short.class).
build();
- /** for automatic conversion */
+ /** for automatic conversion;
+ * @deprecated since 0.10.0 not used; there may be something similar in {@link Reflections} */
+ @Deprecated
+ @SuppressWarnings("rawtypes")
public static Object getMatchingConstructor(Class target, Object ...arguments) {
Constructor[] cc = target.getConstructors();
for (Constructor c: cc) {
@@ -530,374 +252,4 @@ public class TypeCoercions {
}
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));
- }
- });
- }
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
index 47d4c49..36a9ea7 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/internal/TypeCoercionsTest.java
@@ -33,9 +33,10 @@ import java.util.Map;
import java.util.Set;
import org.apache.brooklyn.core.entity.lifecycle.Lifecycle;
+import org.apache.brooklyn.test.Asserts;
import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.ClassCoercionException;
import org.apache.brooklyn.util.core.flags.TypeCoercions;
+import org.apache.brooklyn.util.javalang.coerce.ClassCoercionException;
import org.apache.brooklyn.util.text.StringPredicates;
import org.codehaus.groovy.runtime.GStringImpl;
import org.slf4j.Logger;
@@ -296,11 +297,11 @@ public class TypeCoercionsTest {
Assert.assertEquals(s, ImmutableMap.of("a", "1", "b", "2"));
}
- @Test(expectedExceptions=IllegalArgumentException.class)
+ @Test(expectedExceptions=ClassCoercionException.class)
public void testJsonStringWithoutBracesOrSpaceDisallowedAsMapCoercion() {
// yaml requires spaces after the colon
- Map<?,?> s = TypeCoercions.coerce("a:1,b:2", Map.class);
- Assert.assertEquals(s, ImmutableMap.of("a", 1, "b", 2));
+ TypeCoercions.coerce("a:1,b:2", Map.class);
+ Asserts.shouldHaveFailedPreviously();
}
@Test
@@ -351,7 +352,7 @@ public class TypeCoercionsTest {
assertEquals(TypeCoercions.coerce("1.0", Number.class), (Number) Double.valueOf(1.0));
}
- @Test(expectedExceptions = ClassCoercionException.class)
+ @Test(expectedExceptions = org.apache.brooklyn.util.javalang.coerce.ClassCoercionException.class)
public void testInvalidCoercionThrowsClassCoercionException() {
TypeCoercions.coerce(new Object(), TypeToken.of(Integer.class));
}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
----------------------------------------------------------------------
diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
index dffc143..010bcca 100644
--- a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
+++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/util/DefaultExceptionMapper.java
@@ -31,9 +31,9 @@ import org.apache.brooklyn.core.mgmt.entitlement.Entitlements;
import org.apache.brooklyn.rest.domain.ApiError;
import org.apache.brooklyn.rest.domain.ApiError.Builder;
import org.apache.brooklyn.util.collections.MutableSet;
-import org.apache.brooklyn.util.core.flags.ClassCoercionException;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.exceptions.UserFacingException;
+import org.apache.brooklyn.util.javalang.coerce.ClassCoercionException;
import org.apache.brooklyn.util.text.Strings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/ClassCoercionException.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/ClassCoercionException.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/ClassCoercionException.java
new file mode 100644
index 0000000..fbc1cbe
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/ClassCoercionException.java
@@ -0,0 +1,48 @@
+/*
+ * 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.javalang.coerce;
+
+/**
+ * Thrown to indicate that {@link TypeCoercions} could not cast an object from one
+ * class to another.
+ */
+public class ClassCoercionException extends ClassCastException {
+ private static final long serialVersionUID = -4616045237993172497L;
+
+ private final Throwable cause;
+
+ public ClassCoercionException() {
+ super();
+ cause = null;
+ }
+ public ClassCoercionException(String s) {
+ super(s);
+ cause = null;
+ }
+ public ClassCoercionException(String s, Throwable cause) {
+ super(s);
+ this.cause = cause;
+ }
+
+ @Override
+ public Throwable getCause() {
+ return cause;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CoerceFunctionals.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CoerceFunctionals.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CoerceFunctionals.java
new file mode 100644
index 0000000..b03a8b3
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CoerceFunctionals.java
@@ -0,0 +1,41 @@
+/*
+ * 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.javalang.coerce;
+
+import com.google.common.base.Function;
+
+public class CoerceFunctionals {
+
+ private CoerceFunctionals() {}
+
+ public static class CoerceFunction<T> implements Function<Object, T> {
+ private final TypeCoercer coercer;
+ private final Class<T> type;
+
+ public CoerceFunction(TypeCoercer coercer, Class<T> type) {
+ this.coercer = coercer;
+ this.type = type;
+ }
+ @Override
+ public T apply(Object input) {
+ return coercer.coerce(input, type);
+ }
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
new file mode 100644
index 0000000..94c93e9
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
@@ -0,0 +1,380 @@
+/*
+ * 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.javalang.coerce;
+
+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 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.exceptions.Exceptions;
+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 com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.net.HostAndPort;
+
+public class CommonAdaptorTypeCoercions {
+
+ private final TypeCoercerExtensible coercer;
+
+ public CommonAdaptorTypeCoercions(TypeCoercerExtensible coercer) {
+ this.coercer = coercer;
+ }
+
+ public TypeCoercerExtensible getCoercer() {
+ return coercer;
+ }
+
+ public CommonAdaptorTypeCoercions registerAllAdapters() {
+ registerStandardAdapters();
+ registerRecursiveIterableAdapters();
+ registerClassForNameAdapters();
+ registerCollectionJsonAdapters();
+
+ return this;
+ }
+
+ /** Registers an adapter for use with type coercion. Returns any old adapter registered for this pair. */
+ public synchronized <A,B> Function<? super A,B> registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
+ return coercer.registerAdapter(sourceType, targetType, fn);
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void registerStandardAdapters() {
+ 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(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(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, QuorumCheck.class, new Function<String,QuorumCheck>() {
+ @Override
+ public QuorumCheck apply(final String input) {
+ return QuorumChecks.of(input);
+ }
+ });
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void registerRecursiveIterableAdapters() {
+
+ // these refer to the coercer to recursively coerce;
+ // they throw if there are errors (but the registry apply loop will catch and handle),
+ // as currently the registry does not support Maybe or opting-out
+
+ 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++] = coercer.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++] = coercer.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++] = coercer.coerce(element, int.class);
+ }
+ return result;
+ }
+ });
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void registerClassForNameAdapters() {
+ 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);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("rawtypes")
+ public void registerCollectionJsonAdapters() {
+ 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, 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));
+ }
+ });
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/ec4da197/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/EnumTypeCoercions.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/EnumTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/EnumTypeCoercions.java
new file mode 100644
index 0000000..b57625a
--- /dev/null
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/EnumTypeCoercions.java
@@ -0,0 +1,104 @@
+/*
+ * 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.javalang.coerce;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+import org.apache.brooklyn.util.guava.Functionals;
+import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Enums;
+import org.apache.brooklyn.util.javalang.JavaClassNames;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Function;
+import com.google.common.collect.ImmutableList;
+
+public class EnumTypeCoercions {
+
+ /**
+ * 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);
+ }
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static <T> Function<String, T> stringToEnumUntyped(final Class<? super T> type, @Nullable final T defaultValue) {
+ if (!type.isEnum()) return new Functionals.ConstantFunction<String,T>(null);
+ return (Function<String, T>) new StringToEnumFunction((Class<Enum>)type, (Enum)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) {
+ return tryCoerce(input, type).or(defaultValue);
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static <T> Maybe<T> tryCoerceUntyped(String input, Class<T> targetType) {
+ if (input==null) return null;
+ if (targetType==null) return Maybe.absent("Null enum type");
+ if (!targetType.isEnum()) return Maybe.absent("Type '"+targetType+"' is not an enum");
+ return tryCoerce(input, (Class<Enum>)targetType);
+ }
+
+ public static <E extends Enum<E>> Maybe<E> tryCoerce(String input, Class<E> targetType) {
+ if (input==null) return null;
+ if (targetType==null) return Maybe.absent("Null enum type");
+ if (!targetType.isEnum()) return Maybe.absent("Type '"+targetType+"' is not an enum");
+
+ 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 Maybe.of(Enum.valueOf(targetType, value));
+ } catch (IllegalArgumentException iae) {
+ continue;
+ }
+ }
+ Maybe<E> result = Enums.valueOfIgnoreCase(targetType, input);
+ if (result.isPresent()) return result;
+ return Maybe.absent(new ClassCoercionException("Invalid value '"+input+"' for "+JavaClassNames.simpleClassName(targetType)+"; expected one of "+
+ Arrays.asList(Enums.values(targetType))));
+ }
+
+}