http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/ui/GroovySocketServer.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/ui/GroovySocketServer.java 
b/src/main/groovy/groovy/ui/GroovySocketServer.java
new file mode 100644
index 0000000..b0d27c5
--- /dev/null
+++ b/src/main/groovy/groovy/ui/GroovySocketServer.java
@@ -0,0 +1,226 @@
+/*
+ *  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.ui;
+
+import groovy.lang.GroovyCodeSource;
+import groovy.lang.GroovyRuntimeException;
+import groovy.lang.GroovyShell;
+import groovy.lang.Script;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.regex.Pattern;
+
+/**
+ * Simple server that executes supplied script against a socket.
+ * <p>
+ * Typically this is used from the groovy command line agent but it can be 
+ * invoked programmatically. To run this program from the command line please
+ * refer to the command line documentation at
+ * <a 
href="http://docs.groovy-lang.org/docs/latest/html/documentation/#_running_groovy_from_the_commandline";>
+ * Running Groovy from the commandline</a>.
+ * <p>
+ * Here is an example of how to use this class to open a listening socket on 
the server, 
+ * listen for incoming data, and then echo the data back to the client in 
reverse order: 
+ * <pre>
+ * new GroovySocketServer(
+ *         new GroovyShell(),      // evaluator
+ *         false,                  // is not a file
+ *         "println line.reverse()",         // script to evaluate
+ *         true,                   // return result to client
+ *         1960)                   //port
+ * </pre>
+ * There are several variables in the script binding:
+ * <ul>
+ * <li>line - The data from the socket</li> 
+ * <li>out - The output PrintWriter, should you need it for some reason.</li> 
+ * <li>socket - The socket, should you need it for some reason.</li> 
+ * </ul>
+ * 
+ * @author Jeremy Rayner
+ */
+public class GroovySocketServer implements Runnable {
+    private URL url;
+    private final GroovyShell groovy;
+    private final GroovyCodeSource source;
+    private final boolean autoOutput;
+    private static int counter;
+
+    /**
+     * This creates and starts the socket server on a new Thread. There is no 
need to call run or spawn
+     * a new thread yourself.
+     * @param groovy
+     *       The GroovyShell object that evaluates the incoming text. If you 
need additional classes in the
+     *       classloader then configure that through this object.
+     * @param isScriptFile
+     *       Whether the incoming socket data String will be a script or a 
file path.
+     * @param scriptFilenameOrText
+     *       This will be a groovy script or a file location depending on the 
argument isScriptFile.
+     * @param autoOutput
+     *       whether output should be automatically echoed back to the client
+     * @param port
+     *       the port to listen on
+     *
+     */
+    public GroovySocketServer(GroovyShell groovy, boolean isScriptFile, String 
scriptFilenameOrText, boolean autoOutput, int port) {
+        this(groovy, getCodeSource(isScriptFile, scriptFilenameOrText), 
autoOutput, port);
+    }
+
+    private static GroovyCodeSource getCodeSource(boolean scriptFile, String 
scriptFilenameOrText) {
+        if (scriptFile) {
+            try {
+                if (URI_PATTERN.matcher(scriptFilenameOrText).matches()) {
+                    return new GroovyCodeSource(new URI(scriptFilenameOrText));
+                } else {
+                    return new 
GroovyCodeSource(GroovyMain.searchForGroovyScriptFile(scriptFilenameOrText));
+                }
+            } catch (IOException e) {
+                throw new GroovyRuntimeException("Unable to get script from: " 
+ scriptFilenameOrText, e);
+            } catch (URISyntaxException e) {
+                throw new GroovyRuntimeException("Unable to get script from 
URI: " + scriptFilenameOrText, e);
+            }
+        } else {
+            // We could jump through some hoops to have GroovyShell make our 
script name, but that seems unwarranted.
+            // If we *did* jump through that hoop then we should probably 
change the run loop to not recompile
+            // the script on every iteration since the script text can't 
change (the reason for the recompilation).
+            return new GroovyCodeSource(scriptFilenameOrText, 
generateScriptName(), GroovyShell.DEFAULT_CODE_BASE);
+        }
+    }
+
+    private static synchronized String generateScriptName() {
+        return "ServerSocketScript" + (++counter) + ".groovy";
+    }
+
+
+    // RFC2396
+    // scheme        = alpha *( alpha | digit | "+" | "-" | "." )
+    private static final Pattern URI_PATTERN = 
Pattern.compile("\\p{Alpha}[-+.\\p{Alnum}]*:.*");
+
+    /**
+    * This creates and starts the socket server on a new Thread. There is no 
need to call run or spawn
+    * a new thread yourself. 
+    * @param groovy
+    *       The GroovyShell object that evaluates the incoming text. If you 
need additional classes in the 
+    *       classloader then configure that through this object. 
+    * @param source
+    *       GroovyCodeSource for the Groovy script
+    * @param autoOutput
+    *       whether output should be automatically echoed back to the client
+    * @param port
+    *       the port to listen on
+    * @since 2.3.0
+    */ 
+    public GroovySocketServer(GroovyShell groovy, GroovyCodeSource source, 
boolean autoOutput, int port) {
+        this.groovy = groovy;
+        this.source = source;
+        this.autoOutput = autoOutput;
+        try {
+            url = new URL("http", InetAddress.getLocalHost().getHostAddress(), 
port, "/");
+            System.out.println("groovy is listening on port " + port);
+        } catch (IOException e) { 
+            e.printStackTrace();
+        }
+        new Thread(this).start();
+    }
+
+    /**
+    * Runs this server. There is typically no need to call this method, as the 
object's constructor
+    * creates a new thread and runs this object automatically. 
+    */ 
+    public void run() {
+        try {
+            ServerSocket serverSocket = new ServerSocket(url.getPort());
+            while (true) {
+                // Create one script per socket connection.
+                // This is purposefully not caching the Script
+                // so that the script source file can be changed on the fly,
+                // as each connection is made to the server.
+                //FIXME: Groovy has other mechanisms specifically for watching 
to see if source code changes.
+                // We should probably be using that here.
+                // See also the comment about the fact we recompile a script 
that can't change.
+                Script script = groovy.parse(source);
+                new GroovyClientConnection(script, autoOutput, 
serverSocket.accept());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+    
+    static class GroovyClientConnection implements Runnable {
+        private Script script;
+        private Socket socket;
+        private BufferedReader reader;
+        private PrintWriter writer;
+        private boolean autoOutputFlag;
+    
+        GroovyClientConnection(Script script, boolean autoOutput,Socket 
socket) throws IOException {
+            this.script = script;
+            this.autoOutputFlag = autoOutput;
+            this.socket = socket;
+            reader = new BufferedReader(new 
InputStreamReader(socket.getInputStream()));
+            writer = new PrintWriter(socket.getOutputStream());
+            new Thread(this, "Groovy client connection - " + 
socket.getInetAddress().getHostAddress()).start();
+        }
+        public void run() {
+            try {
+                String line = null;
+                script.setProperty("out", writer);
+                script.setProperty("socket", socket);
+                script.setProperty("init", Boolean.TRUE);
+                while ((line = reader.readLine()) != null) {
+                    // System.out.println(line);
+                    script.setProperty("line", line);
+                    Object o = script.run();
+                    script.setProperty("init", Boolean.FALSE);
+                    if (o != null) {
+                        if ("success".equals(o)) {
+                            break; // to close sockets gracefully etc...
+                        } else {
+                            if (autoOutputFlag) {
+                                writer.println(o);
+                            }
+                        }
+                    }
+                    writer.flush();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                try {
+                    writer.flush();
+                    writer.close();
+                } finally {
+                    try {
+                        socket.close();
+                    } catch (IOException e3) {
+                        e3.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/AbstractFactory.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/AbstractFactory.java 
b/src/main/groovy/groovy/util/AbstractFactory.java
new file mode 100644
index 0000000..54e68e1
--- /dev/null
+++ b/src/main/groovy/groovy/util/AbstractFactory.java
@@ -0,0 +1,63 @@
+/*
+ *  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 abstract class AbstractFactory implements Factory {
+    public boolean isLeaf() {
+        return false;
+    }
+
+    public boolean isHandlesNodeChildren() {
+        return false;
+    }
+
+    public void onFactoryRegistration(FactoryBuilderSupport builder, String 
registeredName, String group) {
+        // do nothing
+    }
+
+    public boolean onHandleNodeAttributes( FactoryBuilderSupport builder, 
Object node,
+            Map attributes ) {
+        return true;
+    }
+
+    public boolean onNodeChildren( FactoryBuilderSupport builder, Object node, 
Closure childContent) {
+        return true;
+    }
+
+    public void onNodeCompleted( FactoryBuilderSupport builder, Object parent, 
Object node ) {
+        // do nothing
+    }
+
+    public void setParent( FactoryBuilderSupport builder, Object parent, 
Object child ) {
+        // do nothing
+    }
+
+    public void setChild( FactoryBuilderSupport builder, Object parent, Object 
child ) {
+        // do nothing
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/BufferedIterator.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/BufferedIterator.java 
b/src/main/groovy/groovy/util/BufferedIterator.java
new file mode 100644
index 0000000..6fa50a9
--- /dev/null
+++ b/src/main/groovy/groovy/util/BufferedIterator.java
@@ -0,0 +1,31 @@
+/*
+ *  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 java.util.Iterator;
+
+/**
+ * An iterator that allows examining the next element without consuming it.
+ *
+ * @author Andrew Taylor
+ * @since 2.5.0
+ */
+public interface BufferedIterator<T> extends Iterator<T> {
+    T head();
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/BuilderSupport.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/BuilderSupport.java 
b/src/main/groovy/groovy/util/BuilderSupport.java
new file mode 100644
index 0000000..f634f1f
--- /dev/null
+++ b/src/main/groovy/groovy/util/BuilderSupport.java
@@ -0,0 +1,228 @@
+/*
+ *  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.MissingMethodException;
+import org.codehaus.groovy.runtime.InvokerHelper;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An abstract base class for creating arbitrary nested trees of objects
+ * or events
+ *
+ * @author <a href="mailto:[email protected]";>James Strachan</a>
+ */
+public abstract class BuilderSupport extends GroovyObjectSupport {
+
+    private Object current;
+    private Closure nameMappingClosure;
+    private final BuilderSupport proxyBuilder;
+
+    public BuilderSupport() {
+        this.proxyBuilder = this;
+    }
+
+    public BuilderSupport(BuilderSupport proxyBuilder) {
+        this(null, proxyBuilder);
+    }
+
+    public BuilderSupport(Closure nameMappingClosure, BuilderSupport 
proxyBuilder) {
+        this.nameMappingClosure = nameMappingClosure;
+        this.proxyBuilder = proxyBuilder;
+    }
+
+    /**
+     * 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 invokeMethod(methodName, null);
+    }
+
+    public Object invokeMethod(String methodName, Object args) {
+        Object name = getName(methodName);
+        return doInvokeMethod(methodName, name, args);
+    }
+
+    protected Object doInvokeMethod(String methodName, Object name, Object 
args) {
+        Object node = null;
+        Closure closure = null;
+        List list = InvokerHelper.asList(args);
+
+        //System.out.println("Called invokeMethod with name: " + name + " 
arguments: " + list);
+
+        switch (list.size()) {
+            case 0:
+                node = proxyBuilder.createNode(name);
+                break;
+            case 1: {
+                Object object = list.get(0);
+                if (object instanceof Map) {
+                    node = proxyBuilder.createNode(name, (Map) object);
+                } else if (object instanceof Closure) {
+                    closure = (Closure) object;
+                    node = proxyBuilder.createNode(name);
+                } else {
+                    node = proxyBuilder.createNode(name, object);
+                }
+            }
+            break;
+            case 2: {
+                Object object1 = list.get(0);
+                Object object2 = list.get(1);
+                if (object1 instanceof Map) {
+                    if (object2 instanceof Closure) {
+                        closure = (Closure) object2;
+                        node = proxyBuilder.createNode(name, (Map) object1);
+                    } else {
+                        node = proxyBuilder.createNode(name, (Map) object1, 
object2);
+                    }
+                } else {
+                    if (object2 instanceof Closure) {
+                        closure = (Closure) object2;
+                        node = proxyBuilder.createNode(name, object1);
+                    } else if (object2 instanceof Map) {
+                        node = proxyBuilder.createNode(name, (Map) object2, 
object1);
+                    } else {
+                        throw new MissingMethodException(name.toString(), 
getClass(), list.toArray(), false);
+                    }
+                }
+            }
+            break;
+            case 3: {
+                Object arg0 = list.get(0);
+                Object arg1 = list.get(1);
+                Object arg2 = list.get(2);
+                if (arg0 instanceof Map && arg2 instanceof Closure) {
+                    closure = (Closure) arg2;
+                    node = proxyBuilder.createNode(name, (Map) arg0, arg1);
+                } else if (arg1 instanceof Map && arg2 instanceof Closure) {
+                    closure = (Closure) arg2;
+                    node = proxyBuilder.createNode(name, (Map) arg1, arg0);
+                } else {
+                    throw new MissingMethodException(name.toString(), 
getClass(), list.toArray(), false);
+                }
+            }
+            break;
+            default: {
+                throw new MissingMethodException(name.toString(), getClass(), 
list.toArray(), false);
+            }
+
+        }
+
+        if (current != null) {
+            proxyBuilder.setParent(current, node);
+        }
+
+        if (closure != null) {
+            // push new node on stack
+            Object oldCurrent = getCurrent();
+            setCurrent(node);
+            // let's register the builder as the delegate
+            setClosureDelegate(closure, node);
+            try {
+                closure.call();
+            } catch (Exception e) {
+                throw new GroovyRuntimeException(e);
+            }
+            setCurrent(oldCurrent);
+        }
+
+        proxyBuilder.nodeCompleted(current, node);
+        return proxyBuilder.postNodeCompletion(current, node);
+    }
+
+    /**
+     * 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
+     */
+    protected void setClosureDelegate(Closure closure, Object node) {
+        closure.setDelegate(this);
+    }
+
+    protected abstract void setParent(Object parent, Object child);
+
+    protected abstract Object createNode(Object name);
+
+    protected abstract Object createNode(Object name, Object value);
+
+    protected abstract Object createNode(Object name, Map attributes);
+
+    protected abstract Object createNode(Object name, Map attributes, Object 
value);
+
+    /**
+     * 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
+     */
+    protected Object getName(String methodName) {
+        if (nameMappingClosure != null) {
+            return nameMappingClosure.call(methodName);
+        }
+        return methodName;
+    }
+
+
+    /**
+     * 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) {
+    }
+
+    /**
+     * 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
+     *
+     * @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) {
+        return node;
+    }
+
+    protected Object getCurrent() {
+        return current;
+    }
+
+    protected void setCurrent(Object current) {
+        this.current = current;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/CharsetToolkit.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/CharsetToolkit.java 
b/src/main/groovy/groovy/util/CharsetToolkit.java
new file mode 100644
index 0000000..e127459
--- /dev/null
+++ b/src/main/groovy/groovy/util/CharsetToolkit.java
@@ -0,0 +1,419 @@
+/*
+ *  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 java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.nio.charset.Charset;
+import java.util.Collection;
+
+/**
+ * Utility class to guess the encoding of a given text file.
+ * <p>
+ * Unicode files encoded in UTF-16 (low or big endian) or UTF-8 files
+ * with a Byte Order Marker are correctly discovered. For UTF-8 files with no 
BOM, if the buffer
+ * is wide enough, the charset should also be discovered.
+ * <p>
+ * A byte buffer of 4KB is used to be able to guess the encoding.
+ * <p>
+ * Usage:
+ * <pre>
+ * CharsetToolkit toolkit = new CharsetToolkit(file);
+ *
+ * // guess the encoding
+ * Charset guessedCharset = toolkit.getCharset();
+ *
+ * // create a reader with the correct charset
+ * BufferedReader reader = toolkit.getReader();
+ *
+ * // read the file content
+ * String line;
+ * while ((line = br.readLine())!= null)
+ * {
+ *     System.out.println(line);
+ * }
+ * </pre>
+ *
+ * @author Guillaume Laforge
+ */
+public class CharsetToolkit {
+    private final byte[] buffer;
+    private Charset defaultCharset;
+    private Charset charset;
+    private boolean enforce8Bit = true;
+    private final File file;
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+
+    /**
+     * Constructor of the <code>CharsetToolkit</code> utility class.
+     *
+     * @param file of which we want to know the encoding.
+     */
+    public CharsetToolkit(File file) throws IOException {
+        this.file = file;
+        this.defaultCharset = getDefaultSystemCharset();
+        this.charset = null;
+        InputStream input = new FileInputStream(file);
+        try {
+            byte[] bytes = new byte[4096];
+            int bytesRead = input.read(bytes);
+            if (bytesRead == -1) {
+                this.buffer = EMPTY_BYTE_ARRAY;
+            }
+            else if (bytesRead < 4096) {
+                byte[] bytesToGuess = new byte[bytesRead];
+                System.arraycopy(bytes, 0, bytesToGuess, 0, bytesRead);
+                this.buffer = bytesToGuess;
+            }
+            else {
+                this.buffer = bytes;
+            }
+        } finally {
+            try {input.close();} catch (IOException e){
+                // IGNORE
+            }
+        }
+    }
+
+    /**
+     * Defines the default <code>Charset</code> used in case the buffer 
represents
+     * an 8-bit <code>Charset</code>.
+     *
+     * @param defaultCharset the default <code>Charset</code> to be returned
+     * if an 8-bit <code>Charset</code> is encountered.
+     */
+    public void setDefaultCharset(Charset defaultCharset) {
+        if (defaultCharset != null)
+            this.defaultCharset = defaultCharset;
+        else
+            this.defaultCharset = getDefaultSystemCharset();
+    }
+
+    public Charset getCharset() {
+        if (this.charset == null)
+            this.charset = guessEncoding();
+        return charset;
+    }
+
+    /**
+     * If US-ASCII is recognized, enforce to return the default encoding, 
rather than US-ASCII.
+     * It might be a file without any special character in the range 128-255, 
but that may be or become
+     * a file encoded with the default <code>charset</code> rather than 
US-ASCII.
+     *
+     * @param enforce a boolean specifying the use or not of US-ASCII.
+     */
+    public void setEnforce8Bit(boolean enforce) {
+        this.enforce8Bit = enforce;
+    }
+
+    /**
+     * Gets the enforce8Bit flag, in case we do not want to ever get a 
US-ASCII encoding.
+     *
+     * @return a boolean representing the flag of use of US-ASCII.
+     */
+    public boolean getEnforce8Bit() {
+        return this.enforce8Bit;
+    }
+
+    /**
+     * Retrieves the default Charset
+     */
+    public Charset getDefaultCharset() {
+        return defaultCharset;
+    }
+
+    /**
+     * Guess the encoding of the provided buffer.
+     * If Byte Order Markers are encountered at the beginning of the buffer, 
we immediately
+     * return the charset implied by this BOM. Otherwise, the file would not 
be a human
+     * readable text file.
+     * <p>
+     * If there is no BOM, this method tries to discern whether the file is 
UTF-8 or not.
+     * If it is not UTF-8, we assume the encoding is the default system 
encoding
+     * (of course, it might be any 8-bit charset, but usually, an 8-bit 
charset is the default one).
+     * <p>
+     * It is possible to discern UTF-8 thanks to the pattern of characters 
with a multi-byte sequence.
+     * <pre>
+     * UCS-4 range (hex.)        UTF-8 octet sequence (binary)
+     * 0000 0000-0000 007F       0xxxxxxx
+     * 0000 0080-0000 07FF       110xxxxx 10xxxxxx
+     * 0000 0800-0000 FFFF       1110xxxx 10xxxxxx 10xxxxxx
+     * 0001 0000-001F FFFF       11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 0020 0000-03FF FFFF       111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+     * 0400 0000-7FFF FFFF       1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 
10xxxxxx
+     * </pre>
+     * With UTF-8, 0xFE and 0xFF never appear.
+     *
+     * @return the Charset recognized.
+     */
+    private Charset guessEncoding() {
+        // if the file has a Byte Order Marker, we can assume the file is in 
UTF-xx
+        // otherwise, the file would not be human readable
+        if (hasUTF8Bom())
+            return Charset.forName("UTF-8");
+        if (hasUTF16LEBom())
+            return Charset.forName("UTF-16LE");
+        if (hasUTF16BEBom())
+            return Charset.forName("UTF-16BE");
+
+        // if a byte has its most significant bit set, the file is in UTF-8 or 
in the default encoding
+        // otherwise, the file is in US-ASCII
+        boolean highOrderBit = false;
+
+        // if the file is in UTF-8, high order bytes must have a certain 
value, in order to be valid
+        // if it's not the case, we can assume the encoding is the default 
encoding of the system
+        boolean validU8Char = true;
+
+        // TODO the buffer is not read up to the end, but up to length - 6
+
+        int length = buffer.length;
+        int i = 0;
+        while (i < length - 6) {
+            byte b0 = buffer[i];
+            byte b1 = buffer[i + 1];
+            byte b2 = buffer[i + 2];
+            byte b3 = buffer[i + 3];
+            byte b4 = buffer[i + 4];
+            byte b5 = buffer[i + 5];
+            if (b0 < 0) {
+                // a high order bit was encountered, thus the encoding is not 
US-ASCII
+                // it may be either an 8-bit encoding or UTF-8
+                highOrderBit = true;
+                // a two-bytes sequence was encountered
+                if (isTwoBytesSequence(b0)) {
+                    // there must be one continuation byte of the form 
10xxxxxx,
+                    // otherwise the following character is is not a valid 
UTF-8 construct
+                    if (!isContinuationChar(b1))
+                        validU8Char = false;
+                    else
+                        i++;
+                }
+                // a three-bytes sequence was encountered
+                else if (isThreeBytesSequence(b0)) {
+                    // there must be two continuation bytes of the form 
10xxxxxx,
+                    // otherwise the following character is is not a valid 
UTF-8 construct
+                    if (!(isContinuationChar(b1) && isContinuationChar(b2)))
+                        validU8Char = false;
+                    else
+                        i += 2;
+                }
+                // a four-bytes sequence was encountered
+                else if (isFourBytesSequence(b0)) {
+                    // there must be three continuation bytes of the form 
10xxxxxx,
+                    // otherwise the following character is is not a valid 
UTF-8 construct
+                    if (!(isContinuationChar(b1) && isContinuationChar(b2) && 
isContinuationChar(b3)))
+                        validU8Char = false;
+                    else
+                        i += 3;
+                }
+                // a five-bytes sequence was encountered
+                else if (isFiveBytesSequence(b0)) {
+                    // there must be four continuation bytes of the form 
10xxxxxx,
+                    // otherwise the following character is is not a valid 
UTF-8 construct
+                    if (!(isContinuationChar(b1)
+                        && isContinuationChar(b2)
+                        && isContinuationChar(b3)
+                        && isContinuationChar(b4)))
+                        validU8Char = false;
+                    else
+                        i += 4;
+                }
+                // a six-bytes sequence was encountered
+                else if (isSixBytesSequence(b0)) {
+                    // there must be five continuation bytes of the form 
10xxxxxx,
+                    // otherwise the following character is is not a valid 
UTF-8 construct
+                    if (!(isContinuationChar(b1)
+                        && isContinuationChar(b2)
+                        && isContinuationChar(b3)
+                        && isContinuationChar(b4)
+                        && isContinuationChar(b5)))
+                        validU8Char = false;
+                    else
+                        i += 5;
+                }
+                else
+                    validU8Char = false;
+            }
+            if (!validU8Char)
+                break;
+            i++;
+        }
+        // if no byte with an high order bit set, the encoding is US-ASCII
+        // (it might have been UTF-7, but this encoding is usually internally 
used only by mail systems)
+        if (!highOrderBit) {
+            // returns the default charset rather than US-ASCII if the 
enforce8Bit flag is set.
+            if (this.enforce8Bit)
+                return this.defaultCharset;
+            else
+                return Charset.forName("US-ASCII");
+        }
+        // if no invalid UTF-8 were encountered, we can assume the encoding is 
UTF-8,
+        // otherwise the file would not be human readable
+        if (validU8Char)
+            return Charset.forName("UTF-8");
+        // finally, if it's not UTF-8 nor US-ASCII, let's assume the encoding 
is the default encoding
+        return this.defaultCharset;
+    }
+
+    /**
+     * If the byte has the form 10xxxxx, then it's a continuation byte of a 
multiple byte character;
+     *
+     * @param b a byte.
+     * @return true if it's a continuation char.
+     */
+    private static boolean isContinuationChar(byte b) {
+        return -128 <= b && b <= -65;
+    }
+
+    /**
+     * If the byte has the form 110xxxx, then it's the first byte of a 
two-bytes sequence character.
+     *
+     * @param b a byte.
+     * @return true if it's the first byte of a two-bytes sequence.
+     */
+    private static boolean isTwoBytesSequence(byte b) {
+        return -64 <= b && b <= -33;
+    }
+
+    /**
+     * If the byte has the form 1110xxx, then it's the first byte of a 
three-bytes sequence character.
+     *
+     * @param b a byte.
+     * @return true if it's the first byte of a three-bytes sequence.
+     */
+    private static boolean isThreeBytesSequence(byte b) {
+        return -32 <= b && b <= -17;
+    }
+
+    /**
+     * If the byte has the form 11110xx, then it's the first byte of a 
four-bytes sequence character.
+     *
+     * @param b a byte.
+     * @return true if it's the first byte of a four-bytes sequence.
+     */
+    private static boolean isFourBytesSequence(byte b) {
+        return -16 <= b && b <= -9;
+    }
+
+    /**
+     * If the byte has the form 11110xx, then it's the first byte of a 
five-bytes sequence character.
+     *
+     * @param b a byte.
+     * @return true if it's the first byte of a five-bytes sequence.
+     */
+    private static boolean isFiveBytesSequence(byte b) {
+        return -8 <= b && b <= -5;
+    }
+
+    /**
+     * If the byte has the form 1110xxx, then it's the first byte of a 
six-bytes sequence character.
+     *
+     * @param b a byte.
+     * @return true if it's the first byte of a six-bytes sequence.
+     */
+    private static boolean isSixBytesSequence(byte b) {
+        return -4 <= b && b <= -3;
+    }
+
+    /**
+     * Retrieve the default charset of the system.
+     *
+     * @return the default <code>Charset</code>.
+     */
+    public static Charset getDefaultSystemCharset() {
+        return Charset.forName(System.getProperty("file.encoding"));
+    }
+
+    /**
+     * Has a Byte Order Marker for UTF-8 (Used by Microsoft's Notepad and 
other editors).
+     *
+     * @return true if the buffer has a BOM for UTF8.
+     */
+    public boolean hasUTF8Bom() {
+        if (buffer.length >= 3)
+            return (buffer[0] == -17 && buffer[1] == -69 && buffer[2] == -65);
+        else
+            return false;
+    }
+
+    /**
+     * Has a Byte Order Marker for UTF-16 Low Endian
+     * (ucs-2le, ucs-4le, and ucs-16le).
+     *
+     * @return true if the buffer has a BOM for UTF-16 Low Endian.
+     */
+    public boolean hasUTF16LEBom() {
+        if (buffer.length >= 2)
+            return (buffer[0] == -1 && buffer[1] == -2);
+        else
+            return false;
+    }
+
+    /**
+     * Has a Byte Order Marker for UTF-16 Big Endian
+     * (utf-16 and ucs-2).
+     *
+     * @return true if the buffer has a BOM for UTF-16 Big Endian.
+     */
+    public boolean hasUTF16BEBom() {
+        if (buffer.length >= 2)
+            return (buffer[0] == -2 && buffer[1] == -1);
+        else
+            return false;
+    }
+
+    /**
+     * Gets a <code>BufferedReader</code> (indeed a 
<code>LineNumberReader</code>) from the <code>File</code>
+     * specified in the constructor of <code>CharsetToolkit</code> using the 
charset discovered or the default
+     * charset if an 8-bit <code>Charset</code> is encountered.
+     *
+     * @return a <code>BufferedReader</code>
+     * @throws FileNotFoundException if the file is not found.
+     */
+    public BufferedReader getReader() throws FileNotFoundException {
+        LineNumberReader reader = new LineNumberReader(new 
InputStreamReader(new FileInputStream(file), getCharset()));
+        if (hasUTF8Bom() || hasUTF16LEBom() || hasUTF16BEBom()) {
+            try {
+                reader.read();
+            }
+            catch (IOException e) {
+                // should never happen, as a file with no content
+                // but with a BOM has at least one char
+            }
+        }
+        return reader;
+    }
+
+    /**
+     * Retrieves all the available <code>Charset</code>s on the platform,
+     * among which the default <code>charset</code>.
+     *
+     * @return an array of <code>Charset</code>s.
+     */
+    public static Charset[] getAvailableCharsets() {
+        Collection collection = Charset.availableCharsets().values();
+        return (Charset[]) collection.toArray(new Charset[collection.size()]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/CliBuilder.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/CliBuilder.groovy 
b/src/main/groovy/groovy/util/CliBuilder.groovy
new file mode 100644
index 0000000..bc7d44a
--- /dev/null
+++ b/src/main/groovy/groovy/util/CliBuilder.groovy
@@ -0,0 +1,798 @@
+/*
+ *  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.cli.CliBuilderException
+import groovy.cli.Option
+import groovy.cli.TypedOption
+import groovy.cli.Unparsed
+import groovy.transform.Undefined
+import org.apache.commons.cli.CommandLine
+import org.apache.commons.cli.CommandLineParser
+import org.apache.commons.cli.DefaultParser
+import org.apache.commons.cli.GnuParser
+import org.apache.commons.cli.HelpFormatter
+import org.apache.commons.cli.Option as CliOption
+import org.apache.commons.cli.Options
+import org.apache.commons.cli.ParseException
+import org.codehaus.groovy.runtime.InvokerHelper
+import org.codehaus.groovy.runtime.MetaClassHelper
+import org.codehaus.groovy.runtime.StringGroovyMethods
+
+import java.lang.annotation.Annotation
+import java.lang.reflect.Array
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+
+/**
+ * Provides a builder to assist the processing of command line arguments.
+ * Two styles are supported: dynamic api style (declarative method calls 
provide a mini DSL for describing options)
+ * and annotation style (annotations on an interface or class describe 
options).
+ * <p>
+ * <b>Dynamic api style</b>
+ * <p>
+ * Typical usage (emulate partial arg processing of unix command: ls -alt 
*.groovy):
+ * <pre>
+ * def cli = new CliBuilder(usage:'ls')
+ * cli.a('display all files')
+ * cli.l('use a long listing format')
+ * cli.t('sort by modification time')
+ * def options = cli.parse(args)
+ * assert options // would be null (false) on failure
+ * assert options.arguments() == ['*.groovy']
+ * assert options.a && options.l && options.t
+ * </pre>
+ * The usage message for this example (obtained using 
<code>cli.usage()</code>) is shown below:
+ * <pre>
+ * usage: ls
+ *  -a   display all files
+ *  -l   use a long listing format
+ *  -t   sort by modification time
+ * </pre>
+ * An underlying parser that supports what is called argument 'bursting' is 
used
+ * by default. Bursting would convert '-alt' into '-a -l -t' provided no long
+ * option exists with value 'alt' and provided that none of 'a', 'l' or 't'
+ * takes an argument (in fact the last one is allowed to take an argument).
+ * The bursting behavior can be turned off by using an
+ * alternate underlying parser. The simplest way to achieve this is by using
+ * the deprecated GnuParser from Commons CLI with the parser property on the 
CliBuilder,
+ * i.e. include <code>parser: new GnuParser()</code> in the constructor call.
+ * <p>
+ * Another example (partial emulation of arg processing for 'ant' command 
line):
+ * <pre>
+ * def cli = new CliBuilder(usage:'ant [options] [targets]',
+ *                          header:'Options:')
+ * cli.help('print this message')
+ * cli.logfile(args:1, argName:'file', 'use given file for log')
+ * cli.D(args:2, valueSeparator:'=', argName:'property=value',
+ *       'use value for given property')
+ * def options = cli.parse(args)
+ * ...
+ * </pre>
+ * Usage message would be:
+ * <pre>
+ * usage: ant [options] [targets]
+ * Options:
+ *  -D &lt;property=value>   use value for given property
+ *  -help                 print this message
+ *  -logfile &lt;file>       use given file for log
+ * </pre>
+ * And if called with the following arguments '-logfile foo -Dbar=baz target'
+ * then the following assertions would be true:
+ * <pre>
+ * assert options // would be null (false) on failure
+ * assert options.arguments() == ['target']
+ * assert options.Ds == ['bar', 'baz']
+ * assert options.logfile == 'foo'
+ * </pre>
+ * Note the use of some special notation. By adding 's' onto an option
+ * that may appear multiple times and has an argument or as in this case
+ * uses a valueSeparator to separate multiple argument values
+ * causes the list of associated argument values to be returned.
+ * <p>
+ * Another example showing long options (partial emulation of arg processing 
for 'curl' command line):
+ * <pre>
+ * def cli = new CliBuilder(usage:'curl [options] &lt;url&gt;')
+ * cli._(longOpt:'basic', 'Use HTTP Basic Authentication')
+ * cli.d(longOpt:'data', args:1, argName:'data', 'HTTP POST data')
+ * cli.G(longOpt:'get', 'Send the -d data with a HTTP GET')
+ * cli.q('If used as the first parameter disables .curlrc')
+ * cli._(longOpt:'url', args:1, argName:'URL', 'Set URL to work with')
+ * </pre>
+ * Which has the following usage message:
+ * <pre>
+ * usage: curl [options] &lt;url>
+ *     --basic         Use HTTP Basic Authentication
+ *  -d,--data &lt;data>   HTTP POST data
+ *  -G,--get           Send the -d data with a HTTP GET
+ *  -q                 If used as the first parameter disables .curlrc
+ *     --url &lt;URL>     Set URL to work with
+ * </pre>
+ * This example shows a common convention. When mixing short and long names, 
the
+ * short names are often one character in size. One character options with
+ * arguments don't require a space between the option and the argument, e.g.
+ * <code>-Ddebug=true</code>. The example also shows
+ * the use of '_' when no short option is applicable.
+ * <p>
+ * Also note that '_' was used multiple times. This is supported but if
+ * any other shortOpt or any longOpt is repeated, then the behavior is 
undefined.
+ * <p>
+ * Short option names may not contain a hyphen. If a long option name contains 
a hyphen, e.g. '--max-wait' then you can either
+ * use the long hand method call <code>options.hasOption('max-wait')</code> or 
surround
+ * the option name in quotes, e.g. <code>options.'max-wait'</code>.
+ * <p>
+ * Although CliBuilder on the whole hides away the underlying library used
+ * for processing the arguments, it does provide some hooks which let you
+ * make use of the underlying library directly should the need arise. For
+ * example, the last two lines of the 'curl' example above could be replaced
+ * with the following:
+ * <pre>
+ * import org.apache.commons.cli.*
+ * ... as before ...
+ * cli << new Option('q', false, 'If used as the first parameter disables 
.curlrc')
+ * cli << Option.builder().longOpt('url').hasArg().argName('URL').
+ *                      desc('Set URL to work with').build()
+ * ...
+ * </pre>
+ *
+ * CliBuilder also supports Argument File processing. If an argument starts 
with
+ * an '@' character followed by a filename, then the contents of the file with 
name
+ * filename are placed into the command line. The feature can be turned off by
+ * setting expandArgumentFiles to false. If turned on, you can still pass a 
real
+ * parameter with an initial '@' character by escaping it with an additional 
'@'
+ * symbol, e.g. '@@foo' will become '@foo' and not be subject to expansion. As 
an
+ * example, if the file temp.args contains the content:
+ * <pre>
+ * -arg1
+ * paramA
+ * paramB paramC
+ * </pre>
+ * Then calling the command line with:
+ * <pre>
+ * someCommand @temp.args -arg2 paramD
+ * </pre>
+ * Is the same as calling this:
+ * <pre>
+ * someCommand -arg1 paramA paramB paramC -arg2 paramD
+ * </pre>
+ * This feature is particularly useful on operating systems which place 
limitations
+ * on the size of the command line (e.g. Windows). The feature is similar to
+ * the 'Command Line Argument File' processing supported by javadoc and javac.
+ * Consult the corresponding documentation for those tools if you wish to see 
further examples.
+ * <p>
+ * <b>Supported Option Properties</b>:
+ * <pre>
+ *   argName:        String
+ *   longOpt:        String
+ *   args:           int or String
+ *   optionalArg:    boolean
+ *   required:       boolean
+ *   type:           Class
+ *   valueSeparator: char
+ *   convert:        Closure
+ *   defaultValue:   String
+ * </pre>
+ * See {@link org.apache.commons.cli.Option} for the meaning of most of these 
properties
+ * and {@link CliBuilderTest} for further examples.
+ * <p>
+ * <b>Annotation style with an interface</b>
+ * <p>
+ * With this style an interface is defined containing an annotated method for 
each option.
+ * It might look like this (following roughly the earlier 'ls' example):
+ * <pre>
+ * import groovy.cli.Option
+ * import groovy.cli.Unparsed
+ *
+ * interface OptionInterface {
+ *     @{@link groovy.cli.Option}(shortName='a', description='display all 
files') boolean all()
+ *     @{@link groovy.cli.Option}(shortName='l', description='use a long 
listing format') boolean longFormat()
+ *     @{@link groovy.cli.Option}(shortName='t', description='sort by 
modification time') boolean time()
+ *     @{@link groovy.cli.Unparsed} List remaining()
+ * }
+ * </pre>
+ * Then this description is supplied to CliBuilder during parsing, e.g.:
+ * <pre>
+ * def args = '-alt *.groovy'.split() // normally from commandline itself
+ * def cli = new CliBuilder(usage:'ls')
+ * def options = cli.parseFromSpec(OptionInterface, args)
+ * assert options.remaining() == ['*.groovy']
+ * assert options.all() && options.longFormat() && options.time()
+ * </pre>
+ * <p>
+ * <b>Annotation style with a class</b>
+ * <p>
+ * With this style a user-supplied instance is used. Annotations on that 
instance's class
+ * members (properties and setter methods) indicate how to set options and 
provide the option details
+ * using annotation attributes.
+ * It might look like this (again using the earlier 'ls' example):
+ * <pre>
+ * import groovy.cli.Option
+ * import groovy.cli.Unparsed
+ *
+ * class OptionClass {
+ *     @{@link groovy.cli.Option}(shortName='a', description='display all 
files') boolean all
+ *     @{@link groovy.cli.Option}(shortName='l', description='use a long 
listing format') boolean longFormat
+ *     @{@link groovy.cli.Option}(shortName='t', description='sort by 
modification time') boolean time
+ *     @{@link groovy.cli.Unparsed} List remaining
+ * }
+ * </pre>
+ * Then this description is supplied to CliBuilder during parsing, e.g.:
+ * <pre>
+ * def args = '-alt *.groovy'.split() // normally from commandline itself
+ * def cli = new CliBuilder(usage:'ls')
+ * def options = new OptionClass()
+ * cli.parseFromInstance(options, args)
+ * assert options.remaining == ['*.groovy']
+ * assert options.all && options.longFormat && options.time
+ * </pre>
+ */
+class CliBuilder {
+
+    /**
+     * Usage summary displayed as the first line when <code>cli.usage()</code> 
is called.
+     */
+    String usage = 'groovy'
+
+    /**
+     * Normally set internally but allows you full customisation of the 
underlying processing engine.
+     */
+    CommandLineParser parser = null
+
+    /**
+     * To change from the default PosixParser to the GnuParser, set this to 
false. Ignored if the parser is explicitly set.
+     * @deprecated use the parser option instead with an instance of your 
preferred parser
+     */
+    @Deprecated
+    Boolean posix = null
+
+    /**
+     * Whether arguments of the form '{@code @}<i>filename</i>' will be 
expanded into the arguments contained within the file named <i>filename</i> 
(default true).
+     */
+    boolean expandArgumentFiles = true
+
+    /**
+     * Normally set internally but can be overridden if you want to customise 
how the usage message is displayed.
+     */
+    HelpFormatter formatter = new HelpFormatter()
+
+    /**
+     * Defaults to stdout but you can provide your own PrintWriter if desired.
+     */
+    PrintWriter writer = new PrintWriter(System.out)
+
+    /**
+     * Optional additional message for usage; displayed after the usage 
summary but before the options are displayed.
+     */
+    String header = ''
+
+    /**
+     * Optional additional message for usage; displayed after the options are 
displayed.
+     */
+    String footer = ''
+
+    /**
+     * Indicates that option processing should continue for all arguments even
+     * if arguments not recognized as options are encountered (default true).
+     */
+    boolean stopAtNonOption = true
+
+    /**
+     * Allows customisation of the usage message width.
+     */
+    int width = HelpFormatter.DEFAULT_WIDTH
+
+    /**
+     * Not normally accessed directly but full access to underlying options if 
needed.
+     */
+    Options options = new Options()
+
+    Map<String, TypedOption> savedTypeOptions = new HashMap<String, 
TypedOption>()
+
+    public <T> TypedOption<T> option(Map args, Class<T> type, String 
description) {
+        def name = args.opt ?: '_'
+        args.type = type
+        args.remove('opt')
+        "$name"(args, description)
+    }
+
+    /**
+     * Internal method: Detect option specification method calls.
+     */
+    def invokeMethod(String name, Object args) {
+        if (args instanceof Object[]) {
+            if (args.size() == 1 && (args[0] instanceof String || args[0] 
instanceof GString)) {
+                def option = option(name, [:], args[0])
+                options.addOption(option)
+
+                return create(option, null, null, null)
+            }
+            if (args.size() == 1 && args[0] instanceof CliOption && name == 
'leftShift') {
+                CliOption option = args[0]
+                options.addOption(option)
+                return create(option, null, null, null)
+            }
+            if (args.size() == 2 && args[0] instanceof Map) {
+                def convert = args[0].remove('convert')
+                def type = args[0].remove('type')
+                def defaultValue = args[0].remove('defaultValue')
+                if (type && !(type instanceof Class)) {
+                    throw new CliBuilderException("'type' must be a Class")
+                }
+                if ((convert || type) && !args[0].containsKey('args') &&
+                        type?.simpleName?.toLowerCase() != 'boolean') {
+                    args[0].args = 1
+                }
+                def option = option(name, args[0], args[1])
+                options.addOption(option)
+                return create(option, type, defaultValue, convert)
+            }
+        }
+        return InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
+    }
+
+    /**
+     * Make options accessible from command line args with parser.
+     * Returns null on bad command lines after displaying usage message.
+     */
+    OptionAccessor parse(args) {
+        if (expandArgumentFiles) args = expandArgumentFiles(args)
+        if (!parser) {
+            parser = posix != null && posix == false ? new GnuParser() : new 
DefaultParser()
+        }
+        try {
+            def accessor = new OptionAccessor(
+                    parser.parse(options, args as String[], stopAtNonOption))
+            accessor.savedTypeOptions = savedTypeOptions
+            return accessor
+        } catch (ParseException pe) {
+            writer.println("error: " + pe.message)
+            usage()
+            return null
+        }
+    }
+
+    /**
+     * Print the usage message with writer (default: System.out) and formatter 
(default: HelpFormatter)
+     */
+    void usage() {
+        formatter.printHelp(writer, width, usage, header, options, 
HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, footer)
+        writer.flush()
+    }
+
+    /**
+     * Given an interface containing members with annotations, derive
+     * the options specification.
+     *
+     * @param optionsClass
+     * @param args
+     * @return an instance containing the processed options
+     */
+    public <T> T parseFromSpec(Class<T> optionsClass, String[] args) {
+        addOptionsFromAnnotations(optionsClass, false)
+        def cli = parse(args)
+        def cliOptions = [:]
+        setOptionsFromAnnotations(cli, optionsClass, cliOptions, false)
+        cliOptions as T
+    }
+
+    /**
+     * Given an instance containing members with annotations, derive
+     * the options specification.
+     *
+     * @param optionInstance
+     * @param args
+     * @return the options instance populated with the processed options
+     */
+    public <T> T parseFromInstance(T optionInstance, args) {
+        addOptionsFromAnnotations(optionInstance.getClass(), true)
+        def cli = parse(args)
+        setOptionsFromAnnotations(cli, optionInstance.getClass(), 
optionInstance, true)
+        optionInstance
+    }
+
+    void addOptionsFromAnnotations(Class optionClass, boolean namesAreSetters) 
{
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method 
m ->
+            Annotation annotation = m.getAnnotation(Option)
+            def typedOption = processAddAnnotation(annotation, m, 
namesAreSetters)
+            options.addOption(typedOption.cliOption)
+        }
+
+        def optionFields = optionClass.declaredFields.findAll { 
it.getAnnotation(Option) }
+        if (optionClass.isInterface() && !optionFields.isEmpty()) {
+            throw new CliBuilderException("@Option only allowed on methods in 
interface " + optionClass.simpleName)
+        }
+        optionFields.each { Field f ->
+            Annotation annotation = f.getAnnotation(Option)
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            def typedOption = processAddAnnotation(annotation, m, true)
+            options.addOption(typedOption.cliOption)
+        }
+    }
+
+    private TypedOption processAddAnnotation(Option annotation, Method m, 
boolean namesAreSetters) {
+        String shortName = annotation.shortName()
+        String description = annotation.description()
+        String defaultValue = annotation.defaultValue()
+        char valueSeparator = 0
+        if (annotation.valueSeparator()) valueSeparator = 
annotation.valueSeparator() as char
+        boolean optionalArg = annotation.optionalArg()
+        Integer numberOfArguments = annotation.numberOfArguments()
+        String numberOfArgumentsString = annotation.numberOfArgumentsString()
+        Class convert = annotation.convert()
+        if (convert == Undefined.CLASS) {
+            convert = null
+        }
+        Map names = calculateNames(annotation.longName(), shortName, m, 
namesAreSetters)
+        def builder = names.short ? CliOption.builder(names.short) : 
CliOption.builder()
+        if (names.long) {
+            builder.longOpt(names.long)
+        }
+        if (numberOfArguments != 1) {
+            if (numberOfArgumentsString) {
+                throw new CliBuilderException("You can't specify both 
'numberOfArguments' and 'numberOfArgumentsString'")
+            }
+        }
+        def details = [:]
+        Class type = namesAreSetters ? (m.parameterTypes.size() > 0 ? 
m.parameterTypes[0] : null) : m.returnType
+        if (optionalArg && (!type || !type.isArray())) {
+            throw new CliBuilderException("Attempted to set optional argument 
for non array type")
+        }
+        def isFlag = type.simpleName.toLowerCase() == 'boolean'
+        if (numberOfArgumentsString) {
+            details.args = numberOfArgumentsString
+            details = adjustDetails(details)
+            if (details.optionalArg) optionalArg = true
+        } else {
+            details.args = isFlag ? 0 : numberOfArguments
+        }
+        if (details?.args == 0 && !(isFlag || type.name == 
'java.lang.Object')) {
+            throw new CliBuilderException("Flag '${names.long ?: names.short}' 
must be Boolean or Object")
+        }
+        if (description) builder.desc(description)
+        if (valueSeparator) builder.valueSeparator(valueSeparator)
+        if (type) {
+            if (isFlag && details.args == 1) {
+                // special flag: treat like normal not boolean expecting 
explicit 'true' or 'false' param
+                isFlag = false
+            }
+            if (!isFlag) {
+                builder.hasArg(true)
+                if (details.containsKey('args')) 
builder.numberOfArgs(details.args)
+            }
+            if (type.isArray()) {
+                builder.optionalArg(optionalArg)
+            }
+        }
+        def typedOption = create(builder.build(), convert ? null : type, 
defaultValue, convert)
+        typedOption
+    }
+
+    private TypedOption create(CliOption o, Class theType, defaultValue, 
convert) {
+        Map<String, Object> result = new TypedOption<Object>()
+        o.with {
+            if (opt != null) result.put("opt", opt)
+            result.put("longOpt", longOpt)
+            result.put("cliOption", o)
+            if (defaultValue) {
+                result.put("defaultValue", defaultValue)
+            }
+            if (convert) {
+                if (theType) {
+                    throw new CliBuilderException("You can't specify 'type' 
when using 'convert'")
+                }
+                result.put("convert", convert)
+                result.put("type", convert instanceof Class ? convert : 
convert.getClass())
+            } else {
+                result.put("type", theType)
+            }
+        }
+        savedTypeOptions[o.longOpt ?: o.opt] = result
+        result
+    }
+
+    def setOptionsFromAnnotations(def cli, Class optionClass, Object t, 
boolean namesAreSetters) {
+        optionClass.methods.findAll{ it.getAnnotation(Option) }.each { Method 
m ->
+            Annotation annotation = m.getAnnotation(Option)
+            Map names = calculateNames(annotation.longName(), 
annotation.shortName(), m, namesAreSetters)
+            processSetAnnotation(m, t, names.long ?: names.short, cli, 
namesAreSetters)
+        }
+        optionClass.declaredFields.findAll { it.getAnnotation(Option) }.each { 
Field f ->
+            Annotation annotation = f.getAnnotation(Option)
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            Map names = calculateNames(annotation.longName(), 
annotation.shortName(), m, true)
+            processSetAnnotation(m, t, names.long ?: names.short, cli, true)
+        }
+        def remaining = cli.arguments()
+        optionClass.methods.findAll{ it.getAnnotation(Unparsed) }.each { 
Method m ->
+            processSetRemaining(m, remaining, t, cli, namesAreSetters)
+        }
+        optionClass.declaredFields.findAll{ it.getAnnotation(Unparsed) }.each 
{ Field f ->
+            String setterName = "set" + 
MetaClassHelper.capitalize(f.getName());
+            Method m = optionClass.getMethod(setterName, f.getType())
+            processSetRemaining(m, remaining, t, cli, namesAreSetters)
+        }
+    }
+
+    private void processSetRemaining(Method m, remaining, Object t, cli, 
boolean namesAreSetters) {
+        def resultType = namesAreSetters ? m.parameterTypes[0] : m.returnType
+        def isTyped = resultType?.isArray()
+        def result
+        def type = null
+        if (isTyped) {
+            type = resultType.componentType
+            result = remaining.collect{ cli.getValue(type, it, null) }
+        } else {
+            result = remaining.toList()
+        }
+        if (namesAreSetters) {
+            m.invoke(t, isTyped ? [result.toArray(Array.newInstance(type, 
result.size()))] as Object[] : result)
+        } else {
+            Map names = calculateNames("", "", m, namesAreSetters)
+            t.put(names.long, { -> result })
+        }
+    }
+
+    private void processSetAnnotation(Method m, Object t, String name, cli, 
boolean namesAreSetters) {
+        def conv = savedTypeOptions[name]?.convert
+        if (conv && conv instanceof Class) {
+            savedTypeOptions[name].convert = conv.newInstance(t, t)
+        }
+        boolean hasArg = savedTypeOptions[name]?.cliOption?.numberOfArgs == 1
+        boolean noArg = savedTypeOptions[name]?.cliOption?.numberOfArgs == 0
+        if (namesAreSetters) {
+            def isBoolArg = m.parameterTypes.size() > 0 && 
m.parameterTypes[0].simpleName.toLowerCase() == 'boolean'
+            boolean isFlag = (isBoolArg && !hasArg) || noArg
+            if (cli.hasOption(name) || isFlag || cli.defaultValue(name)) {
+                m.invoke(t, [isFlag ? cli.hasOption(name) :
+                                     cli.hasOption(name) ? optionValue(cli, 
name) : cli.defaultValue(name)] as Object[])
+            }
+        } else {
+            def isBoolRetType = m.returnType.simpleName.toLowerCase() == 
'boolean'
+            boolean isFlag = (isBoolRetType && !hasArg) || noArg
+            t.put(m.getName(), cli.hasOption(name) ?
+                    { -> isFlag ? true : optionValue(cli, name) } :
+                    { -> isFlag ? false : cli.defaultValue(name) })
+        }
+    }
+
+    private optionValue(cli, String name) {
+        if (savedTypeOptions.containsKey(name)) {
+            return cli.getOptionValue(savedTypeOptions[name])
+        }
+        cli[name]
+    }
+
+    private Map calculateNames(String longName, String shortName, Method m, 
boolean namesAreSetters) {
+        boolean useShort = longName == '_'
+        if (longName == '_') longName = ""
+        def result = longName
+        if (!longName) {
+            result = m.getName()
+            if (namesAreSetters && result.startsWith("set")) {
+                result = 
MetaClassHelper.convertPropertyName(result.substring(3))
+            }
+        }
+        [long: useShort ? "" : result, short: (useShort && !shortName) ? 
result : shortName]
+    }
+
+    // implementation details -------------------------------------
+
+    /**
+     * Internal method: How to create an option from the specification.
+     */
+    CliOption option(shortname, Map details, info) {
+        CliOption option
+        if (shortname == '_') {
+            option = 
CliOption.builder().desc(info).longOpt(details.longOpt).build()
+            details.remove('longOpt')
+        } else {
+            option = new CliOption(shortname, info)
+        }
+        adjustDetails(details).each { key, value ->
+            option[key] = value
+        }
+        return option
+    }
+
+    static Map adjustDetails(Map m) {
+        m.collectMany { k, v ->
+            if (k == 'args' && v == '+') {
+                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES]]
+            } else if (k == 'args' && v == '*') {
+                [[args: org.apache.commons.cli.Option.UNLIMITED_VALUES,
+                  optionalArg: true]]
+            } else if (k == 'args' && v instanceof String) {
+                [[args: Integer.parseInt(v)]]
+            } else {
+                [[(k): v]]
+            }
+        }.sum()
+    }
+
+    static expandArgumentFiles(args) throws IOException {
+        def result = []
+        for (arg in args) {
+            if (arg && arg != '@' && arg[0] == '@') {
+                arg = arg.substring(1)
+                if (arg[0] != '@') {
+                    expandArgumentFile(arg, result)
+                    continue
+                }
+            }
+            result << arg
+        }
+        return result
+    }
+
+    private static expandArgumentFile(name, args) throws IOException {
+        def charAsInt = { String s -> s.toCharacter() as int }
+        new File(name).withReader { r ->
+            new StreamTokenizer(r).with {
+                resetSyntax()
+                wordChars(charAsInt(' '), 255)
+                whitespaceChars(0, charAsInt(' '))
+                commentChar(charAsInt('#'))
+                quoteChar(charAsInt('"'))
+                quoteChar(charAsInt('\''))
+                while (nextToken() != StreamTokenizer.TT_EOF) {
+                    args << sval
+                }
+            }
+        }
+    }
+
+}
+
+class OptionAccessor {
+    CommandLine commandLine
+    Map<String, TypedOption> savedTypeOptions
+
+    OptionAccessor(CommandLine commandLine) {
+        this.commandLine = commandLine
+    }
+
+    boolean hasOption(TypedOption typedOption) {
+        commandLine.hasOption(typedOption.longOpt ?: typedOption.opt)
+    }
+
+    public <T> T defaultValue(String name) {
+        Class<T> type = savedTypeOptions[name]?.type
+        String value = savedTypeOptions[name]?.defaultValue() ? 
savedTypeOptions[name].defaultValue() : null
+        return (T) value ? getTypedValue(type, name, value) : null
+    }
+
+    public <T> T getOptionValue(TypedOption<T> typedOption) {
+        getOptionValue(typedOption, null)
+    }
+
+    public <T> T getOptionValue(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.longOpt ?: typedOption.opt
+        if (commandLine.hasOption(optionName)) {
+            if (typedOption.containsKey('type') && typedOption.type.isArray()) 
{
+                def compType = typedOption.type.componentType
+                return (T) getTypedValuesFromName(optionName, compType)
+            }
+            return getTypedValueFromName(optionName)
+        }
+        return defaultValue
+    }
+
+    private <T> T[] getTypedValuesFromName(String optionName, Class<T> 
compType) {
+        CliOption option = commandLine.options.find{ it.longOpt == optionName }
+        T[] result = null
+        if (option) {
+            int count = 0
+            def optionValues = commandLine.getOptionValues(optionName)
+            for (String optionValue : optionValues) {
+                if (result == null) {
+                    result = (T[]) Array.newInstance(compType, 
optionValues.length)
+                }
+                result[count++] = (T) getTypedValue(compType, optionName, 
optionValue)
+            }
+        }
+        if (result == null) {
+            result = (T[]) Array.newInstance(compType, 0)
+        }
+        return result
+    }
+
+    public <T> T getAt(TypedOption<T> typedOption) {
+        getAt(typedOption, null)
+    }
+
+    public <T> T getAt(TypedOption<T> typedOption, T defaultValue) {
+        String optionName = (String) typedOption.longOpt ?: typedOption.opt
+        if (savedTypeOptions.containsKey(optionName)) {
+            return getTypedValueFromName(optionName)
+        }
+        return defaultValue
+    }
+
+    private <T> T getTypedValueFromName(String optionName) {
+        Class type = savedTypeOptions[optionName].type
+        String optionValue = commandLine.getOptionValue(optionName)
+        return (T) getTypedValue(type, optionName, optionValue)
+    }
+
+    private <T> T getTypedValue(Class<T> type, String optionName, String 
optionValue) {
+        if (savedTypeOptions[optionName]?.cliOption?.numberOfArgs == 0) {
+            return (T) commandLine.hasOption(optionName)
+        }
+        def convert = savedTypeOptions[optionName]?.convert
+        return getValue(type, optionValue, convert)
+    }
+
+    private <T> T getValue(Class<T> type, String optionValue, Closure convert) 
{
+        if (!type) {
+            return (T) optionValue
+        }
+        if (Closure.isAssignableFrom(type) && convert) {
+            return (T) convert(optionValue)
+        }
+        if (type?.simpleName?.toLowerCase() == 'boolean') {
+            return (T) Boolean.parseBoolean(optionValue)
+        }
+        StringGroovyMethods.asType(optionValue, (Class<T>) type)
+    }
+
+    def invokeMethod(String name, Object args) {
+        return 
InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, name, args)
+    }
+
+    def getProperty(String name) {
+        if (!savedTypeOptions.containsKey(name)) {
+            def alt = savedTypeOptions.find{ it.value.opt == name }
+            if (alt) name = alt.key
+        }
+        def methodname = 'getOptionValue'
+        Class type = savedTypeOptions[name]?.type
+        def foundArray = type?.isArray()
+        if (name.size() > 1 && name.endsWith('s')) {
+            def singularName = name[0..-2]
+            if (commandLine.hasOption(singularName) || foundArray) {
+                name = singularName
+                methodname += 's'
+                type = savedTypeOptions[name]?.type
+            }
+        }
+        if (type?.isArray()) {
+            methodname = 'getOptionValues'
+        }
+        if (name.size() == 1) name = name as char
+        def result = 
InvokerHelper.getMetaClass(commandLine).invokeMethod(commandLine, methodname, 
name)
+        if (result != null) {
+            if (result instanceof String[]) {
+                result = result.collect{ type ? getTypedValue(type.isArray() ? 
type.componentType : type, name, it) : it }
+            } else {
+                if (type) result = getTypedValue(type, name, result)
+            }
+        } else if (type?.simpleName != 'boolean' && 
savedTypeOptions[name]?.defaultValue) {
+            result = getTypedValue(type, name, 
savedTypeOptions[name].defaultValue)
+        } else {
+            result = commandLine.hasOption(name)
+        }
+        return result
+    }
+
+    List<String> arguments() {
+        commandLine.args.toList()
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/0ad8c07c/src/main/groovy/groovy/util/ClosureComparator.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/ClosureComparator.java 
b/src/main/groovy/groovy/util/ClosureComparator.java
new file mode 100644
index 0000000..dc70ea6
--- /dev/null
+++ b/src/main/groovy/groovy/util/ClosureComparator.java
@@ -0,0 +1,45 @@
+/*
+ *  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 org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
+
+import java.io.Serializable;
+import java.util.Comparator;
+
+/**
+ * A Comparator which uses a closure to compare 2 values being equal
+ * 
+ * @author <a href="mailto:[email protected]";>James Strachan</a>
+ */
+public class ClosureComparator<T> implements Comparator<T>, Serializable {
+
+    private static final long serialVersionUID = -4593521535656429522L;
+    Closure closure;
+
+    public ClosureComparator(Closure closure) {
+        this.closure = closure;
+    }
+
+    public int compare(T object1, T object2) {
+        Object value = closure.call(object1, object2);
+        return DefaultTypeTransformation.intUnbox(value);
+    }
+}

Reply via email to