improve coercion of items with generics, esp config keys

previously config keys did not properly keep generic information for their 
contents;
now this information is preserved and the results coerced as per the types.
also enhances the ValueResolver to have clearer semantics when coercing 
generics in maps/iterables.


Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo
Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/8e48531c
Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/8e48531c
Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/8e48531c

Branch: refs/heads/master
Commit: 8e48531cca71841a1fd015846a9d40a7c59d92dd
Parents: 312131c
Author: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Authored: Fri Aug 31 13:49:48 2018 +0100
Committer: Alex Heneveld <alex.henev...@cloudsoftcorp.com>
Committed: Fri Aug 31 16:38:55 2018 +0100

----------------------------------------------------------------------
 .../spi/dsl/DslDeferredFunctionCall.java        |  2 +-
 .../spi/dsl/methods/BrooklynDslCommon.java      |  2 +-
 .../brooklyn/spi/dsl/methods/DslComponent.java  |  4 +-
 .../brooklyn/core/config/BasicConfigKey.java    |  2 +-
 .../brooklyn/core/config/ListConfigKey.java     | 38 ++++++--
 .../brooklyn/core/config/MapConfigKey.java      | 39 +++++---
 .../brooklyn/core/config/SetConfigKey.java      | 40 +++++---
 .../core/config/SubElementConfigKey.java        |  8 +-
 .../internal/AbstractCollectionConfigKey.java   |  5 +-
 .../internal/AbstractStructuredConfigKey.java   |  7 +-
 .../core/effector/BasicParameterType.java       | 41 ++++++--
 .../AbstractConfigurationSupportInternal.java   |  2 +-
 .../brooklyn/enricher/stock/Transformer.java    |  2 +-
 .../util/core/flags/MethodCoercions.java        | 30 +++++-
 .../apache/brooklyn/util/core/task/Tasks.java   | 35 ++++---
 .../brooklyn/util/core/task/ValueResolver.java  | 99 ++++++++++++++------
 .../brooklyn/util/core/task/TasksTest.java      | 25 ++---
 .../jclouds/JcloudsLocationResolverTest.java    | 37 +++++---
 .../action/AbstractScheduledEffectorPolicy.java |  2 +-
 .../core/sensor/windows/WinRmCommandSensor.java |  4 +-
 .../org/apache/brooklyn/util/guava/Maybe.java   |  2 +-
 .../apache/brooklyn/util/javalang/Boxing.java   | 10 ++
 22 files changed, 311 insertions(+), 125 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
