You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by ha...@apache.org on 2015/08/17 21:17:59 UTC

[28/42] incubator-brooklyn git commit: [BROOKLYN-162] Refactor package in ./core/util

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/org/apache/brooklyn/core/util/flags/FlagUtils.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/util/flags/FlagUtils.java b/core/src/main/java/org/apache/brooklyn/core/util/flags/FlagUtils.java
new file mode 100644
index 0000000..6f28f9b
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/util/flags/FlagUtils.java
@@ -0,0 +1,587 @@
+/*
+ * 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.core.util.flags;
+
+import static brooklyn.util.GroovyJavaMethods.elvis;
+import static brooklyn.util.GroovyJavaMethods.truth;
+import static com.google.common.base.Preconditions.checkNotNull;
+import groovy.lang.Closure;
+import groovy.lang.GroovyObject;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.brooklyn.api.entity.trait.Configurable;
+import org.apache.brooklyn.core.util.config.ConfigBag;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.config.ConfigKey;
+import brooklyn.config.ConfigKey.HasConfigKey;
+import brooklyn.util.GroovyJavaMethods;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Predicate;
+import com.google.common.base.Predicates;
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+
+
+/** class to help transfer values passed as named arguments to other well-known variables/fields/objects;
+ * see the test case for example usage */
+public class FlagUtils {
+
+    public static final Logger log = LoggerFactory.getLogger(FlagUtils.class);
+    
+    private FlagUtils() {}
+    
+    /** see {@link #setFieldsFromFlags(Object o, ConfigBag)} */
+    public static Map<?, ?> setPublicFieldsFromFlags(Map<?, ?> flags, Object o) {
+        return setFieldsFromFlagsInternal(o, Arrays.asList(o.getClass().getFields()), flags, null, true);
+    }
+
+    /** see {@link #setFieldsFromFlags(Object, ConfigBag)} */
+    public static Map<?, ?> setFieldsFromFlags(Map<?, ?> flags, Object o) {
+        return setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), flags, null, true);
+    }
+    
+    /** sets all fields (including private and static, local and inherited) annotated {@link SetFromFlag} on the given object, 
+     * from the given flags map, returning just those flag-value pairs passed in which do not correspond to SetFromFlags fields 
+     * annotated ConfigKey and HasConfigKey fields are _configured_ (and we assume the object in that case is {@link Configurable});
+     * keys should be ConfigKey, HasConfigKey, or String;
+     * default values are also applied unless that is specified false on one of the variants of this method which takes such an argument
+     */
+    public static void setFieldsFromFlags(Object o, ConfigBag configBag) {
+        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), configBag.getAllConfig(), configBag, true);
+    }
+
+    /** as {@link #setFieldsFromFlags(Object, ConfigBag)}, but allowing control over whether default values should be set */
+    public static void setFieldsFromFlags(Object o, ConfigBag configBag, boolean setDefaultVals) {
+        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), configBag.getAllConfig(), configBag, setDefaultVals);
+    }
+
+    /** as {@link #setFieldsFromFlags(Object, ConfigBag)}, but specifying a subset of flags to use */
+    public static void setFieldsFromFlagsWithBag(Object o, Map<?,?> flags, ConfigBag configBag, boolean setDefaultVals) {
+        setFieldsFromFlagsInternal(o, getAllFields(o.getClass()), flags, configBag, setDefaultVals);
+    }
+
+    /**
+     * Sets the field with the given flag (if it exists) to the given value.
+     * Will attempt to coerce the value to the required type.
+     * Will respect "nullable" on the SetFromFlag annotation.
+     * 
+     * @throws IllegalArgumentException If fieldVal is null and the SetFromFlag annotation set nullable=false
+     */
+    public static boolean setFieldFromFlag(Object o, String flagName, Object fieldVal) {
+        return setFieldFromFlagInternal(checkNotNull(flagName, "flagName"), fieldVal, o, getAllFields(o.getClass()));
+    }
+    
+    /** get all fields (including private and static) on the given object and all supertypes, 
+     * that are annotated with SetFromFlags. 
+     */
+    public static Map<String, ?> getFieldsWithFlags(Object o) {
+        return getFieldsWithFlagsInternal(o, getAllFields(o.getClass()));
+    }
+    
+    /**
+     * Finds the {@link Field} on the given object annotated with the given name flag.
+     */
+    public static Field findFieldForFlag(String flagName, Object o) {
+        return findFieldForFlagInternal(flagName, o, getAllFields(o.getClass()));
+    }
+
+    /** get all fields (including private and static) and their values on the given object and all supertypes, 
+     * where the field is annotated with SetFromFlags. 
+     */
+    public static Map<String, Object> getFieldsWithFlagsExcludingModifiers(Object o, int excludingModifiers) {
+        List<Field> filteredFields = Lists.newArrayList();
+        for (Field contender : getAllFields(o.getClass())) {
+            if ((contender.getModifiers() & excludingModifiers) == 0) {
+                filteredFields.add(contender);
+            }
+        }
+        return getFieldsWithFlagsInternal(o, filteredFields);
+    }
+    
+    /** get all fields with the given modifiers, and their values on the given object and all supertypes, 
+     * where the field is annotated with SetFromFlags. 
+     */
+    public static Map<String, Object> getFieldsWithFlagsWithModifiers(Object o, int requiredModifiers) {
+        List<Field> filteredFields = Lists.newArrayList();
+        for (Field contender : getAllFields(o.getClass())) {
+            if ((contender.getModifiers() & requiredModifiers) == requiredModifiers) {
+                filteredFields.add(contender);
+            }
+        }
+        return getFieldsWithFlagsInternal(o, filteredFields);
+    }
+    
+    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
+     * using the indicated flags/config-bag 
+     * @deprecated since 0.7.0 use {@link #setAllConfigKeys(Map, Configurable, boolean)} */
+    public static Map<String, ?> setAllConfigKeys(Map<String, ?> flagsOrConfig, Configurable instance) {
+        return setAllConfigKeys(flagsOrConfig, instance, false);
+    }
+    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
+     * using the indicated flags/config-bag */
+    public static Map<String, ?> setAllConfigKeys(Map<String, ?> flagsOrConfig, Configurable instance, boolean includeFlags) {
+        ConfigBag bag = new ConfigBag().putAll(flagsOrConfig);
+        setAllConfigKeys(instance, bag, includeFlags);
+        return bag.getUnusedConfigMutable();
+    }
+    
+    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
+     * using the indicated flags/config-bag 
+    * @deprecated since 0.7.0 use {@link #setAllConfigKeys(Configurable, ConfigBag, boolean)} */
+    public static void setAllConfigKeys(Configurable o, ConfigBag bag) {
+        setAllConfigKeys(o, bag, false);
+    }
+    /** sets _all_ accessible _{@link ConfigKey}_ and {@link HasConfigKey} fields on the given object, 
+     * using the indicated flags/config-bag */
+    public static void setAllConfigKeys(Configurable o, ConfigBag bag, boolean includeFlags) {
+        for (Field f: getAllFields(o.getClass())) {
+            ConfigKey<?> key = getFieldAsConfigKey(o, f);
+            if (key!=null) {
+                FlagConfigKeyAndValueRecord record = getFlagConfigKeyRecord(f, key, bag);
+                if ((includeFlags && record.isValuePresent()) || record.getConfigKeyMaybeValue().isPresent()) {
+                    setField(o, f, record.getValueOrNullPreferringConfigKey(), null);
+                }
+            }
+        }
+    }
+    
+    public static class FlagConfigKeyAndValueRecord {
+        private String flagName = null;
+        private ConfigKey<?> configKey = null;
+        private Maybe<Object> flagValue = Maybe.absent();
+        private Maybe<Object> configKeyValue = Maybe.absent();
+        
+        public String getFlagName() {
+            return flagName;
+        }
+        public ConfigKey<?> getConfigKey() {
+            return configKey;
+        }
+        public Maybe<Object> getFlagMaybeValue() {
+            return flagValue;
+        }
+        public Maybe<Object> getConfigKeyMaybeValue() {
+            return configKeyValue;
+        }
+        public Object getValueOrNullPreferringConfigKey() {
+            return getConfigKeyMaybeValue().or(getFlagMaybeValue()).orNull();
+        }
+        public Object getValueOrNullPreferringFlag() {
+            return getFlagMaybeValue().or(getConfigKeyMaybeValue()).orNull();
+        }
+        /** true if value is present for either flag or config key */
+        public boolean isValuePresent() {
+            return flagValue.isPresent() || configKeyValue.isPresent();
+        }
+        
+        @Override
+        public String toString() {
+            return Objects.toStringHelper(this).omitNullValues()
+                .add("flag", flagName)
+                .add("configKey", configKey)
+                .add("flagValue", flagValue.orNull())
+                .add("configKeyValue", configKeyValue.orNull())
+                .toString();
+        }
+    }
+    
+    /** gets all the flags/keys in the given config bag which are applicable to the given type's config keys and flags */
+    public static <T> List<FlagConfigKeyAndValueRecord> findAllFlagsAndConfigKeys(T optionalInstance, Class<? extends T> type, ConfigBag input) {
+        List<FlagConfigKeyAndValueRecord> output = new ArrayList<FlagUtils.FlagConfigKeyAndValueRecord>();
+        for (Field f: getAllFields(type)) {
+            ConfigKey<?> key = getFieldAsConfigKey(optionalInstance, f);
+            FlagConfigKeyAndValueRecord record = getFlagConfigKeyRecord(f, key, input);
+            if (record.isValuePresent())
+                output.add(record);
+        }
+        return output;
+    }
+
+    /** returns the flag/config-key record for the given input */
+    private static FlagConfigKeyAndValueRecord getFlagConfigKeyRecord(Field f, ConfigKey<?> key, ConfigBag input) {
+        FlagConfigKeyAndValueRecord result = new FlagConfigKeyAndValueRecord(); 
+        result.configKey = key;
+        if (key!=null && input.containsKey(key))
+            result.configKeyValue = Maybe.<Object>of(input.getStringKey(key.getName()));
+        SetFromFlag flag = f.getAnnotation(SetFromFlag.class);
+        if (flag!=null) {
+            result.flagName = flag.value();
+            if (input.containsKey(flag.value()))
+                result.flagValue = Maybe.of(input.getStringKey(flag.value()));
+        }
+        return result;
+    }
+
+    /** returns all fields on the given class, superclasses, and interfaces thereof, in that order of preference,
+     * (excluding fields on Object) */
+    public static List<Field> getAllFields(Class<?> base, Closure<Boolean> filter) {
+        return getAllFields(base, GroovyJavaMethods.<Field>predicateFromClosure(filter));
+    }
+    public static List<Field> getAllFields(Class<?> base) {
+        return getAllFields(base, Predicates.<Field>alwaysTrue());
+    }
+    public static List<Field> getAllFields(Class<?> base, Predicate<Field> filter) {
+        return getLocalFields(getAllAssignableTypes(base), filter);
+    }
+    /** returns all fields explicitly declared on the given classes */
+    public static List<Field> getLocalFields(List<Class<?>> classes) {
+        return getLocalFields(classes, Predicates.<Field>alwaysTrue());
+    }
+    public static List<Field> getLocalFields(List<Class<?>> classes, Closure<Boolean> filter) {
+        return getLocalFields(classes, GroovyJavaMethods.<Field>predicateFromClosure(filter));
+    }
+    public static List<Field> getLocalFields(List<Class<?>> classes, Predicate<Field> filter) {
+        List<Field> fields = Lists.newArrayList();
+        for (Class<?> c : classes) {
+            for (Field f : c.getDeclaredFields()) {
+                if (filter.apply(f)) fields.add(f);
+            }
+        }
+        return fields;
+    }
+    
+    /** returns base, superclasses, then interfaces */
+    public static List<Class<?>> getAllAssignableTypes(Class<?> base) {
+        return getAllAssignableTypes(base, new Predicate<Class<?>>() {
+            @Override public boolean apply(Class<?> it) {
+                return (it != Object.class) && (it != GroovyObject.class);
+            }
+        });
+    }
+    public static List<Class<?>> getAllAssignableTypes(Class<?> base, Closure<Boolean> filter) {
+        return getAllAssignableTypes(base, GroovyJavaMethods.<Class<?>>predicateFromClosure(filter));
+    }
+    public static List<Class<?>> getAllAssignableTypes(Class<?> base, Predicate<Class<?>> filter) {
+        List<Class<?>> classes = Lists.newArrayList();
+        for (Class<?> c = base; c != null; c = c.getSuperclass()) {
+            if (filter.apply(c)) classes.add(c);
+        }
+        for (int i=0; i<classes.size(); i++) {
+            for (Class<?> interf : classes.get(i).getInterfaces()) {
+                if (filter.apply(interf) && !(classes.contains(interf))) classes.add(interf);
+            }
+        }
+        return classes;
+    }
+    
+    private static Map<String, Object> getFieldsWithFlagsInternal(Object o, Collection<Field> fields) {
+        Map<String, Object> result = Maps.newLinkedHashMap();
+        for (Field f: fields) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (cf != null) {
+                String flagName = elvis(cf.value(), f.getName());
+                if (truth(flagName)) {
+                    result.put(flagName, getField(o, f));
+                } else {
+                    log.warn("Ignoring field {} of object {} as no flag name available", f, o);
+                }
+            }
+        }
+        return result;
+    }
+
+    private static Field findFieldForFlagInternal(String flagName, Object o, Collection<Field> fields) {
+        for (Field f: fields) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (cf != null) {
+                String contenderName = elvis(cf.value(), f.getName());
+                if (flagName.equals(contenderName)) {
+                    return f;
+                }
+            }
+        }
+        throw new NoSuchElementException("Field with flag "+flagName+" not found on "+o+" of type "+(o != null ? o.getClass() : null));
+    }
+
+    private static boolean setFieldFromFlagInternal(String flagName, Object fieldVal, Object o, Collection<Field> fields) {
+        for (Field f: fields) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (cf != null && flagName.equals(elvis(cf.value(), f.getName()))) {
+                setField(o, f, fieldVal, cf);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static Map<String, ?> setFieldsFromFlagsInternal(Object o, Collection<Field> fields, Map<?,?> flagsOrConfig, ConfigBag bag, boolean setDefaultVals) {
+        if (bag==null) bag = new ConfigBag().putAll(flagsOrConfig);
+        for (Field f: fields) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (cf!=null) setFieldFromConfig(o, f, bag, cf, setDefaultVals);
+        }
+        return bag.getUnusedConfigMutable();
+    }
+
+    private static void setFieldFromConfig(Object o, Field f, ConfigBag bag, SetFromFlag optionalAnnotation, boolean setDefaultVals) {
+        String flagName = optionalAnnotation==null ? null : (String)elvis(optionalAnnotation.value(), f.getName());
+        // prefer flag name, if present
+        if (truth(flagName) && bag.containsKey(flagName)) {
+            setField(o, f, bag.getStringKey(flagName), optionalAnnotation);
+            return;
+        }
+        // first check whether it is a key
+        ConfigKey<?> key = getFieldAsConfigKey(o, f);
+        if (key!=null && bag.containsKey(key)) {
+            Object uncoercedValue = bag.getStringKey(key.getName());
+            setField(o, f, uncoercedValue, optionalAnnotation);
+            return;
+        }
+        if (setDefaultVals && optionalAnnotation!=null && truth(optionalAnnotation.defaultVal())) {
+            Object oldValue;
+            try {
+                f.setAccessible(true);
+                oldValue = f.get(o);
+                if (oldValue==null || oldValue.equals(getDefaultValueForType(f.getType()))) {
+                    setField(o, f, optionalAnnotation.defaultVal(), optionalAnnotation);
+                }
+            } catch (Exception e) {
+                Exceptions.propagate(e);
+            }
+            return;
+        }
+    }
+
+    /** returns the given field as a config key, if it is an accessible config key, otherwise null */
+    private static ConfigKey<?> getFieldAsConfigKey(Object optionalInstance, Field f) {
+        if (optionalInstance==null) {
+            if ((f.getModifiers() & Modifier.STATIC)==0)
+                // non-static field on null instance, can't be set
+                return null;
+        }
+        if (ConfigKey.class.isAssignableFrom(f.getType())) {
+            return (ConfigKey<?>) getField(optionalInstance, f);
+        } else if (HasConfigKey.class.isAssignableFrom(f.getType())) {
+            return ((HasConfigKey<?>)getField(optionalInstance, f)).getConfigKey();
+        }
+        return null;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public static void setConfig(Object objectOfField, ConfigKey<?> key, Object value, SetFromFlag optionalAnnotation) {
+        if (objectOfField instanceof Configurable) {
+            ((Configurable)objectOfField).setConfig((ConfigKey)key, value);
+            return;
+        } else {
+            if (optionalAnnotation==null) {
+                log.warn("Cannot set key "+key.getName()+" on "+objectOfField+": containing class is not Configurable");
+            } else if (!key.getName().equals(optionalAnnotation.value())) {
+                log.warn("Cannot set key "+key.getName()+" on "+objectOfField+" from flag "+optionalAnnotation.value()+": containing class is not Configurable");
+            } else {
+                // if key and flag are the same, then it will probably happen automatically
+                if (log.isDebugEnabled())
+                    log.debug("Cannot set key "+key.getName()+" on "+objectOfField+" from flag "+optionalAnnotation.value()+": containing class is not Configurable");
+            }
+            return;
+        }
+    }
+    
+    /** sets the field to the value, after checking whether the given value can be set 
+     * respecting the constraints of the annotation 
+     */
+    public static void setField(Object objectOfField, Field f, Object value, SetFromFlag optionalAnnotation) {
+        try {
+            ConfigKey<?> key = getFieldAsConfigKey(objectOfField, f);
+            if (key!=null) {
+                setConfig(objectOfField, key, value, optionalAnnotation);
+                return;
+            }
+            
+            if (!f.isAccessible()) f.setAccessible(true);
+            if (optionalAnnotation!=null && optionalAnnotation.immutable()) {
+                Object oldValue = f.get(objectOfField);
+                if (!Objects.equal(oldValue, getDefaultValueForType(f.getType())) && oldValue != value) {
+                    throw new IllegalStateException("Forbidden modification to immutable field "+
+                        f+" in "+objectOfField+": attempting to change to "+value+" when was already "+oldValue);
+                }
+            }
+            if (optionalAnnotation!=null && !optionalAnnotation.nullable() && value==null) {
+                throw new IllegalArgumentException("Forbidden null assignment to non-nullable field "+
+                        f+" in "+objectOfField);
+            }
+            if (optionalAnnotation!=null && (f.getModifiers() & Modifier.STATIC)==Modifier.STATIC)
+                log.warn("Setting static field "+f+" in "+objectOfField+" from flag "+optionalAnnotation.value()+": discouraged");
+
+            Object newValue;
+            try {
+                newValue = TypeCoercions.coerce(value, f.getType());
+            } catch (Exception e) {
+                throw new IllegalArgumentException("Cannot set "+f+" in "+objectOfField+" from type "+value.getClass()+" ("+value+"): "+e, e);
+            }
+            f.set(objectOfField, newValue);
+            if (log.isTraceEnabled()) log.trace("FlagUtils for "+objectOfField+", setting field="+f.getName()+"; val="+value+"; newVal="+newValue+"; key="+key);
+
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+    /** gets the value of the field. 
+     */
+    public static Object getField(Object objectOfField, Field f) {
+        try {
+            if (!f.isAccessible()) f.setAccessible(true);
+            return f.get(objectOfField);
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+    
+    /** returns the default/inital value that is assigned to fields of the givien type;
+     * if the type is not primitive this value is null;
+     * for primitive types it is obvious but not AFAIK programmatically visible
+     * (e.g. 0 for int, false for boolean)  
+     */
+    public static Object getDefaultValueForType(Class<?> t) {
+        if (!t.isPrimitive()) return null;
+        if (t==Integer.TYPE) return (int)0;
+        if (t==Long.TYPE) return (long)0;
+        if (t==Double.TYPE) return (double)0;
+        if (t==Float.TYPE) return (float)0;
+        if (t==Byte.TYPE) return (byte)0;
+        if (t==Short.TYPE) return (short)0;
+        if (t==Character.TYPE) return (char)0;
+        if (t==Boolean.TYPE) return false;
+        //should never happen
+        throw new IllegalStateException("Class "+t+" is an unknown primitive.");
+    }
+
+    /** returns a map of all fields which are annotated 'SetFromFlag', along with the annotation */
+    public static Map<Field,SetFromFlag> getAnnotatedFields(Class<?> type) {
+        Map<Field, SetFromFlag> result = Maps.newLinkedHashMap();
+        for (Field f: getAllFields(type)) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (truth(cf)) result.put(f, cf);
+        }
+        return result;
+    }
+
+    /** returns a map of all {@link ConfigKey} fields which are annotated 'SetFromFlag', along with the annotation */
+    public static Map<ConfigKey<?>,SetFromFlag> getAnnotatedConfigKeys(Class<?> type) {
+        Map<ConfigKey<?>, SetFromFlag> result = Maps.newLinkedHashMap();
+        List<Field> fields = getAllFields(type, new Predicate<Field>() {
+            @Override public boolean apply(Field f) {
+                return (f != null) && ConfigKey.class.isAssignableFrom(f.getType()) && ((f.getModifiers() & Modifier.STATIC)!=0);
+            }});
+        for (Field f: fields) {
+            SetFromFlag cf = f.getAnnotation(SetFromFlag.class);
+            if (cf != null) {
+                ConfigKey<?> key = getFieldAsConfigKey(null, f);
+                if (key != null) {
+                    result.put(key, cf);
+                }
+            }
+        }
+        return result;
+    }
+
+    /** returns a map of all fields which are annotated 'SetFromFlag' with their current values;
+     * useful if you want to clone settings from one object
+     */
+    public static Map<String,Object> getFieldsWithValues(Object o) {
+        try {
+            Map<String, Object> result = Maps.newLinkedHashMap();
+            for (Map.Entry<Field, SetFromFlag> entry : getAnnotatedFields(o.getClass()).entrySet()) {
+                Field f = entry.getKey();
+                SetFromFlag cf = entry.getValue();
+                String flagName = elvis(cf.value(), f.getName());
+                if (truth(flagName)) {
+                    if (!f.isAccessible()) f.setAccessible(true);
+                    result.put(flagName, f.get(o));
+                }
+            }
+            return result;
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+        
+    /**
+     * @throws an IllegalStateException if there are fields required (nullable=false) which are unset 
+     * @throws wrapped IllegalAccessException
+     */
+    public static void checkRequiredFields(Object o) {
+        try {
+            Set<String> unsetFields = Sets.newLinkedHashSet();
+            for (Map.Entry<Field, SetFromFlag> entry : getAnnotatedFields(o.getClass()).entrySet()) {
+                Field f = entry.getKey();
+                SetFromFlag cf = entry.getValue();
+                if (!cf.nullable()) {
+                    String flagName = elvis(cf.value(), f.getName());
+                    if (!f.isAccessible()) f.setAccessible(true);
+                    Object v = f.get(o);
+                    if (v==null) unsetFields.add(flagName);
+                }
+            }
+            if (truth(unsetFields)) {
+                throw new IllegalStateException("Missing required "+(unsetFields.size()>1 ? "fields" : "field")+": "+unsetFields);
+            }
+        } catch (IllegalAccessException e) {
+            throw Throwables.propagate(e);
+        }
+    }
+
+//    /** sets all fields in target annotated with @SetFromFlag using the configuration in the given config bag */
+//    public static void setFieldsFromConfigFlags(Object target, ConfigBag configBag) {
+//        setFieldsFromConfigFlags(target, configBag.getAllConfig(), configBag);
+//    }
+//
+//    
+//    /** sets all fields in target annotated with @SetFromFlag using the configuration in the given configToUse,
+//     * marking used in the given configBag */
+//    public static void setFieldsFromConfigFlags(Object target, Map<?,?> configToUse, ConfigBag configBag) {
+//        for (Map.Entry<?,?> entry: configToUse.entrySet()) {
+//            setFieldFromConfigFlag(target, entry.getKey(), entry.getValue(), configBag);
+//        }
+//    }
+//
+//    public static void setFieldFromConfigFlag(Object target, Object key, Object value, ConfigBag optionalConfigBag) {
+//        String name = null;
+//        if (key instanceof String) name = (String)key;
+//        else if (key instanceof ConfigKey<?>) name = ((ConfigKey<?>)key).getName();
+//        else if (key instanceof HasConfigKey<?>) name = ((HasConfigKey<?>)key).getConfigKey().getName();
+//        else {
+//            if (key!=null) {
+//                log.warn("Invalid config type "+key.getClass().getCanonicalName()+" ("+key+") when configuring "+target+"; ignoring");
+//            }
+//            return;
+//        }
+//        if (setFieldFromFlag(name, value, target)) {
+//            if (optionalConfigBag!=null)
+//                optionalConfigBag.markUsed(name);
+//        }
+//    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/org/apache/brooklyn/core/util/flags/MethodCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/util/flags/MethodCoercions.java b/core/src/main/java/org/apache/brooklyn/core/util/flags/MethodCoercions.java
new file mode 100644
index 0000000..20116cf
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/util/flags/MethodCoercions.java
@@ -0,0 +1,183 @@
+/*
+ * 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.core.util.flags;
+
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
+
+import javax.annotation.Nullable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+/**
+ * A way of binding a loosely-specified method call into a strongly-typed Java method call.
+ */
+public class MethodCoercions {
+
+    /**
+     * Returns a predicate that matches a method with the given name, and a single parameter that
+     * {@link org.apache.brooklyn.core.util.flags.TypeCoercions#tryCoerce(Object, com.google.common.reflect.TypeToken)} can process
+     * from the given argument.
+     *
+     * @param methodName name of the method
+     * @param argument argument that is intended to be given
+     * @return a predicate that will match a compatible method
+     */
+    public static Predicate<Method> matchSingleParameterMethod(final String methodName, final Object argument) {
+        checkNotNull(methodName, "methodName");
+        checkNotNull(argument, "argument");
+
+        return new Predicate<Method>() {
+            @Override
+            public boolean apply(@Nullable Method input) {
+                if (input == null) return false;
+                if (!input.getName().equals(methodName)) return false;
+                Type[] parameterTypes = input.getGenericParameterTypes();
+                return parameterTypes.length == 1
+                        && TypeCoercions.tryCoerce(argument, TypeToken.of(parameterTypes[0])).isPresentAndNonNull();
+
+            }
+        };
+    }
+
+    /**
+     * Tries to find a single-parameter method with a parameter compatible with (can be coerced to) the argument, and
+     * invokes it.
+     *
+     * @param instance the object to invoke the method on
+     * @param methodName the name of the method to invoke
+     * @param argument the argument to the method's parameter.
+     * @return the result of the method call, or {@link brooklyn.util.guava.Maybe#absent()} if method could not be matched.
+     */
+    public static Maybe<?> tryFindAndInvokeSingleParameterMethod(final Object instance, final String methodName, final Object argument) {
+        Class<?> clazz = instance.getClass();
+        Iterable<Method> methods = Arrays.asList(clazz.getMethods());
+        Optional<Method> matchingMethod = Iterables.tryFind(methods, matchSingleParameterMethod(methodName, argument));
+        if (matchingMethod.isPresent()) {
+            Method method = matchingMethod.get();
+            try {
+                Type paramType = method.getGenericParameterTypes()[0];
+                Object coercedArgument = TypeCoercions.coerce(argument, TypeToken.of(paramType));
+                return Maybe.of(method.invoke(instance, coercedArgument));
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw Exceptions.propagate(e);
+            }
+        } else {
+            return Maybe.absent();
+        }
+    }
+
+    /**
+     * Returns a predicate that matches a method with the given name, and parameters that
+     * {@link org.apache.brooklyn.core.util.flags.TypeCoercions#tryCoerce(Object, com.google.common.reflect.TypeToken)} can process
+     * from the given list of arguments.
+     *
+     * @param methodName name of the method
+     * @param arguments arguments that is intended to be given
+     * @return a predicate that will match a compatible method
+     */
+    public static Predicate<Method> matchMultiParameterMethod(final String methodName, final List<?> arguments) {
+        checkNotNull(methodName, "methodName");
+        checkNotNull(arguments, "arguments");
+
+        return new Predicate<Method>() {
+            @Override
+            public boolean apply(@Nullable Method input) {
+                if (input == null) return false;
+                if (!input.getName().equals(methodName)) return false;
+                int numOptionParams = arguments.size();
+                Type[] parameterTypes = input.getGenericParameterTypes();
+                if (parameterTypes.length != numOptionParams) return false;
+
+                for (int paramCount = 0; paramCount < numOptionParams; paramCount++) {
+                    if (!TypeCoercions.tryCoerce(((List) arguments).get(paramCount),
+                            TypeToken.of(parameterTypes[paramCount])).isPresentAndNonNull()) return false;
+                }
+                return true;
+            }
+        };
+    }
+
+    /**
+     * Tries to find a multiple-parameter method with each parameter compatible with (can be coerced to) the
+     * corresponding argument, and invokes it.
+     *
+     * @param instance the object to invoke the method on
+     * @param methodName the name of the method to invoke
+     * @param argument a list of the arguments to the method's parameters.
+     * @return the result of the method call, or {@link brooklyn.util.guava.Maybe#absent()} if method could not be matched.
+     */
+    public static Maybe<?> tryFindAndInvokeMultiParameterMethod(final Object instance, final String methodName, final List<?> arguments) {
+        Class<?> clazz = instance.getClass();
+        Iterable<Method> methods = Arrays.asList(clazz.getMethods());
+        Optional<Method> matchingMethod = Iterables.tryFind(methods, matchMultiParameterMethod(methodName, arguments));
+        if (matchingMethod.isPresent()) {
+            Method method = matchingMethod.get();
+            try {
+                int numOptionParams = ((List)arguments).size();
+                Object[] coercedArguments = new Object[numOptionParams];
+                for (int paramCount = 0; paramCount < numOptionParams; paramCount++) {
+                    Object argument = arguments.get(paramCount);
+                    Type paramType = method.getGenericParameterTypes()[paramCount];
+                    coercedArguments[paramCount] = TypeCoercions.coerce(argument, TypeToken.of(paramType));
+                }
+                return Maybe.of(method.invoke(instance, coercedArguments));
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw Exceptions.propagate(e);
+            }
+        } else {
+            return Maybe.absent();
+        }
+    }
+
+    /**
+     * Tries to find a method with each parameter compatible with (can be coerced to) the corresponding argument, and invokes it.
+     *
+     * @param instance the object to invoke the method on
+     * @param methodName the name of the method to invoke
+     * @param argument a list of the arguments to the method's parameters, or a single argument for a single-parameter method.
+     * @return the result of the method call, or {@link brooklyn.util.guava.Maybe#absent()} if method could not be matched.
+     */
+    public static Maybe<?> tryFindAndInvokeBestMatchingMethod(final Object instance, final String methodName, final Object argument) {
+        if (argument instanceof List) {
+            List<?> arguments = (List<?>) argument;
+
+            // ambiguous case: we can't tell if the user is using the multi-parameter syntax, or the single-parameter
+            // syntax for a method which takes a List parameter. So we try one, then fall back to the other.
+
+            Maybe<?> maybe = tryFindAndInvokeMultiParameterMethod(instance, methodName, arguments);
+            if (maybe.isAbsent())
+                maybe = tryFindAndInvokeSingleParameterMethod(instance, methodName, argument);
+
+            return maybe;
+        } else {
+            return tryFindAndInvokeSingleParameterMethod(instance, methodName, argument);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/org/apache/brooklyn/core/util/flags/SetFromFlag.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/util/flags/SetFromFlag.java b/core/src/main/java/org/apache/brooklyn/core/util/flags/SetFromFlag.java
new file mode 100644
index 0000000..3b69c05
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/util/flags/SetFromFlag.java
@@ -0,0 +1,71 @@
+/*
+ * 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.core.util.flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Annotation to indicate that a variable may be set through the use of a named argument,
+ * looking for the name specified here or inferred from the annotated field/argument/object.
+ * <p>
+ * This is used to automate the processing where named arguments are passed in constructors
+ * and other methods, and the values of those named arguments should be transferred to
+ * other known fields/arguments/objects at runtime.
+ * <p>
+ * Fields on a class are typically set from values in a map with a call to
+ * {@link FlagUtils#setFieldsFromFlags(java.util.Map, Object)}.
+ * That method (and related, in the same class) will attend to the arguments here.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SetFromFlag {
+
+    /** the flag (key) which should be used to find the value; if empty defaults to field/argument/object name */
+    String value() default "";
+    
+    /** whether the object should not be changed once set; defaults to false
+     * <p>
+     * this is partially tested for in many routines, but not all;
+     * when nullable=false the testing (when done) is guaranteed.
+     * however if nullable is allowed we do not distinguish between null and unset
+     * so explicitly setting null then setting to a value is not detected as an illegal mutating.
+     */
+    boolean immutable() default false;
+    
+    /** whether the object is required & should not be set to null; defaults to true.
+     * (there is no 'required' parameter, but setting nullable false then invoking 
+     * e.g. {@link FlagUtils#checkRequiredFields(Object)} has the effect of requiring a value)
+     * <p>
+     * code should call that method explicitly to enforce nullable false;
+     * errors are not done during a call to setFieldsFromFlags 
+     * because fields may be initialised in multiple passes.) 
+     * <p>
+     * this is partially tested for in many routines, but not all
+     */
+    boolean nullable() default true;
+
+    /** The default value, if it is not explicitly set.
+     * <p>
+     * The value will be coerced from String where required, for types supported by {@link TypeCoercions}.
+     * <p>
+     * The field will be initialised with its default value on the first call to setFieldsFromFlags
+     * (or related).  (The field will not be initialised if that method is not called.) 
+     */
+    String defaultVal() default "";
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a4c0e5fd/core/src/main/java/org/apache/brooklyn/core/util/flags/TypeCoercions.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/core/util/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/core/util/flags/TypeCoercions.java
new file mode 100644
index 0000000..2c03620
--- /dev/null
+++ b/core/src/main/java/org/apache/brooklyn/core/util/flags/TypeCoercions.java
@@ -0,0 +1,879 @@
+/*
+ * 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.core.util.flags;
+
+import groovy.lang.Closure;
+import groovy.time.TimeDuration;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.GuardedBy;
+
+import org.apache.brooklyn.api.entity.Entity;
+import org.apache.brooklyn.api.event.AttributeSensor;
+import org.apache.brooklyn.api.event.Sensor;
+import org.apache.brooklyn.core.internal.BrooklynInitialization;
+import org.apache.brooklyn.core.util.task.Tasks;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import brooklyn.entity.basic.BrooklynTaskTags;
+import brooklyn.entity.basic.ClosureEntityFactory;
+import brooklyn.entity.basic.ConfigurableEntityFactory;
+import brooklyn.entity.basic.ConfigurableEntityFactoryFromEntityFactory;
+import brooklyn.event.basic.Sensors;
+import brooklyn.util.JavaGroovyEquivalents;
+import brooklyn.util.collections.MutableSet;
+import brooklyn.util.collections.QuorumCheck;
+import brooklyn.util.collections.QuorumCheck.QuorumChecks;
+import brooklyn.util.exceptions.Exceptions;
+import brooklyn.util.guava.Maybe;
+import brooklyn.util.javalang.Enums;
+import brooklyn.util.net.Cidr;
+import brooklyn.util.net.Networking;
+import brooklyn.util.net.UserAndHostAndPort;
+import brooklyn.util.text.StringEscapes.JavaStringEscapes;
+import brooklyn.util.text.Strings;
+import brooklyn.util.time.Duration;
+import brooklyn.util.time.Time;
+import brooklyn.util.yaml.Yamls;
+
+import com.google.common.base.CaseFormat;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Predicate;
+import com.google.common.collect.HashBasedTable;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import com.google.common.collect.Table;
+import com.google.common.net.HostAndPort;
+import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
+
+@SuppressWarnings("rawtypes")
+public class TypeCoercions {
+
+    private static final Logger log = LoggerFactory.getLogger(TypeCoercions.class);
+    
+    private TypeCoercions() {}
+
+    /** Store the coercion {@link Function functions} in a {@link Table table}. */
+    @GuardedBy("TypeCoercions.class")
+    private static Table<Class, Class, Function> registry = HashBasedTable.create();
+
+    /**
+     * Attempts to coerce {@code value} to {@code targetType}.
+     * <p>
+     * Maintains a registry of adapter functions for type pairs in a {@link Table} which
+     * is searched after checking various strategies, including the following:
+     * <ul>
+     * <li>{@code value.asTargetType()}
+     * <li>{@code TargetType.fromType(value)} (if {@code value instanceof Type})
+     * <li>{@code value.targetTypeValue()} (handy for primitives)
+     * <li>{@code TargetType.valueOf(value)} (for enums)
+     * </ul>
+     * <p>
+     * A default set of adapters will handle most common Java-type coercions
+     * as well as <code>String</code> coercion to:
+     * <ul>
+     * <li> {@link Set}, {@link List}, {@link Map} and similar -- parses as YAML
+     * <li> {@link Date} -- parses using {@link Time#parseDate(String)}
+     * <li> {@link Duration} -- parses using {@link Duration#parse(String)}
+     * </ul>
+     */
+    public static <T> T coerce(Object value, Class<T> targetType) {
+        return coerce(value, TypeToken.of(targetType));
+    }
+
+    /** @see #coerce(Object, Class) */
+    public static <T> Maybe<T> tryCoerce(Object value, TypeToken<T> targetTypeToken) {
+        try {
+            return Maybe.of( coerce(value, targetTypeToken) );
+        } catch (Throwable t) {
+            Exceptions.propagateIfFatal(t);
+            return Maybe.absent(t); 
+        }
+    }
+    
+    /** @see #coerce(Object, Class) */
+    @SuppressWarnings({ "unchecked" })
+    public static <T> T coerce(Object value, TypeToken<T> targetTypeToken) {
+        if (value==null) return null;
+        Class<? super T> targetType = targetTypeToken.getRawType();
+
+        //recursive coercion of parameterized collections and map entries
+        if (targetTypeToken.getType() instanceof ParameterizedType) {
+            if (value instanceof Collection && Collection.class.isAssignableFrom(targetType)) {
+                Type[] arguments = ((ParameterizedType) targetTypeToken.getType()).getActualTypeArguments();
+                if (arguments.length != 1) {
+                    throw new IllegalStateException("Unexpected number of parameters in collection type: " + arguments);
+                }
+                Collection coerced = Lists.newLinkedList();
+                TypeToken<?> listEntryType = TypeToken.of(arguments[0]);
+                for (Object entry : (Iterable<?>) value) {
+                    coerced.add(coerce(entry, listEntryType));
+                }
+                if (Set.class.isAssignableFrom(targetType)) {
+                    return (T) Sets.newLinkedHashSet(coerced);
+                } else {
+                    return (T) Lists.newArrayList(coerced);
+                }
+            } else if (value instanceof Map && Map.class.isAssignableFrom(targetType)) {
+                Type[] arguments = ((ParameterizedType) targetTypeToken.getType()).getActualTypeArguments();
+                if (arguments.length != 2) {
+                    throw new IllegalStateException("Unexpected number of parameters in map type: " + arguments);
+                }
+                Map coerced = Maps.newLinkedHashMap();
+                TypeToken<?> mapKeyType = TypeToken.of(arguments[0]);
+                TypeToken<?> mapValueType = TypeToken.of(arguments[1]);
+                for (Map.Entry entry : ((Map<?,?>) value).entrySet()) {
+                    coerced.put(coerce(entry.getKey(), mapKeyType),  coerce(entry.getValue(), mapValueType));
+                }
+                return (T) Maps.newLinkedHashMap(coerced);
+            }
+        }
+
+        if (targetType.isInstance(value)) return (T) value;
+
+        // TODO use registry first?
+
+        //deal with primitive->primitive casting
+        if (isPrimitiveOrBoxer(targetType) && isPrimitiveOrBoxer(value.getClass())) {
+            // Don't just rely on Java to do its normal casting later; if caller writes
+            // long `l = coerce(new Integer(1), Long.class)` then letting java do its casting will fail,
+            // because an Integer will not automatically be unboxed and cast to a long
+            return castPrimitive(value, (Class<T>)targetType);
+        }
+
+        //deal with string->primitive
+        if (value instanceof String && isPrimitiveOrBoxer(targetType)) {
+            return stringToPrimitive((String)value, (Class<T>)targetType);
+        }
+
+        //deal with primitive->string
+        if (isPrimitiveOrBoxer(value.getClass()) && targetType.equals(String.class)) {
+            return (T) value.toString();
+        }
+
+        //look for value.asType where Type is castable to targetType
+        String targetTypeSimpleName = getVerySimpleName(targetType);
+        if (targetTypeSimpleName!=null && targetTypeSimpleName.length()>0) {
+            for (Method m: value.getClass().getMethods()) {
+                if (m.getName().startsWith("as") && m.getParameterTypes().length==0 &&
+                        targetType.isAssignableFrom(m.getReturnType()) ) {
+                    if (m.getName().equals("as"+getVerySimpleName(m.getReturnType()))) {
+                        try {
+                            return (T) m.invoke(value);
+                        } catch (Exception e) {
+                            throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): "+m.getName()+" adapting failed, "+e);
+                        }
+                    }
+                }
+            }
+        }
+        
+        //now look for static TargetType.fromType(Type t) where value instanceof Type  
+        for (Method m: targetType.getMethods()) {
+            if (((m.getModifiers()&Modifier.STATIC)==Modifier.STATIC) && 
+                    m.getName().startsWith("from") && m.getParameterTypes().length==1 &&
+                    m.getParameterTypes()[0].isInstance(value)) {
+                if (m.getName().equals("from"+getVerySimpleName(m.getParameterTypes()[0]))) {
+                    try {
+                        return (T) m.invoke(null, value);
+                    } catch (Exception e) {
+                        throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): "+m.getName()+" adapting failed, "+e);
+                    }
+                }
+            }
+        }
+        
+       //ENHANCEMENT could look in type hierarchy of both types for a conversion method...
+        
+        //primitives get run through again boxed up
+        Class boxedT = UNBOXED_TO_BOXED_TYPES.get(targetType);
+        Class boxedVT = UNBOXED_TO_BOXED_TYPES.get(value.getClass());
+        if (boxedT!=null || boxedVT!=null) {
+            try {
+                if (boxedT==null) boxedT=targetType;
+                Object boxedV;
+                if (boxedVT==null) { boxedV = value; }
+                else { boxedV = boxedVT.getConstructor(value.getClass()).newInstance(value); }
+                return (T) coerce(boxedV, boxedT);
+            } catch (Exception e) {
+                throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): unboxing failed, "+e);
+            }
+        }
+
+        //for enums call valueOf with the string representation of the value
+        if (targetType.isEnum()) {
+            T result = (T) stringToEnum((Class<Enum>) targetType, null).apply(String.valueOf(value));
+            if (result != null) return result;
+        }
+
+        //now look in registry
+        synchronized (TypeCoercions.class) {
+            Map<Class, Function> adapters = registry.row(targetType);
+            for (Map.Entry<Class, Function> entry : adapters.entrySet()) {
+                if (entry.getKey().isInstance(value)) {
+                    T result = (T) entry.getValue().apply(value);
+                    
+                    // Check if need to unwrap again (e.g. if want List<Integer> and are given a String "1,2,3"
+                    // then we'll have so far converted to List.of("1", "2", "3"). Call recursively.
+                    // First check that value has changed, to avoid stack overflow!
+                    if (!Objects.equal(value, result) && targetTypeToken.getType() instanceof ParameterizedType) {
+                        // Could duplicate check for `result instanceof Collection` etc; but recursive call
+                        // will be fine as if that doesn't match we'll safely reach `targetType.isInstance(value)`
+                        // and just return the result.
+                        return coerce(result, targetTypeToken);
+                    }
+                    return result;
+                }
+            }
+        }
+
+        //not found
+        throw new ClassCoercionException("Cannot coerce type "+value.getClass()+" to "+targetType.getCanonicalName()+" ("+value+"): no adapter known");
+    }
+
+    /**
+     * Returns a function that does a type coercion to the given type. For example,
+     * {@code TypeCoercions.function(Double.class)} will return a function that will
+     * coerce its input value to a {@link Double} (or throw a {@link ClassCoercionException}
+     * if that is not possible).
+     */
+    public static <T> Function<Object, T> function(final Class<T> type) {
+        return new CoerceFunction<T>(type);
+    }
+    
+    private static class CoerceFunction<T> implements Function<Object, T> {
+        private final Class<T> type;
+
+        public CoerceFunction(Class<T> type) {
+            this.type = type;
+        }
+        @Override
+        public T apply(Object input) {
+            return coerce(input, type);
+        }
+    }
+
+    /**
+     * Type coercion {@link Function function} for {@link Enum enums}.
+     * <p>
+     * Tries to convert the string to {@link CaseFormat#UPPER_UNDERSCORE} first,
+     * handling all of the different {@link CaseFormat format} possibilites. Failing 
+     * that, it tries a case-insensitive comparison with the valid enum values.
+     * <p>
+     * Returns {@code defaultValue} if the string cannot be converted.
+     *
+     * @see TypeCoercions#coerce(Object, Class)
+     * @see Enum#valueOf(Class, String)
+     */
+    public static <E extends Enum<E>> Function<String, E> stringToEnum(final Class<E> type, @Nullable final E defaultValue) {
+        return new StringToEnumFunction<E>(type, defaultValue);
+    }
+    
+    private static class StringToEnumFunction<E extends Enum<E>> implements Function<String, E> {
+        private final Class<E> type;
+        private final E defaultValue;
+        
+        public StringToEnumFunction(Class<E> type, @Nullable E defaultValue) {
+            this.type = type;
+            this.defaultValue = defaultValue;
+        }
+        @Override
+        public E apply(String input) {
+            Preconditions.checkNotNull(input, "input");
+            List<String> options = ImmutableList.of(
+                    input,
+                    CaseFormat.LOWER_HYPHEN.to(CaseFormat.UPPER_UNDERSCORE, input),
+                    CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_UNDERSCORE, input),
+                    CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, input),
+                    CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, input));
+            for (String value : options) {
+                try {
+                    return Enum.valueOf(type, value);
+                } catch (IllegalArgumentException iae) {
+                    continue;
+                }
+            }
+            Maybe<E> result = Enums.valueOfIgnoreCase(type, input);
+            return (result.isPresent()) ? result.get() : defaultValue;
+        }
+    }
+
+    /**
+     * Sometimes need to explicitly cast primitives, rather than relying on Java casting.
+     * For example, when using generics then type-erasure means it doesn't actually cast,
+     * which causes tests to fail with 0 != 0.0
+     */
+    @SuppressWarnings("unchecked")
+    public static <T> T castPrimitive(Object value, Class<T> targetType) {
+        if (value==null) return null;
+        assert isPrimitiveOrBoxer(targetType) : "targetType="+targetType;
+        assert isPrimitiveOrBoxer(value.getClass()) : "value="+targetType+"; valueType="+value.getClass();
+
+        Class<?> sourceWrapType = Primitives.wrap(value.getClass());
+        Class<?> targetWrapType = Primitives.wrap(targetType);
+        
+        // optimization, for when already correct type
+        if (sourceWrapType == targetWrapType) {
+            return (T) value;
+        }
+        
+        if (targetWrapType == Boolean.class) {
+            // only char can be mapped to boolean
+            // (we could say 0=false, nonzero=true, but there is no compelling use case so better
+            // to encourage users to write as boolean)
+            if (sourceWrapType == Character.class)
+                return (T) stringToPrimitive(value.toString(), targetType);
+            
+            throw new ClassCoercionException("Cannot cast "+sourceWrapType+" ("+value+") to "+targetType);
+        } else if (sourceWrapType == Boolean.class) {
+            // boolean can't cast to anything else
+            
+            throw new ClassCoercionException("Cannot cast "+sourceWrapType+" ("+value+") to "+targetType);
+        }
+        
+        // for whole-numbers (where casting to long won't lose anything)...
+        long v = 0;
+        boolean islong = true;
+        if (sourceWrapType == Character.class) {
+            v = (long) ((Character)value).charValue();
+        } else if (sourceWrapType == Byte.class) {
+            v = (long) ((Byte)value).byteValue();
+        } else if (sourceWrapType == Short.class) {
+            v = (long) ((Short)value).shortValue();
+        } else if (sourceWrapType == Integer.class) {
+            v = (long) ((Integer)value).intValue();
+        } else if (sourceWrapType == Long.class) {
+            v = ((Long)value).longValue();
+        } else {
+            islong = false;
+        }
+        if (islong) {
+            if (targetWrapType == Character.class) return (T) Character.valueOf((char)v); 
+            if (targetWrapType == Byte.class) return (T) Byte.valueOf((byte)v); 
+            if (targetWrapType == Short.class) return (T) Short.valueOf((short)v); 
+            if (targetWrapType == Integer.class) return (T) Integer.valueOf((int)v); 
+            if (targetWrapType == Long.class) return (T) Long.valueOf((long)v); 
+            if (targetWrapType == Float.class) return (T) Float.valueOf((float)v); 
+            if (targetWrapType == Double.class) return (T) Double.valueOf((double)v);
+            throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
+        }
+        
+        // for real-numbers (cast to double)...
+        double d = 0;
+        boolean isdouble = true;
+        if (sourceWrapType == Float.class) {
+            d = (double) ((Float)value).floatValue();
+        } else if (sourceWrapType == Double.class) {
+            d = (double) ((Double)value).doubleValue();
+        } else {
+            isdouble = false;
+        }
+        if (isdouble) {
+            if (targetWrapType == Character.class) return (T) Character.valueOf((char)d); 
+            if (targetWrapType == Byte.class) return (T) Byte.valueOf((byte)d); 
+            if (targetWrapType == Short.class) return (T) Short.valueOf((short)d); 
+            if (targetWrapType == Integer.class) return (T) Integer.valueOf((int)d); 
+            if (targetWrapType == Long.class) return (T) Long.valueOf((long)d); 
+            if (targetWrapType == Float.class) return (T) Float.valueOf((float)d); 
+            if (targetWrapType == Double.class) return (T) Double.valueOf((double)d);
+            throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
+        } else {
+            throw new IllegalStateException("Unexpected: sourceType="+sourceWrapType+"; targetType="+targetWrapType);
+        }
+    }
+    
+    public static boolean isPrimitiveOrBoxer(Class<?> type) {
+        return Primitives.allPrimitiveTypes().contains(type) || Primitives.allWrapperTypes().contains(type);
+    }
+    
+    @SuppressWarnings("unchecked")
+    public static <T> T stringToPrimitive(String value, Class<T> targetType) {
+        assert Primitives.allPrimitiveTypes().contains(targetType) || Primitives.allWrapperTypes().contains(targetType) : "targetType="+targetType;
+        // If char, then need to do explicit conversion
+        if (targetType == Character.class || targetType == char.class) {
+            if (value.length() == 1) {
+                return (T) (Character) value.charAt(0);
+            } else if (value.length() != 1) {
+                throw new ClassCoercionException("Cannot coerce type String to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
+            }
+        }
+        value = value.trim();
+        // For boolean we could use valueOf, but that returns false whereas we'd rather throw errors on bad values
+        if (targetType == Boolean.class || targetType == boolean.class) {
+            if ("true".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
+            if ("false".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
+            if ("yes".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
+            if ("no".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
+            if ("t".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
+            if ("f".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
+            if ("y".equalsIgnoreCase(value)) return (T) Boolean.TRUE;
+            if ("n".equalsIgnoreCase(value)) return (T) Boolean.FALSE;
+            
+            throw new ClassCoercionException("Cannot coerce type String to "+targetType.getCanonicalName()+" ("+value+"): adapting failed"); 
+        }
+        
+        // Otherwise can use valueOf reflectively
+        Class<?> wrappedType;
+        if (Primitives.allPrimitiveTypes().contains(targetType)) {
+            wrappedType = Primitives.wrap(targetType);
+        } else {
+            wrappedType = targetType;
+        }
+        
+        try {
+            return (T) wrappedType.getMethod("valueOf", String.class).invoke(null, value);
+        } catch (Exception e) {
+            ClassCoercionException tothrow = new ClassCoercionException("Cannot coerce "+JavaStringEscapes.wrapJavaString(value)+" to "+targetType.getCanonicalName()+" ("+value+"): adapting failed");
+            tothrow.initCause(e);
+            throw tothrow;
+        }
+    }
+    
+    /** returns the simple class name, and for any inner class the portion after the $ */
+    public static String getVerySimpleName(Class c) {
+        String s = c.getSimpleName();
+        if (s.indexOf('$')>=0)
+            s = s.substring(s.lastIndexOf('$')+1);
+        return s;
+    }
+    public static final Map<Class,Class> BOXED_TO_UNBOXED_TYPES = ImmutableMap.<Class,Class>builder().
+            put(Integer.class, Integer.TYPE).
+            put(Long.class, Long.TYPE).
+            put(Boolean.class, Boolean.TYPE).
+            put(Byte.class, Byte.TYPE).
+            put(Double.class, Double.TYPE).
+            put(Float.class, Float.TYPE).
+            put(Character.class, Character.TYPE).
+            put(Short.class, Short.TYPE).
+            build();
+    public static final Map<Class,Class> UNBOXED_TO_BOXED_TYPES = ImmutableMap.<Class,Class>builder().
+            put(Integer.TYPE, Integer.class).
+            put(Long.TYPE, Long.class).
+            put(Boolean.TYPE, Boolean.class).
+            put(Byte.TYPE, Byte.class).
+            put(Double.TYPE, Double.class).
+            put(Float.TYPE, Float.class).
+            put(Character.TYPE, Character.class).
+            put(Short.TYPE, Short.class).
+            build();
+    
+    /** for automatic conversion */
+    public static Object getMatchingConstructor(Class target, Object ...arguments) {
+        Constructor[] cc = target.getConstructors();
+        for (Constructor c: cc) {
+            if (c.getParameterTypes().length != arguments.length)
+                continue;
+            boolean matches = true;
+            Class[] tt = c.getParameterTypes();
+            for (int i=0; i<tt.length; i++) {
+                if (arguments[i]!=null && !tt[i].isInstance(arguments[i])) {
+                    matches=false;
+                    break;
+                }
+            }
+            if (matches) 
+                return c;
+        }
+        return null;
+    }
+
+    /** Registers an adapter for use with type coercion. Returns any old adapter. */
+    public synchronized static <A,B> Function registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
+        return registry.put(targetType, sourceType, fn);
+    }
+
+    static { BrooklynInitialization.initTypeCoercionStandardAdapters(); }
+    
+    public static void initStandardAdapters() {
+        registerAdapter(CharSequence.class, String.class, new Function<CharSequence,String>() {
+            @Override
+            public String apply(CharSequence input) {
+                return input.toString();
+            }
+        });
+        registerAdapter(byte[].class, String.class, new Function<byte[],String>() {
+            @Override
+            public String apply(byte[] input) {
+                return new String(input);
+            }
+        });
+        registerAdapter(Collection.class, Set.class, new Function<Collection,Set>() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public Set apply(Collection input) {
+                return Sets.newLinkedHashSet(input);
+            }
+        });
+        registerAdapter(Collection.class, List.class, new Function<Collection,List>() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public List apply(Collection input) {
+                return Lists.newArrayList(input);
+            }
+        });
+        registerAdapter(String.class, InetAddress.class, new Function<String,InetAddress>() {
+            @Override
+            public InetAddress apply(String input) {
+                return Networking.getInetAddressWithFixedName(input);
+            }
+        });
+        registerAdapter(String.class, HostAndPort.class, new Function<String,HostAndPort>() {
+            @Override
+            public HostAndPort apply(String input) {
+                return HostAndPort.fromString(input);
+            }
+        });
+        registerAdapter(String.class, UserAndHostAndPort.class, new Function<String,UserAndHostAndPort>() {
+            @Override
+            public UserAndHostAndPort apply(String input) {
+                return UserAndHostAndPort.fromString(input);
+            }
+        });
+        registerAdapter(String.class, Cidr.class, new Function<String,Cidr>() {
+            @Override
+            public Cidr apply(String input) {
+                return new Cidr(input);
+            }
+        });
+        registerAdapter(String.class, URL.class, new Function<String,URL>() {
+            @Override
+            public URL apply(String input) {
+                try {
+                    return new URL(input);
+                } catch (Exception e) {
+                    throw Exceptions.propagate(e);
+                }
+            }
+        });
+        registerAdapter(String.class, URI.class, new Function<String,URI>() {
+            @Override
+            public URI apply(String input) {
+                return URI.create(input);
+            }
+        });
+        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(brooklyn.entity.basic.EntityFactory.class, ConfigurableEntityFactory.class, new Function<brooklyn.entity.basic.EntityFactory,ConfigurableEntityFactory>() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public ConfigurableEntityFactory apply(brooklyn.entity.basic.EntityFactory input) {
+                if (input instanceof ConfigurableEntityFactory) return (ConfigurableEntityFactory)input;
+                return new ConfigurableEntityFactoryFromEntityFactory(input);
+            }
+        });
+        @SuppressWarnings({"unused", "deprecation"})
+        Function<?,?> ignoredVarHereToAllowSuppressDeprecationWarning2 = registerAdapter(Closure.class, brooklyn.entity.basic.EntityFactory.class, new Function<Closure,brooklyn.entity.basic.EntityFactory>() {
+            @SuppressWarnings("unchecked")
+            @Override
+            public brooklyn.entity.basic.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 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));
+            }
+        });
+    }
+}