http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/util/Expando.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/util/Expando.java b/src/main/groovy/groovy/util/Expando.java new file mode 100644 index 0000000..f009551 --- /dev/null +++ b/src/main/groovy/groovy/util/Expando.java @@ -0,0 +1,175 @@ +/* + * 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.Closure; +import groovy.lang.GroovyObjectSupport; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.MetaExpandoProperty; +import groovy.lang.MissingPropertyException; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + + +/** + * Represents a dynamically expandable bean. + * + * @author <a href="mailto:[email protected]">James Strachan</a> + * @author Hein Meling + * @author Pilho Kim + */ +public class Expando extends GroovyObjectSupport { + + private Map expandoProperties; + + public Expando() { + } + + public Expando(Map expandoProperties) { + this.expandoProperties = expandoProperties; + } + + /** + * @return the dynamically expanded properties + */ + public Map getProperties() { + if (expandoProperties == null) { + expandoProperties = createMap(); + } + return expandoProperties; + } + + public List getMetaPropertyValues() { + // run through all our current properties and create MetaProperty objects + List ret = new ArrayList(); + for (Object o : getProperties().entrySet()) { + Entry entry = (Entry) o; + ret.add(new MetaExpandoProperty(entry)); + } + + return ret; + } + + public Object getProperty(String property) { + // always use the expando properties first + Object result = getProperties().get(property); + if (result != null) return result; + try { + return super.getProperty(property); + } + catch (MissingPropertyException e) { + // IGNORE + } + return null; + } + + public void setProperty(String property, Object newValue) { + // always use the expando properties + getProperties().put(property, newValue); + } + + public Object invokeMethod(String name, Object args) { + try { + return super.invokeMethod(name, args); + } + catch (GroovyRuntimeException e) { + // br should get a "native" property match first. getProperty includes such fall-back logic + Object value = this.getProperty(name); + if (value instanceof Closure) { + Closure closure = (Closure) value; + closure = (Closure) closure.clone(); + closure.setDelegate(this); + return closure.call((Object[]) args); + } else { + throw e; + } + } + + } + + /** + * This allows toString to be overridden by a closure <i>field</i> method attached + * to the expando object. + * + * @see java.lang.Object#toString() + */ + public String toString() { + Object method = getProperties().get("toString"); + if (method != null && method instanceof Closure) { + // invoke overridden toString closure method + Closure closure = (Closure) method; + closure.setDelegate(this); + return closure.call().toString(); + } else { + return expandoProperties.toString(); + } + } + + /** + * This allows equals to be overridden by a closure <i>field</i> method attached + * to the expando object. + * + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object obj) { + Object method = getProperties().get("equals"); + if (method != null && method instanceof Closure) { + // invoke overridden equals closure method + Closure closure = (Closure) method; + closure.setDelegate(this); + Boolean ret = (Boolean) closure.call(obj); + return ret.booleanValue(); + } else { + return super.equals(obj); + } + } + + /** + * This allows hashCode to be overridden by a closure <i>field</i> method attached + * to the expando object. + * + * @see java.lang.Object#hashCode() + */ + public int hashCode() { + Object method = getProperties().get("hashCode"); + if (method != null && method instanceof Closure) { + // invoke overridden hashCode closure method + Closure closure = (Closure) method; + closure.setDelegate(this); + Integer ret = (Integer) closure.call(); + return ret.intValue(); + } else { + return super.hashCode(); + } + } + + /** + * Factory method to create a new Map used to store the expando properties map + * + * @return a newly created Map implementation + */ + protected Map createMap() { + return new HashMap(); + } + +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/util/Factory.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/util/Factory.java b/src/main/groovy/groovy/util/Factory.java new file mode 100644 index 0000000..c8e8fc7 --- /dev/null +++ b/src/main/groovy/groovy/util/Factory.java @@ -0,0 +1,90 @@ +/* + * 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.Closure; + +import java.util.Map; + +/** + * @author <a href="mailto:[email protected]">Andres Almiray</a> + * @author Danno Ferrin + */ +public interface Factory { + /** + * + * @return true if no child closures should be processed + */ + boolean isLeaf(); + + /** + * Does this factory "Own" it's child closure. + * + * @return true if the factory should have onContentClosure() called, + * false if the builder should handle it + */ + boolean isHandlesNodeChildren(); + + /** + * Called when a factory is registered to a builder + * @param builder the build the factory has been registered to + * @param registeredName the name the factory has been registered under + */ + void onFactoryRegistration(FactoryBuilderSupport builder, String registeredName, String registeredGroupName); + + /** + * @param builder the FactoryBuilder + * @param name the name of the node being built + * @param value the 'value' argument in the build node + * @param attributes the attributes of the build arg + * @return the object created for the builder + * @throws InstantiationException if attempting to instantiate an interface or abstract class + * @throws IllegalAccessException if the instance can't be created due to a security violation + */ + Object newInstance( FactoryBuilderSupport builder, Object name, Object value, Map attributes ) + throws InstantiationException, IllegalAccessException; + + /** + * @param builder the FactoryBuilder + * @param node the node (returned from newINstance) to consider the attributes for + * @param attributes the attributes, a mutable set + * @return true if the factory builder should use standard bean property matching for the remaining attributes + */ + boolean onHandleNodeAttributes( FactoryBuilderSupport builder, Object node, Map attributes ); + + /** + * Only called if it isLeaf is false and isHandlesNodeChildren is true + * @param builder the FactoryBuilder + * @param node the node (returned from newINstance) to consider the attributes for + * @param childContent the child content closure of the builder + * @return true if the factory builder should apply default node processing to the content child + */ + boolean onNodeChildren( FactoryBuilderSupport builder, Object node, Closure childContent); + + /** + * @param builder the FactoryBuilder + * @param parent the parent node (null if 'root') + * @param node the node just completed + */ + void onNodeCompleted( FactoryBuilderSupport builder, Object parent, Object node ); + + void setParent( FactoryBuilderSupport builder, Object parent, Object child ); + + void setChild( FactoryBuilderSupport builder, Object parent, Object child ); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/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:[email protected]">James Strachan</a> + * @author <a href="mailto:[email protected]">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 & 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/10110145/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/10110145/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/10110145/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); + } + +}
