Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id D475C200B41 for ; Thu, 7 Jul 2016 11:18:39 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id D2E41160A59; Thu, 7 Jul 2016 09:18:39 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id AF68B160A85 for ; Thu, 7 Jul 2016 11:18:37 +0200 (CEST) Received: (qmail 48423 invoked by uid 500); 7 Jul 2016 09:18:36 -0000 Mailing-List: contact commits-help@brooklyn.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@brooklyn.apache.org Delivered-To: mailing list commits@brooklyn.apache.org Received: (qmail 47879 invoked by uid 99); 7 Jul 2016 09:18:35 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 07 Jul 2016 09:18:35 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id A0DD9E78B2; Thu, 7 Jul 2016 09:18:35 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: heneveld@apache.org To: commits@brooklyn.apache.org Date: Thu, 07 Jul 2016 09:18:39 -0000 Message-Id: <6e497e9353d64961852c71976e5e6820@git.apache.org> In-Reply-To: <0974a625d0544bef8d8b6d1ff1d5ade4@git.apache.org> References: <0974a625d0544bef8d8b6d1ff1d5ade4@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [05/10] brooklyn-server git commit: refactor TypeCoercions so that most is in utils with an interface archived-at: Thu, 07 Jul 2016 09:18:40 -0000 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 Authored: Fri Jun 24 11:02:52 2016 +0100 Committer: Alex Heneveld 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 ClassCoercionException 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 registry = HashBasedTable.create(); - - /** - * Attempts to coerce {@code value} to {@code targetType}. - *

- * Maintains a registry of adapter functions for type pairs in a {@link Table} which - * is searched after checking various strategies, including the following: - *

    - *
  • {@code value.asTargetType()} - *
  • {@code TargetType.fromType(value)} (if {@code value instanceof Type}) - *
  • {@code value.targetTypeValue()} (handy for primitives) - *
  • {@code TargetType.valueOf(value)} (for enums) - *
- *

- * A default set of adapters will handle most common Java-type coercions - * as well as String coercion to: - *

    - *
  • {@link Set}, {@link List}, {@link Map} and similar -- parses as YAML - *
  • {@link Date} -- parses using {@link Time#parseDate(String)} - *
  • {@link Duration} -- parses using {@link Duration#parse(String)} - *
- */ - public static T coerce(Object value, Class 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 coerce(Object input, Class type) { return coercer.coerce(input, type); } + public static T coerce(Object input, TypeToken type) { return coercer.coerce(input, type); } + public static Maybe tryCoerce(Object input, Class type) { return coercer.tryCoerce(input, type); } + public static Maybe tryCoerce(Object input, TypeToken type) { return coercer.tryCoerce(input, type); } - /** @see #coerce(Object, Class); allows a null value in the contents of the Maybe */ - public static Maybe tryCoerce(Object value, TypeToken targetTypeToken) { - try { - return Maybe.ofAllowingNull( coerce(value, targetTypeToken) ); - } catch (Throwable t) { - Exceptions.propagateIfFatal(t); - return Maybe.absent(t); - } + public static Function registerAdapter(Class sourceType, Class targetType, Function fn) { + return coercer.registerAdapter(sourceType, targetType, fn); } - /** @see #coerce(Object, Class) */ - @SuppressWarnings({ "unchecked" }) - public static T coerce(Object value, TypeToken targetTypeToken) { - if (value==null) return null; - Class targetType = targetTypeToken.getRawType(); + public static Function function(final Class 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() { + @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)targetType); - } - - //deal with string->primitive - if (value instanceof String && isPrimitiveOrBoxer(targetType)) { - return stringToPrimitive((String)value, (Class)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() { + @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() { + @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() { + @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() { + @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() { + @Override + public Predicate apply(final Closure closure) { + return new Predicate() { + @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) targetType, null).apply(String.valueOf(value)); - if (result != null) return result; - } - - //now look in registry - synchronized (TypeCoercions.class) { - Map adapters = registry.row(targetType); - for (Map.Entry 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 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() { + @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() { + @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 Function function(final Class type) { - return new CoerceFunction(type); + }); + registerAdapter(TimeDuration.class, Long.class, new Function() { + @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 implements Function { - private final Class type; - public CoerceFunction(Class 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}. - *

- * 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. - *

- * 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 > Function stringToEnum(final Class type, @Nullable final E defaultValue) { - return new StringToEnumFunction(type, defaultValue); + return EnumTypeCoercions.stringToEnum(type, defaultValue); } - - private static class StringToEnumFunction> implements Function { - private final Class type; - private final E defaultValue; - public StringToEnumFunction(Class type, @Nullable E defaultValue) { - this.type = type; - this.defaultValue = defaultValue; - } - @Override - public E apply(String input) { - Preconditions.checkNotNull(input, "input"); - List 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 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 castPrimitive(Object value, Class 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 stringToPrimitive(String value, Class 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 inverse() method */ + @SuppressWarnings("rawtypes") public static final Map BOXED_TO_UNBOXED_TYPES = ImmutableMap.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 UNBOXED_TO_BOXED_TYPES = ImmutableMap.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 Function registerAdapter(Class sourceType, Class targetType, Function fn) { - return registry.put(targetType, sourceType, fn); - } - - static { BrooklynInitialization.initTypeCoercionStandardAdapters(); } - - public static void initStandardAdapters() { - registerAdapter(CharSequence.class, String.class, new Function() { - @Override - public String apply(CharSequence input) { - return input.toString(); - } - }); - registerAdapter(byte[].class, String.class, new Function() { - @Override - public String apply(byte[] input) { - return new String(input); - } - }); - registerAdapter(Collection.class, Set.class, new Function() { - @SuppressWarnings("unchecked") - @Override - public Set apply(Collection input) { - return Sets.newLinkedHashSet(input); - } - }); - registerAdapter(Collection.class, List.class, new Function() { - @SuppressWarnings("unchecked") - @Override - public List apply(Collection input) { - return Lists.newArrayList(input); - } - }); - registerAdapter(String.class, InetAddress.class, new Function() { - @Override - public InetAddress apply(String input) { - return Networking.getInetAddressWithFixedName(input); - } - }); - registerAdapter(String.class, HostAndPort.class, new Function() { - @Override - public HostAndPort apply(String input) { - return HostAndPort.fromString(input); - } - }); - registerAdapter(String.class, UserAndHostAndPort.class, new Function() { - @Override - public UserAndHostAndPort apply(String input) { - return UserAndHostAndPort.fromString(input); - } - }); - registerAdapter(String.class, Cidr.class, new Function() { - @Override - public Cidr apply(String input) { - return new Cidr(input); - } - }); - registerAdapter(String.class, URL.class, new Function() { - @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() { - @Override - public String apply(URL input) { - return input.toString(); - } - }); - registerAdapter(String.class, URI.class, new Function() { - @Override - public URI apply(String input) { - return URI.create(input); - } - }); - registerAdapter(URI.class, String.class, new Function() { - @Override - public String apply(URI input) { - return input.toString(); - } - }); - registerAdapter(Closure.class, ConfigurableEntityFactory.class, new Function() { - @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() { - @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() { - @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() { - @Override - public Predicate apply(final Closure closure) { - return new Predicate() { - @Override public boolean apply(Object input) { - return (Boolean) closure.call(input); - } - }; - } - }); - registerAdapter(Closure.class, Function.class, new 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() { - @Override - public Duration apply(final Object input) { - return org.apache.brooklyn.util.time.Duration.of(input); - } - }); - registerAdapter(Object.class, TimeDuration.class, new Function() { - @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() { - @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() { - @Override public AtomicLong apply(final Integer input) { - return new AtomicLong(input); - } - }); - registerAdapter(Long.class, AtomicLong.class, new Function() { - @Override public AtomicLong apply(final Long input) { - return new AtomicLong(input); - } - }); - registerAdapter(String.class, AtomicLong.class, new Function() { - @Override public AtomicLong apply(final String input) { - return new AtomicLong(Long.parseLong(input.trim())); - } - }); - registerAdapter(Integer.class, AtomicInteger.class, new Function() { - @Override public AtomicInteger apply(final Integer input) { - return new AtomicInteger(input); - } - }); - registerAdapter(String.class, AtomicInteger.class, new Function() { - @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() { - @Override - public Number apply(String input) { - return Double.valueOf(input); - } - }); - registerAdapter(BigDecimal.class, Double.class, new Function() { - @Override - public Double apply(BigDecimal input) { - return input.doubleValue(); - } - }); - registerAdapter(BigInteger.class, Long.class, new Function() { - @Override - public Long apply(BigInteger input) { - return input.longValue(); - } - }); - registerAdapter(BigInteger.class, Integer.class, new Function() { - @Override - public Integer apply(BigInteger input) { - return input.intValue(); - } - }); - registerAdapter(String.class, BigDecimal.class, new Function() { - @Override - public BigDecimal apply(String input) { - return new BigDecimal(input); - } - }); - registerAdapter(Double.class, BigDecimal.class, new Function() { - @Override - public BigDecimal apply(Double input) { - return BigDecimal.valueOf(input); - } - }); - registerAdapter(String.class, BigInteger.class, new Function() { - @Override - public BigInteger apply(String input) { - return new BigInteger(input); - } - }); - registerAdapter(Long.class, BigInteger.class, new Function() { - @Override - public BigInteger apply(Long input) { - return BigInteger.valueOf(input); - } - }); - registerAdapter(Integer.class, BigInteger.class, new Function() { - @Override - public BigInteger apply(Integer input) { - return BigInteger.valueOf(input); - } - }); - registerAdapter(String.class, Date.class, new Function() { - @Override - public Date apply(final String input) { - return Time.parseDate(input); - } - }); - registerAdapter(String.class, Class.class, new Function() { - @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() { - @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() { - @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() { - @Override - public List apply(final String input) { - return JavaStringEscapes.unwrapJsonishListIfPossible(input); - } - }); - registerAdapter(String.class, Set.class, new Function() { - @Override - public Set apply(final String input) { - return MutableSet.copyOf(JavaStringEscapes.unwrapJsonishListIfPossible(input)).asUnmodifiable(); - } - }); - registerAdapter(String.class, QuorumCheck.class, new Function() { - @Override - public QuorumCheck apply(final String input) { - return QuorumChecks.of(input); - } - }); - registerAdapter(Iterable.class, String[].class, new Function() { - @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() { - @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() { - @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() { - @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 implements Function { + private final TypeCoercer coercer; + private final Class type; + + public CoerceFunction(TypeCoercer coercer, Class 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 Function registerAdapter(Class sourceType, Class targetType, Function fn) { + return coercer.registerAdapter(sourceType, targetType, fn); + } + + @SuppressWarnings("rawtypes") + public void registerStandardAdapters() { + registerAdapter(CharSequence.class, String.class, new Function() { + @Override + public String apply(CharSequence input) { + return input.toString(); + } + }); + registerAdapter(byte[].class, String.class, new Function() { + @Override + public String apply(byte[] input) { + return new String(input); + } + }); + registerAdapter(Collection.class, Set.class, new Function() { + @SuppressWarnings("unchecked") + @Override + public Set apply(Collection input) { + return Sets.newLinkedHashSet(input); + } + }); + registerAdapter(Collection.class, List.class, new Function() { + @SuppressWarnings("unchecked") + @Override + public List apply(Collection input) { + return Lists.newArrayList(input); + } + }); + registerAdapter(String.class, InetAddress.class, new Function() { + @Override + public InetAddress apply(String input) { + return Networking.getInetAddressWithFixedName(input); + } + }); + registerAdapter(String.class, HostAndPort.class, new Function() { + @Override + public HostAndPort apply(String input) { + return HostAndPort.fromString(input); + } + }); + registerAdapter(String.class, UserAndHostAndPort.class, new Function() { + @Override + public UserAndHostAndPort apply(String input) { + return UserAndHostAndPort.fromString(input); + } + }); + registerAdapter(String.class, Cidr.class, new Function() { + @Override + public Cidr apply(String input) { + return new Cidr(input); + } + }); + registerAdapter(String.class, URL.class, new Function() { + @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() { + @Override + public String apply(URL input) { + return input.toString(); + } + }); + registerAdapter(String.class, URI.class, new Function() { + @Override + public URI apply(String input) { + return URI.create(input); + } + }); + registerAdapter(URI.class, String.class, new Function() { + @Override + public String apply(URI input) { + return input.toString(); + } + }); + registerAdapter(Object.class, Duration.class, new Function() { + @Override + public Duration apply(final Object input) { + return org.apache.brooklyn.util.time.Duration.of(input); + } + }); + + registerAdapter(Integer.class, AtomicLong.class, new Function() { + @Override public AtomicLong apply(final Integer input) { + return new AtomicLong(input); + } + }); + registerAdapter(Long.class, AtomicLong.class, new Function() { + @Override public AtomicLong apply(final Long input) { + return new AtomicLong(input); + } + }); + registerAdapter(String.class, AtomicLong.class, new Function() { + @Override public AtomicLong apply(final String input) { + return new AtomicLong(Long.parseLong(input.trim())); + } + }); + registerAdapter(Integer.class, AtomicInteger.class, new Function() { + @Override public AtomicInteger apply(final Integer input) { + return new AtomicInteger(input); + } + }); + registerAdapter(String.class, AtomicInteger.class, new Function() { + @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() { + @Override + public Number apply(String input) { + return Double.valueOf(input); + } + }); + registerAdapter(BigDecimal.class, Double.class, new Function() { + @Override + public Double apply(BigDecimal input) { + return input.doubleValue(); + } + }); + registerAdapter(BigInteger.class, Long.class, new Function() { + @Override + public Long apply(BigInteger input) { + return input.longValue(); + } + }); + registerAdapter(BigInteger.class, Integer.class, new Function() { + @Override + public Integer apply(BigInteger input) { + return input.intValue(); + } + }); + registerAdapter(String.class, BigDecimal.class, new Function() { + @Override + public BigDecimal apply(String input) { + return new BigDecimal(input); + } + }); + registerAdapter(Double.class, BigDecimal.class, new Function() { + @Override + public BigDecimal apply(Double input) { + return BigDecimal.valueOf(input); + } + }); + registerAdapter(String.class, BigInteger.class, new Function() { + @Override + public BigInteger apply(String input) { + return new BigInteger(input); + } + }); + registerAdapter(Long.class, BigInteger.class, new Function() { + @Override + public BigInteger apply(Long input) { + return BigInteger.valueOf(input); + } + }); + registerAdapter(Integer.class, BigInteger.class, new Function() { + @Override + public BigInteger apply(Integer input) { + return BigInteger.valueOf(input); + } + }); + registerAdapter(String.class, Date.class, new Function() { + @Override + public Date apply(final String input) { + return Time.parseDate(input); + } + }); + registerAdapter(String.class, QuorumCheck.class, new Function() { + @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() { + @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() { + @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() { + @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() { + @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() { + @Override + public List apply(final String input) { + return JavaStringEscapes.unwrapJsonishListIfPossible(input); + } + }); + registerAdapter(String.class, Set.class, new Function() { + @Override + public Set apply(final String input) { + return MutableSet.copyOf(JavaStringEscapes.unwrapJsonishListIfPossible(input)).asUnmodifiable(); + } + }); + registerAdapter(String.class, Map.class, new Function() { + @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}. + *

+ * 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. + *

+ * Returns {@code defaultValue} if the string cannot be converted. + * + * @see TypeCoercions#coerce(Object, Class) + * @see Enum#valueOf(Class, String) + */ + public static > Function stringToEnum(final Class type, @Nullable final E defaultValue) { + return new StringToEnumFunction(type, defaultValue); + } + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static Function stringToEnumUntyped(final Class type, @Nullable final T defaultValue) { + if (!type.isEnum()) return new Functionals.ConstantFunction(null); + return (Function) new StringToEnumFunction((Class)type, (Enum)defaultValue); + } + + private static class StringToEnumFunction> implements Function { + private final Class type; + private final E defaultValue; + + public StringToEnumFunction(Class 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 Maybe tryCoerceUntyped(String input, Class 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)targetType); + } + + public static > Maybe tryCoerce(String input, Class 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 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 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)))); + } + +}