better ordering of type coercions, run "wrong bean" coercions after standard coercions

negative indexed coercions run after the standard coercions, and the natural order comparator is used so that 11 runs after 9
diff --git a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
index 3840c0b..35b1752 100644
--- a/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
+++ b/core/src/main/java/org/apache/brooklyn/util/core/flags/TypeCoercions.java
@@ -381,7 +381,7 @@
                 }
             });
 
-            registerAdapter("81-wrong-bean-to-map-or-bean", new TryCoercer() {
+            registerAdapter("-20-wrong-bean-to-map-or-bean", new TryCoercer() {
 
                 @Override
                 public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) {
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
index 20c8649..a792c70 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/CommonAdaptorTypeCoercions.java
@@ -88,7 +88,9 @@
     public synchronized <A,B> Function<? super A,B> registerAdapter(Class<A> sourceType, Class<B> targetType, Function<? super A,B> fn) {
         return coercer.registerAdapter(sourceType, targetType, fn);
     }
-    /** Registers an adapter for use with type coercion. */
+    /** Registers an adapter for use with type coercion. nameAndOrder is of the form NUM-NAME with the natural order prevailing (ordered by NUM numerically),
+     * eg 1-x before 2-x before 9-x before 11-x;
+     * negative indexed orders are processed after, with -1-x before -2-x before -11-x */
     public synchronized void registerAdapter(String nameAndOrder, TryCoercer coerceFn) {
         coercer.registerAdapter(nameAndOrder, coerceFn);
     }
diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
index c235d53..fe5ec0c 100644
--- a/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
+++ b/utils/common/src/main/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensible.java
@@ -20,10 +20,26 @@
 
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
-import java.util.*;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
 import java.util.stream.Collectors;
 
-import com.google.common.collect.*;
+import com.google.common.annotations.Beta;
+import com.google.common.base.Function;
+import com.google.common.base.Objects;
+import com.google.common.collect.HashBasedTable;
+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.reflect.TypeToken;
 import org.apache.brooklyn.core.validation.BrooklynValidation;
 import org.apache.brooklyn.util.collections.MutableList;
 import org.apache.brooklyn.util.exceptions.Exceptions;
@@ -32,17 +48,13 @@
 import org.apache.brooklyn.util.guava.TypeTokens;
 import org.apache.brooklyn.util.javalang.Boxing;
 import org.apache.brooklyn.util.javalang.Reflections;
+import org.apache.brooklyn.util.text.NaturalOrderComparator;
 import org.apache.brooklyn.util.text.Strings;
 import org.apache.brooklyn.util.time.Duration;
 import org.apache.brooklyn.util.time.Time;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.google.common.annotations.Beta;
-import com.google.common.base.Function;
-import com.google.common.base.Objects;
-import com.google.common.reflect.TypeToken;
-
 /**
  * Attempts to coerce {@code value} to {@code targetType}.
  * <p>
@@ -88,9 +100,7 @@
     private Table<Class<?>, Class<?>, Function<?,?>> registry = HashBasedTable.create();
 
     /** Store the generic coercers, ordered by the name; reset each time updated */
-    private SortedMap<String,TryCoercer> genericCoercersByName = Maps.newTreeMap();
-    /** Put the list in a cache, reset each time the map is updated. */
-    private List<TryCoercer> genericCoercers = new ArrayList<>();
+    private SortedMap<String,TryCoercer> genericCoercersByName = Maps.newTreeMap(NaturalOrderComparator.INSTANCE);
 
     @Override
     public <T> T coerce(Object value, Class<T> targetType) {
@@ -148,43 +158,14 @@
         if (targetType.isInstance(value)) return Maybe.of( (T) value );
 
         targetTypeToken = TypeTokens.getTypeToken(targetTypeToken, targetType);
-        for (TryCoercer coercer : genericCoercers) {
-            result = coercer.tryCoerce(value, targetTypeToken);
-            
-            if (result!=null && result.isPresentAndNonNull()) {
-                // 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.get()) && !Objects.equal(value.getClass(), result.get().getClass())
-                        // previously did this just for generics but it's more useful than that, e.g. if was a WrappedValue
-                        //&& targetTypeToken.getType() instanceof ParameterizedType
-                        ) {
-                    Maybe<T> resultM = tryCoerce(result.get(), targetTypeToken);
-                    if (resultM!=null) {
-                        if (resultM.isPresent()) return resultM;
-                        // if couldn't coerce parameterized types then back out of this coercer
-                        result = resultM;
-                    }
-                } else {
-                    return result;
-                }
-            }
-            
-            if (result!=null) {
-                if (result.isAbsent()) errors.add(result);
-                else {
-                    if (coercer instanceof TryCoercer.TryCoercerReturningNull) {
-                        return result;
-                    } else {
-                        String c = getCoercerName(coercer);
-                        log.warn("Coercer " + c + " returned wrapped null when coercing " + value);
-                        errors.add(Maybe.absent("coercion returned null ("+c+")"));
-                        // coercers the return null should implement 'TryCoercerReturningNull'
-                    }
-                }
+        for (Entry<String, TryCoercer> mapEntry : genericCoercersByName.entrySet()) {
+            String coercerName = mapEntry.getKey();
+            if (coercerName != null && !coercerName.startsWith("-")) {
+                Maybe<T> resultM = applyCoercer(value, targetTypeToken, errors, mapEntry.getValue(), coercerName);
+                if (resultM != null) return resultM;
             }
         }
-        
+
         //ENHANCEMENT could look in type hierarchy of both types for a conversion method...
         
         //at this point, if either is primitive then run instead over boxed types
@@ -242,6 +223,15 @@
             }
         }
 
+        // now try negative ordered coercers
+        for (Entry<String, TryCoercer> mapEntry : genericCoercersByName.entrySet()) {
+            String coercerName = mapEntry.getKey();
+            if (coercerName != null && coercerName.startsWith("-")) {
+                Maybe<T> resultM = applyCoercer(value, targetTypeToken, errors, mapEntry.getValue(), coercerName);
+                if (resultM != null) return resultM;
+            }
+        }
+
         // not found
         if (!errors.isEmpty()) {
             if (errors.size()==1) return Iterables.getOnlyElement(errors);
@@ -256,11 +246,42 @@
         return Maybe.absent(new ClassCoercionException("Cannot coerce type "+value.getClass().getCanonicalName()+" to "+targetTypeToken+" ("+value+"): no adapter known"));
     }
 
-    protected String getCoercerName(TryCoercer coercer) {
-        Optional<Map.Entry<String, TryCoercer>> bn = genericCoercersByName.entrySet().stream().filter(es -> Objects.equal(coercer, es.getValue())).findAny();
-        if (bn.isPresent()) return bn.get().getKey();
-        int index = genericCoercers.indexOf(coercer);
-        return coercer.toString() + (index>=0 ? " (index "+index+")" : "");
+    private <T> Maybe<T> applyCoercer(Object value, TypeToken<T> targetTypeToken, List<Maybe<T>> errors, TryCoercer coercer, String coercerName) {
+        Maybe<T> result;
+        result = coercer.tryCoerce(value, targetTypeToken);
+
+        if (result!=null && result.isPresentAndNonNull()) {
+            // 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.get()) && !Objects.equal(value.getClass(), result.get().getClass())
+                    // previously did this just for generics but it's more useful than that, e.g. if was a WrappedValue
+                    //&& targetTypeToken.getType() instanceof ParameterizedType
+                    ) {
+                Maybe<T> resultM = tryCoerce(result.get(), targetTypeToken);
+                if (resultM!=null) {
+                    if (resultM.isPresent()) return resultM;
+                    // if couldn't coerce parameterized types then back out of this coercer
+                    result = resultM;
+                }
+            } else {
+                return result;
+            }
+        }
+
+        if (result!=null) {
+            if (result.isAbsent()) errors.add(result);
+            else {
+                if (coercer instanceof TryCoercer.TryCoercerReturningNull) {
+                    return result;
+                } else {
+                    log.warn("Coercer " + coercerName + " returned wrapped null when coercing " + value);
+                    errors.add(Maybe.absent("coercion returned null ("+coercerName+")"));
+                    // coercers that return null should implement 'TryCoercerReturningNull'
+                }
+            }
+        }
+        return null;
     }
 
     @SuppressWarnings("unchecked")
@@ -353,9 +374,8 @@
         TreeMap<String, TryCoercer> gcn = Maps.newTreeMap(genericCoercersByName);
         gcn.put(nameAndOrder, fn);
         genericCoercersByName = gcn;
-        genericCoercers = ImmutableList.copyOf(genericCoercersByName.values());
     }
-    
+
     /** @deprecated since introduction, use {@link #registerAdapter(String, TryCoercer)} */
     @Beta @Deprecated
     public void registerAdapter(TryCoercer fn) {
diff --git a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensibleTest.java b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensibleTest.java
index 5c86b2c..2d67597 100644
--- a/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensibleTest.java
+++ b/utils/common/src/test/java/org/apache/brooklyn/util/javalang/coerce/TypeCoercerExtensibleTest.java
@@ -81,4 +81,37 @@
             return MoreObjects.toStringHelper(this).add("val", val).toString();
         }
     }
+
+    void registerOrderedConditionalMyClassAdapter(int order, String requiredToken) {
+        coercer.registerAdapter(""+order+"-"+requiredToken,
+                new TryCoercer() {
+                    @Override
+                    @SuppressWarnings("unchecked")
+                    public <T> Maybe<T> tryCoerce(Object input, TypeToken<T> type) {
+                        if (input instanceof String && type.getRawType().isAssignableFrom(Integer.class) && ((String)input).contains(requiredToken))
+                            return Maybe.of((T)(Object) (order*100+((String)input).length()));
+                        return null;
+                    }
+                });
+    }
+
+    @Test
+    public void testPriority() {
+        registerOrderedConditionalMyClassAdapter(-1, "11");
+        registerOrderedConditionalMyClassAdapter(1, "a");
+        registerOrderedConditionalMyClassAdapter(2, "b");
+        registerOrderedConditionalMyClassAdapter(3, "33");
+        registerOrderedConditionalMyClassAdapter(-2, "22");
+
+        assertEquals(coerce("a", Integer.class), (Integer) 101);
+        assertEquals(coerce("ab", Integer.class), (Integer) 102);
+        assertEquals(coerce("bb", Integer.class), (Integer) 202);
+        assertEquals(coerce("33", Integer.class), (Integer) 302);
+        assertEquals(coerce("ab11", Integer.class), (Integer) 104); // coercer 1-a applies
+        assertEquals(coerce("d11", Integer.class), (Integer) (-97)); // coercer -1-11 applies
+        assertEquals(coerce("d1122", Integer.class), (Integer) (-95)); // coercer -1-11 applies
+        assertEquals(coerce("d2222", Integer.class), (Integer) (-195)); // coercer -2-22 applies
+        assertEquals(coerce("33", Integer.class), (Integer) (302)); // coercer 3-33 applies before Integer.toString
+        assertEquals(coerce("1122", Integer.class), (Integer) (1122)); // Integer.fromString coercion applies before negative coercers
+    }
 }