You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by lu...@apache.org on 2015/06/17 23:09:43 UTC

[43/57] [partial] struts git commit: Merges xwork packages into struts

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
new file mode 100644
index 0000000..69e78b7
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkBasicConverter.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright 2002-2007,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 com.opensymphony.xwork2.conversion.impl;
+
+import com.opensymphony.xwork2.XWorkConstants;
+import com.opensymphony.xwork2.XWorkException;
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.lang.reflect.Member;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Map;
+
+
+/**
+ * <!-- START SNIPPET: javadoc -->
+ * <p/>
+ * XWork will automatically handle the most common type conversion for you. This includes support for converting to
+ * and from Strings for each of the following:
+ * <p/>
+ * <ul>
+ * <li>String</li>
+ * <li>boolean / Boolean</li>
+ * <li>char / Character</li>
+ * <li>int / Integer, float / Float, long / Long, double / Double</li>
+ * <li>dates - uses the SHORT format for the Locale associated with the current request</li>
+ * <li>arrays - assuming the individual strings can be coverted to the individual items</li>
+ * <li>collections - if not object type can be determined, it is assumed to be a String and a new ArrayList is
+ * created</li>
+ * </ul>
+ * <p/> Note that with arrays the type conversion will defer to the type of the array elements and try to convert each
+ * item individually. As with any other type conversion, if the conversion can't be performed the standard type
+ * conversion error reporting is used to indicate a problem occurred while processing the type conversion.
+ * <!-- END SNIPPET: javadoc -->
+ *
+ * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
+ * @author Mike Mosiewicz
+ * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ */
+public class XWorkBasicConverter extends DefaultTypeConverter {
+
+    private Container container;
+
+    @Inject
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+
+    @Override
+    public Object convertValue(Map<String, Object> context, Object o, Member member, String propertyName, Object value, Class toType) {
+        Object result = null;
+
+        if (value == null || toType.isAssignableFrom(value.getClass())) {
+            // no need to convert at all, right?
+            return value;
+        }
+
+        if (toType == String.class) {
+            /* the code below has been disabled as it causes sideffects in Struts2 (XW-512)
+            // if input (value) is a number then use special conversion method (XW-490)
+            Class inputType = value.getClass();
+            if (Number.class.isAssignableFrom(inputType)) {
+                result = doConvertFromNumberToString(context, value, inputType);
+                if (result != null) {
+                    return result;
+                }
+            }*/
+            // okay use default string conversion
+            result = doConvertToString(context, value);
+        } else if (toType == boolean.class) {
+            result = doConvertToBoolean(value);
+        } else if (toType == Boolean.class) {
+            result = doConvertToBoolean(value);
+        } else if (toType.isArray()) {
+            result = doConvertToArray(context, o, member, propertyName, value, toType);
+        } else if (Date.class.isAssignableFrom(toType)) {
+            result = doConvertToDate(context, value, toType);
+        } else if (Calendar.class.isAssignableFrom(toType)) {
+            result = doConvertToCalendar(context, value);
+        } else if (Collection.class.isAssignableFrom(toType)) {
+            result = doConvertToCollection(context, o, member, propertyName, value, toType);
+        } else if (toType == Character.class) {
+            result = doConvertToCharacter(value);
+        } else if (toType == char.class) {
+            result = doConvertToCharacter(value);
+        } else if (Number.class.isAssignableFrom(toType) || toType.isPrimitive()) {
+            result = doConvertToNumber(context, value, toType);
+        } else if (toType == Class.class) {
+            result = doConvertToClass(value);
+        }
+
+        if (result == null) {
+            if (value instanceof Object[]) {
+                Object[] array = (Object[]) value;
+
+                if (array.length >= 1) {
+                    value = array[0];
+                } else {
+                    value = null;
+                }
+
+                // let's try to convert the first element only
+                result = convertValue(context, o, member, propertyName, value, toType);
+            } else if (!"".equals(value)) { // we've already tried the types we know
+                result = super.convertValue(context, value, toType);
+            }
+
+            if (result == null && value != null && !"".equals(value)) {
+                throw new XWorkException("Cannot create type " + toType + " from value " + value);
+            }
+        }
+
+        return result;
+    }
+
+    private Object doConvertToCalendar(Map<String, Object> context, Object value) {
+        Object result = null;
+        Date dateResult = (Date) doConvertToDate(context, value, Date.class);
+        if (dateResult != null) {
+            Calendar calendar = Calendar.getInstance();
+            calendar.setTime(dateResult);
+            result = calendar;
+        }
+        return result;
+    }
+
+    private Object doConvertToCharacter(Object value) {
+        if (value instanceof String) {
+            String cStr = (String) value;
+            return (cStr.length() > 0) ? cStr.charAt(0) : null;
+        }
+        return null;
+    }
+
+    private Object doConvertToBoolean(Object value) {
+        if (value instanceof String) {
+            String bStr = (String) value;
+            return Boolean.valueOf(bStr);
+        }
+        return null;
+    }
+
+    private Class doConvertToClass(Object value) {
+        Class clazz = null;
+        if (value != null && value instanceof String && ((String) value).length() > 0) {
+            try {
+                clazz = Class.forName((String) value);
+            } catch (ClassNotFoundException e) {
+                throw new XWorkException(e.getLocalizedMessage(), e);
+            }
+        }
+        return clazz;
+    }
+
+    private Object doConvertToCollection(Map<String, Object> context, Object o, Member member, String prop, Object value, Class toType) {
+        TypeConverter converter = container.getInstance(CollectionConverter.class);
+        if (converter == null) {
+            throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.COLLECTION_CONVERTER);
+        }
+        return converter.convertValue(context, o, member, prop, value, toType);
+    }
+
+    private Object doConvertToArray(Map<String, Object> context, Object o, Member member, String prop, Object value, Class toType) {
+        TypeConverter converter = container.getInstance(ArrayConverter.class);
+        if (converter == null) {
+            throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.ARRAY_CONVERTER);
+        }
+        return converter.convertValue(context, o, member, prop, value, toType);
+    }
+
+    private Object doConvertToDate(Map<String, Object> context, Object value, Class toType) {
+        TypeConverter converter = container.getInstance(DateConverter.class);
+        if (converter == null) {
+            throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.DATE_CONVERTER);
+        }
+        return converter.convertValue(context, null, null, null, value, toType);
+    }
+
+    private Object doConvertToNumber(Map<String, Object> context, Object value, Class toType) {
+        TypeConverter converter = container.getInstance(NumberConverter.class);
+        if (converter == null) {
+            throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.NUMBER_CONVERTER);
+        }
+        return converter.convertValue(context, null, null, null, value, toType);
+    }
+
+    private Object doConvertToString(Map<String, Object> context, Object value) {
+        TypeConverter converter = container.getInstance(StringConverter.class);
+        if (converter == null) {
+            throw new XWorkException("TypeConverter with name [#0] must be registered first!", XWorkConstants.STRING_CONVERTER);
+        }
+        return converter.convertValue(context, null, null, null, value, null);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
new file mode 100644
index 0000000..c0086ee
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/XWorkConverter.java
@@ -0,0 +1,595 @@
+/*
+ * Copyright 2002-2006,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 com.opensymphony.xwork2.conversion.impl;
+
+import com.opensymphony.xwork2.*;
+import com.opensymphony.xwork2.conversion.*;
+import com.opensymphony.xwork2.conversion.annotations.Conversion;
+import com.opensymphony.xwork2.conversion.annotations.TypeConversion;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.*;
+import com.opensymphony.xwork2.util.reflection.ReflectionContextState;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * XWorkConverter is a singleton used by many of the Struts 2's Ognl extention points,
+ * such as InstantiatingNullHandler, XWorkListPropertyAccessor etc to do object
+ * conversion.
+ * <p/>
+ * <!-- START SNIPPET: javadoc -->
+ * <p/>
+ * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web
+ * is type-agnostic (everything is a string in HTTP), Struts 2's type conversion features are very useful. For instance,
+ * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have
+ * Struts 2 do the conversion both from String to Point and from Point to String.
+ * <p/>
+ * <p/> Using this "point" example, if your action (or another compound object in which you are setting properties on)
+ * has a corresponding ClassName-conversion.properties file, Struts 2 will use the configured type converters for
+ * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following
+ * entry to <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter
+ * interface):
+ * <p/>
+ * <p/><b>point = com.acme.PointConverter</b>
+ * <p/>
+ * <p/> Your type converter should be sure to check what class type it is being requested to convert. Because it is used
+ * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in
+ * to Points, and one that turns Points in to Strings.
+ * <p/>
+ * <p/> After this is done, you can now reference your point (using &lt;s:property value="point"/&gt; in JSP or ${point}
+ * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be
+ * converted back to a Point once again.
+ * <p/>
+ * <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file
+ * <b>xwork-conversion.properties</b> in the root of your class path (typically WEB-INF/classes) and providing a
+ * property in the form of the class name of the object you wish to convert on the left hand side and the class name of
+ * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean
+ * adding the following entry:
+ * <p/>
+ * <p/><b>com.acme.Point = com.acme.PointConverter</b>
+ * <p/>
+ * <!-- END SNIPPET: javadoc -->
+ * <p/>
+ * <p/>
+ * <p/>
+ * <!-- START SNIPPET: i18n-note -->
+ * <p/>
+ * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out
+ * properly formatted dates. Rather, you should use the i18n features of Struts 2 (and consult the JavaDocs for JDK's
+ * MessageFormat object) to see how a properly formatted date should be displayed.
+ * <p/>
+ * <!-- END SNIPPET: i18n-note -->
+ * <p/>
+ * <p/>
+ * <p/>
+ * <!-- START SNIPPET: error-reporting -->
+ * <p/>
+ * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the
+ * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string,
+ * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to
+ * distinguish between a user not entering a value vs. entering a blank value.
+ * <p/>
+ * <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>,
+ * which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name)
+ * in your global i18n resource bundle.
+ * <p/>
+ * <p/>However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n
+ * key associated with just your action (Action.properties) using the pattern <b>invalid.fieldvalue.xxx</b>, where xxx
+ * is the field name.
+ * <p/>
+ * <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map
+ * called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the
+ * errors can be reported accordingly.
+ * <p/>
+ * <!-- END SNIPPET: error-reporting -->
+ *
+ * @author <a href="mailto:plightbo@gmail.com">Pat Lightbody</a>
+ * @author Rainer Hermanns
+ * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a>
+ * @author tm_jee
+ * @version $Date$ $Id$
+ * @see XWorkBasicConverter
+ */
+public class XWorkConverter extends DefaultTypeConverter {
+
+    private static final Logger LOG = LogManager.getLogger(XWorkConverter.class);
+
+    public static final String REPORT_CONVERSION_ERRORS = "report.conversion.errors";
+    public static final String CONVERSION_PROPERTY_FULLNAME = "conversion.property.fullName";
+    public static final String CONVERSION_ERROR_PROPERTY_PREFIX = "invalid.fieldvalue.";
+    public static final String CONVERSION_COLLECTION_PREFIX = "Collection_";
+
+    public static final String LAST_BEAN_CLASS_ACCESSED = "last.bean.accessed";
+    public static final String LAST_BEAN_PROPERTY_ACCESSED = "last.property.accessed";
+    public static final String MESSAGE_INDEX_PATTERN = "\\[\\d+\\]\\.";
+    public static final String MESSAGE_INDEX_BRACKET_PATTERN = "[\\[\\]\\.]";
+    public static final String PERIOD = ".";
+    public static final Pattern messageIndexPattern = Pattern.compile(MESSAGE_INDEX_PATTERN);
+
+    private TypeConverter defaultTypeConverter;
+    private FileManager fileManager;
+    private boolean reloadingConfigs;
+
+    private ConversionFileProcessor fileProcessor;
+    private ConversionAnnotationProcessor annotationProcessor;
+
+    private TypeConverterHolder converterHolder;
+
+    protected XWorkConverter() {
+    }
+
+    @Inject
+    public void setDefaultTypeConverter(XWorkBasicConverter converter) {
+        this.defaultTypeConverter = converter;
+    }
+
+    @Inject
+    public void setFileManagerFactory(FileManagerFactory fileManagerFactory) {
+        this.fileManager = fileManagerFactory.getFileManager();
+    }
+
+    @Inject(value = XWorkConstants.RELOAD_XML_CONFIGURATION, required = false)
+    public void setReloadingConfigs(String reloadingConfigs) {
+        this.reloadingConfigs = Boolean.parseBoolean(reloadingConfigs);
+    }
+
+    @Inject
+    public void setConversionPropertiesProcessor(ConversionPropertiesProcessor propertiesProcessor) {
+        // note: this file is deprecated
+        propertiesProcessor.process("xwork-default-conversion.properties");
+        propertiesProcessor.process("xwork-conversion.properties");
+    }
+
+    @Inject
+    public void setConversionFileProcessor(ConversionFileProcessor fileProcessor) {
+        this.fileProcessor = fileProcessor;
+    }
+
+    @Inject
+    public void setConversionAnnotationProcessor(ConversionAnnotationProcessor annotationProcessor) {
+        this.annotationProcessor = annotationProcessor;
+    }
+
+    @Inject
+    public void setTypeConverterHolder(TypeConverterHolder converterHolder) {
+        this.converterHolder = converterHolder;
+    }
+
+    public static String getConversionErrorMessage(String propertyName, ValueStack stack) {
+        String defaultMessage = LocalizedTextUtil.findDefaultText(XWorkMessages.DEFAULT_INVALID_FIELDVALUE,
+                ActionContext.getContext().getLocale(),
+                new Object[]{
+                        propertyName
+                });
+
+        List<String> indexValues = getIndexValues(propertyName);
+
+        propertyName = removeAllIndexesInPropertyName(propertyName);
+
+        String getTextExpression = "getText('" + CONVERSION_ERROR_PROPERTY_PREFIX + propertyName + "','" + defaultMessage + "')";
+        String message = (String) stack.findValue(getTextExpression);
+
+        if (message == null) {
+            message = defaultMessage;
+        } else {
+            message = MessageFormat.format(message, indexValues.toArray());
+        }
+
+        return message;
+    }
+
+    private static String removeAllIndexesInPropertyName(String propertyName) {
+        return propertyName.replaceAll(MESSAGE_INDEX_PATTERN, PERIOD);
+    }
+
+    private static List<String> getIndexValues(String propertyName) {
+        Matcher matcher = messageIndexPattern.matcher(propertyName);
+        List<String> indexes = new ArrayList<>();
+        while (matcher.find()) {
+            Integer index = new Integer(matcher.group().replaceAll(MESSAGE_INDEX_BRACKET_PATTERN, "")) + 1;
+            indexes.add(Integer.toString(index));
+        }
+        return indexes;
+    }
+
+    public String buildConverterFilename(Class clazz) {
+        String className = clazz.getName();
+        return className.replace('.', '/') + "-conversion.properties";
+    }
+
+    @Override
+    public Object convertValue(Map<String, Object> map, Object o, Class aClass) {
+        return convertValue(map, null, null, null, o, aClass);
+    }
+
+    /**
+     * Convert value from one form to another.
+     * Minimum requirement of arguments:
+     * <ul>
+     * <li>supplying context, toClass and value</li>
+     * <li>supplying context, target and value.</li>
+     * </ul>
+     *
+     * @see TypeConverter#convertValue(java.util.Map, java.lang.Object, java.lang.reflect.Member, java.lang.String, java.lang.Object, java.lang.Class)
+     */
+    @Override
+    public Object convertValue(Map<String, Object> context, Object target, Member member, String property, Object value, Class toClass) {
+        //
+        // Process the conversion using the default mappings, if one exists
+        //
+        TypeConverter tc = null;
+
+        if ((value != null) && (toClass == value.getClass())) {
+            return value;
+        }
+
+        // allow this method to be called without any context
+        // i.e. it can be called with as little as "Object value" and "Class toClass"
+        if (target != null) {
+            Class clazz = target.getClass();
+
+            Object[] classProp = null;
+
+            // this is to handle weird issues with setValue with a different type
+            if ((target instanceof CompoundRoot) && (context != null)) {
+                classProp = getClassProperty(context);
+            }
+
+            if (classProp != null) {
+                clazz = (Class) classProp[0];
+                property = (String) classProp[1];
+            }
+
+            tc = (TypeConverter) getConverter(clazz, property);
+            LOG.debug("field-level type converter for property [{}] = {}", property, (tc == null ? "none found" : tc));
+        }
+
+        if (tc == null && context != null) {
+            // ok, let's see if we can look it up by path as requested in XW-297
+            Object lastPropertyPath = context.get(ReflectionContextState.CURRENT_PROPERTY_PATH);
+            Class clazz = (Class) context.get(XWorkConverter.LAST_BEAN_CLASS_ACCESSED);
+            if (lastPropertyPath != null && clazz != null) {
+                String path = lastPropertyPath + "." + property;
+                tc = (TypeConverter) getConverter(clazz, path);
+            }
+        }
+
+        if (tc == null) {
+            if (toClass.equals(String.class) && (value != null) && !(value.getClass().equals(String.class) || value.getClass().equals(String[].class))) {
+                // when converting to a string, use the source target's class's converter
+                tc = lookup(value.getClass());
+            } else {
+                // when converting from a string, use the toClass's converter
+                tc = lookup(toClass);
+            }
+
+            if (LOG.isDebugEnabled())
+                LOG.debug("global-level type converter for property [{}] = {} ", property, (tc == null ? "none found" : tc));
+        }
+
+
+        if (tc != null) {
+            try {
+                return tc.convertValue(context, target, member, property, value, toClass);
+            } catch (Exception e) {
+                LOG.debug("Unable to convert value using type converter [{}]", tc.getClass().getName(), e);
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        }
+
+        if (defaultTypeConverter != null) {
+            try {
+                LOG.debug("Falling back to default type converter [{}]", defaultTypeConverter);
+                return defaultTypeConverter.convertValue(context, target, member, property, value, toClass);
+            } catch (Exception e) {
+                LOG.debug("Unable to convert value using type converter [{}]", defaultTypeConverter.getClass().getName(), e);
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        } else {
+            try {
+                LOG.debug("Falling back to Ognl's default type conversion");
+                return super.convertValue(value, toClass);
+            } catch (Exception e) {
+                LOG.debug("Unable to convert value using type converter [{}]", super.getClass().getName(), e);
+                handleConversionException(context, property, value, target);
+
+                return TypeConverter.NO_CONVERSION_POSSIBLE;
+            }
+        }
+    }
+
+    /**
+     * Looks for a TypeConverter in the default mappings.
+     *
+     * @param className name of the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    public TypeConverter lookup(String className, boolean isPrimitive) {
+        if (converterHolder.containsUnknownMapping(className) && !converterHolder.containsDefaultMapping(className)) {
+            return null;
+        }
+
+        TypeConverter result = converterHolder.getDefaultMapping(className);
+
+        //Looks for super classes
+        if (result == null && !isPrimitive) {
+            Class clazz = null;
+
+            try {
+                clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
+            } catch (ClassNotFoundException cnfe) {
+                LOG.debug("Cannot load class {}", className, cnfe);
+            }
+
+            result = lookupSuper(clazz);
+
+            if (result != null) {
+                //Register now, the next lookup will be faster
+                registerConverter(className, result);
+            } else {
+                // if it isn't found, never look again (also faster)
+                registerConverterNotFound(className);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Looks for a TypeConverter in the default mappings.
+     *
+     * @param clazz the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    public TypeConverter lookup(Class clazz) {
+        TypeConverter result = lookup(clazz.getName(), clazz.isPrimitive());
+
+        if (result == null && clazz.isPrimitive()) {
+            /**
+             * if it is primitive use default converter which allows to define different converters per type
+             * @see XWorkBasicConverter
+             */
+            return defaultTypeConverter;
+        }
+
+        return result;
+    }
+
+    protected Object getConverter(Class clazz, String property) {
+        LOG.debug("Retrieving convert for class [{}] and property [{}]", clazz, property);
+
+        synchronized (clazz) {
+            if ((property != null) && !converterHolder.containsNoMapping(clazz)) {
+                try {
+                    Map<String, Object> mapping = converterHolder.getMapping(clazz);
+
+                    if (mapping == null) {
+                        mapping = buildConverterMapping(clazz);
+                    } else {
+                        mapping = conditionalReload(clazz, mapping);
+                    }
+
+                    Object converter = mapping.get(property);
+                    if (converter == null && LOG.isDebugEnabled()) {
+                        LOG.debug("Converter is null for property [{}]. Mapping size [{}]:", property, mapping.size());
+                        for (String next : mapping.keySet()) {
+                            LOG.debug("{}:{}", next, mapping.get(next));
+                        }
+                    }
+                    return converter;
+                } catch (Throwable t) {
+                    LOG.debug("Got exception trying to resolve convert for class [{}] and property [{}]", clazz, property, t);
+                    converterHolder.addNoMapping(clazz);
+                }
+            }
+        }
+        return null;
+    }
+
+    protected void handleConversionException(Map<String, Object> context, String property, Object value, Object object) {
+        if (context != null && (Boolean.TRUE.equals(context.get(REPORT_CONVERSION_ERRORS)))) {
+            String realProperty = property;
+            String fullName = (String) context.get(CONVERSION_PROPERTY_FULLNAME);
+
+            if (fullName != null) {
+                realProperty = fullName;
+            }
+
+            Map<String, Object> conversionErrors = (Map<String, Object>) context.get(ActionContext.CONVERSION_ERRORS);
+
+            if (conversionErrors == null) {
+                conversionErrors = new HashMap<>();
+                context.put(ActionContext.CONVERSION_ERRORS, conversionErrors);
+            }
+
+            conversionErrors.put(realProperty, value);
+        }
+    }
+
+    public synchronized void registerConverter(String className, TypeConverter converter) {
+        converterHolder.addDefaultMapping(className, converter);
+    }
+
+    public synchronized void registerConverterNotFound(String className) {
+        converterHolder.addUnknownMapping(className);
+    }
+
+    private Object[] getClassProperty(Map<String, Object> context) {
+        Object lastClass = context.get(LAST_BEAN_CLASS_ACCESSED);
+        Object lastProperty = context.get(LAST_BEAN_PROPERTY_ACCESSED);
+        return (lastClass != null && lastProperty != null) ? new Object[] {lastClass, lastProperty} : null;
+    }
+
+    /**
+     * Looks for converter mappings for the specified class and adds it to an existing map.  Only new converters are
+     * added.  If a converter is defined on a key that already exists, the converter is ignored.
+     *
+     * @param mapping an existing map to add new converter mappings to
+     * @param clazz   class to look for converter mappings for
+     */
+    protected void addConverterMapping(Map<String, Object> mapping, Class clazz) {
+        // Process <clazz>-conversion.properties file
+        String converterFilename = buildConverterFilename(clazz);
+        fileProcessor.process(mapping, clazz, converterFilename);
+
+        // Process annotations
+        Annotation[] annotations = clazz.getAnnotations();
+
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof Conversion) {
+                Conversion conversion = (Conversion) annotation;
+                for (TypeConversion tc : conversion.conversions()) {
+                    if (mapping.containsKey(tc.key())) {
+                        break;
+                    }
+                    if (LOG.isDebugEnabled()) {
+                        if (StringUtils.isEmpty(tc.key())) {
+                            LOG.debug("WARNING! key of @TypeConversion [{}] applied to [{}] is empty!", tc.converter(), clazz.getName());
+                        } else {
+                            LOG.debug("TypeConversion [{}] with key: [{}]", tc.converter(), tc.key());
+                        }
+                    }
+                    annotationProcessor.process(mapping, tc, tc.key());
+                }
+            }
+        }
+
+        // Process annotated methods
+        for (Method method : clazz.getMethods()) {
+            annotations = method.getAnnotations();
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof TypeConversion) {
+                    TypeConversion tc = (TypeConversion) annotation;
+                    if (mapping.containsKey(tc.key())) {
+                        break;
+                    }
+                    String key = tc.key();
+                    // Default to the property name
+                    if (StringUtils.isEmpty(key)) {
+                        key = AnnotationUtils.resolvePropertyName(method);
+                        LOG.debug("Retrieved key [{}] from method name [{}]", key, method.getName());
+                    }
+                    annotationProcessor.process(mapping, tc, key);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Looks for converter mappings for the specified class, traversing up its class hierarchy and interfaces and adding
+     * any additional mappings it may find.  Mappings lower in the hierarchy have priority over those higher in the
+     * hierarcy.
+     *
+     * @param clazz the class to look for converter mappings for
+     * @return the converter mappings
+     */
+    protected Map<String, Object> buildConverterMapping(Class clazz) throws Exception {
+        Map<String, Object> mapping = new HashMap<>();
+
+        // check for conversion mapping associated with super classes and any implemented interfaces
+        Class curClazz = clazz;
+
+        while (!curClazz.equals(Object.class)) {
+            // add current class' mappings
+            addConverterMapping(mapping, curClazz);
+
+            // check interfaces' mappings
+            Class[] interfaces = curClazz.getInterfaces();
+
+            for (Class anInterface : interfaces) {
+                addConverterMapping(mapping, anInterface);
+            }
+
+            curClazz = curClazz.getSuperclass();
+        }
+
+        if (mapping.size() > 0) {
+            converterHolder.addMapping(clazz, mapping);
+        } else {
+            converterHolder.addNoMapping(clazz);
+        }
+
+        return mapping;
+    }
+
+    private Map<String, Object> conditionalReload(Class clazz, Map<String, Object> oldValues) throws Exception {
+        Map<String, Object> mapping = oldValues;
+
+        if (reloadingConfigs) {
+            URL fileUrl = ClassLoaderUtil.getResource(buildConverterFilename(clazz), clazz);
+            if (fileManager.fileNeedsReloading(fileUrl)) {
+                mapping = buildConverterMapping(clazz);
+            }
+        }
+
+        return mapping;
+    }
+
+    /**
+     * Recurses through a class' interfaces and class hierarchy looking for a TypeConverter in the default mapping that
+     * can handle the specified class.
+     *
+     * @param clazz the class the TypeConverter must handle
+     * @return a TypeConverter to handle the specified class or null if none can be found
+     */
+    TypeConverter lookupSuper(Class clazz) {
+        TypeConverter result = null;
+
+        if (clazz != null) {
+            result = converterHolder.getDefaultMapping(clazz.getName());
+
+            if (result == null) {
+                // Looks for direct interfaces (depth = 1 )
+                Class[] interfaces = clazz.getInterfaces();
+
+                for (Class anInterface : interfaces) {
+                    if (converterHolder.containsDefaultMapping(anInterface.getName())) {
+                        result = converterHolder.getDefaultMapping(anInterface.getName());
+                        break;
+                    }
+                }
+
+                if (result == null) {
+                    // Looks for the superclass
+                    // If 'clazz' is the Object class, an interface, a primitive type or void then clazz.getSuperClass() returns null
+                    result = lookupSuper(clazz.getSuperclass());
+                }
+            }
+        }
+
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
new file mode 100644
index 0000000..340dc84
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/ConversionDescription.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright 2002-2006,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 com.opensymphony.xwork2.conversion.metadata;
+
+import com.opensymphony.xwork2.conversion.annotations.ConversionRule;
+import com.opensymphony.xwork2.conversion.impl.DefaultObjectTypeDeterminer;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * <code>ConversionDescription</code>
+ *
+ * @author Rainer Hermanns
+ * @version $Id$
+ */
+public class ConversionDescription {
+
+    /**
+     * Jakarta commons-logging reference.
+     */
+    protected static Logger log = null;
+
+
+    public static final String KEY_PREFIX = "Key_";
+    public static final String ELEMENT_PREFIX = "Element_";
+    public static final String KEY_PROPERTY_PREFIX = "KeyProperty_";
+    public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_";
+
+    /**
+     * Key used for type conversion of maps.
+     */
+    String MAP_PREFIX = "Map_";
+
+    public String property;
+    public String typeConverter = "";
+    public String rule = "";
+    public String value = "";
+    public String fullQualifiedClassName;
+    public String type = null;
+
+    public ConversionDescription() {
+        log = LogManager.getLogger(this.getClass());
+    }
+
+    /**
+     * Creates an ConversionDescription with the specified property name.
+     *
+     * @param property
+     */
+    public ConversionDescription(String property) {
+        this.property = property;
+        log = LogManager.getLogger(this.getClass());
+    }
+
+    /**
+     * <p>
+     * Sets the property name to be inserted into the related conversion.properties file.<br/>
+     * Note: Do not add COLLECTION_PREFIX or MAP_PREFIX keys to property names.
+     * </p>
+     *
+     * @param property The property to be converted.
+     */
+    public void setProperty(String property) {
+        this.property = property;
+    }
+
+    /**
+     * Sets the class name of the type converter to be used.
+     *
+     * @param typeConverter The class name of the type converter.
+     */
+    public void setTypeConverter(String typeConverter) {
+        this.typeConverter = typeConverter;
+    }
+
+    /**
+     * Sets the rule prefix for COLLECTION_PREFIX or MAP_PREFIX key.
+     * Defaults to en emtpy String.
+     *
+     * @param rule
+     */
+    public void setRule(String rule) {
+        if (rule != null && rule.length() > 0) {
+            if (rule.equals(ConversionRule.COLLECTION.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX;
+            } else if (rule.equals(ConversionRule.ELEMENT.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.ELEMENT_PREFIX;
+            } else if (rule.equals(ConversionRule.KEY.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.KEY_PREFIX;
+            } else if (rule.equals(ConversionRule.KEY_PROPERTY.toString())) {
+                this.rule = DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX;
+            } else if (rule.equals(ConversionRule.MAP.toString())) {
+                this.rule = MAP_PREFIX;
+            }
+        }
+    }
+
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public String getValue() {
+        return value;
+    }
+
+    public void setValue(String value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the conversion description as property entry.
+     * <p>
+     * Example:<br/>
+     * property.name = converter.className<br/>
+     * Collection_property.name = converter.className<br/>
+     * Map_property.name = converter.className
+     * KeyProperty_name = id
+     * </p>
+     *
+     * @return the conversion description as property entry.
+     */
+    public String asProperty() {
+        StringWriter sw = new StringWriter();
+        PrintWriter writer = null;
+        try {
+            writer = new PrintWriter(sw);
+            writer.print(rule);
+            writer.print(property);
+            writer.print("=");
+            if ( rule.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) && value != null && value.length() > 0 ) {
+                writer.print(value);
+            } else {
+                writer.print(typeConverter);
+            }
+        } finally {
+            if (writer != null) {
+                writer.flush();
+                writer.close();
+            }
+        }
+
+        return sw.toString();
+
+    }
+
+    /**
+     * Returns the fullQualifiedClassName attribute is used to create the special <code>conversion.properties</code> file name.
+     *
+     * @return fullQualifiedClassName
+     */
+    public String getFullQualifiedClassName() {
+        return fullQualifiedClassName;
+    }
+
+    /**
+     * The fullQualifiedClassName attribute is used to create the special <code>conversion.properties</code> file name.
+     *
+     * @param fullQualifiedClassName
+     */
+    public void setFullQualifiedClassName(String fullQualifiedClassName) {
+        this.fullQualifiedClassName = fullQualifiedClassName;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
new file mode 100644
index 0000000..c50a611
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/conversion/metadata/package.html
@@ -0,0 +1 @@
+<body>Type conversion meta data classes.</body>

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
new file mode 100644
index 0000000..d33cb1d
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ActionFactory.java
@@ -0,0 +1,18 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+
+import java.util.Map;
+
+/**
+ * Used by {@link com.opensymphony.xwork2.ObjectFactory} to build actions
+ */
+public interface ActionFactory {
+
+    /**
+     * Builds action instance
+     */
+    Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception;
+
+}
+

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
new file mode 100644
index 0000000..289eefe
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ConverterFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.conversion.TypeConverter;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link TypeConverter}
+ */
+public interface ConverterFactory {
+
+    /**
+     * Build converter of given type
+     *
+     * @param converterClass to instantiate
+     * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+     * @return instance of converterClass with inject dependencies
+     */
+    TypeConverter buildConverter(Class<? extends TypeConverter> converterClass, Map<String, Object> extraContext) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
new file mode 100644
index 0000000..a17484a
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultActionFactory.java
@@ -0,0 +1,25 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.entities.ActionConfig;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultActionFactory implements ActionFactory {
+
+    private ObjectFactory objectFactory;
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+    public Object buildAction(String actionName, String namespace, ActionConfig config, Map<String, Object> extraContext) throws Exception {
+        return objectFactory.buildBean(config.getClassName(), extraContext);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
new file mode 100644
index 0000000..e35222e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultConverterFactory.java
@@ -0,0 +1,30 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.conversion.TypeConverter;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultConverterFactory implements ConverterFactory {
+
+    private static final Logger LOG = LogManager.getLogger(DefaultConverterFactory.class);
+
+    private Container container;
+
+    @Inject
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+
+    public TypeConverter buildConverter(Class<? extends TypeConverter> converterClass, Map<String, Object> extraContext) throws Exception {
+        LOG.debug("Creating converter of type [{}]", converterClass.getCanonicalName());
+        return container.getInstance(converterClass);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
new file mode 100644
index 0000000..9f0195f
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultInterceptorFactory.java
@@ -0,0 +1,67 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultInterceptorFactory implements InterceptorFactory {
+
+    private ObjectFactory objectFactory;
+    private ReflectionProvider reflectionProvider;
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+    @Inject
+    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+        this.reflectionProvider = reflectionProvider;
+    }
+
+    public Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException {
+        String interceptorClassName = interceptorConfig.getClassName();
+        Map<String, String> thisInterceptorClassParams = interceptorConfig.getParams();
+        Map<String, String> params = (thisInterceptorClassParams == null) ? new HashMap<String, String>() : new HashMap<>(thisInterceptorClassParams);
+        params.putAll(interceptorRefParams);
+
+        String message;
+        Throwable cause;
+
+        try {
+            // interceptor instances are long-lived and used across user sessions, so don't try to pass in any extra context
+            Interceptor interceptor = (Interceptor) objectFactory.buildBean(interceptorClassName, null);
+            reflectionProvider.setProperties(params, interceptor);
+            interceptor.init();
+
+            return interceptor;
+        } catch (InstantiationException e) {
+            cause = e;
+            message = "Unable to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+        } catch (IllegalAccessException e) {
+            cause = e;
+            message = "IllegalAccessException while attempting to instantiate an instance of Interceptor class [" + interceptorClassName + "].";
+        } catch (ClassCastException e) {
+            cause = e;
+            message = "Class [" + interceptorClassName + "] does not implement com.opensymphony.xwork2.interceptor.Interceptor";
+        } catch (Exception e) {
+            cause = e;
+            message = "Caught Exception while registering Interceptor class " + interceptorClassName;
+        } catch (NoClassDefFoundError e) {
+            cause = e;
+            message = "Could not load class " + interceptorClassName + ". Perhaps it exists but certain dependencies are not available?";
+        }
+
+        throw new ConfigurationException(message, cause, interceptorConfig);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
new file mode 100644
index 0000000..a5a6c79
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultResultFactory.java
@@ -0,0 +1,54 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.reflection.ReflectionException;
+import com.opensymphony.xwork2.util.reflection.ReflectionExceptionHandler;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultResultFactory implements ResultFactory {
+
+    private ObjectFactory objectFactory;
+    private ReflectionProvider reflectionProvider;
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+    @Inject
+    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+        this.reflectionProvider = reflectionProvider;
+    }
+
+    public Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception {
+        String resultClassName = resultConfig.getClassName();
+        Result result = null;
+
+        if (resultClassName != null) {
+            result = (Result) objectFactory.buildBean(resultClassName, extraContext);
+            Map<String, String> params = resultConfig.getParams();
+            if (params != null) {
+                for (Map.Entry<String, String> paramEntry : params.entrySet()) {
+                    try {
+                        reflectionProvider.setProperty(paramEntry.getKey(), paramEntry.getValue(), result, extraContext, true);
+                    } catch (ReflectionException ex) {
+                        if (result instanceof ReflectionExceptionHandler) {
+                            ((ReflectionExceptionHandler) result).handle(ex);
+                        }
+                    }
+                }
+            }
+        }
+
+        return result;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
new file mode 100644
index 0000000..58fb339
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultUnknownHandlerFactory.java
@@ -0,0 +1,25 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.UnknownHandler;
+import com.opensymphony.xwork2.inject.Container;
+import com.opensymphony.xwork2.inject.Inject;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultUnknownHandlerFactory implements UnknownHandlerFactory {
+
+    private Container container;
+
+    @Inject
+    public void setContainer(Container container) {
+        this.container = container;
+    }
+
+    public UnknownHandler buildUnknownHandler(String unknownHandlerName, Map<String, Object> extraContext) throws Exception {
+        return container.getInstance(UnknownHandler.class, unknownHandlerName);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
new file mode 100644
index 0000000..8ce8b77
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/DefaultValidatorFactory.java
@@ -0,0 +1,34 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.ObjectFactory;
+import com.opensymphony.xwork2.inject.Inject;
+import com.opensymphony.xwork2.util.reflection.ReflectionProvider;
+import com.opensymphony.xwork2.validator.Validator;
+
+import java.util.Map;
+
+/**
+ * Default implementation
+ */
+public class DefaultValidatorFactory implements ValidatorFactory {
+
+    private ObjectFactory objectFactory;
+    private ReflectionProvider reflectionProvider;
+
+    @Inject
+    public void setObjectFactory(ObjectFactory objectFactory) {
+        this.objectFactory = objectFactory;
+    }
+
+    @Inject
+    public void setReflectionProvider(ReflectionProvider reflectionProvider) {
+        this.reflectionProvider = reflectionProvider;
+    }
+
+    public Validator buildValidator(String className, Map<String, Object> params, Map<String, Object> extraContext) throws Exception {
+        Validator validator = (Validator) objectFactory.buildBean(className, extraContext);
+        reflectionProvider.setProperties(params, validator, extraContext);
+
+        return validator;
+    }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
new file mode 100644
index 0000000..742e7ab
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/InterceptorFactory.java
@@ -0,0 +1,28 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.config.ConfigurationException;
+import com.opensymphony.xwork2.config.entities.InterceptorConfig;
+import com.opensymphony.xwork2.interceptor.Interceptor;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.interceptor.Interceptor}
+ */
+public interface InterceptorFactory {
+
+    /**
+     * Builds an Interceptor from the InterceptorConfig and the Map of
+     * parameters from the interceptor reference. Implementations of this method
+     * should ensure that the Interceptor is parameterized with both the
+     * parameters from the Interceptor config and the interceptor ref Map (the
+     * interceptor ref params take precedence), and that the Interceptor.init()
+     * method is called on the Interceptor instance before it is returned.
+     *
+     * @param interceptorConfig    the InterceptorConfig from the configuration
+     * @param interceptorRefParams a Map of params provided in the Interceptor reference in the
+     *                             Action mapping or InterceptorStack definition
+     */
+    Interceptor buildInterceptor(InterceptorConfig interceptorConfig, Map<String, String> interceptorRefParams) throws ConfigurationException;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
new file mode 100644
index 0000000..c7191cb
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ResultFactory.java
@@ -0,0 +1,15 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.Result;
+import com.opensymphony.xwork2.config.entities.ResultConfig;
+
+import java.util.Map;
+
+/**
+ * Used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.Result}
+ */
+public interface ResultFactory {
+
+    Result buildResult(ResultConfig resultConfig, Map<String, Object> extraContext) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
new file mode 100644
index 0000000..244f3ab
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/UnknownHandlerFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.UnknownHandler;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link com.opensymphony.xwork2.UnknownHandler}
+ */
+public interface UnknownHandlerFactory {
+
+    /**
+     * Builds unknown handler of given name
+     *
+     * @param unknownHandlerName name of unknown handler defined in struts.xml
+     * @param extraContext extra params
+     * @return instance of {@link com.opensymphony.xwork2.UnknownHandler} with injected dependencies
+     */
+    UnknownHandler buildUnknownHandler(String unknownHandlerName, Map<String, Object> extraContext) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java b/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
new file mode 100644
index 0000000..bbaa36e
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/factory/ValidatorFactory.java
@@ -0,0 +1,21 @@
+package com.opensymphony.xwork2.factory;
+
+import com.opensymphony.xwork2.validator.Validator;
+
+import java.util.Map;
+
+/**
+ * Dedicated interface used by {@link com.opensymphony.xwork2.ObjectFactory} to build {@link Validator}
+ */
+public interface ValidatorFactory {
+
+    /**
+     * Build a Validator of the given type and set the parameters on it
+     *
+     * @param className the type of Validator to build
+     * @param params    property name -> value Map to set onto the Validator instance
+     * @param extraContext a Map of extra context which uses the same keys as the {@link com.opensymphony.xwork2.ActionContext}
+     */
+    Validator buildValidator(String className, Map<String, Object> params, Map<String, Object> extraContext) throws Exception;
+
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java b/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
new file mode 100644
index 0000000..d75d464
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/ConstructionContext.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * 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 com.opensymphony.xwork2.inject;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Context of a dependency construction. Used to manage circular references.
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+class ConstructionContext<T> {
+
+  T currentReference;
+  boolean constructing;
+
+  List<DelegatingInvocationHandler<T>> invocationHandlers;
+
+  T getCurrentReference() {
+    return currentReference;
+  }
+
+  void removeCurrentReference() {
+    this.currentReference = null;
+  }
+
+  void setCurrentReference(T currentReference) {
+    this.currentReference = currentReference;
+  }
+
+  boolean isConstructing() {
+    return constructing;
+  }
+
+  void startConstruction() {
+    this.constructing = true;
+  }
+
+  void finishConstruction() {
+    this.constructing = false;
+    invocationHandlers = null;
+  }
+
+  Object createProxy(Class<? super T> expectedType) {
+    // TODO: if I create a proxy which implements all the interfaces of
+    // the implementation type, I'll be able to get away with one proxy
+    // instance (as opposed to one per caller).
+
+    if (!expectedType.isInterface()) {
+      throw new DependencyException(expectedType.getName() + " is not an interface.");
+    }
+
+    if (invocationHandlers == null) {
+      invocationHandlers = new ArrayList<DelegatingInvocationHandler<T>>();
+    }
+
+    DelegatingInvocationHandler<T> invocationHandler = new DelegatingInvocationHandler<>();
+    invocationHandlers.add(invocationHandler);
+
+    return Proxy.newProxyInstance(
+      expectedType.getClassLoader(),
+      new Class[] { expectedType },
+      invocationHandler
+    );
+  }
+
+  void setProxyDelegates(T delegate) {
+    if (invocationHandlers != null) {
+      for (DelegatingInvocationHandler<T> invocationHandler : invocationHandlers) {
+        invocationHandler.setDelegate(delegate);
+      }
+    }
+  }
+
+  static class DelegatingInvocationHandler<T> implements InvocationHandler {
+
+    T delegate;
+
+    public Object invoke(Object proxy, Method method, Object[] args)
+        throws Throwable {
+      if (delegate == null) {
+        throw new IllegalStateException(
+            "Not finished constructing. Please don't call methods on this"
+                + " object until the caller's construction is complete.");
+      }
+
+      try {
+        return method.invoke(delegate, args);
+      } catch (IllegalAccessException | IllegalArgumentException e) {
+        throw new RuntimeException(e);
+      } catch (InvocationTargetException e) {
+        throw e.getTargetException();
+      }
+    }
+
+    void setDelegate(T delegate) {
+      this.delegate = delegate;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Container.java b/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
new file mode 100644
index 0000000..bca6032
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/Container.java
@@ -0,0 +1,113 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ *
+ * 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 com.opensymphony.xwork2.inject;
+
+import java.io.Serializable;
+import java.util.Set;
+
+/**
+ * Injects dependencies into constructors, methods and fields annotated with
+ * {@link Inject}. Immutable.
+ *
+ * <p>When injecting a method or constructor, you can additionally annotate
+ * its parameters with {@link Inject} and specify a dependency name. When a
+ * parameter has no annotation, the container uses the name from the method or
+ * constructor's {@link Inject} annotation respectively.
+ *
+ * <p>For example:
+ *
+ * <pre>
+ *  class Foo {
+ *
+ *    // Inject the int constant named "i".
+ *    &#64;Inject("i") int i;
+ *
+ *    // Inject the default implementation of Bar and the String constant
+ *    // named "s".
+ *    &#64;Inject Foo(Bar bar, @Inject("s") String s) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Baz and the Bob implementation
+ *    // named "foo".
+ *    &#64;Inject void initialize(Baz baz, @Inject("foo") Bob bob) {
+ *      ...
+ *    }
+ *
+ *    // Inject the default implementation of Tee.
+ *    &#64;Inject void setTee(Tee tee) {
+ *      ...
+ *    }
+ *  }
+ * </pre>
+ *
+ * <p>To create and inject an instance of {@code Foo}:
+ *
+ * <pre>
+ *  Container c = ...;
+ *  Foo foo = c.inject(Foo.class);
+ * </pre>
+ *
+ * @see ContainerBuilder
+ * @author crazybob@google.com (Bob Lee)
+ */
+public interface Container extends Serializable {
+
+  /**
+   * Default dependency name.
+   */
+  String DEFAULT_NAME = "default";
+
+  /**
+   * Injects dependencies into the fields and methods of an existing object.
+   */
+  void inject(Object o);
+
+  /**
+   * Creates and injects a new instance of type {@code implementation}.
+   */
+  <T> T inject(Class<T> implementation);
+
+  /**
+   * Gets an instance of the given dependency which was declared in
+   * {@link com.opensymphony.xwork2.inject.ContainerBuilder}.
+   */
+  <T> T getInstance(Class<T> type, String name);
+
+  /**
+   * Convenience method.&nbsp;Equivalent to {@code getInstance(type,
+   * DEFAULT_NAME)}.
+   */
+  <T> T getInstance(Class<T> type);
+  
+  /**
+   * Gets a set of all registered names for the given type
+   * @param type The instance type
+   * @return A set of registered names or empty set if no instances are registered for that type
+   */
+  Set<String> getInstanceNames(Class<?> type);
+
+  /**
+   * Sets the scope strategy for the current thread.
+   */
+  void setScopeStrategy(Scope.Strategy scopeStrategy);
+
+  /**
+   * Removes the scope strategy for the current thread.
+   */
+  void removeScopeStrategy();
+}

http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
new file mode 100644
index 0000000..54f5be6
--- /dev/null
+++ b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerBuilder.java
@@ -0,0 +1,510 @@
+/**
+ * Copyright (C) 2006 Google Inc.
+ * <p/>
+ * 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
+ * <p/>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p/>
+ * 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 com.opensymphony.xwork2.inject;
+
+import java.lang.reflect.Member;
+import java.util.*;
+import java.util.logging.Logger;
+
+/**
+ * Builds a dependency injection {@link Container}. The combination of
+ * dependency type and name uniquely identifies a dependency mapping; you can
+ * use the same name for two different types. Not safe for concurrent use.
+ *
+ * <p>Adds the following factories by default:
+ *
+ * <ul>
+ *   <li>Injects the current {@link Container}.
+ *   <li>Injects the {@link Logger} for the injected member's declaring class.
+ * </ul>
+ *
+ * @author crazybob@google.com (Bob Lee)
+ */
+public final class ContainerBuilder {
+
+    final Map<Key<?>, InternalFactory<?>> factories = new HashMap<>();
+    final List<InternalFactory<?>> singletonFactories = new ArrayList<>();
+    final List<Class<?>> staticInjections = new ArrayList<>();
+    boolean created;
+    boolean allowDuplicates = false;
+
+    private static final InternalFactory<Container> CONTAINER_FACTORY =
+            new InternalFactory<Container>() {
+                public Container create(InternalContext context) {
+                    return context.getContainer();
+                }
+            };
+
+    private static final InternalFactory<Logger> LOGGER_FACTORY =
+            new InternalFactory<Logger>() {
+                public Logger create(InternalContext context) {
+                    Member member = context.getExternalContext().getMember();
+                    return member == null ? Logger.getAnonymousLogger()
+                            : Logger.getLogger(member.getDeclaringClass().getName());
+                }
+            };
+
+    /**
+     * Constructs a new builder.
+     */
+    public ContainerBuilder() {
+        // In the current container as the default Container implementation.
+        factories.put(Key.newInstance(Container.class, Container.DEFAULT_NAME), CONTAINER_FACTORY);
+
+        // Inject the logger for the injected member's declaring class.
+        factories.put(Key.newInstance(Logger.class, Container.DEFAULT_NAME), LOGGER_FACTORY);
+    }
+
+    /**
+     * Maps a dependency. All methods in this class ultimately funnel through
+     * here.
+     */
+    private <T> ContainerBuilder factory(final Key<T> key,
+                                         InternalFactory<? extends T> factory, Scope scope) {
+        ensureNotCreated();
+        checkKey(key);
+        final InternalFactory<? extends T> scopedFactory = scope.scopeFactory(key.getType(), key.getName(), factory);
+        factories.put(key, scopedFactory);
+        if (scope == Scope.SINGLETON) {
+            singletonFactories.add(new InternalFactory<T>() {
+                public T create(InternalContext context) {
+                    try {
+                        context.setExternalContext(ExternalContext.newInstance(null, key, context.getContainerImpl()));
+                        return scopedFactory.create(context);
+                    } finally {
+                        context.setExternalContext(null);
+                    }
+                }
+            });
+        }
+        return this;
+    }
+
+    /**
+     * Ensures a key isn't already mapped.
+     */
+    private void checkKey(Key<?> key) {
+        if (factories.containsKey(key) && !allowDuplicates) {
+            throw new DependencyException("Dependency mapping for " + key + " already exists.");
+        }
+    }
+
+    /**
+     * Maps a factory to a given dependency type and name.
+     *
+     * @param type    of dependency
+     * @param name    of dependency
+     * @param factory creates objects to inject
+     * @param scope   scope of injected instances
+     * @return this builder
+     */
+    public <T> ContainerBuilder factory(final Class<T> type, final String name,
+                                        final Factory<? extends T> factory, Scope scope) {
+        InternalFactory<T> internalFactory = new InternalFactory<T>() {
+
+            public T create(InternalContext context) {
+                try {
+                    Context externalContext = context.getExternalContext();
+                    return factory.create(externalContext);
+                } catch (Exception e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return new LinkedHashMap<String, Object>() {{
+                    put("type", type);
+                    put("name", name);
+                    put("factory", factory);
+                }}.toString();
+            }
+        };
+
+        return factory(Key.newInstance(type, name), internalFactory, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, factory, scope)}.
+     *
+     * @see #factory(Class, String, Factory, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, Factory<? extends T> factory, Scope scope) {
+        return factory(type, Container.DEFAULT_NAME, factory, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type, name, factory,
+     * Scope.DEFAULT)}.
+     *
+     * @see #factory(Class, String, Factory, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, String name, Factory<? extends T> factory) {
+        return factory(type, name, factory, Scope.DEFAULT);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, factory, Scope.DEFAULT)}.
+     *
+     * @see #factory(Class, String, Factory, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, Factory<? extends T> factory) {
+        return factory(type, Container.DEFAULT_NAME, factory, Scope.DEFAULT);
+    }
+
+    /**
+     * Maps an implementation class to a given dependency type and name. Creates
+     * instances using the container, recursively injecting dependencies.
+     *
+     * @param type           of dependency
+     * @param name           of dependency
+     * @param implementation class
+     * @param scope          scope of injected instances
+     * @return this builder
+     */
+    public <T> ContainerBuilder factory(final Class<T> type, final String name,
+                                        final Class<? extends T> implementation, final Scope scope) {
+        // This factory creates new instances of the given implementation.
+        // We have to lazy load the constructor because the Container
+        // hasn't been created yet.
+        InternalFactory<? extends T> factory = new InternalFactory<T>() {
+
+            volatile ContainerImpl.ConstructorInjector<? extends T> constructor;
+
+            @SuppressWarnings("unchecked")
+            public T create(InternalContext context) {
+                if (constructor == null) {
+                    this.constructor =
+                            context.getContainerImpl().getConstructor(implementation);
+                }
+                return (T) constructor.construct(context, type);
+            }
+
+            @Override
+            public String toString() {
+                return new LinkedHashMap<String, Object>() {{
+                    put("type", type);
+                    put("name", name);
+                    put("implementation", implementation);
+                    put("scope", scope);
+                }}.toString();
+            }
+        };
+
+        return factory(Key.newInstance(type, name), factory, scope);
+    }
+
+    /**
+     * Maps an implementation class to a given dependency type and name. Creates
+     * instances using the container, recursively injecting dependencies.
+     * <p/>
+     * <p>Sets scope to value from {@link Scoped} annotation on the
+     * implementation class. Defaults to {@link Scope#DEFAULT} if no annotation
+     * is found.
+     *
+     * @param type           of dependency
+     * @param name           of dependency
+     * @param implementation class
+     * @return this builder
+     */
+    public <T> ContainerBuilder factory(final Class<T> type, String name,
+                                        final Class<? extends T> implementation) {
+        Scoped scoped = implementation.getAnnotation(Scoped.class);
+        Scope scope = scoped == null ? Scope.DEFAULT : scoped.value();
+        return factory(type, name, implementation, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, implementation)}.
+     *
+     * @see #factory(Class, String, Class)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, Class<? extends T> implementation) {
+        return factory(type, Container.DEFAULT_NAME, implementation);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, type)}.
+     *
+     * @see #factory(Class, String, Class)
+     */
+    public <T> ContainerBuilder factory(Class<T> type) {
+        return factory(type, Container.DEFAULT_NAME, type);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type, name, type)}.
+     *
+     * @see #factory(Class, String, Class)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, String name) {
+        return factory(type, name, type);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, implementation, scope)}.
+     *
+     * @see #factory(Class, String, Class, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, Class<? extends T> implementation, Scope scope) {
+        return factory(type, Container.DEFAULT_NAME, implementation, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type,
+     * Container.DEFAULT_NAME, type, scope)}.
+     *
+     * @see #factory(Class, String, Class, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, Scope scope) {
+        return factory(type, Container.DEFAULT_NAME, type, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code factory(type, name, type,
+     * scope)}.
+     *
+     * @see #factory(Class, String, Class, Scope)
+     */
+    public <T> ContainerBuilder factory(Class<T> type, String name, Scope scope) {
+        return factory(type, name, type, scope);
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code alias(type, Container.DEFAULT_NAME,
+     * type)}.
+     *
+     * @see #alias(Class, String, String)
+     */
+    public <T> ContainerBuilder alias(Class<T> type, String alias) {
+        return alias(type, Container.DEFAULT_NAME, alias);
+    }
+
+    /**
+     * Maps an existing factory to a new name.
+     *
+     * @param type  of dependency
+     * @param name  of dependency
+     * @param alias of to the dependency
+     * @return this builder
+     */
+    public <T> ContainerBuilder alias(Class<T> type, String name, String alias) {
+        return alias(Key.newInstance(type, name), Key.newInstance(type, alias));
+    }
+
+    /**
+     * Maps an existing dependency. All methods in this class ultimately funnel through
+     * here.
+     */
+    private <T> ContainerBuilder alias(final Key<T> key,
+                                       final Key<T> aliasKey) {
+        ensureNotCreated();
+        checkKey(aliasKey);
+
+        final InternalFactory<? extends T> scopedFactory = (InternalFactory<? extends T>) factories.get(key);
+        if (scopedFactory == null) {
+            throw new DependencyException("Dependency mapping for " + key + " doesn't exists.");
+        }
+        factories.put(aliasKey, scopedFactory);
+        return this;
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, String value) {
+        return constant(String.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, int value) {
+        return constant(int.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, long value) {
+        return constant(long.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, boolean value) {
+        return constant(boolean.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, double value) {
+        return constant(double.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, float value) {
+        return constant(float.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, short value) {
+        return constant(short.class, name, value);
+    }
+
+    /**
+     * Maps a constant value to the given name.
+     */
+    public ContainerBuilder constant(String name, char value) {
+        return constant(char.class, name, value);
+    }
+
+    /**
+     * Maps a class to the given name.
+     */
+    public ContainerBuilder constant(String name, Class value) {
+        return constant(Class.class, name, value);
+    }
+
+    /**
+     * Maps an enum to the given name.
+     */
+    public <E extends Enum<E>> ContainerBuilder constant(String name, E value) {
+        return constant(value.getDeclaringClass(), name, value);
+    }
+
+    /**
+     * Maps a constant value to the given type and name.
+     */
+    private <T> ContainerBuilder constant(final Class<T> type, final String name, final T value) {
+        InternalFactory<T> factory = new InternalFactory<T>() {
+            public T create(InternalContext ignored) {
+                return value;
+            }
+
+            @Override
+            public String toString() {
+                return new LinkedHashMap<String, Object>() {
+                    {
+                        put("type", type);
+                        put("name", name);
+                        put("value", value);
+                    }
+                }.toString();
+            }
+        };
+
+        return factory(Key.newInstance(type, name), factory, Scope.DEFAULT);
+    }
+
+    /**
+     * Upon creation, the {@link Container} will inject static fields and methods
+     * into the given classes.
+     *
+     * @param types for which static members will be injected
+     */
+    public ContainerBuilder injectStatics(Class<?>... types) {
+        staticInjections.addAll(Arrays.asList(types));
+        return this;
+    }
+
+    /**
+     * Returns true if this builder contains a mapping for the given type and
+     * name.
+     */
+    public boolean contains(Class<?> type, String name) {
+        return factories.containsKey(Key.newInstance(type, name));
+    }
+
+    /**
+     * Convenience method.&nbsp;Equivalent to {@code contains(type,
+     * Container.DEFAULT_NAME)}.
+     */
+    public boolean contains(Class<?> type) {
+        return contains(type, Container.DEFAULT_NAME);
+    }
+
+    /**
+     * Creates a {@link Container} instance. Injects static members for classes
+     * which were registered using {@link #injectStatics(Class...)}.
+     *
+     * @param loadSingletons If true, the container will load all singletons
+     *  now. If false, the container will lazily load singletons. Eager loading
+     *  is appropriate for production use while lazy loading can speed
+     *  development.
+     * @throws IllegalStateException if called more than once
+     */
+    public Container create(boolean loadSingletons) {
+        ensureNotCreated();
+        created = true;
+        final ContainerImpl container = new ContainerImpl(new HashMap<>(factories));
+        if (loadSingletons) {
+            container.callInContext(new ContainerImpl.ContextualCallable<Void>() {
+                public Void call(InternalContext context) {
+                    for (InternalFactory<?> factory : singletonFactories) {
+                        factory.create(context);
+                    }
+                    return null;
+                }
+            });
+        }
+        container.injectStatics(staticInjections);
+        return container;
+    }
+
+    /**
+     * Currently we only support creating one Container instance per builder.
+     * If we want to support creating more than one container per builder,
+     * we should move to a "factory factory" model where we create a factory
+     * instance per Container. Right now, one factory instance would be
+     * shared across all the containers, singletons synchronize on the
+     * container when lazy loading, etc.
+     */
+    private void ensureNotCreated() {
+        if (created) {
+            throw new IllegalStateException("Container already created.");
+        }
+    }
+
+    public void setAllowDuplicates(boolean val) {
+        allowDuplicates = val;
+    }
+
+    /**
+     * Implemented by classes which participate in building a container.
+     */
+    public interface Command {
+
+        /**
+         * Contributes factories to the given builder.
+         *
+         * @param builder
+         */
+        void build(ContainerBuilder builder);
+    }
+}