You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2017/12/20 04:29:33 UTC

[25/47] groovy git commit: Move source files to proper packages

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/FactoryBuilderSupport.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/FactoryBuilderSupport.java b/src/main/groovy/groovy/util/FactoryBuilderSupport.java
new file mode 100644
index 0000000..8dbcff0
--- /dev/null
+++ b/src/main/groovy/groovy/util/FactoryBuilderSupport.java
@@ -0,0 +1,1363 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util;
+
+import groovy.lang.Binding;
+import groovy.lang.Closure;
+import groovy.lang.DelegatingMetaClass;
+import groovy.lang.GroovyClassLoader;
+import groovy.lang.MetaClass;
+import groovy.lang.MissingMethodException;
+import groovy.lang.MissingPropertyException;
+import groovy.lang.Reference;
+import groovy.lang.Script;
+import org.codehaus.groovy.runtime.InvokerHelper;
+import org.codehaus.groovy.runtime.MetaClassHelper;
+import org.codehaus.groovy.runtime.metaclass.MissingMethodExceptionNoStack;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Mix of BuilderSupport and SwingBuilder's factory support.
+ *
+ * Warning: this implementation is not thread safe and should not be used
+ * across threads in a multi-threaded environment.  A locking mechanism
+ * should be implemented by the subclass if use is expected across
+ * multiple threads.
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ * @author <a href="mailto:aalmiray@users.sourceforge.com">Andres Almiray</a>
+ * @author Danno Ferrin
+ */
+public abstract class FactoryBuilderSupport extends Binding {
+    public static final String CURRENT_FACTORY = "_CURRENT_FACTORY_";
+    public static final String PARENT_FACTORY = "_PARENT_FACTORY_";
+    public static final String PARENT_NODE = "_PARENT_NODE_";
+    public static final String CURRENT_NODE = "_CURRENT_NODE_";
+    public static final String PARENT_CONTEXT = "_PARENT_CONTEXT_";
+    public static final String PARENT_NAME = "_PARENT_NAME_";
+    public static final String CURRENT_NAME = "_CURRENT_NAME_";
+    public static final String OWNER = "owner";
+    public static final String PARENT_BUILDER = "_PARENT_BUILDER_";
+    public static final String CURRENT_BUILDER = "_CURRENT_BUILDER_";
+    public static final String CHILD_BUILDER = "_CHILD_BUILDER_";
+    public static final String SCRIPT_CLASS_NAME = "_SCRIPT_CLASS_NAME_";
+    private static final Logger LOG = Logger.getLogger(FactoryBuilderSupport.class.getName());
+    private static final Comparator<Method> METHOD_COMPARATOR = new Comparator<Method>() {
+        public int compare(final Method o1, final Method o2) {
+            int cmp = o1.getName().compareTo(o2.getName());
+            if (cmp != 0) return cmp;
+            cmp = o1.getParameterTypes().length - o2.getParameterTypes().length;
+            return cmp;
+        }
+    };
+
+    /**
+     * Throws an exception if value is null.
+     *
+     * @param value the node's value
+     * @param name  the node's name
+     */
+    public static void checkValueIsNull(Object value, Object name) {
+        if (value != null) {
+            throw new RuntimeException("'" + name + "' elements do not accept a value argument.");
+        }
+    }
+
+    /**
+     * Checks type of value against builder type
+     *
+     * @param value the node's value
+     * @param name  the node's name
+     * @param type  a Class that may be assignable to the value's class
+     * @return true if type is assignable to the value's class, false if value
+     *         is null.
+     */
+    public static boolean checkValueIsType(Object value, Object name, Class type) {
+        if (value != null) {
+            if (type.isAssignableFrom(value.getClass())) {
+                return true;
+            } else {
+                throw new RuntimeException("The value argument of '" + name + "' must be of type "
+                        + type.getName() + ". Found: " + value.getClass());
+            }
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Checks values against factory's type
+     *
+     * @param value the node's value
+     * @param name  the node's name
+     * @param type  a Class that may be assignable to the value's class
+     * @return Returns true if type is assignable to the value's class, false if value is
+     *         null or a String.
+     */
+    public static boolean checkValueIsTypeNotString(Object value, Object name, Class type) {
+        if (value != null) {
+            if (type.isAssignableFrom(value.getClass())) {
+                return true;
+            } else if (value instanceof String) {
+                return false;
+            } else {
+                throw new RuntimeException("The value argument of '" + name + "' must be of type "
+                        + type.getName() + " or a String. Found: " + value.getClass());
+            }
+        } else {
+            return false;
+        }
+    }
+
+    private final ThreadLocal<LinkedList<Map<String, Object>>> contexts = new ThreadLocal<LinkedList<Map<String, Object>>>();
+    protected LinkedList<Closure> attributeDelegates = new LinkedList<Closure>(); //
+    private final List<Closure> disposalClosures = new ArrayList<Closure>(); // because of reverse iteration use ArrayList
+    private final Map<String, Factory> factories = new HashMap<String, Factory>();
+    private Closure nameMappingClosure;
+    private final ThreadLocal<FactoryBuilderSupport> localProxyBuilder = new ThreadLocal<FactoryBuilderSupport>();
+    private FactoryBuilderSupport globalProxyBuilder;
+    protected LinkedList<Closure> preInstantiateDelegates = new LinkedList<Closure>();
+    protected LinkedList<Closure> postInstantiateDelegates = new LinkedList<Closure>();
+    protected LinkedList<Closure> postNodeCompletionDelegates = new LinkedList<Closure>();
+    protected Closure methodMissingDelegate;
+    protected Closure propertyMissingDelegate;
+    protected Map<String, Closure[]> explicitProperties = new HashMap<String, Closure[]>();
+    protected Map<String, Closure> explicitMethods = new HashMap<String, Closure>();
+    protected Map<String, Set<String>> registrationGroup = new HashMap<String, Set<String>>();
+    protected String registrationGroupName = ""; // use binding to store?
+
+    protected boolean autoRegistrationRunning = false;
+    protected boolean autoRegistrationComplete = false;
+
+    public FactoryBuilderSupport() {
+        this(false);
+    }
+
+    public FactoryBuilderSupport(boolean init) {
+        globalProxyBuilder = this;
+        registrationGroup.put(registrationGroupName, new TreeSet<String>());
+        if (init) {
+            autoRegisterNodes();
+        }
+    }
+
+    private Set<String> getRegistrationGroup(String name) {
+        Set<String> group = registrationGroup.get(name);
+        if (group == null ) {
+            group = new TreeSet<String>();
+            registrationGroup.put(name, group);
+        }
+        return group;
+    }
+
+    /**
+     * Ask the nodes to be registered
+     */
+    public void autoRegisterNodes() {
+        // if java did atomic blocks, this would be one
+        synchronized (this) {
+            if (autoRegistrationRunning || autoRegistrationComplete) {
+                // registration already done or in process, abort
+                return;
+            }
+        }
+        autoRegistrationRunning = true;
+        try {
+            callAutoRegisterMethods(getClass());
+        } finally {
+            autoRegistrationComplete = true;
+            autoRegistrationRunning = false;
+        }
+    }
+
+    private void callAutoRegisterMethods(Class declaredClass) {
+        if (declaredClass == null) {
+            return;
+        }
+        callAutoRegisterMethods(declaredClass.getSuperclass());
+
+        Method[] declaredMethods = declaredClass.getDeclaredMethods();
+        Arrays.sort(declaredMethods, METHOD_COMPARATOR);
+        for (Method method : declaredMethods) {
+            if (method.getName().startsWith("register") && method.getParameterTypes().length == 0) {
+                registrationGroupName = method.getName().substring("register".length());
+                registrationGroup.put(registrationGroupName, new TreeSet<String>());
+                try {
+                    if (Modifier.isPublic(method.getModifiers())) {
+                        method.invoke(this);
+                    }
+                } catch (IllegalAccessException e) {
+                    throw new RuntimeException("Could not init " + getClass().getName() + " because of an access error in " + declaredClass.getName() + "." + method.getName(), e);
+                } catch (InvocationTargetException e) {
+                    throw new RuntimeException("Could not init " + getClass().getName() + " because of an exception in " + declaredClass.getName() + "." + method.getName(), e);
+                } finally {
+                    registrationGroupName = "";
+                }
+            }
+        }
+    }
+
+    /**
+     * @param name the name of the variable to lookup
+     * @return the variable value
+     */
+    public Object getVariable(String name) {
+        try {
+            return getProxyBuilder().doGetVariable(name);
+        } catch(MissingPropertyException mpe) {
+            if(mpe.getProperty().equals(name) && propertyMissingDelegate != null) {
+                return propertyMissingDelegate.call(new Object[]{name});
+            }
+            throw mpe;
+        }
+    }
+
+    private Object doGetVariable(String name) {
+        return super.getVariable(name);
+    }
+
+    /**
+     * Sets the value of the given variable
+     *
+     * @param name  the name of the variable to set
+     * @param value the new value for the given variable
+     */
+    public void setVariable(String name, Object value) {
+        getProxyBuilder().doSetVariable(name, value);
+    }
+
+    private void doSetVariable(String name, Object value) {
+        super.setVariable(name, value);
+    }
+
+    public Map getVariables() {
+        return getProxyBuilder().doGetVariables();
+    }
+
+    private Map doGetVariables() {
+        return super.getVariables();
+    }
+
+    /**
+     * Overloaded to make variables appear as bean properties or via the subscript operator
+     */
+    public Object getProperty(String property) {
+        try {
+            return getProxyBuilder().doGetProperty(property);
+        } catch (MissingPropertyException mpe) {
+            if ((getContext() != null) && (getContext().containsKey(property))) {
+                return getContext().get(property);
+            } else {
+                try {
+                    return getMetaClass().getProperty(this, property);
+                } catch(MissingPropertyException mpe2) {
+                    if(mpe2.getProperty().equals(property) && propertyMissingDelegate != null) {
+                        return propertyMissingDelegate.call(new Object[]{property});
+                    }
+                    throw mpe2;
+                }
+            }
+        }
+    }
+
+    private Object doGetProperty(String property) {
+        Closure[] accessors = resolveExplicitProperty(property);
+        if (accessors != null) {
+            if (accessors[0] == null) {
+                // write only property
+                throw new MissingPropertyException(property + " is declared as write only");
+            } else {
+                return accessors[0].call();
+            }
+        } else {
+            return super.getProperty(property);
+        }
+    }
+
+    /**
+     * Overloaded to make variables appear as bean properties or via the subscript operator
+     */
+    public void setProperty(String property, Object newValue) {
+        getProxyBuilder().doSetProperty(property, newValue);
+    }
+
+    private void doSetProperty(String property, Object newValue) {
+        Closure[] accessors = resolveExplicitProperty(property);
+        if (accessors != null) {
+            if (accessors[1] == null) {
+                // read only property
+                throw new MissingPropertyException(property + " is declared as read only");
+            } else {
+                accessors[1].call(newValue);
+            }
+        } else {
+            super.setProperty(property, newValue);
+        }
+    }
+
+    /**
+     * @return the factory map (Unmodifiable Map).
+     */
+    public Map<String, Factory> getFactories() {
+        return Collections.unmodifiableMap(getProxyBuilder().factories);
+    }
+
+    /**
+     * @return the explicit methods map (Unmodifiable Map).
+     */
+    public Map<String, Closure> getExplicitMethods() {
+        return Collections.unmodifiableMap(getProxyBuilder().explicitMethods);
+    }
+
+    /**
+     * @return the explicit properties map (Unmodifiable Map).
+     */
+    public Map<String, Closure[]> getExplicitProperties() {
+        return Collections.unmodifiableMap(getProxyBuilder().explicitProperties);
+    }
+
+    /**
+     * @return the factory map (Unmodifiable Map).
+     */
+    public Map<String, Factory> getLocalFactories() {
+        return Collections.unmodifiableMap(factories);
+    }
+
+    /**
+     * @return the explicit methods map (Unmodifiable Map).
+     */
+    public Map<String, Closure> getLocalExplicitMethods() {
+        return Collections.unmodifiableMap(explicitMethods);
+    }
+
+    /**
+     * @return the explicit properties map (Unmodifiable Map).
+     */
+    public Map<String, Closure[]> getLocalExplicitProperties() {
+        return Collections.unmodifiableMap(explicitProperties);
+    }
+
+    public Set<String> getRegistrationGroups() {
+        return Collections.unmodifiableSet(registrationGroup.keySet());
+    }
+
+    public Set<String> getRegistrationGroupItems(String group) {
+        Set<String> groupSet = registrationGroup.get(group);
+        if (groupSet != null) {
+            return Collections.unmodifiableSet(groupSet);
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    public List<Closure> getAttributeDelegates() {
+        return Collections.unmodifiableList(attributeDelegates);
+    }
+
+    public List<Closure> getPreInstantiateDelegates() {
+        return Collections.unmodifiableList(preInstantiateDelegates);
+    }
+
+    public List<Closure> getPostInstantiateDelegates() {
+        return Collections.unmodifiableList(postInstantiateDelegates);
+    }
+
+    public List<Closure> getPostNodeCompletionDelegates() {
+        return Collections.unmodifiableList(postNodeCompletionDelegates);
+    }
+
+    public Closure getMethodMissingDelegate() {
+        return methodMissingDelegate;
+    }
+
+    public void setMethodMissingDelegate(Closure delegate) {
+        methodMissingDelegate = delegate;
+    }
+
+    public Closure getPropertyMissingDelegate() {
+        return propertyMissingDelegate;
+    }
+
+    public void setPropertyMissingDelegate(Closure delegate) {
+        propertyMissingDelegate = delegate;
+    }
+
+    /**
+     * @return the context of the current node.
+     */
+    public Map<String, Object> getContext() {
+        LinkedList<Map<String, Object>> contexts = getProxyBuilder().contexts.get();
+        if (contexts != null && !contexts.isEmpty()) {
+            return contexts.getFirst();
+        }
+        return null;
+    }
+
+    /**
+     * @return the current node being built.
+     */
+    public Object getCurrent() {
+        return getContextAttribute(CURRENT_NODE);
+    }
+
+    /**
+     * @return the factory that built the current node.
+     */
+    public Factory getCurrentFactory() {
+        return (Factory) getContextAttribute(CURRENT_FACTORY);
+    }
+
+    /**
+     * @return the factory of the parent of the current node.
+     */
+    public String getCurrentName() {
+        return (String) getContextAttribute(CURRENT_NAME);
+    }
+
+    /**
+     * @return the builder that built the current node.
+     */
+    public FactoryBuilderSupport getCurrentBuilder() {
+        return (FactoryBuilderSupport) getContextAttribute(CURRENT_BUILDER);
+    }
+
+    /**
+     * @return the node of the parent of the current node.
+     */
+    public Object getParentNode() {
+        return getContextAttribute(PARENT_NODE);
+    }
+
+    /**
+     * @return the factory of the parent of the current node.
+     */
+    public Factory getParentFactory() {
+        return (Factory) getContextAttribute(PARENT_FACTORY);
+    }
+
+    /**
+     * @return the context of the parent of the current node.
+     */
+    public Map getParentContext() {
+        return (Map) getContextAttribute(PARENT_CONTEXT);
+    }
+
+    /**
+     * @return the name of the parent of the current node.
+     */
+    public String getParentName() {
+        return (String) getContextAttribute(PARENT_NAME);
+    }
+
+    public FactoryBuilderSupport getChildBuilder() {
+        return (FactoryBuilderSupport) getContextAttribute(CHILD_BUILDER);
+    }
+
+    public Object getContextAttribute(String key) {
+        Map context = getContext();
+        if (context != null) {
+            return context.get(key);
+        }
+        return null;
+    }
+
+    /**
+     * Convenience method when no arguments are required
+     *
+     * @param methodName the name of the method to invoke
+     * @return the result of the call
+     */
+    public Object invokeMethod(String methodName) {
+        return getProxyBuilder().invokeMethod(methodName, null);
+    }
+
+    public Object invokeMethod(String methodName, Object args) {
+        Object name = getProxyBuilder().getName(methodName);
+        Object result;
+        Object previousContext = getProxyBuilder().getContext();
+        try {
+            result = getProxyBuilder().doInvokeMethod(methodName, name, args);
+        } catch (RuntimeException e) {
+            // remove contexts created after we started
+            if (getContexts().contains(previousContext)) {
+                Map<String, Object> context = getProxyBuilder().getContext();
+                while (context != null && context != previousContext) {
+                    getProxyBuilder().popContext();
+                    context = getProxyBuilder().getContext();
+                }
+            }
+            throw e;
+        }
+        return result;
+    }
+
+    /**
+     * Add an attribute delegate so it can intercept attributes being set.
+     * Attribute delegates are fired in a FILO pattern, so that nested delegates
+     * get first crack.
+     *
+     * @param attrDelegate the closure to be called
+     * @return attrDelegate
+     */
+    public Closure addAttributeDelegate(Closure attrDelegate) {
+        getProxyBuilder().attributeDelegates.addFirst(attrDelegate);
+        return attrDelegate;
+    }
+
+    /**
+     * Remove the most recently added instance of the attribute delegate.
+     *
+     * @param attrDelegate the instance of the closure to be removed
+     */
+    public void removeAttributeDelegate(Closure attrDelegate) {
+        getProxyBuilder().attributeDelegates.remove(attrDelegate);
+    }
+
+    /**
+     * Add a preInstantiate delegate so it can intercept nodes before they are
+     * created. PreInstantiate delegates are fired in a FILO pattern, so that
+     * nested delegates get first crack.
+     *
+     * @param delegate the closure to invoke
+     * @return delegate
+     */
+    public Closure addPreInstantiateDelegate(Closure delegate) {
+        getProxyBuilder().preInstantiateDelegates.addFirst(delegate);
+        return delegate;
+    }
+
+    /**
+     * Remove the most recently added instance of the preInstantiate delegate.
+     *
+     * @param delegate the closure to invoke
+     */
+    public void removePreInstantiateDelegate(Closure delegate) {
+        getProxyBuilder().preInstantiateDelegates.remove(delegate);
+    }
+
+    /**
+     * Add a postInstantiate delegate so it can intercept nodes after they are
+     * created. PostInstantiate delegates are fired in a FILO pattern, so that
+     * nested delegates get first crack.
+     *
+     * @param delegate the closure to invoke
+     * @return delegate
+     */
+    public Closure addPostInstantiateDelegate(Closure delegate) {
+        getProxyBuilder().postInstantiateDelegates.addFirst(delegate);
+        return delegate;
+    }
+
+    /**
+     * Remove the most recently added instance of the postInstantiate delegate.
+     *
+     * @param delegate the closure to invoke
+     */
+    public void removePostInstantiateDelegate(Closure delegate) {
+        getProxyBuilder().postInstantiateDelegates.remove(delegate);
+    }
+
+    /**
+     * Add a nodeCompletion delegate so it can intercept nodes after they done
+     * with building. NodeCompletion delegates are fired in a FILO pattern, so
+     * that nested delegates get first crack.
+     *
+     * @param delegate the closure to invoke
+     * @return delegate
+     */
+    public Closure addPostNodeCompletionDelegate(Closure delegate) {
+        getProxyBuilder().postNodeCompletionDelegates.addFirst(delegate);
+        return delegate;
+    }
+
+    /**
+     * Remove the most recently added instance of the nodeCompletion delegate.
+     *
+     * @param delegate the closure to be removed
+     */
+    public void removePostNodeCompletionDelegate(Closure delegate) {
+        getProxyBuilder().postNodeCompletionDelegates.remove(delegate);
+    }
+
+    public void registerExplicitProperty(String name, Closure getter, Closure setter) {
+        registerExplicitProperty(name, registrationGroupName, getter, setter);
+    }
+
+    public void registerExplicitProperty(String name, String groupName, Closure getter, Closure setter) {
+        // set the delegate to FBS so the closure closes over the builder
+        if (getter != null) getter.setDelegate(this);
+        if (setter != null) setter.setDelegate(this);
+        explicitProperties.put(name, new Closure[]{getter, setter});
+        String methodNameBase = MetaClassHelper.capitalize(name);
+        if (getter != null) {
+            getRegistrationGroup(groupName).add("get" + methodNameBase);
+        }
+        if (setter != null) {
+            getRegistrationGroup(groupName).add("set" + methodNameBase);
+        }
+    }
+
+    public void registerExplicitMethod(String name, Closure closure) {
+        registerExplicitMethod(name, registrationGroupName, closure);
+    }
+
+    public void registerExplicitMethod(String name, String groupName, Closure closure) {
+        // set the delegate to FBS so the closure closes over the builder
+        closure.setDelegate(this);
+        explicitMethods.put(name, closure);
+        getRegistrationGroup(groupName).add(name);
+    }
+
+    /**
+     * Registers a factory for a JavaBean.<br>
+     * The JavaBean class should have a no-args constructor.
+     *
+     * @param theName   name of the node
+     * @param beanClass the factory to handle the name
+     */
+    public void registerBeanFactory(String theName, Class beanClass) {
+        registerBeanFactory(theName, registrationGroupName, beanClass);
+    }
+
+    /**
+     * Registers a factory for a JavaBean.<br>
+     * The JavaBean class should have a no-args constructor.
+     *
+     * @param theName   name of the node
+     * @param groupName thr group to register this node in
+     * @param beanClass the factory to handle the name
+     */
+    public void registerBeanFactory(String theName, String groupName, final Class beanClass) {
+        getProxyBuilder().registerFactory(theName, new AbstractFactory() {
+            public Object newInstance(FactoryBuilderSupport builder, Object name, Object value,
+                                      Map properties) throws InstantiationException, IllegalAccessException {
+                if (checkValueIsTypeNotString(value, name, beanClass)) {
+                    return value;
+                } else {
+                    return beanClass.newInstance();
+                }
+            }
+        });
+        getRegistrationGroup(groupName).add(theName);
+    }
+
+    /**
+     * Registers a factory for a node name.
+     *
+     * @param name    the name of the node
+     * @param factory the factory to return the values
+     */
+    public void registerFactory(String name, Factory factory) {
+        registerFactory(name, registrationGroupName, factory);
+    }
+
+    /**
+     * Registers a factory for a node name.
+     *
+     * @param name      the name of the node
+     * @param groupName thr group to register this node in
+     * @param factory   the factory to return the values
+     */
+    public void registerFactory(String name, String groupName, Factory factory) {
+        getProxyBuilder().factories.put(name, factory);
+        getRegistrationGroup(groupName).add(name);
+        factory.onFactoryRegistration(this, name, groupName);
+    }
+
+    /**
+     * This method is responsible for instantiating a node and configure its
+     * properties.
+     *
+     * @param name       the name of the node
+     * @param attributes the attributes for the node
+     * @param value      the value arguments for the node
+     * @return the object return from the factory
+     */
+    protected Object createNode(Object name, Map attributes, Object value) {
+        Object node;
+
+        Factory factory = getProxyBuilder().resolveFactory(name, attributes, value);
+        if (factory == null) {
+            LOG.log(Level.WARNING, "Could not find match for name '" + name + "'");
+            throw new MissingMethodExceptionNoStack((String) name, Object.class, new Object[]{attributes, value});
+            //return null;
+        }
+        getProxyBuilder().getContext().put(CURRENT_FACTORY, factory);
+        getProxyBuilder().getContext().put(CURRENT_NAME, String.valueOf(name));
+        getProxyBuilder().preInstantiate(name, attributes, value);
+        try {
+            node = factory.newInstance(getProxyBuilder().getChildBuilder(), name, value, attributes);
+            if (node == null) {
+                LOG.log(Level.WARNING, "Factory for name '" + name + "' returned null");
+                return null;
+            }
+
+            if (LOG.isLoggable(Level.FINE)) {
+                LOG.fine("For name: " + name + " created node: " + node);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Failed to create component for '" + name + "' reason: "
+                    + e, e);
+        }
+        getProxyBuilder().postInstantiate(name, attributes, node);
+        getProxyBuilder().handleNodeAttributes(node, attributes);
+        return node;
+    }
+
+    /**
+     * This is a hook for subclasses to plugin a custom strategy for mapping
+     * names to factories.
+     *
+     * @param name       the name of the factory
+     * @param attributes the attributes from the node
+     * @param value      value arguments from te node
+     * @return the Factory associated with name.<br>
+     */
+    protected Factory resolveFactory(Object name, Map attributes, Object value) {
+        getProxyBuilder().getContext().put(CHILD_BUILDER, getProxyBuilder());
+        return getProxyBuilder().getFactories().get(name);
+    }
+
+    /**
+     * This is a hook for subclasses to plugin a custom strategy for mapping
+     * names to explicit methods.
+     *
+     * @param methodName the name of the explicit method
+     * @param args       the arguments for the method
+     * @return the closure for the matched explicit method.<br>
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected Closure resolveExplicitMethod(String methodName, Object args) {
+        return getExplicitMethods().get(methodName);
+    }
+
+    /**
+     * This is a hook for subclasses to plugin a custom strategy for mapping
+     * names to property methods.
+     *
+     * @param propertyName the name of the explicit method
+     * @return the get and set closures (in that order) for the matched explicit property.<br>
+     */
+    protected Closure[] resolveExplicitProperty(String propertyName) {
+        return getExplicitProperties().get(propertyName);
+    }
+
+    /**
+     * This method is the workhorse of the builder.
+     *
+     * @param methodName the name of the method being invoked
+     * @param name       the name of the node
+     * @param args       the arguments passed into the node
+     * @return the object from the factory
+     */
+    private Object doInvokeMethod(String methodName, Object name, Object args) {
+        Reference explicitResult = new Reference();
+        if (checkExplicitMethod(methodName, args, explicitResult)) {
+            return explicitResult.get();
+        } else {
+            try {
+                return dispatchNodeCall(name, args);
+            } catch(MissingMethodException mme) {
+                if(mme.getMethod().equals(methodName) && methodMissingDelegate != null) {
+                    return methodMissingDelegate.call(new Object[]{methodName, args});
+                }
+                throw mme;
+            }
+        }
+    }
+
+    protected boolean checkExplicitMethod(String methodName, Object args, Reference result) {
+        Closure explicitMethod = resolveExplicitMethod(methodName, args);
+        if (explicitMethod != null) {
+            if (args instanceof Object[]) {
+                result.set(explicitMethod.call((Object[]) args));
+            } else {
+                //todo push through InvokerHelper.asList?
+                result.set(explicitMethod.call(args));
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Use {@link FactoryBuilderSupport#dispatchNodeCall(Object, Object)} instead.
+     */
+    @Deprecated
+    protected Object dispathNodeCall(Object name, Object args) {
+        return dispatchNodeCall(name, args);
+    }
+
+    protected Object dispatchNodeCall(Object name, Object args) {
+        Object node;
+        Closure closure = null;
+        List list = InvokerHelper.asList(args);
+
+        final boolean needToPopContext;
+        if (getProxyBuilder().getContexts().isEmpty()) {
+            // should be called on first build method only
+            getProxyBuilder().newContext();
+            needToPopContext = true;
+        } else {
+            needToPopContext = false;
+        }
+
+        try {
+            Map namedArgs = Collections.EMPTY_MAP;
+
+            // the arguments come in like [named_args?, args..., closure?]
+            // so peel off a hashmap from the front, and a closure from the
+            // end and presume that is what they meant, since there is
+            // no way to distinguish node(a:b,c,d) {..} from
+            // node([a:b],[c,d], {..}), i.e. the user can deliberately confuse
+            // the builder and there is nothing we can really do to prevent
+            // that
+
+            if ((!list.isEmpty())
+                    && (list.get(0) instanceof LinkedHashMap)) {
+                namedArgs = (Map) list.get(0);
+                list = list.subList(1, list.size());
+            }
+            if ((!list.isEmpty())
+                    && (list.get(list.size() - 1) instanceof Closure)) {
+                closure = (Closure) list.get(list.size() - 1);
+                list = list.subList(0, list.size() - 1);
+            }
+            Object arg;
+            if (list.isEmpty()) {
+                arg = null;
+            } else if (list.size() == 1) {
+                arg = list.get(0);
+            } else {
+                arg = list;
+            }
+            node = getProxyBuilder().createNode(name, namedArgs, arg);
+
+            Object current = getProxyBuilder().getCurrent();
+            if (current != null) {
+                getProxyBuilder().setParent(current, node);
+            }
+
+            if (closure != null) {
+                Factory parentFactory = getProxyBuilder().getCurrentFactory();
+                if (parentFactory.isLeaf()) {
+                    throw new RuntimeException("'" + name + "' doesn't support nesting.");
+                }
+                boolean processContent = true;
+                if (parentFactory.isHandlesNodeChildren()) {
+                    processContent = parentFactory.onNodeChildren(this, node, closure);
+                }
+                if (processContent) {
+                    // push new node on stack
+                    String parentName = getProxyBuilder().getCurrentName();
+                    Map parentContext = getProxyBuilder().getContext();
+                    getProxyBuilder().newContext();
+                    try {
+                        getProxyBuilder().getContext().put(OWNER, closure.getOwner());
+                        getProxyBuilder().getContext().put(CURRENT_NODE, node);
+                        getProxyBuilder().getContext().put(PARENT_FACTORY, parentFactory);
+                        getProxyBuilder().getContext().put(PARENT_NODE, current);
+                        getProxyBuilder().getContext().put(PARENT_CONTEXT, parentContext);
+                        getProxyBuilder().getContext().put(PARENT_NAME, parentName);
+                        getProxyBuilder().getContext().put(PARENT_BUILDER, parentContext.get(CURRENT_BUILDER));
+                        getProxyBuilder().getContext().put(CURRENT_BUILDER, parentContext.get(CHILD_BUILDER));
+                        // lets register the builder as the delegate
+                        getProxyBuilder().setClosureDelegate(closure, node);
+                        closure.call();
+                    } finally {
+                        getProxyBuilder().popContext();
+                    }
+                }
+            }
+
+            getProxyBuilder().nodeCompleted(current, node);
+            node = getProxyBuilder().postNodeCompletion(current, node);
+        } finally {
+            if (needToPopContext) {
+                // pop the first context
+                getProxyBuilder().popContext();
+            }
+        }
+        return node;
+    }
+
+    /**
+     * A hook to allow names to be converted into some other object such as a
+     * QName in XML or ObjectName in JMX.
+     *
+     * @param methodName the name of the desired method
+     * @return the object representing the name
+     */
+    public Object getName(String methodName) {
+        if (getProxyBuilder().nameMappingClosure != null) {
+            return getProxyBuilder().nameMappingClosure.call(methodName);
+        }
+        return methodName;
+    }
+
+    /**
+     * Proxy builders are useful for changing the building context, thus
+     * enabling mix &amp; match builders.
+     *
+     * @return the current builder that serves as a proxy.<br>
+     */
+    protected FactoryBuilderSupport getProxyBuilder() {
+        FactoryBuilderSupport proxy = localProxyBuilder.get();
+        if (proxy == null) {
+            return globalProxyBuilder;
+        } else {
+            return proxy;
+        }
+    }
+
+    /**
+     * Sets the builder to be used as a proxy.
+     *
+     * @param proxyBuilder the new proxy
+     */
+    protected void setProxyBuilder(FactoryBuilderSupport proxyBuilder) {
+        globalProxyBuilder = proxyBuilder;
+    }
+
+    public Closure getNameMappingClosure() {
+        return nameMappingClosure;
+    }
+
+    public void setNameMappingClosure(Closure nameMappingClosure) {
+        this.nameMappingClosure = nameMappingClosure;
+    }
+
+    /**
+     * Assigns any existing properties to the node.<br>
+     * It will call attributeDelegates before passing control to the factory
+     * that built the node.
+     *
+     * @param node       the object returned by tne node factory
+     * @param attributes the attributes for the node
+     */
+    protected void handleNodeAttributes(Object node, Map attributes) {
+        // first, short circuit
+        if (node == null) {
+            return;
+        }
+
+        for (Closure attrDelegate : getProxyBuilder().getAttributeDelegates()) {
+            FactoryBuilderSupport builder = this;
+            if (attrDelegate.getOwner() instanceof FactoryBuilderSupport) {
+                builder = (FactoryBuilderSupport) attrDelegate.getOwner();
+            } else if (attrDelegate.getDelegate() instanceof FactoryBuilderSupport) {
+                builder = (FactoryBuilderSupport) attrDelegate.getDelegate();
+            }
+
+            attrDelegate.call(new Object[]{builder, node, attributes});
+        }
+
+        if (getProxyBuilder().getCurrentFactory().onHandleNodeAttributes(getProxyBuilder().getChildBuilder(), node, attributes)) {
+            getProxyBuilder().setNodeAttributes(node, attributes);
+        }
+    }
+
+    /**
+     * Pushes a new context on the stack.
+     */
+    protected void newContext() {
+        getContexts().addFirst(new HashMap<String, Object>());
+    }
+
+    /**
+     * A hook to allow nodes to be processed once they have had all of their
+     * children applied.
+     *
+     * @param node   the current node being processed
+     * @param parent the parent of the node being processed
+     */
+    protected void nodeCompleted(Object parent, Object node) {
+        getProxyBuilder().getCurrentFactory().onNodeCompleted(getProxyBuilder().getChildBuilder(), parent, node);
+    }
+
+    /**
+     * Removes the last context from the stack.
+     *
+     * @return the content just removed
+     */
+    protected Map<String, Object> popContext() {
+        if (!getProxyBuilder().getContexts().isEmpty()) {
+            return getProxyBuilder().getContexts().removeFirst();
+        }
+        return null;
+    }
+
+    /**
+     * A hook after the factory creates the node and before attributes are set.<br>
+     * It will call any registered postInstantiateDelegates, if you override
+     * this method be sure to call this impl somewhere in your code.
+     *
+     * @param name       the name of the node
+     * @param attributes the attributes for the node
+     * @param node       the object created by the node factory
+     */
+    protected void postInstantiate(Object name, Map attributes, Object node) {
+        for (Closure postInstantiateDelegate : getProxyBuilder().getPostInstantiateDelegates()) {
+            (postInstantiateDelegate).call(new Object[]{this, attributes, node});
+        }
+    }
+
+    /**
+     * A hook to allow nodes to be processed once they have had all of their
+     * children applied and allows the actual node object that represents the
+     * Markup element to be changed.<br>
+     * It will call any registered postNodeCompletionDelegates, if you override
+     * this method be sure to call this impl at the end of your code.
+     *
+     * @param node   the current node being processed
+     * @param parent the parent of the node being processed
+     * @return the node, possibly new, that represents the markup element
+     */
+    protected Object postNodeCompletion(Object parent, Object node) {
+        for (Closure postNodeCompletionDelegate : getProxyBuilder().getPostNodeCompletionDelegates()) {
+            (postNodeCompletionDelegate).call(new Object[]{this, parent, node});
+        }
+
+        return node;
+    }
+
+    /**
+     * A hook before the factory creates the node.<br>
+     * It will call any registered preInstantiateDelegates, if you override this
+     * method be sure to call this impl somewhere in your code.
+     *
+     * @param name       the name of the node
+     * @param attributes the attributes of the node
+     * @param value      the value argument(s) of the node
+     */
+    protected void preInstantiate(Object name, Map attributes, Object value) {
+        for (Closure preInstantiateDelegate : getProxyBuilder().getPreInstantiateDelegates()) {
+            (preInstantiateDelegate).call(new Object[]{this, attributes, value});
+        }
+    }
+
+    /**
+     * Clears the context stack.
+     */
+    protected void reset() {
+        getProxyBuilder().getContexts().clear();
+    }
+
+    /**
+     * A strategy method to allow derived builders to use builder-trees and
+     * switch in different kinds of builders. This method should call the
+     * setDelegate() method on the closure which by default passes in this but
+     * if node is-a builder we could pass that in instead (or do something wacky
+     * too)
+     *
+     * @param closure the closure on which to call setDelegate()
+     * @param node    the node value that we've just created, which could be a
+     *                builder
+     */
+    @SuppressWarnings({"UnusedDeclaration"})
+    protected void setClosureDelegate(Closure closure, Object node) {
+        closure.setDelegate(this);
+    }
+
+    /**
+     * Maps attributes key/values to properties on node.
+     *
+     * @param node       the object from the node
+     * @param attributes the attributes to be set
+     */
+    protected void setNodeAttributes(Object node, Map attributes) {
+        // set the properties
+        //noinspection unchecked
+        for (Map.Entry entry : (Set<Map.Entry>) attributes.entrySet()) {
+            String property = entry.getKey().toString();
+            Object value = entry.getValue();
+            InvokerHelper.setProperty(node, property, value);
+        }
+    }
+
+    /**
+     * Strategy method to establish parent/child relationships.
+     *
+     * @param parent the object from the parent node
+     * @param child  the object from the child node
+     */
+    protected void setParent(Object parent, Object child) {
+        getProxyBuilder().getCurrentFactory().setParent(getProxyBuilder().getChildBuilder(), parent, child);
+        Factory parentFactory = getProxyBuilder().getParentFactory();
+        if (parentFactory != null) {
+            parentFactory.setChild(getProxyBuilder().getCurrentBuilder(), parent, child);
+        }
+    }
+
+    /**
+     * @return the stack of available contexts.
+     */
+    protected LinkedList<Map<String, Object>> getContexts() {
+        LinkedList<Map<String, Object>> contexts = getProxyBuilder().contexts.get();
+        if (contexts == null) {
+            contexts = new LinkedList<Map<String, Object>>();
+            getProxyBuilder().contexts.set(contexts);
+        }
+        return contexts;
+    }
+
+    /**
+     * Stores the thread local states in a Map that can be passed across threads
+     * @return the map
+     */
+    protected Map<String, Object> getContinuationData() {
+        Map<String, Object> data = new HashMap<String, Object>();
+        data.put("proxyBuilder", localProxyBuilder.get());
+        data.put("contexts", contexts.get());
+        return data;
+    }
+
+    /**
+     * Restores the state of the current builder to the same state as an older build.
+     * 
+     * Caution, this will destroy rather than merge the current build context if there is any,
+     * @param data the data retrieved from a compatible getContinuationData call
+     */
+    protected void restoreFromContinuationData(Map<String, Object> data) {
+        //noinspection unchecked
+        localProxyBuilder.set((FactoryBuilderSupport) data.get("proxyBuilder"));
+        //noinspection unchecked
+        contexts.set((LinkedList<Map<String, Object>>) data.get("contexts"));
+    }
+
+    public Object build(Class viewClass) {
+        if (Script.class.isAssignableFrom(viewClass)) {
+            Script script = InvokerHelper.createScript(viewClass, this);
+            return build(script);
+        } else {
+            throw new RuntimeException("Only scripts can be executed via build(Class)");
+        }
+    }
+
+    public Object build(Script script) {
+        // this used to be synchronized, but we also used to remove the
+        // metaclass.  Since adding the metaclass is now a side effect, we
+        // don't need to ensure the meta-class won't be observed and don't
+        // need to hide the side effect.
+        MetaClass scriptMetaClass = script.getMetaClass();
+        script.setMetaClass(new FactoryInterceptorMetaClass(scriptMetaClass, this));
+        script.setBinding(this);
+        Object oldScriptName = getProxyBuilder().getVariables().get(SCRIPT_CLASS_NAME);
+        try {
+            getProxyBuilder().setVariable(SCRIPT_CLASS_NAME, script.getClass().getName());
+            return script.run();
+        } finally {
+            if(oldScriptName != null) {
+                getProxyBuilder().setVariable(SCRIPT_CLASS_NAME, oldScriptName);
+            } else {
+                getProxyBuilder().getVariables().remove(SCRIPT_CLASS_NAME);
+            }
+        }
+    }
+
+    public Object build(final String script, GroovyClassLoader loader) {
+        return build(loader.parseClass(script));
+    }
+
+    /**
+     * Switches the builder's proxyBuilder during the execution of a closure.<br>
+     * This is useful to temporary change the building context to another builder
+     * without the need for a contrived setup. It will also take care of restoring
+     * the previous proxyBuilder when the execution finishes, even if an exception
+     * was thrown from inside the closure.
+     *
+     * @param builder the temporary builder to switch to as proxyBuilder.
+     * @param closure the closure to be executed under the temporary builder.
+     * @return the execution result of the closure.
+     * @throws RuntimeException - any exception the closure might have thrown during
+     *                          execution.
+     */
+    public Object withBuilder(FactoryBuilderSupport builder, Closure closure) {
+        if (builder == null || closure == null) {
+            return null;
+        }
+
+        Object result = null;
+        Object previousContext = getProxyBuilder().getContext();
+        FactoryBuilderSupport previousProxyBuilder = localProxyBuilder.get();
+        try {
+            localProxyBuilder.set(builder);
+            closure.setDelegate(builder);
+            result = closure.call();
+        }
+        catch (RuntimeException e) {
+            // remove contexts created after we started
+            localProxyBuilder.set(previousProxyBuilder);
+            if (getProxyBuilder().getContexts().contains(previousContext)) {
+                Map<String, Object> context = getProxyBuilder().getContext();
+                while (context != null && context != previousContext) {
+                    getProxyBuilder().popContext();
+                    context = getProxyBuilder().getContext();
+                }
+            }
+            throw e;
+        }
+        finally {
+            localProxyBuilder.set(previousProxyBuilder);
+        }
+
+        return result;
+    }
+
+    /**
+     * Switches the builder's proxyBuilder during the execution of a closure.<br>
+     * This is useful to temporary change the building context to another builder
+     * without the need for a contrived setup. It will also take care of restoring
+     * the previous proxyBuilder when the execution finishes, even if an exception
+     * was thrown from inside the closure. Additionally it will use the closure's
+     * result as the value for the node identified by 'name'.
+     *
+     * @param builder the temporary builder to switch to as proxyBuilder.
+     * @param name    the node to build on the 'parent' builder.
+     * @param closure the closure to be executed under the temporary builder.
+     * @return a node that responds to value of name with the closure's result as its
+     *         value.
+     * @throws RuntimeException - any exception the closure might have thrown during
+     *                          execution.
+     */
+    public Object withBuilder(FactoryBuilderSupport builder, String name, Closure closure) {
+        if (name == null) {
+            return null;
+        }
+        Object result = getProxyBuilder().withBuilder(builder, closure);
+        return getProxyBuilder().invokeMethod(name, new Object[]{result});
+    }
+
+    /**
+     * Switches the builder's proxyBuilder during the execution of a closure.<br>
+     * This is useful to temporary change the building context to another builder
+     * without the need for a contrived setup. It will also take care of restoring
+     * the previous proxyBuilder when the execution finishes, even if an exception
+     * was thrown from inside the closure. Additionally it will use the closure's
+     * result as the value for the node identified by 'name' and assign any attributes
+     * that might have been set.
+     *
+     * @param attributes additional properties for the node on the parent builder.
+     * @param builder    the temporary builder to switch to as proxyBuilder.
+     * @param name       the node to build on the 'parent' builder.
+     * @param closure    the closure to be executed under the temporary builder.
+     * @return a node that responds to value of name with the closure's result as its
+     *         value.
+     * @throws RuntimeException - any exception the closure might have thrown during
+     *                          execution.
+     */
+    public Object withBuilder(Map attributes, FactoryBuilderSupport builder, String name, Closure closure) {
+        if (name == null) {
+            return null;
+        }
+        Object result = getProxyBuilder().withBuilder(builder, closure);
+        return getProxyBuilder().invokeMethod(name, new Object[]{attributes, result});
+    }
+
+    public void addDisposalClosure(Closure closure) {
+        disposalClosures.add(closure);
+    }
+
+    public List<Closure> getDisposalClosures() {
+        return Collections.unmodifiableList(disposalClosures);
+    }
+
+    public void dispose() {
+        for (int i = disposalClosures.size() - 1; i >= 0; i--) {
+            disposalClosures.get(i).call();
+        }
+    }
+}
+
+class FactoryInterceptorMetaClass extends DelegatingMetaClass {
+
+    FactoryBuilderSupport builder;
+
+    public FactoryInterceptorMetaClass(MetaClass delegate, FactoryBuilderSupport builder) {
+        super(delegate);
+        this.builder = builder;
+    }
+
+    public Object invokeMethod(Object object, String methodName, Object arguments) {
+        try {
+            return delegate.invokeMethod(object, methodName, arguments);
+        } catch (MissingMethodException mme) {
+            // attempt builder resolution
+            try {
+                if (builder.getMetaClass().respondsTo(builder, methodName).isEmpty()) {
+                    // dispatch to factories if it is not a literal method
+                    return builder.invokeMethod(methodName, arguments);
+                } else {
+                    return InvokerHelper.invokeMethod(builder, methodName, arguments);
+                }
+            } catch (MissingMethodException mme2) {
+                // chain secondary exception
+                Throwable root = mme;
+                while (root.getCause() != null) {
+                    root = root.getCause();
+                }
+                root.initCause(mme2);
+                // throw original
+                throw mme;
+            }
+        }
+    }
+
+    public Object invokeMethod(Object object, String methodName, Object[] arguments) {
+        try {
+            return delegate.invokeMethod(object, methodName, arguments);
+        } catch (MissingMethodException mme) {
+            // attempt builder resolution
+            try {
+                if (builder.getMetaClass().respondsTo(builder, methodName).isEmpty()) {
+                    // dispatch to factories if it is not a literal method
+                    return builder.invokeMethod(methodName, arguments);
+                } else {
+                    return InvokerHelper.invokeMethod(builder, methodName, arguments);
+                }
+            } catch (MissingMethodException mme2) {
+                // chain secondary exception
+                Throwable root = mme;
+                while (root.getCause() != null) {
+                    root = root.getCause();
+                }
+                root.initCause(mme2);
+                // throw original
+                throw mme;
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/FileNameByRegexFinder.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/FileNameByRegexFinder.groovy b/src/main/groovy/groovy/util/FileNameByRegexFinder.groovy
new file mode 100644
index 0000000..fbb16e7
--- /dev/null
+++ b/src/main/groovy/groovy/util/FileNameByRegexFinder.groovy
@@ -0,0 +1,43 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util
+
+/**
+ * Find files according to a base directory and an includes and excludes pattern.
+ * The include and exclude patterns conform to regex conventions.
+ *
+ * @author Dierk Koenig
+ * @author Paul King
+ */
+class FileNameByRegexFinder implements IFileNameFinder {
+
+    List<String> getFileNames(String basedir, String pattern) {
+        getFileNames(basedir, pattern, "")
+    }
+
+    List<String> getFileNames(String basedir, String pattern, String excludesPattern) {
+        def result = []
+        new File(basedir).eachFileRecurse {
+            if (it.path =~ pattern && (!excludesPattern || !(it.path =~ excludesPattern))) {
+                result << it.absolutePath
+            }
+        }
+        return result
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/FileTreeBuilder.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/FileTreeBuilder.groovy b/src/main/groovy/groovy/util/FileTreeBuilder.groovy
new file mode 100644
index 0000000..f76c95a
--- /dev/null
+++ b/src/main/groovy/groovy/util/FileTreeBuilder.groovy
@@ -0,0 +1,183 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util
+
+import groovy.transform.CompileStatic
+
+/**
+ * A builder dedicated at generating a file directory structure from a
+ * specification. For example, imagine that you want to create the following tree:
+ * <pre>
+ * src/
+ *  |--- main
+ *  |     |--- groovy
+ *  |            |--- Foo.groovy
+ *  |--- test
+ *        |--- groovy
+ *               |--- FooTest.groovy
+ *
+ * </pre>
+ *
+ * <p>Then you can create the structure using:</p>
+ * <pre><code>
+ *     def tree = new FileTreeBuilder()
+ *     tree.dir('src') {
+ *        dir('main') {
+ *           dir('groovy') {
+ *              file('Foo.groovy', 'println "Hello"')
+ *           }
+ *        }
+ *        dir('test') {
+ *           dir('groovy') {
+ *              file('FooTest.groovy', 'class FooTest extends GroovyTestCase {}')
+ *           }
+ *        }
+ *     }
+ * </code></pre>
+ *
+ * <p>or with this shorthand syntax:</p>
+ * <pre><code>
+ *     def tree = new FileTreeBuilder()
+ *     tree.src {
+ *        main {
+ *           groovy {
+ *              'Foo.groovy'('println "Hello"')
+ *           }
+ *        }
+ *        test {
+ *           groovy {
+ *              'FooTest.groovy'('class FooTest extends GroovyTestCase {}')
+ *           }
+ *        }
+ *     }
+ * </code></pre>
+ *
+ * @since 2.4.2
+ */
+@CompileStatic
+class FileTreeBuilder {
+
+    File baseDir
+
+    FileTreeBuilder(File baseDir = new File('.')) {
+        this.baseDir = baseDir
+    }
+
+    /**
+     * Creates a file with the specified name and the text contents using the system default encoding.
+     * @param name name of the file to be created
+     * @param contents the contents of the file, written using the system default encoding
+     * @return the file being created
+     */
+    File file(String name, CharSequence contents) {
+        new File(baseDir, name) << contents
+    }
+
+    /**
+     * Creates a file with the specified name and the specified binary contents
+     * @param name name of the file to be created
+     * @param contents the contents of the file
+     * @return the file being created
+     */
+    File file(String name, byte[] contents) {
+        new File(baseDir, name) << contents
+    }
+
+    /**
+     * Creates a file with the specified name and the contents from the source file (copy).
+     * @param name name of the file to be created
+     * @param contents the contents of the file
+     * @return the file being created
+     */
+    File file(String name, File source) {
+        // TODO: Avoid using bytes and prefer streaming copy
+        file(name, source.bytes)
+    }
+
+    /**
+     * Creates a new file in the current directory, whose contents is going to be generated in the
+     * closure. The delegate of the closure is the file being created.
+     * @param name name of the file to create
+     * @param spec closure for generating the file contents
+     * @return the created file
+     */
+    File file(String name, @DelegatesTo(value = File, strategy = Closure.DELEGATE_FIRST) Closure spec) {
+        def file = new File(baseDir, name)
+        def clone = (Closure) spec.clone()
+        clone.delegate = file
+        clone.resolveStrategy = Closure.DELEGATE_FIRST
+        clone(file)
+        file
+    }
+
+    /**
+     * Creates a new empty directory
+     * @param name the name of the directory to create
+     * @return the created directory
+     */
+    File dir(String name) {
+        def f = new File(baseDir, name)
+        f.mkdirs()
+        f
+    }
+
+    /**
+     * Creates a new directory and allows to specify a subdirectory structure using the closure as a specification
+     * @param name name of the directory to be created
+     * @param cl specification of the subdirectory structure
+     * @return the created directory
+     */
+    File dir(String name, @DelegatesTo(value = FileTreeBuilder, strategy = Closure.DELEGATE_FIRST) Closure cl) {
+        def oldBase = baseDir
+        def newBase = dir(name)
+        try {
+            baseDir = newBase
+            cl.delegate = this
+            cl.resolveStrategy = Closure.DELEGATE_FIRST
+            cl()
+        } finally {
+            baseDir = oldBase
+        }
+        newBase
+    }
+
+    File call(@DelegatesTo(value = FileTreeBuilder, strategy = Closure.DELEGATE_FIRST) Closure spec) {
+        def clone = (Closure) spec.clone()
+        clone.delegate = this
+        clone.resolveStrategy = Closure.DELEGATE_FIRST
+        clone.call()
+        baseDir
+    }
+
+
+    def methodMissing(String name, args) {
+        if (args instanceof Object[] && ((Object[]) args).length == 1) {
+            def arg = ((Object[]) args)[0]
+            if (arg instanceof Closure) {
+                dir(name, arg)
+            } else if (arg instanceof CharSequence) {
+                file(name, arg.toString())
+            } else if (arg instanceof byte[]) {
+                file(name, arg)
+            } else if (arg instanceof File) {
+                file(name, arg)
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/GroovyCollections.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/GroovyCollections.java b/src/main/groovy/groovy/util/GroovyCollections.java
new file mode 100644
index 0000000..dff062d
--- /dev/null
+++ b/src/main/groovy/groovy/util/GroovyCollections.java
@@ -0,0 +1,293 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package groovy.util;
+
+import org.codehaus.groovy.runtime.DefaultGroovyMethods;
+import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
+import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A Collections utility class
+ *
+ * @author Paul King
+ * @author Jim White
+ */
+public class GroovyCollections {
+    /**
+     * Finds all combinations of items from the given collections.
+     *
+     * @param collections the given collections
+     * @return a List of the combinations found
+     * @see #combinations(Collection)
+     */
+    public static List combinations(Object[] collections) {
+        return combinations((Iterable)Arrays.asList(collections));
+    }
+
+    /**
+     * Finds all non-null subsequences of a list.
+     * E.g. <code>subsequences([1, 2, 3])</code> would be:
+     * [[1, 2, 3], [1, 3], [2, 3], [1, 2], [1], [2], [3]]
+     *
+     * @param items the List of items
+     * @return the subsequences from items
+     */
+    public static <T> Set<List<T>> subsequences(List<T> items) {
+        // items.inject([]){ ss, h -> ss.collect { it + [h] }  + ss + [[h]] }
+        Set<List<T>> ans = new HashSet<List<T>>();
+        for (T h : items) {
+            Set<List<T>> next = new HashSet<List<T>>();
+            for (List<T> it : ans) {
+                List<T> sublist = new ArrayList<T>(it);
+                sublist.add(h);
+                next.add(sublist);
+            }
+            next.addAll(ans);
+            List<T> hlist = new ArrayList<T>();
+            hlist.add(h);
+            next.add(hlist);
+            ans = next;
+        }
+        return ans;
+    }
+
+    /**
+     * @param collections the given collections
+     * @deprecated use combinations(Iterable)
+     */
+    @Deprecated
+    public static List combinations(Collection collections) {
+        return combinations((Iterable)collections);
+    }
+
+    /**
+     * Finds all combinations of items from the given Iterable aggregate of collections.
+     * So, <code>combinations([[true, false], [true, false]])</code>
+     * is <code>[[true, true], [false, true], [true, false], [false, false]]</code>
+     * and <code>combinations([['a', 'b'],[1, 2, 3]])</code>
+     * is <code>[['a', 1], ['b', 1], ['a', 2], ['b', 2], ['a', 3], ['b', 3]]</code>.
+     * If a non-collection item is given, it is treated as a singleton collection,
+     * i.e. <code>combinations([[1, 2], 'x'])</code> is <code>[[1, 'x'], [2, 'x']]</code>.
+     *
+     * @param collections the Iterable of given collections
+     * @return a List of the combinations found
+     * @since 2.2.0
+     */
+    public static List combinations(Iterable collections) {
+        List collectedCombos = new ArrayList();
+        for (Object collection : collections) {
+            Iterable items = DefaultTypeTransformation.asCollection(collection);
+            if (collectedCombos.isEmpty()) {
+                for (Object item : items) {
+                    List l = new ArrayList();
+                    l.add(item);
+                    collectedCombos.add(l);
+                }
+            } else {
+                List savedCombos = new ArrayList(collectedCombos);
+                List newCombos = new ArrayList();
+                for (Object value : items) {
+                    for (Object savedCombo : savedCombos) {
+                        List oldList = new ArrayList((List) savedCombo);
+                        oldList.add(value);
+                        newCombos.add(oldList);
+                    }
+                }
+                collectedCombos = newCombos;
+            }
+        }
+        return collectedCombos;
+    }
+
+    public static <T> List<List<T>> inits(Iterable<T> collections) {
+        List<T> copy = DefaultGroovyMethods.toList(collections);
+        List<List<T>> result = new ArrayList<List<T>>();
+        for (int i = copy.size(); i >= 0; i--) {
+            List<T> next = copy.subList(0, i);
+            result.add(next);
+        }
+        return result;
+    }
+
+    public static <T> List<List<T>> tails(Iterable<T> collections) {
+        List<T> copy = DefaultGroovyMethods.toList(collections);
+        List<List<T>> result = new ArrayList<List<T>>();
+        for (int i = 0; i <= copy.size(); i++) {
+            List<T> next = copy.subList(i, copy.size());
+            result.add(next);
+        }
+        return result;
+    }
+
+    /**
+     * Transposes an array of lists.
+     *
+     * @param lists the given lists
+     * @return a List of the transposed lists
+     * @see #transpose(List)
+     */
+    public static List transpose(Object[] lists) {
+        return transpose(Arrays.asList(lists));
+    }
+
+    /**
+     * Transposes the given lists.
+     * So, <code>transpose([['a', 'b'], [1, 2]])</code>
+     * is <code>[['a', 1], ['b', 2]]</code> and
+     * <code>transpose([['a', 'b', 'c']])</code>
+     * is <code>[['a'], ['b'], ['c']]</code>.
+     *
+     * @param lists the given lists
+     * @return a List of the transposed lists
+     */
+    public static List transpose(List lists) {
+        List result = new ArrayList();
+        if (lists.isEmpty()) return result;
+        int minSize = Integer.MAX_VALUE;
+        for (Object listLike : lists) {
+            List list = (List) DefaultTypeTransformation.castToType(listLike, List.class);
+            if (list.size() < minSize) minSize = list.size();
+        }
+        if (minSize == 0) return result;
+        for (int i = 0; i < minSize; i++) {
+            result.add(new ArrayList());
+        }
+        for (Object listLike : lists) {
+            List list = (List) DefaultTypeTransformation.castToType(listLike, List.class);
+            for (int i = 0; i < minSize; i++) {
+                List resultList = (List) result.get(i);
+                resultList.add(list.get(i));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Selects the minimum value found in an array of items, so
+     * min([2, 4, 6] as Object[]) == 2.
+     *
+     * @param items an array of items
+     * @return the minimum value
+     */
+    public static <T> T min(T[] items) {
+        return min((Iterable<T>)Arrays.asList(items));
+    }
+
+    /**
+     * @deprecated use min(Iterable)
+     */
+    @Deprecated
+    public static <T> T min(Collection<T> items) {
+        return min((Iterable<T>)items);
+    }
+
+    /**
+     * Selects the minimum value found in an Iterable of items.
+     *
+     * @param items an Iterable
+     * @return the minimum value
+     * @since 2.2.0
+     */
+    public static <T> T min(Iterable<T> items) {
+        T answer = null;
+        for (T value : items) {
+            if (value != null) {
+                if (answer == null || ScriptBytecodeAdapter.compareLessThan(value, answer)) {
+                    answer = value;
+                }
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * Selects the maximum value found in an array of items, so
+     * min([2, 4, 6] as Object[]) == 6.
+     *
+     * @param items an array of items
+     * @return the maximum value
+     */
+    public static <T> T max(T[] items) {
+        return max((Iterable<T>)Arrays.asList(items));
+    }
+
+    /**
+     * @deprecated use max(Iterable)
+     */
+    @Deprecated
+    public static <T> T max(Collection<T> items) {
+        return max((Iterable<T>)items);
+    }
+
+    /**
+     * Selects the maximum value found in an Iterable.
+     *
+     * @param items a Collection
+     * @return the maximum value
+     * @since 2.2.0
+     */
+    public static <T> T max(Iterable<T> items) {
+        T answer = null;
+        for (T value : items) {
+            if (value != null) {
+                if (answer == null || ScriptBytecodeAdapter.compareGreaterThan(value, answer)) {
+                    answer = value;
+                }
+            }
+        }
+        return answer;
+    }
+
+    /**
+     * Sums all the items from an array of items.
+     *
+     * @param items an array of items
+     * @return the sum of the items
+     */
+    public static Object sum(Object[] items) {
+        return sum((Iterable)Arrays.asList(items));
+    }
+
+    /**
+     * @deprecated use sum(Iterable)
+     */
+    @Deprecated
+    public static Object sum(Collection items) {
+        return sum((Iterable)items);
+    }
+
+    /**
+     * Sums all the given items.
+     *
+     * @param items an Iterable of items
+     * @return the sum of the item
+     * @since 2.2.0
+     */
+    public static Object sum(Iterable items) {
+        return DefaultGroovyMethods.sum(items);
+    }
+
+}