index 1c1cef5..291fb23 100644
--- 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/DslDeferredFunctionCall.java
@@ -185,7 +185,7 @@ public class DslDeferredFunctionCall extends 
BrooklynDslDeferredSupplier<Object>
     protected Maybe<?> resolve(Object object, boolean immediate) {
         return Tasks.resolving(object, Object.class)
             .context(entity().getExecutionContext())
-            .deep(true)
+            .deep(true, true)
             .immediately(immediate)
             .iterator()
             .nextOrLast(DslFunctionSource.class);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/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 e265e84..74fc306 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
@@ -598,7 +598,7 @@ public class BrooklynDslCommon {
 
             final Function<Object, Object> resolver = new Function<Object, 
Object>() {
                 @Override public Object apply(Object value) {
-                    Maybe<Object> result = Tasks.resolving(value, 
Object.class).context(executionContext).deep(true).immediately(true).getMaybe();
+                    Maybe<Object> result = Tasks.resolving(value, 
Object.class).context(executionContext).deep(true, 
true).immediately(true).getMaybe();
                     if (result.isAbsent()) {
                         throw new ImmediateValueNotAvailableException();
                     } else {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
----------------------------------------------------------------------
diff --git 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
index 9dc5ed1..bee320f 100644
--- 
a/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
+++ 
b/camp/camp-brooklyn/src/main/java/org/apache/brooklyn/camp/brooklyn/spi/dsl/methods/DslComponent.java
@@ -677,7 +677,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
             if (!resolved) {
                 // attempt to resolve, and recurse
                 final ExecutionContext executionContext = 
entity().getExecutionContext();
-                Maybe<Object> resolvedSi = Tasks.resolving(si, 
Object.class).deep(true).immediately(true).context(executionContext).getMaybe();
+                Maybe<Object> resolvedSi = Tasks.resolving(si, 
Object.class).deep(true, 
true).immediately(true).context(executionContext).getMaybe();
                 if (resolvedSi.isAbsent()) return Maybe.absent();
                 return getImmediately(resolvedSi.get(), true);
             }
@@ -780,7 +780,7 @@ public class DslComponent extends 
BrooklynDslDeferredSupplier<Entity> implements
                     .as(Object.class)
                     .context(findExecutionContext(this))
                     .immediately(immediately)
-                    .deep(true)
+                    .deep(true, true)
                     .description("Resolving substitutions " + substitutions + 
" for template " + template)
                     .get();
         }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java 
b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
index 28026c9..cbcb94c 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/BasicConfigKey.java
@@ -296,7 +296,7 @@ public class BasicConfigKey<T> implements 
ConfigKeySelfExtracting<T>, Serializab
     }
 
     /** @see ConfigKey#getTypeName() */
-    @Override public String getTypeName() { return getType().getName(); }
+    @Override public String getTypeName() { return getTypeToken().toString(); }
 
     /** @see ConfigKey#getType() */
     @Override public Class<? super T> getType() { return 
TypeTokens.getRawType(typeToken, type); }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java 
b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
index 7a95728..e2e7849 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/ListConfigKey.java
@@ -32,6 +32,7 @@ import org.apache.brooklyn.util.collections.MutableList;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.reflect.TypeParameter;
 import com.google.common.reflect.TypeToken;
 
 /** A config key representing a list of values. 
@@ -63,16 +64,21 @@ public class ListConfigKey<V> extends 
AbstractCollectionConfigKey<List<V>,List<O
     @SuppressWarnings("unused")
     private static final Logger log = 
LoggerFactory.getLogger(ListConfigKey.class);
     
+    @SuppressWarnings("serial")
+    private static <V> TypeToken<List<V>> typeTokenFor(TypeToken<V> subType) {
+        return new TypeToken<List<V>>() {}
+                 .where(new TypeParameter<V>() {}, subType);
+    }
+    
     public static class Builder<V> extends 
BasicConfigKey.Builder<List<V>,Builder<V>> {
-        protected Class<V> subType;
+        protected TypeToken<V> subType;
         
         public Builder(TypeToken<V> subType, String name) {
-            super(new TypeToken<List<V>>() {}, name);
-            this.subType = (Class<V>) subType.getRawType();
+            super(typeTokenFor(subType), name);
+            this.subType = checkNotNull(subType);
         }
         public Builder(Class<V> subType, String name) {
-            super(new TypeToken<List<V>>() {}, name);
-            this.subType = checkNotNull(subType, "subType");
+            this(TypeToken.of(subType), name);
         }
         public Builder(ListConfigKey<V> key) {
             this(key.getName(), key);
@@ -110,17 +116,29 @@ public class ListConfigKey<V> extends 
AbstractCollectionConfigKey<List<V>,List<O
         super(builder, builder.subType);
     }
 
-    public ListConfigKey(Class<V> subType, String name) {
+    public ListConfigKey(TypeToken<V> subType, String name) {
         this(subType, name, name, null);
     }
 
-    public ListConfigKey(Class<V> subType, String name, String description) {
+    public ListConfigKey(TypeToken<V> subType, String name, String 
description) {
         this(subType, name, description, null);
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked" })
+    public ListConfigKey(TypeToken<V> subType, String name, String 
description, List<? extends V> defaultValue) {
+        super(typeTokenFor(subType), subType, name, description, 
(List<V>)defaultValue);
+    }
+
+    public ListConfigKey(Class<V> subType, String name) {
+        this(TypeToken.of(subType), name);
+    }
+
+    public ListConfigKey(Class<V> subType, String name, String description) {
+        this(TypeToken.of(subType), name, description);
+    }
+
     public ListConfigKey(Class<V> subType, String name, String description, 
List<? extends V> defaultValue) {
-        super((Class)List.class, subType, name, description, 
(List<V>)defaultValue);
+        this(TypeToken.of(subType), name, description, defaultValue);
     }
 
     @Override
@@ -143,7 +161,7 @@ public class ListConfigKey<V> extends 
AbstractCollectionConfigKey<List<V>,List<O
         /** when passed as a value to a ListConfigKey, causes each of these 
items to be added.
          * if you have just one, no need to wrap in a mod. */
         // to prevent confusion (e.g. if a list is passed) we require two 
objects here.
-        public static final <T> ListModification<T> add(final T o1, final T 
o2, final T ...oo) {
+        public static final <T> ListModification<T> add(final T o1, final T 
o2, @SuppressWarnings("unchecked") final T ...oo) {
             List<T> l = new ArrayList<T>();
             l.add(o1); l.add(o2);
             for (T o: oo) l.add(o);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java 
b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
index e213bf7..e7e5691 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/MapConfigKey.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.core.config;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
@@ -39,6 +37,7 @@ import org.slf4j.LoggerFactory;
 
 import com.google.common.base.Supplier;
 import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeParameter;
 import com.google.common.reflect.TypeToken;
 
 /** A config key which represents a map, where contents can be accessed 
directly via subkeys.
@@ -65,16 +64,21 @@ public class MapConfigKey<V> extends 
AbstractStructuredConfigKey<Map<String,V>,M
         return new Builder<V>(key);
     }
 
+    @SuppressWarnings("serial")
+    private static <V> TypeToken<Map<String,V>> typeTokenFor(TypeToken<V> 
subType) {
+        return new TypeToken<Map<String,V>>() {}
+                 .where(new TypeParameter<V>() {}, subType);
+    }
+    
     public static class Builder<V> extends BasicConfigKey.Builder<Map<String, 
V>,Builder<V>> {
-        protected Class<V> subType;
+        protected TypeToken<V> subType;
         
         public Builder(TypeToken<V> subType, String name) {
-            super(new TypeToken<Map<String, V>>() {}, name);
-            this.subType = (Class<V>) subType.getRawType();
+            super(typeTokenFor(subType), name);
+            this.subType = subType;
         }
         public Builder(Class<V> subType, String name) {
-            super(new TypeToken<Map<String, V>>() {}, name);
-            this.subType = checkNotNull(subType, "subType");
+            this(TypeToken.of(subType), name);
         }
         public Builder(MapConfigKey<V> key) {
             this(key.getName(), key);
@@ -112,22 +116,33 @@ public class MapConfigKey<V> extends 
AbstractStructuredConfigKey<Map<String,V>,M
         super(builder, builder.subType);
     }
 
-    public MapConfigKey(Class<V> subType, String name) {
+    public MapConfigKey(TypeToken<V> subType, String name) {
         this(subType, name, name, null);
     }
 
-    public MapConfigKey(Class<V> subType, String name, String description) {
+    public MapConfigKey(TypeToken<V> subType, String name, String description) 
{
         this(subType, name, description, null);
     }
 
     // TODO it isn't clear whether defaultValue is an initialValue, or a value 
to use when map is empty
     // probably the latter, currently ... but maybe better to say that map 
configs are never null, 
     // and defaultValue is really an initial value?
-    @SuppressWarnings({ "unchecked", "rawtypes" })
-    public MapConfigKey(Class<V> subType, String name, String description, 
Map<String, V> defaultValue) {
-        super((Class)Map.class, subType, name, description, defaultValue);
+    public MapConfigKey(TypeToken<V> subType, String name, String description, 
Map<String, V> defaultValue) {
+        super(typeTokenFor(subType), subType, name, description, defaultValue);
     }
 
+    public MapConfigKey(Class<V> subType, String name) {
+        this(TypeToken.of(subType), name);
+    }
+
+    public MapConfigKey(Class<V> subType, String name, String description) {
+        this(TypeToken.of(subType), name, description);
+    }
+
+    public MapConfigKey(Class<V> subType, String name, String description, 
Map<String, V> defaultValue) {
+        this(TypeToken.of(subType), name, description, defaultValue);
+    }
+    
     @Override
     public String toString() {
         return String.format("%s[MapConfigKey:%s]", name, getTypeName());

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java 
b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
index 495a82c..486690c 100644
--- a/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
+++ b/core/src/main/java/org/apache/brooklyn/core/config/SetConfigKey.java
@@ -18,8 +18,6 @@
  */
 package org.apache.brooklyn.core.config;
 
-import static com.google.common.base.Preconditions.checkNotNull;
-
 import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedHashSet;
@@ -31,6 +29,7 @@ import org.apache.brooklyn.util.collections.MutableSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.reflect.TypeParameter;
 import com.google.common.reflect.TypeToken;
 
 /** A config key representing a set of values. 
@@ -54,16 +53,21 @@ public class SetConfigKey<V> extends 
AbstractCollectionConfigKey<Set<V>, Set<Obj
     @SuppressWarnings("unused")
     private static final Logger log = 
LoggerFactory.getLogger(SetConfigKey.class);
 
+    @SuppressWarnings("serial")
+    private static <V> TypeToken<Set<V>> typeTokenFor(TypeToken<V> subType) {
+        return new TypeToken<Set<V>>() {}
+                 .where(new TypeParameter<V>() {}, subType);
+    }
+
     public static class Builder<V> extends 
BasicConfigKey.Builder<Set<V>,Builder<V>> {
-        protected Class<V> subType;
+        protected TypeToken<V> subType;
         
         public Builder(TypeToken<V> subType, String name) {
-            super(new TypeToken<Set<V>>() {}, name);
-            this.subType = (Class<V>) subType.getRawType();
+            super(typeTokenFor(subType), name);
+            this.subType = subType;
         }
         public Builder(Class<V> subType, String name) {
-            super(new TypeToken<Set<V>>() {}, name);
-            this.subType = checkNotNull(subType, "subType");
+            this(TypeToken.of(subType), name);
         }
         public Builder(SetConfigKey<V> key) {
             this(key.getName(), key);
@@ -101,17 +105,29 @@ public class SetConfigKey<V> extends 
AbstractCollectionConfigKey<Set<V>, Set<Obj
         super(builder, builder.subType);
     }
 
-    public SetConfigKey(Class<V> subType, String name) {
+    public SetConfigKey(TypeToken<V> subType, String name) {
         this(subType, name, name, null);
     }
 
-    public SetConfigKey(Class<V> subType, String name, String description) {
+    public SetConfigKey(TypeToken<V> subType, String name, String description) 
{
         this(subType, name, description, null);
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @SuppressWarnings({ "unchecked" })
+    public SetConfigKey(TypeToken<V> subType, String name, String description, 
Set<? extends V> defaultValue) {
+        super(typeTokenFor(subType), subType, name, description, (Set<V>) 
defaultValue);
+    }
+    
+    public SetConfigKey(Class<V> subType, String name) {
+        this(TypeToken.of(subType), name, name, null);
+    }
+
+    public SetConfigKey(Class<V> subType, String name, String description) {
+        this(TypeToken.of(subType), name, description, null);
+    }
+
     public SetConfigKey(Class<V> subType, String name, String description, 
Set<? extends V> defaultValue) {
-        super((Class)Set.class, subType, name, description, (Set) 
defaultValue);
+        this(TypeToken.of(subType), name, description, defaultValue);
     }
 
     @Override
@@ -134,7 +150,7 @@ public class SetConfigKey<V> extends 
AbstractCollectionConfigKey<Set<V>, Set<Obj
         /** when passed as a value to a SetConfigKey, causes each of these 
items to be added.
          * if you have just one, no need to wrap in a mod. */
         // to prevent confusion (e.g. if a set is passed) we require two 
objects here.
-        public static final <T> SetModification<T> add(final T o1, final T o2, 
final T ...oo) {
+        public static final <T> SetModification<T> add(final T o1, final T o2, 
@SuppressWarnings("unchecked") final T ...oo) {
             Set<T> l = new LinkedHashSet<T>();
             l.add(o1); l.add(o2);
             for (T o: oo) l.add(o);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/SubElementConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/SubElementConfigKey.java 
b/core/src/main/java/org/apache/brooklyn/core/config/SubElementConfigKey.java
index 1c0b525..0bb71dc 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/config/SubElementConfigKey.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/config/SubElementConfigKey.java
@@ -24,6 +24,8 @@ import org.apache.brooklyn.api.mgmt.ExecutionContext;
 import org.apache.brooklyn.config.ConfigKey;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 
+import com.google.common.reflect.TypeToken;
+
 @SuppressWarnings("rawtypes")
 public class SubElementConfigKey<T> extends BasicConfigKey<T> {
     
@@ -31,13 +33,13 @@ public class SubElementConfigKey<T> extends 
BasicConfigKey<T> {
     
     public final ConfigKey parent;
 
-    public SubElementConfigKey(ConfigKey parent, Class<T> type, String name) {
+    public SubElementConfigKey(ConfigKey parent, TypeToken<T> type, String 
name) {
         this(parent, type, name, name, null);
     }
-    public SubElementConfigKey(ConfigKey parent, Class<T> type, String name, 
String description) {
+    public SubElementConfigKey(ConfigKey parent, TypeToken<T> type, String 
name, String description) {
         this(parent, type, name, description, null);
     }
-    public SubElementConfigKey(ConfigKey parent, Class<T> type, String name, 
String description, T defaultValue) {
+    public SubElementConfigKey(ConfigKey parent, TypeToken<T> type, String 
name, String description, T defaultValue) {
         super(type, name, description, defaultValue);
         this.parent = parent;
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
 
b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
index c7f9775..9039091 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractCollectionConfigKey.java
@@ -33,17 +33,18 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
 
 public abstract class AbstractCollectionConfigKey<T, RawT extends 
Collection<Object>, V> extends AbstractStructuredConfigKey<T, RawT, V> {
 
     private static final long serialVersionUID = 8225955960120637643L;
     private static final Logger log = 
LoggerFactory.getLogger(AbstractCollectionConfigKey.class);
     
-    protected AbstractCollectionConfigKey(BasicConfigKey.Builder<T,?> builder, 
Class<V> subType) {
+    protected AbstractCollectionConfigKey(BasicConfigKey.Builder<T,?> builder, 
TypeToken<V> subType) {
         super(builder, subType);
     }
 
-    protected AbstractCollectionConfigKey(Class<T> type, Class<V> subType, 
String name, String description, T defaultValue) {
+    protected AbstractCollectionConfigKey(TypeToken<T> type, TypeToken<V> 
subType, String name, String description, T defaultValue) {
         super(type, subType, name, description, defaultValue);
     }
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
 
b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
index d834283..e2c6430 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/config/internal/AbstractStructuredConfigKey.java
@@ -29,19 +29,20 @@ import org.apache.brooklyn.core.config.SubElementConfigKey;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 
 import com.google.common.collect.Maps;
+import com.google.common.reflect.TypeToken;
 
 public abstract class AbstractStructuredConfigKey<T,RawT,V> extends 
BasicConfigKey<T> implements StructuredConfigKey {
 
     private static final long serialVersionUID = 7806267541029428561L;
 
-    protected final Class<V> subType;
+    protected final TypeToken<V> subType;
 
-    protected AbstractStructuredConfigKey(BasicConfigKey.Builder<T,?> builder, 
Class<V> subType) {
+    protected AbstractStructuredConfigKey(BasicConfigKey.Builder<T,?> builder, 
TypeToken<V> subType) {
         super(builder);
         this.subType = subType;
     }
 
-    public AbstractStructuredConfigKey(Class<T> type, Class<V> subType, String 
name, String description, T defaultValue) {
+    public AbstractStructuredConfigKey(TypeToken<T> type, TypeToken<V> 
subType, String name, String description, T defaultValue) {
         super(type, name, description, defaultValue);
         this.subType = subType;
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java 
b/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
index 1d6f537..eb9af70 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/effector/BasicParameterType.java
@@ -32,7 +32,8 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
     private static final long serialVersionUID = -5521879180483663919L;
     
     private String name;
-    private TypeToken<T> type;
+    private Class<T> type;
+    private TypeToken<T> typeT;
     private String description;
     private Boolean hasDefaultValue = null;
     private T defaultValue = null;
@@ -44,7 +45,17 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
     @SuppressWarnings("unchecked")
     public BasicParameterType(Map<?, ?> arguments) {
         if (arguments.containsKey("name")) name = (String) 
arguments.get("name");
-        if (arguments.containsKey("type")) type = 
TypeCoercions.coerce(arguments.get("type"), TypeToken.class);
+        
+        if (arguments.containsKey("typeT")) {
+            Object t = arguments.get("typeT");
+            typeT = TypeCoercions.coerce(t, TypeToken.class);
+        } else if (arguments.containsKey("type")) {
+            Object t = arguments.get("type");
+            if (t instanceof Class) type = ((Class<T>)t);
+            else if (t instanceof TypeToken) typeT = ((TypeToken<T>)t);
+            else typeT = TypeCoercions.coerce(t, TypeToken.class);
+        }
+        
         if (arguments.containsKey("description")) description = (String) 
arguments.get("description");
         if (arguments.containsKey("defaultValue")) defaultValue = (T) 
arguments.get("defaultValue");
     }
@@ -77,9 +88,15 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
         this(name, type, description, defaultValue, true);
     }
     
+    @SuppressWarnings("unchecked")
     public BasicParameterType(String name, TypeToken<T> type, String 
description, T defaultValue, boolean hasDefaultValue) {
         this.name = name;
-        this.type = type;
+        if (type!=null && type.equals(TypeToken.of(type.getRawType()))) {
+            // prefer Class if it's already a raw type; keeps persistence 
simpler (and the same as before)
+            this.type = (Class<T>) type.getRawType();
+        } else {
+            this.typeT = type;
+        }
         this.description = description;
         this.defaultValue = defaultValue;
         if (defaultValue!=null && 
!defaultValue.getClass().equals(Object.class)) {
@@ -94,13 +111,21 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
 
     @SuppressWarnings("unchecked")
     @Override
-    public Class<T> getParameterClass() { return (Class<T>) type.getRawType(); 
}
+    public Class<T> getParameterClass() {
+        if (typeT!=null) return (Class<T>) typeT.getRawType();
+        if (type!=null) return type;
+        return null;
+    }
 
     @Override
-    public TypeToken<T> getParameterType() { return type; }
+    public TypeToken<T> getParameterType() { 
+        if (typeT!=null) return typeT;
+        if (type!=null) return TypeToken.of(type);
+        return null;
+    }
 
     @Override
-    public String getParameterClassName() { return type.toString(); }
+    public String getParameterClassName() { return 
getParameterType().toString(); }
 
     @Override
     public String getDescription() { return description; }
@@ -125,7 +150,7 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
     
     @Override
     public int hashCode() {
-        return Objects.hashCode(name, description, type, defaultValue);
+        return Objects.hashCode(name, description, getParameterType(), 
defaultValue);
     }
 
     @Override
@@ -133,7 +158,7 @@ public class BasicParameterType<T> implements 
ParameterType<T> {
         return (obj instanceof ParameterType) &&
                 Objects.equal(name, ((ParameterType<?>)obj).getName()) &&
                 Objects.equal(description, 
((ParameterType<?>)obj).getDescription()) &&
-                Objects.equal(type, 
((ParameterType<?>)obj).getParameterType()) &&
+                Objects.equal(getParameterType(), 
((ParameterType<?>)obj).getParameterType()) &&
                 Objects.equal(defaultValue, 
((ParameterType<?>)obj).getDefaultValue());
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java
 
b/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java
index f67f1f5..a901e22 100644
--- 
a/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java
+++ 
b/core/src/main/java/org/apache/brooklyn/core/objs/AbstractConfigurationSupportInternal.java
@@ -129,7 +129,7 @@ public abstract class AbstractConfigurationSupportInternal 
implements BrooklynOb
         Maybe<Object> resolved = Tasks.resolving(unresolved)
                 .as(Object.class)
                 .immediately(true)
-                .deep(true)
+                .deep(true, true)
                 .context(getContext())
                 .description("Resolving raw value of simple config "+key)
                 .getMaybe();

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/enricher/stock/Transformer.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/enricher/stock/Transformer.java 
b/core/src/main/java/org/apache/brooklyn/enricher/stock/Transformer.java
index b41de3d..89f0935 100644
--- a/core/src/main/java/org/apache/brooklyn/enricher/stock/Transformer.java
+++ b/core/src/main/java/org/apache/brooklyn/enricher/stock/Transformer.java
@@ -134,7 +134,7 @@ public class Transformer<T,U> extends 
AbstractTransformer<T,U> {
         return (U) Tasks.resolving(rawVal).as(targetSensor.getTypeToken())
                 .context(entity)
                 .description("Computing sensor "+targetSensor+" from "+rawVal)
-                .deep(true)
+                .deep(true, false)
                 .immediately(true)
                 .getMaybe().orNull();
     }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/util/core/flags/MethodCoercions.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/flags/MethodCoercions.java 
b/core/src/main/java/org/apache/brooklyn/util/core/flags/MethodCoercions.java
index a022edf..a11ef69 100644
--- 
a/core/src/main/java/org/apache/brooklyn/util/core/flags/MethodCoercions.java
+++ 
b/core/src/main/java/org/apache/brooklyn/util/core/flags/MethodCoercions.java
@@ -30,7 +30,9 @@ import javax.annotation.Nullable;
 
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
+import org.apache.brooklyn.util.javalang.Boxing;
 import org.apache.brooklyn.util.javalang.Reflections;
+import org.omg.CORBA.portable.BoxedValueHelper;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
@@ -87,8 +89,18 @@ public class MethodCoercions {
             Method accessibleMethod = 
Reflections.findAccessibleMethod(method).get();
             try {
                 Type paramType = method.getGenericParameterTypes()[0];
-                Object coercedArgument = TypeCoercions.coerce(argument, 
TypeToken.of(paramType));
-                return Maybe.of(accessibleMethod.invoke(instance, 
coercedArgument));
+                Maybe<?> coercedArgumentM = TypeCoercions.tryCoerce(argument, 
TypeToken.of(paramType));
+                RuntimeException exception = 
Maybe.getException(coercedArgumentM);
+                if (coercedArgumentM.isPresent() && 
coercedArgumentM.get()!=null) {
+                    if 
(!Boxing.boxedTypeToken(paramType).getRawType().isAssignableFrom(coercedArgumentM.get().getClass()))
 {
+                        exception = new IllegalArgumentException("Type 
mismatch after coercion; "+coercedArgumentM.get()+" is not a 
"+TypeToken.of(paramType));
+                    }
+                }
+                if (coercedArgumentM.isAbsent() || exception!=null) {
+                    return Maybe.absent("Cannot convert parameter for 
"+method+": "+
+                        
Exceptions.collapseText(Maybe.getException(coercedArgumentM)), exception);
+                }
+                return Maybe.of(accessibleMethod.invoke(instance, 
coercedArgumentM.get()));
             } catch (IllegalAccessException | InvocationTargetException e) {
                 throw Exceptions.propagate(e);
             }
@@ -175,7 +187,19 @@ public class MethodCoercions {
                 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));
+                    
+                    Maybe<?> coercedArgumentM = 
TypeCoercions.tryCoerce(argument, TypeToken.of(paramType));
+                    RuntimeException exception = 
Maybe.getException(coercedArgumentM);
+                    if (coercedArgumentM.isPresent() && 
coercedArgumentM.get()!=null) {
+                        if 
(!Boxing.boxedTypeToken(paramType).getRawType().isAssignableFrom(coercedArgumentM.get().getClass()))
 {
+                            exception = new IllegalArgumentException("Type 
mismatch after coercion; "+coercedArgumentM.get()+" is not a 
"+TypeToken.of(paramType));
+                        }
+                    }
+                    if (coercedArgumentM.isAbsent() || exception!=null) {
+                        return Maybe.absent("Cannot convert parameter 
"+(paramCount+1)+" for "+method+": "+
+                            
Exceptions.collapseText(Maybe.getException(coercedArgumentM)), exception);
+                    }
+                    coercedArguments[paramCount] = coercedArgumentM.get();
                 }
                 return Maybe.of(accessibleMethod.invoke(instanceOrClazz, 
coercedArguments));
             } catch (IllegalAccessException | InvocationTargetException e) {

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java 
b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java
index dc42e64..184a278 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/Tasks.java
@@ -171,9 +171,9 @@ public class Tasks {
         return resolveValue(v, TypeToken.of(type), exec, contextMessage);
     }
     
-    /** @see #resolveDeepValue(Object, TypeToken, ExecutionContext, String) */
-    public static Object resolveDeepValue(Object v, TypeToken<?> type, 
ExecutionContext exec) throws ExecutionException, InterruptedException {
-        return resolveDeepValue(v, type, exec, null);
+    /** @see #resolveDeepValueExactly(Object, TypeToken, ExecutionContext, 
String) */
+    public static <T> T resolveDeepValueExactly(Object v, TypeToken<T> type, 
ExecutionContext exec) throws ExecutionException, InterruptedException {
+        return resolveDeepValueExactly(v, type, exec, null);
     }
     /** @see #resolveDeepValue(Object, Class, ExecutionContext, String) */
     public static Object resolveDeepValue(Object v, Class<?> type, 
ExecutionContext exec) throws ExecutionException, InterruptedException {
@@ -183,7 +183,24 @@ public class Tasks {
     /**
      * Resolves the given object, blocking on futures and coercing it to the 
given type. If the object is a 
      * map or iterable (or a list of map of maps, etc, etc) then walks these 
maps/iterables to convert all of 
-     * their values to the given type. For example, the following will return 
a list containing a map with "1"="true":
+     * their values. This expects a type token parameterized with generics, 
and those generics
+     * will be used to coerce the keys and entries.
+     * 
+     * 
+     * For example, the following will return a list containing a map with 
"1": Boolean.TRUE:
+     * 
+     *   {@code Object result = 
resolveDeepValue(ImmutableList.of(ImmutableMap.of(1, "true")), 
+     *      new TypeToken<List<Map<String,Boolean>>>() {}, exec)} 
+     *
+     * For a simpler mechanism, see {@link #resolveDeepValue(Object, Class, 
ExecutionContext, String)}.
+     */
+    public static <T> T resolveDeepValueExactly(Object v, TypeToken<T> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
+        return new ValueResolver<T>(v, type).context(exec).deep(true, 
false).description(contextMessage).get();
+    }
+    /** As @see #resolveDeepValueExactly(Object, TypeToken, ExecutionContext, 
String) except the type supplied here is
+     * used to coerce non-map/iterable entries inside any encountered 
map/iterable:.
+     * 
+     * For example, the following will return a list containing a map with 
"1": "true":
      * 
      *   {@code Object result = 
resolveDeepValue(ImmutableList.of(ImmutableMap.of(1, true)), String.class, 
exec)} 
      *
@@ -191,13 +208,9 @@ public class Tasks {
      * the type should normally be Object, not the type of the collection. 
This differs from
      * {@link #resolveValue(Object, Class, ExecutionContext, String)} which 
will accept {@link Map} and {@link Collection}
      * as the required type.
-     */
-    public static <T> T resolveDeepValue(Object v, TypeToken<T> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
-        return new ValueResolver<T>(v, 
type).context(exec).deep(true).description(contextMessage).get();
-    }
-    /** @see #resolveDeepValue(Object, TypeToken, ExecutionContext, String) */
-    public static <T> T resolveDeepValue(Object v, Class<T> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
-        return resolveDeepValue(v, TypeToken.of(type), exec, contextMessage);
+     * */
+    public static Object resolveDeepValue(Object v, Class<?> type, 
ExecutionContext exec, String contextMessage) throws ExecutionException, 
InterruptedException {
+        return new ValueResolver<>(v, 
TypeToken.of(type)).context(exec).deep(true, 
true).description(contextMessage).get();
     }
 
     /** sets extra status details on the current task, if possible (otherwise 
does nothing).

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java
----------------------------------------------------------------------
diff --git 
a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java 
b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java
index f6588f8..f39af09 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/task/ValueResolver.java
@@ -19,7 +19,6 @@
 package org.apache.brooklyn.util.core.task;
 
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.Callable;
@@ -33,12 +32,14 @@ import org.apache.brooklyn.api.mgmt.TaskAdaptable;
 import org.apache.brooklyn.api.mgmt.TaskFactory;
 import org.apache.brooklyn.core.entity.EntityInternal;
 import org.apache.brooklyn.core.mgmt.BrooklynTaskTags;
+import org.apache.brooklyn.util.collections.MutableSet;
 import org.apache.brooklyn.util.core.flags.TypeCoercions;
 import 
org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateUnsupportedException;
 import 
org.apache.brooklyn.util.core.task.ImmediateSupplier.ImmediateValueNotAvailableException;
 import org.apache.brooklyn.util.exceptions.Exceptions;
 import org.apache.brooklyn.util.guava.Maybe;
 import org.apache.brooklyn.util.javalang.JavaClassNames;
+import org.apache.brooklyn.util.javalang.Reflections;
 import org.apache.brooklyn.util.repeat.Repeater;
 import org.apache.brooklyn.util.time.CountdownTimer;
 import org.apache.brooklyn.util.time.Duration;
@@ -49,13 +50,12 @@ import org.slf4j.LoggerFactory;
 import com.google.common.annotations.Beta;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.common.reflect.TypeToken;
 
 /** 
  * Resolves a given object, as follows:
  * <li> If it is a {@link Tasks} or a {@link DeferredSupplier} then get its 
contents
- * <li> If it's a map and {@link #deep(boolean)} is requested, it applies 
resolution to contents
+ * <li> If it's a map/iterable and {@link #deep(boolean, Boolean)} is 
requested, it applies resolution to contents
  * <li> It applies coercion
  * <p>
  * Fluent-style API exposes a number of other options.
@@ -109,6 +109,7 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
     ExecutionContext exec;
     String description;
     boolean forceDeep;
+    Boolean deepTraversalUsesRootType;
     /** null means do it if you can; true means always, false means never */
     Boolean embedResolutionInTask;
     /** timeout on execution, if possible, or if embedResolutionInTask is true 
*/
@@ -184,7 +185,7 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
         ValueResolver<S> result = new ValueResolver<S>(newValue, superType)
             .context(exec).description(description)
             .embedResolutionInTask(embedResolutionInTask)
-            .deep(forceDeep)
+            .deep(forceDeep, deepTraversalUsesRootType)
             .timeout(timeout)
             .immediately(immediately)
             .recursive(recursive);
@@ -254,9 +255,23 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
         else return Maybe.absent("No default value set");
     }
     
-    /** causes nested structures (maps, lists) to be descended and nested 
unresolved values resolved */
+    /** causes nested structures (maps, lists) to be descended and nested 
unresolved values resolved.
+     * for legacy reasons this sets deepTraversalUsesRootType.
+     * @deprecated use {@link #deep(boolean, boolean)} */
     public ValueResolver<T> deep(boolean forceDeep) {
+        return deep(true, true);
+    }
+    /** causes nested structures (maps, lists) to be descended and nested 
unresolved values resolved.
+     * if the second argument is true, the type specified here is used against 
non-map/iterable items
+     * inside maps and iterables encountered. if false, any generic signature 
on the supplied type
+     * is traversed to match contained items. if null (default), it is 
inferred from the type,
+     * those with generics mean true, and those without mean false. 
+     * 
+     * see {@link Tasks#resolveDeepValue(Object, Class, ExecutionContext, 
String)} and 
+     * {@link Tasks#resolveDeepValueExactly(Object, TypeToken, 
ExecutionContext, String)}. */
+    public ValueResolver<T> deep(boolean forceDeep, Boolean 
deepTraversalUsesRootType) {
         this.forceDeep = forceDeep;
+        this.deepTraversalUsesRootType = deepTraversalUsesRootType;
         return this;
     }
 
@@ -363,9 +378,10 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
         checkTypeNotNull();
         Object v = this.value;
         
-        //if the expected type is a closure or map and that's what we have, 
we're done (or if it's null);
-        //but not allowed to return a future or DeferredSupplier as the 
resolved value
-        if (v==null || (!forceDeep && typeT.getRawType().isInstance(v) && 
!Future.class.isInstance(v) && !DeferredSupplier.class.isInstance(v) && 
!TaskFactory.class.isInstance(v)))
+        //if the expected type is what we have, we're done (or if it's null);
+        //but not allowed to return a future or DeferredSupplier as the 
resolved value,
+        //and not if generics signatures might be different
+        if (v==null || (!forceDeep && 
TypeToken.of(typeT.getRawType()).equals(typeT) && 
typeT.getRawType().isInstance(v) && !Future.class.isInstance(v) && 
!DeferredSupplier.class.isInstance(v) && !TaskFactory.class.isInstance(v)))
             return Maybe.of((T) v);
         
         try {
@@ -516,21 +532,51 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
             } else {
                 if (supportsDeepResolution(v)) {
 
-                    // FIXME-TT why is this done
-                    if (2>1) throw new IllegalStateException("Deep resolution 
with type "+typeT);
+                    // allows user to resolveValue(map, String) with the effect
+                    // that things _in_ the collection would be resolved as 
string.
+                    // alternatively use generics.
+                    boolean useRootObect = typeT.getRawType()==Object.class || 
Boolean.TRUE.equals(deepTraversalUsesRootType);
+                    
+                    TypeToken<?>[] innerTypes = new TypeToken<?>[0];
+                    if (!useRootObect) {
+                        if (!TypeToken.of(typeT.getRawType()).equals(typeT)) {
+                            innerTypes = 
Reflections.getGenericParameterTypeTokens(typeT);
+                        } else {
+                            if (deepTraversalUsesRootType==null) {
+                                // for null, autodetect
+                                useRootObect = true;
+                            } else {
+                                // we will warn and fall back to autodetect 
for legacy reasons
+                                // (same if the number of inner types above is 
wrong)
+                            }
+                        }
+                    }
                         
                     // restrict deep resolution to the same set of types as 
calling code;
                     // in particular need to avoid for "interesting iterables" 
such as PortRange
                     
                     if (v instanceof Map) {
+                        TypeToken<?> keyT = typeT;
+                        TypeToken<?> valT = typeT;
+                        if (!useRootObect) {
+                            if (innerTypes.length==2) {
+                                keyT = innerTypes[0];
+                                valT = innerTypes[1];
+                            } else {
+                                // deprecated in 1.0.0
+                                log.warn("Coercing deep into map "+v+" when 
expected to coerce to incompatible "+typeT+"; "
+                                    + "will attempt conversion of keys and 
values, but this behaviour is deprecated. "
+                                    + "Should correctly request conversion to 
generic TypeToken<Map<...>>.");
+                            }
+                        }
                         //and if a map or list we look inside
                         Map result = Maps.newLinkedHashMap();
                         for (Map.Entry<?,?> entry : ((Map<?,?>)v).entrySet()) {
-                            Maybe<?> kk = new ValueResolver(entry.getKey(), 
typeT, this)
+                            Maybe<?> kk = new ValueResolver(entry.getKey(), 
keyT, this)
                                 .description( (description!=null ? 
description+", " : "") + "map key "+entry.getKey() )
                                 .getMaybe();
                             if (kk.isAbsent()) return (Maybe<T>)kk;
-                            Maybe<?> vv = new ValueResolver(entry.getValue(), 
typeT, this)
+                            Maybe<?> vv = new ValueResolver(entry.getValue(), 
valT, this)
                                 .description( (description!=null ? 
description+", " : "") + "map value for key "+kk.get() )
                                 .getMaybe();
                             if (vv.isAbsent()) return (Maybe<T>)vv;
@@ -538,24 +584,23 @@ public class ValueResolver<T> implements 
DeferredSupplier<T>, Iterable<Maybe<Obj
                         }
                         return Maybe.of((T) result);
         
-                    } else if (v instanceof Set) {
-                        Set result = Sets.newLinkedHashSet();
-                        int count = 0;
-                        for (Object it : (Set)v) {
-                            Maybe<?> vv = new ValueResolver(it, typeT, this)
-                                .description( (description!=null ? 
description+", " : "") + "entry "+count )
-                                .getMaybe();
-                            if (vv.isAbsent()) return (Maybe<T>)vv;
-                            result.add(vv.get());
-                            count++;
-                        }
-                        return Maybe.of((T) result);
-        
                     } else if (v instanceof Iterable) {
-                        List result = Lists.newArrayList();
+                        TypeToken<?> entryT = typeT;
+                        if (!useRootObect) {
+                            if (innerTypes.length==1) {
+                                entryT = innerTypes[0];
+                            } else {
+                                // deprecated in 1.0.0
+                                log.warn("Coercing deep into iterable "+v+" 
when expected to coerce to incompatible "+typeT+"; "
+                                    + "will attempt conversion of keys and 
values, but this behaviour is deprecated. "
+                                    + "Should correctly request conversion to 
generic TypeToken<Map<...>>.");
+                            }
+                        }
+                        
+                        Collection<Object> result = v instanceof Set ? 
MutableSet.of() : Lists.newArrayList();
                         int count = 0;
                         for (Object it : (Iterable)v) {
-                            Maybe<?> vv = new ValueResolver(it, typeT, this)
+                            Maybe<?> vv = new ValueResolver(it, entryT, this)
                                 .description( (description!=null ? 
description+", " : "") + "entry "+count )
                                 .getMaybe();
                             if (vv.isAbsent()) return (Maybe<T>)vv;

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/core/src/test/java/org/apache/brooklyn/util/core/task/TasksTest.java
----------------------------------------------------------------------
diff --git 
a/core/src/test/java/org/apache/brooklyn/util/core/task/TasksTest.java 
b/core/src/test/java/org/apache/brooklyn/util/core/task/TasksTest.java
index e36fadd..a5d4f69 100644
--- a/core/src/test/java/org/apache/brooklyn/util/core/task/TasksTest.java
+++ b/core/src/test/java/org/apache/brooklyn/util/core/task/TasksTest.java
@@ -52,7 +52,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.reflect.TypeToken;
 import com.google.common.util.concurrent.Callables;
 
-
+@SuppressWarnings("serial")
 public class TasksTest extends BrooklynAppUnitTestSupport {
 
     private ExecutionContext executionContext;
@@ -84,24 +84,24 @@ public class TasksTest extends BrooklynAppUnitTestSupport {
     public void testResolvesMapWithAttributeWhenReady() throws Exception {
         app.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
         Map<?,?> orig = ImmutableMap.of("mykey", attributeWhenReady(app, 
TestApplication.MY_ATTRIBUTE));
-        Map<?,?> expected = ImmutableMap.of("mykey", "myval");
-        assertResolvesValue(orig, String.class, expected);
+        Map<String,String> expected = ImmutableMap.of("mykey", "myval");
+        assertResolvesValue(orig, new TypeToken<Map<String,String>>() {}, 
expected);
     }
     
     @Test
     public void testResolvesSetWithAttributeWhenReady() throws Exception {
         app.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
         Set<?> orig = ImmutableSet.of(attributeWhenReady(app, 
TestApplication.MY_ATTRIBUTE));
-        Set<?> expected = ImmutableSet.of("myval");
-        assertResolvesValue(orig, String.class, expected);
+        Set<String> expected = ImmutableSet.of("myval");
+        assertResolvesValue(orig, new TypeToken<Set<String>>() {}, expected);
     }
     
     @Test
     public void testResolvesMapOfMapsWithAttributeWhenReady() throws Exception 
{
         app.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
         Map<?,?> orig = ImmutableMap.of("mykey", ImmutableMap.of("mysubkey", 
attributeWhenReady(app, TestApplication.MY_ATTRIBUTE)));
-        Map<?,?> expected = ImmutableMap.of("mykey", 
ImmutableMap.of("mysubkey", "myval"));
-        assertResolvesValue(orig, String.class, expected);
+        Map<String,Map<String,String>> expected = ImmutableMap.of("mykey", 
ImmutableMap.of("mysubkey", "myval"));
+        assertResolvesValue(orig, new 
TypeToken<Map<String,Map<String,String>>>() {}, expected);
     }
     
     @Test
@@ -109,12 +109,15 @@ public class TasksTest extends BrooklynAppUnitTestSupport 
{
         app.sensors().set(TestApplication.MY_ATTRIBUTE, "myval");
         // using Iterables.concat so that orig is of type FluentIterable 
rather than List etc
         List<?> orig = 
ImmutableList.copyOf(ImmutableList.of(ImmutableMap.of("mykey", 
attributeWhenReady(app, TestApplication.MY_ATTRIBUTE))));
-        Iterable<Map<?,?>> expected = 
ImmutableList.<Map<?,?>>of(ImmutableMap.of("mykey", "myval"));
-        assertResolvesValue(orig, String.class, expected);
+        Iterable<Map<String,String>> expected = 
ImmutableList.of(ImmutableMap.of("mykey", "myval"));
+        assertResolvesValue(orig, new 
TypeToken<Iterable<Map<String,String>>>() {}, expected);
     }
     
-    private void assertResolvesValue(Object actual, Class<?> type, Object 
expected) throws Exception {
-        Object result = Tasks.resolveValue(actual, TypeToken.of(type), 
executionContext);
+    private <T> void assertResolvesValue(Object actual, Class<T> type, T 
expected) throws Exception {
+        assertResolvesValue(actual, TypeToken.of(type), expected);
+    }
+    private <T> void assertResolvesValue(Object actual, TypeToken<T> type, T 
expected) throws Exception {
+        Object result = Tasks.resolveValue(actual, type, executionContext);
         assertEquals(result, expected);
     }
     

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
----------------------------------------------------------------------
diff --git 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
index 62328a2..0c45c95 100644
--- 
a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
+++ 
b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationResolverTest.java
@@ -37,7 +37,6 @@ import 
org.apache.brooklyn.location.jclouds.domain.JcloudsContext;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.collections.MutableMap;
 import org.apache.brooklyn.util.collections.MutableSet;
-import org.jclouds.Context;
 import org.jclouds.compute.ComputeService;
 import org.jclouds.compute.domain.Image;
 import org.slf4j.Logger;
@@ -49,6 +48,7 @@ import org.testng.annotations.Test;
 
 import com.google.common.base.Function;
 import com.google.common.collect.Iterables;
+import com.google.common.reflect.TypeToken;
 
 public class JcloudsLocationResolverTest {
 
@@ -369,15 +369,18 @@ public class JcloudsLocationResolverTest {
     @Test
     public void testResolvesListAndMapProperties() throws Exception {
         brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop1", "[ 
a, b ]");
-        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "{ 
a: 1, b: 2 }");
+        brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "{ 
a: 1, b: \"2\" }");
         brooklynProperties.put("brooklyn.location.named.foo", 
"jclouds:softlayer:ams01");
         
         JcloudsLocation l = resolve("named:foo");
         assertJcloudsEquals(l, "softlayer", "ams01");
-        assertEquals(l.config().get(new SetConfigKey<String>(String.class, 
"prop1")), MutableSet.of("a", "b"));
-        assertEquals(l.config().get(new MapConfigKey<String>(String.class, 
"prop2")), MutableMap.of("a", 1, "b", 2));
+        assertEquals(l.config().get(new SetConfigKey<>(String.class, 
"prop1")), MutableSet.of("a", "b"));
+        assertEquals(l.config().get(new MapConfigKey<>(String.class, 
"prop2")), MutableMap.of("a", "1", "b", "2"));
+        assertEquals(l.config().get(new MapConfigKey<>(Integer.class, 
"prop2")), MutableMap.of("a", 1, "b", 2));
+        assertEquals(l.config().get(new MapConfigKey<>(Object.class, 
"prop2")), MutableMap.of("a", 1, "b", "2"));
     }
     
+    @SuppressWarnings("serial")
     @Test
     public void testResolvesListAndMapPropertiesWithoutMergeOnInheritance() 
throws Exception {
         // since prop2 does not specify DEEP_MERGE config inheritance, we 
overwrite
@@ -385,21 +388,29 @@ public class JcloudsLocationResolverTest {
         brooklynProperties.put("brooklyn.location.jclouds.softlayer.prop2", "{ 
a: 1, b: 2 }");
         brooklynProperties.put("brooklyn.location.named.foo", 
"jclouds:softlayer:ams01");
         
-        brooklynProperties.put("brooklyn.location.named.foo.prop1", "[ a: 1, 
c: 3 ]");
+        brooklynProperties.put("brooklyn.location.named.foo.prop1", "[ a: 1, 
c: \"3\" ]");
         brooklynProperties.put("brooklyn.location.named.foo.prop2", "{ b: 3, 
c: 3 }");
         brooklynProperties.put("brooklyn.location.named.bar", "named:foo");
-        brooklynProperties.put("brooklyn.location.named.bar.prop2", "{ c: 4, 
d: 4 }");
+        brooklynProperties.put("brooklyn.location.named.bar.prop2", "{ c: 4, 
d: \"4\" }");
         
         JcloudsLocation l = resolve("named:bar");
         assertJcloudsEquals(l, "softlayer", "ams01");
         
-        Set<? extends String> prop1 = l.config().get(new 
SetConfigKey<String>(String.class, "prop1"));
+        Set<?> prop1 = l.config().get(new SetConfigKey<String>(String.class, 
"prop1"));
         log.info("prop1: "+prop1);
-        assertEquals(prop1, MutableSet.of("a: 1", "c: 3"));
+        assertEquals(prop1, MutableSet.of("a: 1", "c: \"3\""));
+        prop1 = l.config().get(new SetConfigKey<>(new 
TypeToken<Map<String,Integer>>() {}, "prop1"));
+        assertEquals(prop1, MutableSet.of(MutableMap.of("a", 1), 
MutableMap.of("c", 3)));
+        prop1 = l.config().get(new SetConfigKey<>(new 
TypeToken<Map<Object,Object>>() {}, "prop1"));
+        assertEquals(prop1, MutableSet.of(MutableMap.of("a", 1), 
MutableMap.of("c", "3")));
         
-        Map<String, String> prop2 = l.config().get(new 
MapConfigKey<String>(String.class, "prop2"));
+        Map<String, ?> prop2 = l.config().get(new 
MapConfigKey<String>(String.class, "prop2"));
         log.info("prop2: "+prop2);
+        assertEquals(prop2, MutableMap.of("c", "4", "d", "4"));
+        prop2 = l.config().get(new MapConfigKey<>(Integer.class, "prop2"));
         assertEquals(prop2, MutableMap.of("c", 4, "d", 4));
+        prop2 = l.config().get(new MapConfigKey<>(Object.class, "prop2"));
+        assertEquals(prop2, MutableMap.of("c", 4, "d", "4"));
         
         Map<String, String> prop3 = l.config().get(new 
MapConfigKey<String>(String.class, "prop3"));
         log.info("prop3: "+prop3);
@@ -415,18 +426,18 @@ public class JcloudsLocationResolverTest {
         brooklynProperties.put("brooklyn.location.named.foo.prop1", "[ a: 1, 
c: 3 ]");
         brooklynProperties.put("brooklyn.location.named.foo.prop2", "{ b: 3, 
c: 3 }");
         brooklynProperties.put("brooklyn.location.named.bar", "named:foo");
-        brooklynProperties.put("brooklyn.location.named.bar.prop2", "{ c: 4, 
d: 4 }");
+        brooklynProperties.put("brooklyn.location.named.bar.prop2", "{ c: 4, 
d: \"4\" }");
         
         // dot-qualified keys now DO get interpreted (sept 2016)
-        brooklynProperties.put("brooklyn.location.named.foo.prop2.z", 9);
+        brooklynProperties.put("brooklyn.location.named.foo.prop2.z", "9");
         brooklynProperties.put("brooklyn.location.named.foo.prop3.z", 10);
         
         JcloudsLocation l = resolve("named:bar");
         assertJcloudsEquals(l, "softlayer", "ams01");
         
-        Map<String, String> prop2 = l.config().get(new 
MapConfigKey<String>(String.class, "prop2"));
+        Map<String, Integer> prop2 = l.config().get(new 
MapConfigKey<>(Integer.class, "prop2"));
         log.info("prop2: "+prop2);
-        assertEquals(prop2, MutableMap.of("c", 4, "d", 4, "z", "9"));
+        assertEquals(prop2, MutableMap.of("c", 4, "d", 4, "z", 9));
         
         Map<String, String> prop3 = l.config().get(new 
MapConfigKey<String>(String.class, "prop3"));
         log.info("prop3: "+prop3);

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
----------------------------------------------------------------------
diff --git 
a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
 
b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
index b61e012..9350d52 100644
--- 
a/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
+++ 
b/policy/src/main/java/org/apache/brooklyn/policy/action/AbstractScheduledEffectorPolicy.java
@@ -261,7 +261,7 @@ public abstract class AbstractScheduledEffectorPolicy 
extends AbstractPolicy imp
             Map<String, Object> args = EntityInitializers.resolve(bag, 
EFFECTOR_ARGUMENTS);
             LOG.debug("{}: Resolving arguments for {}: {}", new Object[] { 
this, effector.getName(), Iterables.toString(args.keySet()) });
             Map<String, Object> resolved = (Map) Tasks.resolving(args, 
Object.class)
-                    .deep(true)
+                    .deep(true, true)
                     .context(entity)
                     .get();
 

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java
----------------------------------------------------------------------
diff --git 
a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java
 
b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java
index 3ee5e24..d1e9d9a 100644
--- 
a/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java
+++ 
b/software/winrm/src/main/java/org/apache/brooklyn/core/sensor/windows/WinRmCommandSensor.java
@@ -50,6 +50,7 @@ import com.google.common.base.Function;
 import com.google.common.base.Functions;
 import com.google.common.base.Preconditions;
 import com.google.common.base.Supplier;
+import com.google.common.reflect.TypeToken;
 
 /** 
  * Configurable {@link EntityInitializer} which adds an WinRm sensor feed 
running the <code>command</code> supplied
@@ -97,6 +98,7 @@ public final class WinRmCommandSensor<T> extends 
AbstractAddSensorFeed<T> {
         final Duration logWarningGraceTime = 
EntityInitializers.resolve(params, LOG_WARNING_GRACE_TIME);
 
         Supplier<Map<String,String>> envSupplier = new 
Supplier<Map<String,String>>() {
+            @SuppressWarnings("serial")
             @Override
             public Map<String, String> get() {
                 Map<String, String> env = 
MutableMap.copyOf(entity.getConfig(SENSOR_ENVIRONMENT));
@@ -106,7 +108,7 @@ public final class WinRmCommandSensor<T> extends 
AbstractAddSensorFeed<T> {
 
                 // Try to resolve the configuration in the env Map
                 try {
-                    env = (Map<String, String>) Tasks.resolveDeepValue(env, 
String.class, ((EntityInternal) entity).getExecutionContext());
+                    env = Tasks.resolveDeepValueExactly(env, new 
TypeToken<Map<String,String>>() {}, ((EntityInternal) 
entity).getExecutionContext());
                 } catch (InterruptedException | ExecutionException e) {
                     Exceptions.propagateIfFatal(e);
                 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java 
b/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
index 5b34249..439f922 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/guava/Maybe.java
@@ -501,6 +501,6 @@ public abstract class Maybe<T> implements Serializable, 
Supplier<T> {
 
     /** Finds the {@link Absent#getException()} if {@link #isAbsent()}, or 
null */
     public static RuntimeException getException(Maybe<?> t) {
-        return ((Maybe.Absent<?>)t).getException();
+        return t instanceof Maybe.Absent ? ((Maybe.Absent<?>)t).getException() 
: null;
     }
 }

http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/8e48531c/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
----------------------------------------------------------------------
diff --git 
a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java 
b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
index 5838df9..7237988 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/Boxing.java
@@ -19,11 +19,13 @@
 package org.apache.brooklyn.util.javalang;
 
 import java.lang.reflect.Array;
+import java.lang.reflect.Type;
 
 import org.apache.brooklyn.util.guava.Maybe;
 
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.primitives.Primitives;
+import com.google.common.reflect.TypeToken;
 
 /** Conveniences for working with primitives and their boxed (wrapper) types.
  * NB: there is redundancy with {@link Primitives}
@@ -77,6 +79,14 @@ public class Boxing {
             return PRIMITIVE_TO_BOXED.get(type);
         return type;
     }
+    
+    public static TypeToken<?> boxedTypeToken(Type type) {
+        TypeToken<?> tt = TypeToken.of(type);
+        Class<?> clazz = tt.getRawType();
+        if (PRIMITIVE_TO_BOXED.containsKey(clazz))
+            return TypeToken.of(PRIMITIVE_TO_BOXED.get(clazz));
+        return tt;
+    }
 
     public static boolean isPrimitiveOrBoxedObject(Object o) {
         if (o==null) return false;

Reply via email to