You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by th...@apache.org on 2014/12/21 20:56:45 UTC

[19/35] tapestry-5 git commit: Fourth pass creating the BeanModel and Commons packages.

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java
new file mode 100644
index 0000000..49b0b15
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/AnnotationProviderChain.java
@@ -0,0 +1,59 @@
+// Copyright 2008 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+
+/**
+ * Chain of command for {@link org.apache.tapestry5.ioc.AnnotationProvider}.
+ */
+public class AnnotationProviderChain implements AnnotationProvider
+{
+    private final AnnotationProvider[] providers;
+
+    public AnnotationProviderChain(AnnotationProvider[] providers)
+    {
+        this.providers = providers;
+    }
+
+    /**
+     * Creates an AnnotationProvider from the list of providers.  Returns either an {@link AnnotationProviderChain} or
+     * the sole element in the list.
+     */
+    public static AnnotationProvider create(List<AnnotationProvider> providers)
+    {
+        int size = providers.size();
+
+        if (size == 1) return providers.get(0);
+
+        return new AnnotationProviderChain(providers.toArray(new AnnotationProvider[providers.size()]));
+    }
+
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+    {
+        for (AnnotationProvider p : providers)
+        {
+            T result = p.getAnnotation(annotationClass);
+
+            if (result != null) return result;
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java
new file mode 100644
index 0000000..4a5dece
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/CompoundCoercion.java
@@ -0,0 +1,54 @@
+// Copyright 2006, 2007 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.services.Coercion;
+
+/**
+ * Combines two coercions to create a coercion through an intermediate type.
+ *
+ * @param <S> The source (input) type
+ * @param <I> The intermediate type
+ * @param <T> The target (output) type
+ */
+public class CompoundCoercion<S, I, T> implements Coercion<S, T>
+{
+    private final Coercion<S, I> op1;
+
+    private final Coercion<I, T> op2;
+
+    public CompoundCoercion(Coercion<S, I> op1, Coercion<I, T> op2)
+    {
+        this.op1 = op1;
+        this.op2 = op2;
+    }
+
+    @Override
+    public T coerce(S input)
+    {
+        // Run the input through the first operation (S --> I), then run the result of that through
+        // the second operation (I --> T).
+
+        I intermediate = op1.coerce(input);
+
+        return op2.coerce(intermediate);
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("%s, %s", op1, op2);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java
new file mode 100644
index 0000000..e92ef2d
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/ServiceMessages.java
@@ -0,0 +1,68 @@
+// Copyright 2006, 2007, 2011, 2012 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.Messages;
+import org.apache.tapestry5.ioc.internal.util.MessagesImpl;
+import org.apache.tapestry5.ioc.services.Coercion;
+import org.apache.tapestry5.plastic.PlasticUtils;
+
+public class ServiceMessages
+{
+    private final static Messages MESSAGES = MessagesImpl.forClass(ServiceMessages.class);
+
+    private ServiceMessages()
+    {
+    }
+
+    public static String noSuchProperty(Class clazz, String propertyName)
+    {
+        return MESSAGES.format("no-such-property", clazz.getName(), propertyName);
+    }
+
+
+    public static String readFailure(String propertyName, Object instance, Throwable cause)
+    {
+        return MESSAGES.format("read-failure", propertyName, instance, cause);
+    }
+
+    public static String propertyTypeMismatch(String propertyName, Class sourceClass, Class propertyType,
+                                              Class expectedType)
+    {
+        return MESSAGES.format("property-type-mismatch", propertyName, sourceClass.getName(), propertyType.getName(),
+                expectedType.getName());
+    }
+
+    public static String shutdownListenerError(Object listener, Throwable cause)
+    {
+        return MESSAGES.format("shutdown-listener-error", listener, cause);
+    }
+
+    public static String failedCoercion(Object input, Class targetType, Coercion coercion, Throwable cause)
+    {
+        return MESSAGES.format("failed-coercion", String.valueOf(input), PlasticUtils.toTypeName(targetType),
+                coercion, cause);
+    }
+
+    public static String registryShutdown(String serviceId)
+    {
+        return MESSAGES.format("registry-shutdown", serviceId);
+    }
+
+    public static String serviceBuildFailure(String serviceId, Throwable cause)
+    {
+        return MESSAGES.format("service-build-failure", serviceId, cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java
new file mode 100644
index 0000000..0769b7e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/StringLocation.java
@@ -0,0 +1,65 @@
+// Copyright 2007 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.ioc.Location;
+import org.apache.tapestry5.ioc.Resource;
+
+/**
+ * Implementation of {@link Location} used when the underlying resource isn't really known.
+ */
+public final class StringLocation implements Location
+{
+    private final String description;
+
+    private final int line;
+
+    public StringLocation(String description, int line)
+    {
+        this.description = description;
+        this.line = line;
+    }
+
+    @Override
+    public String toString()
+    {
+        return description;
+    }
+
+    /**
+     * Returns 0.
+     */
+    @Override
+    public int getColumn()
+    {
+        return 0;
+    }
+
+    @Override
+    public int getLine()
+    {
+        return line;
+    }
+
+    /**
+     * Returns null; we don't know where the file really is (it's probably a class on the class path).
+     */
+    @Override
+    public Resource getResource()
+    {
+        return null;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java
new file mode 100644
index 0000000..6481384
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/services/TypeCoercerImpl.java
@@ -0,0 +1,508 @@
+// Copyright 2006, 2007, 2008, 2010, 2012 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.services;
+
+import org.apache.tapestry5.func.F;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InheritanceSearch;
+import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils;
+import org.apache.tapestry5.ioc.internal.util.LockSupport;
+import org.apache.tapestry5.ioc.services.Coercion;
+import org.apache.tapestry5.ioc.services.CoercionTuple;
+import org.apache.tapestry5.ioc.services.TypeCoercer;
+import org.apache.tapestry5.ioc.util.AvailableValues;
+import org.apache.tapestry5.ioc.util.UnknownValueException;
+import org.apache.tapestry5.plastic.PlasticUtils;
+import org.apache.tapestry5.util.StringToEnumCoercion;
+
+import java.util.*;
+
+@SuppressWarnings("all")
+public class TypeCoercerImpl extends LockSupport implements TypeCoercer
+{
+    // Constructed from the service's configuration.
+
+    private final Map<Class, List<CoercionTuple>> sourceTypeToTuple = CollectionFactory.newMap();
+
+    /**
+     * A coercion to a specific target type. Manages a cache of coercions to specific types.
+     */
+    private class TargetCoercion
+    {
+        private final Class type;
+
+        private final Map<Class, Coercion> cache = CollectionFactory.newConcurrentMap();
+
+        TargetCoercion(Class type)
+        {
+            this.type = type;
+        }
+
+        void clearCache()
+        {
+            cache.clear();
+        }
+
+        Object coerce(Object input)
+        {
+            Class sourceType = input != null ? input.getClass() : Void.class;
+
+            if (type.isAssignableFrom(sourceType))
+            {
+                return input;
+            }
+
+            Coercion c = getCoercion(sourceType);
+
+            try
+            {
+                return type.cast(c.coerce(input));
+            } catch (Exception ex)
+            {
+                throw new RuntimeException(ServiceMessages.failedCoercion(input, type, c, ex), ex);
+            }
+        }
+
+        String explain(Class sourceType)
+        {
+            return getCoercion(sourceType).toString();
+        }
+
+        private Coercion getCoercion(Class sourceType)
+        {
+            Coercion c = cache.get(sourceType);
+
+            if (c == null)
+            {
+                c = findOrCreateCoercion(sourceType, type);
+                cache.put(sourceType, c);
+            }
+
+            return c;
+        }
+    }
+
+    /**
+     * Map from a target type to a TargetCoercion for that type.
+     */
+    private final Map<Class, TargetCoercion> typeToTargetCoercion = new WeakHashMap<Class, TargetCoercion>();
+
+    private static final Coercion NO_COERCION = new Coercion<Object, Object>()
+    {
+        @Override
+        public Object coerce(Object input)
+        {
+            return input;
+        }
+    };
+
+    private static final Coercion COERCION_NULL_TO_OBJECT = new Coercion<Void, Object>()
+    {
+        @Override
+        public Object coerce(Void input)
+        {
+            return null;
+        }
+
+        @Override
+        public String toString()
+        {
+            return "null --> null";
+        }
+    };
+
+    public TypeCoercerImpl(Collection<CoercionTuple> tuples)
+    {
+        for (CoercionTuple tuple : tuples)
+        {
+            Class key = tuple.getSourceType();
+
+            InternalCommonsUtils.addToMapList(sourceTypeToTuple, key, tuple);
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public Object coerce(Object input, Class targetType)
+    {
+        assert targetType != null;
+
+        Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
+
+        if (effectiveTargetType.isInstance(input))
+        {
+            return input;
+        }
+
+
+        return getTargetCoercion(effectiveTargetType).coerce(input);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <S, T> Coercion<S, T> getCoercion(Class<S> sourceType, Class<T> targetType)
+    {
+        assert sourceType != null;
+        assert targetType != null;
+
+        Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType);
+        Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
+
+        if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
+        {
+            return NO_COERCION;
+        }
+
+        return getTargetCoercion(effectiveTargetType).getCoercion(effectiveSourceType);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <S, T> String explain(Class<S> sourceType, Class<T> targetType)
+    {
+        assert sourceType != null;
+        assert targetType != null;
+
+        Class effectiveTargetType = PlasticUtils.toWrapperType(targetType);
+        Class effectiveSourceType = PlasticUtils.toWrapperType(sourceType);
+
+        // Is a coercion even necessary? Not if the target type is assignable from the
+        // input value.
+
+        if (effectiveTargetType.isAssignableFrom(effectiveSourceType))
+        {
+            return "";
+        }
+
+        return getTargetCoercion(effectiveTargetType).explain(effectiveSourceType);
+    }
+
+    private TargetCoercion getTargetCoercion(Class targetType)
+    {
+        try
+        {
+            acquireReadLock();
+
+            TargetCoercion tc = typeToTargetCoercion.get(targetType);
+
+            return tc != null ? tc : createAndStoreNewTargetCoercion(targetType);
+        } finally
+        {
+            releaseReadLock();
+        }
+    }
+
+    private TargetCoercion createAndStoreNewTargetCoercion(Class targetType)
+    {
+        try
+        {
+            upgradeReadLockToWriteLock();
+
+            // Inner check since some other thread may have beat us to it.
+
+            TargetCoercion tc = typeToTargetCoercion.get(targetType);
+
+            if (tc == null)
+            {
+                tc = new TargetCoercion(targetType);
+                typeToTargetCoercion.put(targetType, tc);
+            }
+
+            return tc;
+        } finally
+        {
+            downgradeWriteLockToReadLock();
+        }
+    }
+
+    @Override
+    public void clearCache()
+    {
+        try
+        {
+            acquireReadLock();
+
+            // There's no need to clear the typeToTargetCoercion map, as it is a WeakHashMap and
+            // will release the keys for classes that are no longer in existence. On the other hand,
+            // there's likely all sorts of references to unloaded classes inside each TargetCoercion's
+            // individual cache, so clear all those.
+
+            for (TargetCoercion tc : typeToTargetCoercion.values())
+            {
+                // Can tc ever be null?
+
+                tc.clearCache();
+            }
+        } finally
+        {
+            releaseReadLock();
+        }
+    }
+
+    /**
+     * Here's the real meat; we do a search of the space to find coercions, or a system of
+     * coercions, that accomplish
+     * the desired coercion.
+     * <p/>
+     * There's <strong>TREMENDOUS</strong> room to improve this algorithm. For example, inheritance lists could be
+     * cached. Further, there's probably more ways to early prune the search. However, even with dozens or perhaps
+     * hundreds of tuples, I suspect the search will still grind to a conclusion quickly.
+     * <p/>
+     * The order of operations should help ensure that the most efficient tuple chain is located. If you think about how
+     * tuples are added to the queue, there are two factors: size (the number of steps in the coercion) and
+     * "class distance" (that is, number of steps up the inheritance hiearchy). All the appropriate 1 step coercions
+     * will be considered first, in class distance order. Along the way, we'll queue up all the 2 step coercions, again
+     * in class distance order. By the time we reach some of those, we'll have begun queueing up the 3 step coercions, and
+     * so forth, until we run out of input tuples we can use to fabricate multi-step compound coercions, or reach a
+     * final response.
+     * <p/>
+     * This does create a good number of short lived temporary objects (the compound tuples), but that's what the GC is
+     * really good at.
+     *
+     * @param sourceType
+     * @param targetType
+     * @return coercer from sourceType to targetType
+     */
+    @SuppressWarnings("unchecked")
+    private Coercion findOrCreateCoercion(Class sourceType, Class targetType)
+    {
+        if (sourceType == Void.class)
+        {
+            return searchForNullCoercion(targetType);
+        }
+
+        // These are instance variables because this method may be called concurrently.
+        // On a true race, we may go to the work of seeking out and/or fabricating
+        // a tuple twice, but it's more likely that different threads are looking
+        // for different source/target coercions.
+
+        Set<CoercionTuple> consideredTuples = CollectionFactory.newSet();
+        LinkedList<CoercionTuple> queue = CollectionFactory.newLinkedList();
+
+        seedQueue(sourceType, targetType, consideredTuples, queue);
+
+        while (!queue.isEmpty())
+        {
+            CoercionTuple tuple = queue.removeFirst();
+
+            // If the tuple results in a value type that is assignable to the desired target type,
+            // we're done! Later, we may add a concept of "cost" (i.e. number of steps) or
+            // "quality" (how close is the tuple target type to the desired target type). Cost
+            // is currently implicit, as compound tuples are stored deeper in the queue,
+            // so simpler coercions will be located earlier.
+
+            Class tupleTargetType = tuple.getTargetType();
+
+            if (targetType.isAssignableFrom(tupleTargetType))
+            {
+                return tuple.getCoercion();
+            }
+
+            // So .. this tuple doesn't get us directly to the target type.
+            // However, it *may* get us part of the way. Each of these
+            // represents a coercion from the source type to an intermediate type.
+            // Now we're going to look for conversions from the intermediate type
+            // to some other type.
+
+            queueIntermediates(sourceType, targetType, tuple, consideredTuples, queue);
+        }
+
+        // Not found anywhere. Identify the source and target type and a (sorted) list of
+        // all the known coercions.
+
+        throw new UnknownValueException(String.format("Could not find a coercion from type %s to type %s.",
+                sourceType.getName(), targetType.getName()), buildCoercionCatalog());
+    }
+
+    /**
+     * Coercion from null is special; we match based on the target type and its not a spanning
+     * search. In many cases, we
+     * return a pass-thru that leaves the value as null.
+     *
+     * @param targetType
+     *         desired type
+     * @return the coercion
+     */
+    private Coercion searchForNullCoercion(Class targetType)
+    {
+        List<CoercionTuple> tuples = getTuples(Void.class, targetType);
+
+        for (CoercionTuple tuple : tuples)
+        {
+            Class tupleTargetType = tuple.getTargetType();
+
+            if (targetType.equals(tupleTargetType))
+                return tuple.getCoercion();
+        }
+
+        // Typical case: no match, this coercion passes the null through
+        // as null.
+
+        return COERCION_NULL_TO_OBJECT;
+    }
+
+    /**
+     * Builds a string listing all the coercions configured for the type coercer, sorted
+     * alphabetically.
+     */
+    @SuppressWarnings("unchecked")
+    private AvailableValues buildCoercionCatalog()
+    {
+        List<CoercionTuple> masterList = CollectionFactory.newList();
+
+        for (List<CoercionTuple> list : sourceTypeToTuple.values())
+        {
+            masterList.addAll(list);
+        }
+
+        return new AvailableValues("Configured coercions", masterList);
+    }
+
+    /**
+     * Seeds the pool with the initial set of coercions for the given type.
+     */
+    private void seedQueue(Class sourceType, Class targetType, Set<CoercionTuple> consideredTuples,
+                           LinkedList<CoercionTuple> queue)
+    {
+        // Work from the source type up looking for tuples
+
+        for (Class c : new InheritanceSearch(sourceType))
+        {
+            List<CoercionTuple> tuples = getTuples(c, targetType);
+
+            if (tuples == null)
+            {
+                continue;
+            }
+
+            for (CoercionTuple tuple : tuples)
+            {
+                queue.addLast(tuple);
+                consideredTuples.add(tuple);
+            }
+
+            // Don't pull in Object -> type coercions when doing
+            // a search from null.
+
+            if (sourceType == Void.class)
+            {
+                return;
+            }
+        }
+    }
+
+    /**
+     * Creates and adds to the pool a new set of coercions based on an intermediate tuple. Adds
+     * compound coercion tuples
+     * to the end of the queue.
+     *
+     * @param sourceType
+     *         the source type of the coercion
+     * @param targetType
+     *         TODO
+     * @param intermediateTuple
+     *         a tuple that converts from the source type to some intermediate type (that is not
+     *         assignable to the target type)
+     * @param consideredTuples
+     *         set of tuples that have already been added to the pool (directly, or as a compound
+     *         coercion)
+     * @param queue
+     *         the work queue of tuples
+     */
+    @SuppressWarnings("unchecked")
+    private void queueIntermediates(Class sourceType, Class targetType, CoercionTuple intermediateTuple,
+                                    Set<CoercionTuple> consideredTuples, LinkedList<CoercionTuple> queue)
+    {
+        Class intermediateType = intermediateTuple.getTargetType();
+
+        for (Class c : new InheritanceSearch(intermediateType))
+        {
+            for (CoercionTuple tuple : getTuples(c, targetType))
+            {
+                if (consideredTuples.contains(tuple))
+                {
+                    continue;
+                }
+
+                Class newIntermediateType = tuple.getTargetType();
+
+                // If this tuple is for coercing from an intermediate type back towards our
+                // initial source type, then ignore it. This should only be an optimization,
+                // as branches that loop back towards the source type will
+                // eventually be considered and discarded.
+
+                if (sourceType.isAssignableFrom(newIntermediateType))
+                {
+                    continue;
+                }
+
+                // The intermediateTuple coercer gets from S --> I1 (an intermediate type).
+                // The current tuple's coercer gets us from I2 --> X. where I2 is assignable
+                // from I1 (i.e., I2 is a superclass/superinterface of I1) and X is a new
+                // intermediate type, hopefully closer to our eventual target type.
+
+                Coercion compoundCoercer = new CompoundCoercion(intermediateTuple.getCoercion(), tuple.getCoercion());
+
+                CoercionTuple compoundTuple = new CoercionTuple(sourceType, newIntermediateType, compoundCoercer, false);
+
+                // So, every tuple that is added to the queue can take as input the sourceType.
+                // The target type may be another intermediate type, or may be something
+                // assignable to the target type, which will bring the search to a successful
+                // conclusion.
+
+                queue.addLast(compoundTuple);
+                consideredTuples.add(tuple);
+            }
+        }
+    }
+
+    /**
+     * Returns a non-null list of the tuples from the source type.
+     *
+     * @param sourceType
+     *         used to locate tuples
+     * @param targetType
+     *         used to add synthetic tuples
+     * @return non-null list of tuples
+     */
+    private List<CoercionTuple> getTuples(Class sourceType, Class targetType)
+    {
+        List<CoercionTuple> tuples = sourceTypeToTuple.get(sourceType);
+
+        if (tuples == null)
+        {
+            tuples = Collections.emptyList();
+        }
+
+        // So, when we see String and an Enum type, we add an additional synthetic tuple to the end
+        // of the real list. This is the easiest way to accomplish this is a thread-safe and class-reloading
+        // safe way (i.e., what if the Enum is defined by a class loader that gets discarded?  Don't want to cause
+        // memory leaks by retaining an instance). In any case, there are edge cases where we may create
+        // the tuple unnecessarily (such as when an explicit string-to-enum coercion is part of the TypeCoercer
+        // configuration), but on the whole, this is cheap and works.
+
+        if (sourceType == String.class && Enum.class.isAssignableFrom(targetType))
+        {
+            tuples = extend(tuples, new CoercionTuple(sourceType, targetType, new StringToEnumCoercion(targetType)));
+        }
+
+        return tuples;
+    }
+
+    private static <T> List<T> extend(List<T> list, T extraValue)
+    {
+        return F.flow(list).append(extraValue).toList();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java
new file mode 100644
index 0000000..f1830a7
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InheritanceSearch.java
@@ -0,0 +1,159 @@
+// Copyright 2006 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import org.apache.tapestry5.plastic.PlasticUtils;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Set;
+
+/**
+ * Used to search from a particular class up the inheritance hierarchy of extended classes and implemented interfaces.
+ * <p/>
+ * The search starts with the initial class (provided in the constructor). It progresses up the inheritance chain, but
+ * skips java.lang.Object.
+ * <p/>
+ * Once classes are exhausted, the inheritance hierarchy is searched. This is a breadth-first search, rooted in the
+ * interfaces implemented by the initial class at its super classes.
+ * <p/>
+ * Once all interfaces are exhausted, java.lang.Object is returned (it is always returned last).
+ * <p/>
+ * Two minor tweak to normal inheritance rules: <ul> <li> Normally, the parent class of an <em>object</em> array is
+ * java.lang.Object, which is odd because FooService[] is assignable to Object[]. Thus, we tweak the search so that the
+ * effective super class of FooService[] is Object[]. <li> The "super class" of a primtive type is its <em>wrapper type</em>,
+ * with the exception of void, whose "super class" is left at its normal value (Object.class) </ul>
+ * <p/>
+ * This class implements the {@link Iterable} interface, so it can be used directly in a for loop: <code> for (Class
+ * search : new InheritanceSearch(startClass)) { ... } </code>
+ * <p/>
+ * This class is not thread-safe.
+ */
+public class InheritanceSearch implements Iterator<Class>, Iterable<Class>
+{
+    private Class searchClass;
+
+    private final Set<Class> addedInterfaces = CollectionFactory.newSet();
+
+    private final LinkedList<Class> interfaceQueue = CollectionFactory.newLinkedList();
+
+    private enum State
+    {
+        CLASS, INTERFACE, DONE
+    }
+
+    private State state;
+
+    public InheritanceSearch(Class searchClass)
+    {
+        this.searchClass = searchClass;
+
+        queueInterfaces(searchClass);
+
+        state = searchClass == Object.class ? State.INTERFACE : State.CLASS;
+    }
+
+    private void queueInterfaces(Class searchClass)
+    {
+        for (Class intf : searchClass.getInterfaces())
+        {
+            if (addedInterfaces.contains(intf)) continue;
+
+            interfaceQueue.addLast(intf);
+            addedInterfaces.add(intf);
+        }
+    }
+
+    @Override
+    public Iterator<Class> iterator()
+    {
+        return this;
+    }
+
+    @Override
+    public boolean hasNext()
+    {
+        return state != State.DONE;
+    }
+
+    @Override
+    public Class next()
+    {
+        switch (state)
+        {
+            case CLASS:
+
+                Class result = searchClass;
+
+                searchClass = parentOf(searchClass);
+
+                if (searchClass == null) state = State.INTERFACE;
+                else queueInterfaces(searchClass);
+
+                return result;
+
+            case INTERFACE:
+
+                if (interfaceQueue.isEmpty())
+                {
+                    state = State.DONE;
+                    return Object.class;
+                }
+
+                Class intf = interfaceQueue.removeFirst();
+
+                queueInterfaces(intf);
+
+                return intf;
+
+            default:
+                throw new IllegalStateException();
+        }
+
+    }
+
+    /**
+     * Returns the parent of the given class. Tweaks inheritance for object arrays. Returns null instead of
+     * Object.class.
+     */
+    private Class parentOf(Class clazz)
+    {
+        if (clazz != void.class && clazz.isPrimitive()) return PlasticUtils.toWrapperType(clazz);
+
+        if (clazz.isArray() && clazz != Object[].class)
+        {
+            Class componentType = clazz.getComponentType();
+
+            while (componentType.isArray()) componentType = componentType.getComponentType();
+
+            if (!componentType.isPrimitive()) return Object[].class;
+        }
+
+        Class parent = clazz.getSuperclass();
+
+        return parent != Object.class ? parent : null;
+    }
+
+    /**
+     * @throws UnsupportedOperationException
+     *         always
+     */
+    @Override
+    public void remove()
+    {
+        throw new UnsupportedOperationException();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java
new file mode 100644
index 0000000..3c391e0
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalCommonsUtils.java
@@ -0,0 +1,388 @@
+// Copyright 2006-2014 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry5.ioc.AnnotationProvider;
+import org.apache.tapestry5.ioc.Locatable;
+import org.apache.tapestry5.ioc.Location;
+import org.apache.tapestry5.ioc.Messages;
+import org.apache.tapestry5.ioc.internal.NullAnnotationProvider;
+
+/**
+ * Utility methods class for the Commons package.
+ */
+public class InternalCommonsUtils {
+
+	/**
+	 * @since 5.3
+	 */
+	public final static AnnotationProvider NULL_ANNOTATION_PROVIDER = new NullAnnotationProvider();
+	private static final Pattern NON_WORD_PATTERN = Pattern.compile("[^\\w]");
+
+    /**
+     * Adds a value to a specially organized map where the values are lists of objects. This somewhat simulates a map
+     * that allows multiple values for the same key.
+     *
+     * @param map
+     *         to store value into
+     * @param key
+     *         for which a value is added
+     * @param value
+     *         to add
+     * @param <K>
+     *         the type of key
+     * @param <V>
+     *         the type of the list
+     */
+    public static <K, V> void addToMapList(Map<K, List<V>> map, K key, V value)
+    {
+        List<V> list = map.get(key);
+
+        if (list == null)
+        {
+            list = CollectionFactory.newList();
+            map.put(key, list);
+        }
+
+        list.add(value);
+    }
+
+	/**
+	 * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not
+	 * convertable to a location.
+	 */
+	
+	public static Location locationOf(Object location)
+	{
+	    if (location == null)
+	        return null;
+	
+	    if (location instanceof Location)
+	        return (Location) location;
+	
+	    if (location instanceof Locatable)
+	        return ((Locatable) location).getLocation();
+	
+	    return null;
+	}
+
+	public static AnnotationProvider toAnnotationProvider(final Method element)
+	{
+	    if (element == null)
+	        return NULL_ANNOTATION_PROVIDER;
+	
+	    return new AnnotationProvider()
+	    {
+	        @Override
+	        public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+	        {
+	            return element.getAnnotation(annotationClass);
+	        }
+	    };
+	}
+
+	/**
+	 * Used to convert a property expression into a key that can be used to locate various resources (Blocks, messages,
+	 * etc.). Strips out any punctuation characters, leaving just words characters (letters, number and the
+	 * underscore).
+	 *
+	 * @param expression a property expression
+	 * @return the expression with punctuation removed
+	 */
+	public static String extractIdFromPropertyExpression(String expression)
+	{
+	    return replace(expression, NON_WORD_PATTERN, "");
+	}
+
+	public static String replace(String input, Pattern pattern, String replacement)
+	{
+	    return pattern.matcher(input).replaceAll(replacement);
+	}
+
+	/**
+	 * Looks for a label within the messages based on the id. If found, it is used, otherwise the name is converted to a
+	 * user presentable form.
+	 */
+	public static String defaultLabel(String id, Messages messages, String propertyExpression)
+	{
+	    String key = id + "-label";
+	
+	    if (messages.contains(key))
+	        return messages.get(key);
+	
+	    return toUserPresentable(extractIdFromPropertyExpression(InternalCommonsUtils.lastTerm(propertyExpression)));
+	}
+
+	/**
+	 * Capitalizes the string, and inserts a space before each upper case character (or sequence of upper case
+	 * characters). Thus "userId" becomes "User Id", etc. Also, converts underscore into space (and capitalizes the
+	 * following word), thus "user_id" also becomes "User Id".
+	 */
+	public static String toUserPresentable(String id)
+	{
+	    StringBuilder builder = new StringBuilder(id.length() * 2);
+	
+	    char[] chars = id.toCharArray();
+	    boolean postSpace = true;
+	    boolean upcaseNext = true;
+	
+	    for (char ch : chars)
+	    {
+	        if (upcaseNext)
+	        {
+	            builder.append(Character.toUpperCase(ch));
+	            upcaseNext = false;
+	
+	            continue;
+	        }
+	
+	        if (ch == '_')
+	        {
+	            builder.append(' ');
+	            upcaseNext = true;
+	            continue;
+	        }
+	
+	        boolean upperCase = Character.isUpperCase(ch);
+	
+	        if (upperCase && !postSpace)
+	            builder.append(' ');
+	
+	        builder.append(ch);
+	
+	        postSpace = upperCase;
+	    }
+	
+	    return builder.toString();
+	}
+
+	/**
+	 * @since 5.3
+	 */
+	public static AnnotationProvider toAnnotationProvider(final Class element)
+	{
+	    return new AnnotationProvider()
+	    {
+	        @Override
+	        public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+	        {
+	            return annotationClass.cast(element.getAnnotation(annotationClass));
+	        }
+	    };
+	}
+
+	/**
+	 * Pattern used to eliminate leading and trailing underscores and dollar signs.
+	 */
+	static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$",
+	        Pattern.CASE_INSENSITIVE);
+
+	/**
+	 * Converts a method to a user presentable string consisting of the containing class name, the method name, and the
+	 * short form of the parameter list (the class name of each parameter type, shorn of the package name portion).
+	 *
+	 * @param method
+	 * @return short string representation
+	 */
+	public static String asString(Method method)
+	{
+	    StringBuilder buffer = new StringBuilder();
+	
+	    buffer.append(method.getDeclaringClass().getName());
+	    buffer.append(".");
+	    buffer.append(method.getName());
+	    buffer.append("(");
+	
+	    for (int i = 0; i < method.getParameterTypes().length; i++)
+	    {
+	        if (i > 0)
+	            buffer.append(", ");
+	
+	        String name = method.getParameterTypes()[i].getSimpleName();
+	
+	        buffer.append(name);
+	    }
+	
+	    return buffer.append(")").toString();
+	}
+
+	/**
+	 * Strips leading "_" and "$" and trailing "_" from the name.
+	 */
+	public static String stripMemberName(String memberName)
+	{
+	    assert InternalCommonsUtils.isNonBlank(memberName);
+	    Matcher matcher = NAME_PATTERN.matcher(memberName);
+	
+	    if (!matcher.matches())
+	        throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName));
+	
+	    return matcher.group(1);
+	}
+
+	/**
+	 * Joins together some number of elements to form a comma separated list.
+	 */
+	public static String join(List elements)
+	{
+	    return InternalCommonsUtils.join(elements, ", ");
+	}
+
+	/**
+	 * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the
+	 * string "(blank)".
+	 *
+	 * @param elements
+	 *         objects to be joined together
+	 * @param separator
+	 *         used between elements when joining
+	 */
+	public static String join(List elements, String separator)
+	{
+	    switch (elements.size())
+	    {
+	        case 0:
+	            return "";
+	
+	        case 1:
+	            return elements.get(0).toString();
+	
+	        default:
+	
+	            StringBuilder buffer = new StringBuilder();
+	            boolean first = true;
+	
+	            for (Object o : elements)
+	            {
+	                if (!first)
+	                    buffer.append(separator);
+	
+	                String string = String.valueOf(o);
+	
+	                if (string.equals(""))
+	                    string = "(blank)";
+	
+	                buffer.append(string);
+	
+	                first = false;
+	            }
+	
+	            return buffer.toString();
+	    }
+	}
+
+	/**
+	 * Creates a sorted copy of the provided elements, then turns that into a comma separated list.
+	 *
+	 * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or
+	 *         empty
+	 */
+	public static String joinSorted(Collection elements)
+	{
+	    if (elements == null || elements.isEmpty())
+	        return "(none)";
+	
+	    List<String> list = CollectionFactory.newList();
+	
+	    for (Object o : elements)
+	        list.add(String.valueOf(o));
+	
+	    Collections.sort(list);
+	
+	    return join(list);
+	}
+
+	/**
+	 * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
+	 */
+	
+	public static boolean isBlank(String input)
+	{
+	    return input == null || input.length() == 0 || input.trim().length() == 0;
+	}
+
+	/**
+	 * Capitalizes a string, converting the first character to uppercase.
+	 */
+	public static String capitalize(String input)
+	{
+	    if (input.length() == 0)
+	        return input;
+	
+	    return input.substring(0, 1).toUpperCase() + input.substring(1);
+	}
+
+	public static boolean isNonBlank(String input)
+	{
+	    return !isBlank(input);
+	}
+
+	/**
+	 * Return true if the input string contains the marker for symbols that must be expanded.
+	 */
+	public static boolean containsSymbols(String input)
+	{
+	    return input.contains("${");
+	}
+
+	/**
+	 * Searches the string for the final period ('.') character and returns everything after that. The input string is
+	 * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property
+	 * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period
+	 * character.
+	 */
+	public static String lastTerm(String input)
+	{
+	    assert isNonBlank(input);
+	    int dotx = input.lastIndexOf('.');
+	
+	    if (dotx < 0)
+	        return input;
+	
+	    return input.substring(dotx + 1);
+	}
+
+	/**
+	 * Extracts the string keys from a map and returns them in sorted order. The keys are converted to strings.
+	 *
+	 * @param map
+	 *         the map to extract keys from (may be null)
+	 * @return the sorted keys, or the empty set if map is null
+	 */
+	
+	public static List<String> sortedKeys(Map map)
+	{
+	    if (map == null)
+	        return Collections.emptyList();
+	
+	    List<String> keys = CollectionFactory.newList();
+	
+	    for (Object o : map.keySet())
+	        keys.add(String.valueOf(o));
+	
+	    Collections.sort(keys);
+	
+	    return keys;
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java
deleted file mode 100644
index e345593..0000000
--- a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/InternalStringUtils.java
+++ /dev/null
@@ -1,203 +0,0 @@
-// Copyright 2006-2014 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc.internal.util;
-
-import java.lang.reflect.Method;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * String-related utilities used within various internal implementations of the Apache Tapestry subprojects.
- * Broken off Tapestry-IoC's InternalUtils.
- */
-@SuppressWarnings("all")
-public class InternalStringUtils
-{
-	
-    /**
-     * Pattern used to eliminate leading and trailing underscores and dollar signs.
-     */
-    private static final Pattern NAME_PATTERN = Pattern.compile("^[_|$]*([\\p{javaJavaIdentifierPart}]+?)[_|$]*$",
-            Pattern.CASE_INSENSITIVE);
-
-    /**
-     * Converts a method to a user presentable string consisting of the containing class name, the method name, and the
-     * short form of the parameter list (the class name of each parameter type, shorn of the package name portion).
-     *
-     * @param method
-     * @return short string representation
-     */
-    public static String asString(Method method)
-    {
-        StringBuilder buffer = new StringBuilder();
-
-        buffer.append(method.getDeclaringClass().getName());
-        buffer.append(".");
-        buffer.append(method.getName());
-        buffer.append("(");
-
-        for (int i = 0; i < method.getParameterTypes().length; i++)
-        {
-            if (i > 0)
-                buffer.append(", ");
-
-            String name = method.getParameterTypes()[i].getSimpleName();
-
-            buffer.append(name);
-        }
-
-        return buffer.append(")").toString();
-    }
-
-    /**
-     * Strips leading "_" and "$" and trailing "_" from the name.
-     */
-    public static String stripMemberName(String memberName)
-    {
-        assert isNonBlank(memberName);
-        Matcher matcher = NAME_PATTERN.matcher(memberName);
-
-        if (!matcher.matches())
-            throw new IllegalArgumentException(String.format("Input '%s' is not a valid Java identifier.", memberName));
-
-        return matcher.group(1);
-    }
-
-    /**
-     * Joins together some number of elements to form a comma separated list.
-     */
-    public static String join(List elements)
-    {
-        return join(elements, ", ");
-    }
-
-    /**
-     * Joins together some number of elements. If a value in the list is the empty string, it is replaced with the
-     * string "(blank)".
-     *
-     * @param elements
-     *         objects to be joined together
-     * @param separator
-     *         used between elements when joining
-     */
-    public static String join(List elements, String separator)
-    {
-        switch (elements.size())
-        {
-            case 0:
-                return "";
-
-            case 1:
-                return elements.get(0).toString();
-
-            default:
-
-                StringBuilder buffer = new StringBuilder();
-                boolean first = true;
-
-                for (Object o : elements)
-                {
-                    if (!first)
-                        buffer.append(separator);
-
-                    String string = String.valueOf(o);
-
-                    if (string.equals(""))
-                        string = "(blank)";
-
-                    buffer.append(string);
-
-                    first = false;
-                }
-
-                return buffer.toString();
-        }
-    }
-
-    /**
-     * Creates a sorted copy of the provided elements, then turns that into a comma separated list.
-     *
-     * @return the elements converted to strings, sorted, joined with comma ... or "(none)" if the elements are null or
-     *         empty
-     */
-    public static String joinSorted(Collection elements)
-    {
-        if (elements == null || elements.isEmpty())
-            return "(none)";
-
-        List<String> list = CollectionFactory.newList();
-
-        for (Object o : elements)
-            list.add(String.valueOf(o));
-
-        Collections.sort(list);
-
-        return join(list);
-    }
-
-    /**
-     * Returns true if the input is null, or is a zero length string (excluding leading/trailing whitespace).
-     */
-
-    public static boolean isBlank(String input)
-    {
-        return input == null || input.length() == 0 || input.trim().length() == 0;
-    }
-
-    public static boolean isNonBlank(String input)
-    {
-        return !isBlank(input);
-    }
-
-    /**
-     * Capitalizes a string, converting the first character to uppercase.
-     */
-    public static String capitalize(String input)
-    {
-        if (input.length() == 0)
-            return input;
-
-        return input.substring(0, 1).toUpperCase() + input.substring(1);
-    }
-
-    /**
-     * Return true if the input string contains the marker for symbols that must be expanded.
-     */
-    public static boolean containsSymbols(String input)
-    {
-        return input.contains("${");
-    }
-
-    /**
-     * Searches the string for the final period ('.') character and returns everything after that. The input string is
-     * generally a fully qualified class name, though tapestry-core also uses this method for the occasional property
-     * expression (which is also dot separated). Returns the input string unchanged if it does not contain a period
-     * character.
-     */
-    public static String lastTerm(String input)
-    {
-        assert isNonBlank(input);
-        int dotx = input.lastIndexOf('.');
-
-        if (dotx < 0)
-            return input;
-
-        return input.substring(dotx + 1);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java
new file mode 100644
index 0000000..41de363
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/LockSupport.java
@@ -0,0 +1,89 @@
+// Copyright 2012 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Base class for classes that need to manage a ReadWriteLock.
+ */
+public abstract class LockSupport
+{
+    private final Lock readLock, writeLock;
+
+    protected LockSupport()
+    {
+        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
+
+        readLock = lock.readLock();
+        writeLock = lock.writeLock();
+    }
+
+    /**
+     * Locks the shared read lock. Any number of threads may lock the read lock at the same time.
+     */
+    protected final void acquireReadLock()
+    {
+        readLock.lock();
+    }
+
+    /**
+     * Takes the exclusive write lock. Once started, no other thread lock the read or write lock. When this method returns,
+     * this thread will have locked the write lock and no other thread will have either the read or write lock.
+     * Note that this thread must first drop the read lock (if it has it) before attempting to take the write lock, or this method will block forever.
+     */
+    protected final void takeWriteLock()
+    {
+        writeLock.lock();
+    }
+
+    /**
+     * Releases the shared read lock.
+     */
+    protected final void releaseReadLock()
+    {
+        readLock.unlock();
+    }
+
+    /**
+     * Releases the  exclusive read lock.
+     */
+    protected final void releaseWriteLock()
+    {
+        writeLock.unlock();
+    }
+
+    /**
+     * Releases the read lock, then takes the write lock. There's a short window where the thread will have neither lock:
+     * during that window, some other thread may have a chance to take the write lock. In code, you'll often see a second check
+     * inside the code that has the write lock to see if the update to perform is still necessary.
+     */
+    protected final void upgradeReadLockToWriteLock()
+    {
+        releaseReadLock();
+        // This is that instant where another thread may grab the write lock. Very rare, but possible.
+        takeWriteLock();
+    }
+
+    /**
+     * Takes the read lock then releases the write lock.
+     */
+    protected final void downgradeWriteLockToReadLock()
+    {
+        acquireReadLock();
+        releaseWriteLock();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java
new file mode 100644
index 0000000..f4d8949
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessageFormatterImpl.java
@@ -0,0 +1,65 @@
+// Copyright 2006-2013 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import org.apache.tapestry5.ioc.MessageFormatter;
+import org.apache.tapestry5.ioc.util.ExceptionUtils;
+
+import java.util.Locale;
+
+
+public class MessageFormatterImpl implements MessageFormatter
+{
+    private final String format;
+
+    private final Locale locale;
+
+    public MessageFormatterImpl(String format, Locale locale)
+    {
+        this.format = format;
+        this.locale = locale;
+    }
+
+    @Override
+    public String format(Object... args)
+    {
+        for (int i = 0; i < args.length; i++)
+        {
+            Object arg = args[i];
+
+            if (Throwable.class.isInstance(arg))
+            {
+                args[i] = ExceptionUtils.toMessage((Throwable) arg);
+            }
+        }
+
+        // Might be tempting to create a Formatter object and just keep reusing it ... but
+        // Formatters are not threadsafe.
+
+        return String.format(locale, format, args);
+    }
+
+    /**
+     * Returns the underlying format string for this formatter.
+     *
+     * @since 5.4
+     */
+    @Override
+    public String toString()
+    {
+        return format;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java
new file mode 100644
index 0000000..c06ef90
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/MessagesImpl.java
@@ -0,0 +1,74 @@
+// Copyright 2006, 2012 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.internal.util;
+
+import org.apache.tapestry5.ioc.Messages;
+import org.apache.tapestry5.ioc.util.AbstractMessages;
+
+import java.util.*;
+
+/**
+ * Implementation of {@link org.apache.tapestry5.ioc.Messages} based around a {@link java.util.ResourceBundle}.
+ */
+public class MessagesImpl extends AbstractMessages
+{
+    private final Map<String, String> properties = CollectionFactory.newCaseInsensitiveMap();
+
+    /**
+     * Finds the messages for a given Messages utility class. Strings the trailing "Messages" and replaces it with
+     * "Strings" to form the base path. Loads the bundle using the default locale, and the class' class loader.
+     *
+     * @param forClass
+     * @return Messages for the class
+     */
+    public static Messages forClass(Class forClass)
+    {
+        String className = forClass.getName();
+        String stringsClassName = className.replaceAll("Messages$", "Strings");
+
+        Locale locale = Locale.getDefault();
+
+        ResourceBundle bundle = ResourceBundle.getBundle(stringsClassName, locale, forClass.getClassLoader());
+
+        return new MessagesImpl(locale, bundle);
+    }
+
+    public MessagesImpl(Locale locale, ResourceBundle bundle)
+    {
+        super(locale);
+
+        // Our best (threadsafe) chance to determine all the available keys.
+        Enumeration<String> e = bundle.getKeys();
+        while (e.hasMoreElements())
+        {
+            String key = e.nextElement();
+            String value = bundle.getString(key);
+
+            properties.put(key, value);
+        }
+    }
+
+    @Override
+    protected String valueForKey(String key)
+    {
+        return properties.get(key);
+    }
+
+    @Override
+    public Set<String> getKeys()
+    {
+        return properties.keySet();
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
index d8d8018..6e23c5b 100644
--- a/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/internal/util/TapestryException.java
@@ -34,7 +34,7 @@ public class TapestryException extends RuntimeException implements Locatable
      */
     public TapestryException(String message, Object location, Throwable cause)
     {
-        this(message, locationOf(location), cause);
+        this(message, InternalCommonsUtils.locationOf(location), cause);
     }
 
     /**
@@ -71,26 +71,5 @@ public class TapestryException extends RuntimeException implements Locatable
 
         return String.format("%s [at %s]", super.toString(), location);
     }
-    
-    /**
-     * Sniffs the object to see if it is a {@link Location} or {@link Locatable}. Returns null if null or not
-     * convertable to a location.
-     * Copied from InternalUtils to avoid having it moved to BeanModel or Commons subprojects.
-     */
-
-    private static Location locationOf(Object location)
-    {
-        if (location == null)
-            return null;
-
-        if (location instanceof Location)
-            return (Location) location;
-
-        if (location instanceof Locatable)
-            return ((Locatable) location).getLocation();
-
-        return null;
-    }
-
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java
new file mode 100644
index 0000000..590c337
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/AbstractMessages.java
@@ -0,0 +1,94 @@
+// Copyright 2006, 2009, 2011 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.util;
+
+import org.apache.tapestry5.ioc.MessageFormatter;
+import org.apache.tapestry5.ioc.Messages;
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.MessageFormatterImpl;
+
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Abstract implementation of {@link Messages} that doesn't know where values come from (that information is supplied in
+ * a subclass, via the {@link #valueForKey(String)} method).
+ */
+public abstract class AbstractMessages implements Messages
+{
+    /**
+     * String key to MF instance.
+     */
+    private final Map<String, MessageFormatter> cache = CollectionFactory.newConcurrentMap();
+
+    private final Locale locale;
+
+    protected AbstractMessages(Locale locale)
+    {
+        this.locale = locale;
+    }
+
+    /**
+     * Invoked to provide the value for a particular key. This may be invoked multiple times even for the same key. The
+     * implementation should <em>ignore the case of the key</em>.
+     *
+     * @param key the key to obtain a value for (case insensitive)
+     * @return the value for the key, or null if this instance can not provide the value
+     */
+    protected abstract String valueForKey(String key);
+
+
+    @Override
+    public boolean contains(String key)
+    {
+        return valueForKey(key) != null;
+    }
+
+    @Override
+    public String get(String key)
+    {
+        if (contains(key)) return valueForKey(key);
+
+        return String.format("[[missing key: %s]]", key);
+    }
+
+    @Override
+    public MessageFormatter getFormatter(String key)
+    {
+        MessageFormatter result = cache.get(key);
+
+        if (result == null)
+        {
+            result = buildMessageFormatter(key);
+            cache.put(key, result);
+        }
+
+        return result;
+    }
+
+    private MessageFormatter buildMessageFormatter(String key)
+    {
+        String format = get(key);
+
+        return new MessageFormatterImpl(format, locale);
+    }
+
+    @Override
+    public String format(String key, Object... args)
+    {
+        return getFormatter(key).format(args);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
index 8c9cb3f..b2f1386 100644
--- a/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/AvailableValues.java
@@ -20,7 +20,7 @@ import java.util.List;
 import java.util.Map;
 
 import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.ioc.internal.util.InternalStringUtils;
+import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils;
 
 /**
  * Used (as part of a {@link UnknownValueException} to identify what available values
@@ -81,7 +81,7 @@ public class AvailableValues
     @Override
     public String toString()
     {
-        return String.format("AvailableValues[%s: %s]", valueType, InternalStringUtils.join(values));
+        return String.format("AvailableValues[%s: %s]", valueType, InternalCommonsUtils.join(values));
     }
 
 }

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java b/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java
new file mode 100644
index 0000000..01ef99e
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/ioc/util/TimeInterval.java
@@ -0,0 +1,195 @@
+// Copyright 2007, 2008, 2010, 2011 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.ioc.util;
+
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils;
+
+/**
+ * Used to represent a period of time, specifically as a configuration value. This is often used to specify timeouts.
+ * <p/>
+ * TimePeriods are parsed from strings.
+ * <p/>
+ * The string specifys a number of terms. The values of all the terms are summed together to form the total time period.
+ * Each term consists of a number followed by a unit. Units (from largest to smallest) are: <dl> <dt>y <dd>year <dt>d
+ * <dd>day <dt>h <dd>hour <dt>m <dd>minute <dt>s <dd>second <dt>ms <dd>millisecond </dl> <p>  Example: "2 h 30 m". By
+ * convention, terms are specified largest to smallest.  A term without a unit is assumed to be milliseconds.  Units are
+ * case insensitive ("h" or "H" are treated the same).
+ */
+public class TimeInterval
+{
+    private static final Map<String, Long> UNITS = CollectionFactory.newCaseInsensitiveMap();
+
+    private static final long MILLISECOND = 1000l;
+
+    static
+    {
+        UNITS.put("ms", 1l);
+        UNITS.put("s", MILLISECOND);
+        UNITS.put("m", 60 * MILLISECOND);
+        UNITS.put("h", 60 * UNITS.get("m"));
+        UNITS.put("d", 24 * UNITS.get("h"));
+        UNITS.put("y", 365 * UNITS.get("d"));
+    }
+
+    /**
+     * The unit keys, sorted in descending order.
+     */
+    private static final String[] UNIT_KEYS =
+    { "y", "d", "h", "m", "s", "ms" };
+
+    private static final Pattern PATTERN = Pattern.compile("\\s*(\\d+)\\s*([a-z]*)", Pattern.CASE_INSENSITIVE);
+
+    private final long milliseconds;
+
+    /**
+     * Creates a TimeInterval for a string.
+     * 
+     * @param input
+     *            the string specifying the amount of time in the period
+     */
+    public TimeInterval(String input)
+    {
+        this(parseMilliseconds(input));
+    }
+
+    public TimeInterval(long milliseconds)
+    {
+        this.milliseconds = milliseconds;
+    }
+
+    public long milliseconds()
+    {
+        return milliseconds;
+    }
+
+    public long seconds()
+    {
+        return milliseconds / MILLISECOND;
+    }
+
+    /**
+     * Converts the milliseconds back into a string (compatible with {@link #TimeInterval(String)}).
+     * 
+     * @since 5.2.0
+     */
+    public String toDescription()
+    {
+        StringBuilder builder = new StringBuilder();
+
+        String sep = "";
+
+        long remainder = milliseconds;
+
+        for (String key : UNIT_KEYS)
+        {
+            if (remainder == 0)
+                break;
+
+            long value = UNITS.get(key);
+
+            long units = remainder / value;
+
+            if (units > 0)
+            {
+                builder.append(sep);
+                builder.append(units);
+                builder.append(key);
+
+                sep = " ";
+
+                remainder = remainder % value;
+            }
+        }
+
+        return builder.toString();
+    }
+
+    static long parseMilliseconds(String input)
+    {
+        long milliseconds = 0l;
+
+        Matcher matcher = PATTERN.matcher(input);
+
+        matcher.useAnchoringBounds(true);
+
+        // TODO: Notice non matching characters and reject input, including at end
+
+        int lastMatchEnd = -1;
+
+        while (matcher.find())
+        {
+            int start = matcher.start();
+
+            if (lastMatchEnd + 1 < start)
+            {
+                String invalid = input.substring(lastMatchEnd + 1, start);
+                throw new RuntimeException(String.format("Unexpected string '%s' (in time interval '%s').", invalid, input));
+            }
+
+            lastMatchEnd = matcher.end();
+
+            long count = Long.parseLong(matcher.group(1));
+            String units = matcher.group(2);
+
+            if (units.length() == 0)
+            {
+                milliseconds += count;
+                continue;
+            }
+
+            Long unitValue = UNITS.get(units);
+
+            if (unitValue == null)
+                throw new RuntimeException(String.format("Unknown time interval unit '%s' (in '%s').  Defined units: %s.", units, input, InternalCommonsUtils.joinSorted(UNITS.keySet())));
+
+            milliseconds += count * unitValue;
+        }
+
+        if (lastMatchEnd + 1 < input.length())
+        {
+            String invalid = input.substring(lastMatchEnd + 1);
+            throw new RuntimeException(String.format("Unexpected string '%s' (in time interval '%s').", invalid, input));
+        }
+
+        return milliseconds;
+    }
+
+    @Override
+    public String toString()
+    {
+        return String.format("TimeInterval[%d ms]", milliseconds);
+    }
+
+    @Override
+    public boolean equals(Object obj)
+    {
+        if (obj == null)
+            return false;
+
+        if (obj instanceof TimeInterval)
+        {
+            TimeInterval tp = (TimeInterval) obj;
+
+            return milliseconds == tp.milliseconds;
+        }
+
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java
----------------------------------------------------------------------
diff --git a/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java b/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java
new file mode 100644
index 0000000..bdde866
--- /dev/null
+++ b/commons/src/main/java/org/apache/tapestry5/util/StringToEnumCoercion.java
@@ -0,0 +1,91 @@
+// Copyright 2007, 2008, 2010, 2011 The Apache Software Foundation
+//
+// Licensed 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.tapestry5.util;
+
+import java.util.Map;
+
+import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
+import org.apache.tapestry5.ioc.internal.util.InternalCommonsUtils;
+import org.apache.tapestry5.ioc.services.Coercion;
+import org.apache.tapestry5.ioc.util.AvailableValues;
+import org.apache.tapestry5.ioc.util.UnknownValueException;
+
+/**
+ * A {@link org.apache.tapestry5.ioc.services.Coercion} for converting strings into an instance of a particular
+ * enumerated type. The {@link Enum#name() name} is used as the key to identify the enum instance, in a case-insensitive
+ * fashion.
+ * <p>
+ * Moved from tapestry-core to tapestry-ioc is release 5.3, but kept in same package for compatibility.
+ * 
+ * @param <T>
+ *            the type of enumeration
+ */
+public final class StringToEnumCoercion<T extends Enum> implements Coercion<String, T>
+{
+    private final Class<T> enumClass;
+
+    private final Map<String, T> stringToEnum = CollectionFactory.newCaseInsensitiveMap();
+
+    public StringToEnumCoercion(Class<T> enumClass)
+    {
+        this(enumClass, enumClass.getEnumConstants());
+    }
+
+    public StringToEnumCoercion(Class<T> enumClass, T... values)
+    {
+        this.enumClass = enumClass;
+
+        for (T value : values)
+            stringToEnum.put(value.name(), value);
+    }
+
+    @Override
+    public T coerce(String input)
+    {
+        if (InternalCommonsUtils.isBlank(input))
+            return null;
+
+        T result = stringToEnum.get(input);
+
+        if (result == null)
+        {
+            String message = String.format("Input '%s' does not identify a value from enumerated type %s.", input,
+                    enumClass.getName());
+
+            throw new UnknownValueException(message, new AvailableValues(enumClass.getName() + " enum constants",
+                    stringToEnum));
+        }
+
+        return result;
+    }
+
+    /**
+     * Allows an alias value (alternate) string to reference a value.
+     * 
+     * @since 5.2.2
+     */
+    public StringToEnumCoercion<T> addAlias(String alias, T value)
+    {
+        stringToEnum.put(alias, value);
+
+        return this;
+    }
+
+    public static <T extends Enum> StringToEnumCoercion<T> create(Class<T> enumClass)
+    {
+        return new StringToEnumCoercion<T>(enumClass);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
index e69377f..e032975 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/TapestryInternalUtils.java
@@ -59,7 +59,7 @@ public class TapestryInternalUtils
      */
     public static String toUserPresentable(String id)
     {
-        return InternalBeanModelUtils.toUserPresentable(id);
+        return InternalUtils.toUserPresentable(id);
     }
 
     public static Map<String, String> mapFromKeysAndValues(String... keysAndValues)
@@ -228,7 +228,7 @@ public class TapestryInternalUtils
      */
     public static String extractIdFromPropertyExpression(String expression)
     {
-        return InternalBeanModelUtils.extractIdFromPropertyExpression(expression);
+        return InternalUtils.extractIdFromPropertyExpression(expression);
     }
 
     /**
@@ -237,7 +237,7 @@ public class TapestryInternalUtils
      */
     public static String defaultLabel(String id, Messages messages, String propertyExpression)
     {
-        return InternalBeanModelUtils.defaultLabel(id, messages, propertyExpression);
+        return InternalUtils.defaultLabel(id, messages, propertyExpression);
     }
 
     /**
@@ -304,7 +304,7 @@ public class TapestryInternalUtils
 
     private static String replace(String input, Pattern pattern, String replacement)
     {
-        return InternalBeanModelUtils.replace(input, pattern, replacement);
+        return InternalUtils.replace(input, pattern, replacement);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java
deleted file mode 100644
index 43519dc..0000000
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/StringInternerImpl.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2009, 2012 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.internal.services;
-
-import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
-import org.apache.tapestry5.services.ComponentClasses;
-import org.apache.tapestry5.services.InvalidationEventHub;
-
-import javax.annotation.PostConstruct;
-import java.util.Map;
-
-public class StringInternerImpl implements StringInterner
-{
-    private final Map<String, String> cache = CollectionFactory.newConcurrentMap();
-
-    @PostConstruct
-    public void setupInvalidation(@ComponentClasses InvalidationEventHub hub)
-    {
-        hub.clearOnInvalidation(cache);
-    }
-
-    public String intern(String string)
-    {
-        String result = cache.get(string);
-
-        // Not yet in the cache?  Add it.
-
-        if (result == null)
-        {
-            cache.put(string, string);
-            result = string;
-        }
-
-        return result;
-    }
-
-    public String format(String format, Object... arguments)
-    {
-        return intern(String.format(format, arguments));
-    }
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java
deleted file mode 100644
index 03814f5..0000000
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/Configuration.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright 2006, 2008, 2009 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc;
-
-/**
- * Object passed into a service contributor method that allows the method provide contributed values to the service's
- * configuration.
- * <p/>
- * A service can <em>collect</em> contributions in three different ways:
- * <ul>
- * <li>As an un-ordered collection of values</li>
- * <li>As an ordered list of values (where each value has a unique id, pre-requisites and post-requisites)</li>
- * <li>As a map of keys and values
- * </ul>
- * <p/>
- * This implementation is used for un-ordered configuration data.
- * <p/>
- * The service defines the <em>type</em> of contribution, in terms of a base class or service interface. Contributions
- * must be compatible with the type.
- */
-public interface Configuration<T>
-{
-    /**
-     * Adds an object to the service's contribution.
-     * 
-     * @param object
-     *            to add to the service's configuration
-     */
-    void add(T object);
-
-    /**
-     * Automatically instantiates an instance of the class, with dependencies injected, and adds it to the
-     * configuration. When the configuration type is an interface and the class to be contributed is a local file,
-     * then a reloadable proxy for the class will be created and contributed.
-     * 
-     * @param clazz
-     *            what class to instantiate
-     * @since 5.1.0.0
-     */
-    void addInstance(Class<? extends T> clazz);
-}

http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/c7bf35ce/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java b/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java
deleted file mode 100644
index 47c6026..0000000
--- a/tapestry-ioc/src/main/java/org/apache/tapestry5/ioc/MappedConfiguration.java
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright 2006, 2008, 2009, 2010 The Apache Software Foundation
-//
-// Licensed 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.tapestry5.ioc;
-
-/**
- * Object passed into a service contributor method that allows the method provide contributed values to the service's
- * configuration.
- * <p/>
- * A service can <em>collect</em> contributions in three different ways:
- * <ul>
- * <li>As an un-ordered collection of values</li>
- * <li>As an ordered list of values (where each value has a unique id, pre-requisites and post-requisites)</li>
- * <li>As a map of keys and values
- * </ul>
- * <p/>
- * The service defines the <em>type</em> of contribution, in terms of a base class or service interface. Contributions
- * must be compatible with the type.
- */
-public interface MappedConfiguration<K, V>
-{
-
-    /**
-     * Adds a keyed object to the service's contribution.
-     * 
-     * @param key
-     *            unique id for the value
-     * @param value
-     *            to contribute
-     * @throws IllegalArgumentException
-     *             if key is not unique
-     */
-    void add(K key, V value);
-
-    /**
-     * Overrides an existing contribution by its key.
-     * 
-     * @param key
-     *            unique id of value to override
-     * @param value
-     *            new value, or null to remove the key entirely
-     * @since 5.1.0.0
-     */
-    void override(K key, V value);
-
-    /**
-     * Adds a keyed object as an instantiated instance (with dependencies injected) of a class. When the value
-     * type is an interface and the class to be contributed is a local file,
-     * then a reloadable proxy for the value class will be created and contributed.
-     * 
-     * @param key
-     *            unique id for the value
-     * @param clazz
-     *            class to instantiate and contribute
-     * @since 5.1.0.0
-     */
-    void addInstance(K key, Class<? extends V> clazz);
-
-    /**
-     * Overrides an existing contribution with a new instance. When the value
-     * type is an interface and the class to be contributed is a local file,
-     * then a reloadable proxy for the value class will be created and contributed.
-     * 
-     * @param key
-     *            unique id of value to override
-     * @param clazz
-     *            class to instantiate as override
-     */
-    void overrideInstance(K key, Class<? extends V> clazz);
-}