http://git-wip-us.apache.org/repos/asf/groovy/blob/d638ca43/src/main/groovy/util/BuilderSupport.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/BuilderSupport.java b/src/main/groovy/util/BuilderSupport.java deleted file mode 100644 index f634f1f..0000000 --- a/src/main/groovy/util/BuilderSupport.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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/d638ca43/src/main/groovy/util/CharsetToolkit.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/CharsetToolkit.java b/src/main/groovy/util/CharsetToolkit.java deleted file mode 100644 index e127459..0000000 --- a/src/main/groovy/util/CharsetToolkit.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * 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/d638ca43/src/main/groovy/util/CliBuilder.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/CliBuilder.groovy b/src/main/groovy/util/CliBuilder.groovy deleted file mode 100644 index bc7d44a..0000000 --- a/src/main/groovy/util/CliBuilder.groovy +++ /dev/null @@ -1,798 +0,0 @@ -/* - * 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 <property=value> use value for given property - * -help print this message - * -logfile <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] <url>') - * 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] <url> - * --basic Use HTTP Basic Authentication - * -d,--data <data> HTTP POST data - * -G,--get Send the -d data with a HTTP GET - * -q If used as the first parameter disables .curlrc - * --url <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/d638ca43/src/main/groovy/util/ClosureComparator.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/ClosureComparator.java b/src/main/groovy/util/ClosureComparator.java deleted file mode 100644 index dc70ea6..0000000 --- a/src/main/groovy/util/ClosureComparator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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); - } -} http://git-wip-us.apache.org/repos/asf/groovy/blob/d638ca43/src/main/groovy/util/ConfigObject.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/ConfigObject.java b/src/main/groovy/util/ConfigObject.java deleted file mode 100644 index c76bc97..0000000 --- a/src/main/groovy/util/ConfigObject.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * 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.GroovyObjectSupport; -import groovy.lang.GroovyRuntimeException; -import groovy.lang.Writable; -import org.codehaus.groovy.runtime.DefaultGroovyMethods; -import org.codehaus.groovy.runtime.InvokerHelper; -import org.codehaus.groovy.runtime.StringGroovyMethods; -import org.codehaus.groovy.syntax.Types; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.StringWriter; -import java.io.Writer; -import java.net.URL; -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Properties; -import java.util.Set; - -/** - * A ConfigObject at a simple level is a Map that creates configuration entries (other ConfigObjects) when referencing them. - * This means that navigating to foo.bar.stuff will not return null but nested ConfigObjects which are of course empty maps - * The Groovy truth can be used to check for the existence of "real" entries. - * - * @author Graeme Rocher - * @author Guillaume Laforge (rewrite in Java related to security constraints on Google App Engine) - * @since 1.5 - */ -public class ConfigObject extends GroovyObjectSupport implements Writable, Map, Cloneable { - - static final Collection<String> KEYWORDS = Types.getKeywords(); - - static final String TAB_CHARACTER = "\t"; - - /** - * The config file that was used when parsing this ConfigObject - */ - private URL configFile; - - private HashMap delegateMap = new LinkedHashMap(); - - public ConfigObject(URL file) { - this.configFile = file; - } - - public ConfigObject() { - this(null); - } - - public URL getConfigFile() { - return configFile; - } - - public void setConfigFile(URL configFile) { - this.configFile = configFile; - } - - /** - * Writes this config object into a String serialized representation which can later be parsed back using the parse() - * method - * - * @see groovy.lang.Writable#writeTo(java.io.Writer) - */ - public Writer writeTo(Writer outArg) throws IOException { - BufferedWriter out = new BufferedWriter(outArg); - try { - writeConfig("", this, out, 0, false); - } finally { - out.flush(); - } - - return outArg; - } - - - /** - * Overrides the default getProperty implementation to create nested ConfigObject instances on demand - * for non-existent keys - */ - public Object getProperty(String name) { - if ("configFile".equals(name)) - return this.configFile; - - if (!containsKey(name)) { - ConfigObject prop = new ConfigObject(this.configFile); - put(name, prop); - - return prop; - } - - return get(name); - } - - /** - * A ConfigObject is a tree structure consisting of nested maps. This flattens the maps into - * a single level structure like a properties file - */ - public Map flatten() { - return flatten(null); - } - - /** - * Flattens this ConfigObject populating the results into the target Map - * - * @see ConfigObject#flatten() - */ - public Map flatten(Map target) { - if (target == null) - target = new ConfigObject(); - populate("", target, this); - - return target; - } - - /** - * Merges the given map with this ConfigObject overriding any matching configuration entries in this ConfigObject - * - * @param other The ConfigObject to merge with - * @return The result of the merge - */ - public Map merge(ConfigObject other) { - return doMerge(this, other); - } - - - /** - * Converts this ConfigObject into a the java.util.Properties format, flattening the tree structure beforehand - * - * @return A java.util.Properties instance - */ - public Properties toProperties() { - Properties props = new Properties(); - flatten(props); - - props = convertValuesToString(props); - - return props; - } - - /** - * Converts this ConfigObject ino the java.util.Properties format, flatten the tree and prefixing all entries with the given prefix - * - * @param prefix The prefix to append before property entries - * @return A java.util.Properties instance - */ - public Properties toProperties(String prefix) { - Properties props = new Properties(); - populate(prefix + ".", props, this); - - props = convertValuesToString(props); - - return props; - } - - private Map doMerge(Map config, Map other) { - for (Object o : other.entrySet()) { - Map.Entry next = (Map.Entry) o; - Object key = next.getKey(); - Object value = next.getValue(); - - Object configEntry = config.get(key); - - if (configEntry == null) { - config.put(key, value); - - continue; - } else { - if (configEntry instanceof Map && !((Map) configEntry).isEmpty() && value instanceof Map) { - // recur - doMerge((Map) configEntry, (Map) value); - } else { - config.put(key, value); - } - } - } - - return config; - } - - private void writeConfig(String prefix, ConfigObject map, BufferedWriter out, int tab, boolean apply) throws IOException { - String space = apply ? StringGroovyMethods.multiply(TAB_CHARACTER, tab) : ""; - - for (Object o1 : map.keySet()) { - String key = (String) o1; - Object v = map.get(key); - - if (v instanceof ConfigObject) { - ConfigObject value = (ConfigObject) v; - - if (!value.isEmpty()) { - - Object dotsInKeys = null; - for (Object o : value.entrySet()) { - Entry e = (Entry) o; - String k = (String) e.getKey(); - if (k.indexOf('.') > -1) { - dotsInKeys = e; - break; - } - } - - int configSize = value.size(); - Object firstKey = value.keySet().iterator().next(); - Object firstValue = value.values().iterator().next(); - - int firstSize; - if (firstValue instanceof ConfigObject) { - firstSize = ((ConfigObject) firstValue).size(); - } else { - firstSize = 1; - } - - if (configSize == 1 || DefaultGroovyMethods.asBoolean(dotsInKeys)) { - if (firstSize == 1 && firstValue instanceof ConfigObject) { - key = KEYWORDS.contains(key) ? InvokerHelper.inspect(key) : key; - String writePrefix = prefix + key + "." + firstKey + "."; - writeConfig(writePrefix, (ConfigObject) firstValue, out, tab, true); - } else if (!DefaultGroovyMethods.asBoolean(dotsInKeys) && firstValue instanceof ConfigObject) { - writeNode(key, space, tab, value, out); - } else { - for (Object j : value.keySet()) { - Object v2 = value.get(j); - Object k2 = ((String) j).indexOf('.') > -1 ? InvokerHelper.inspect(j) : j; - if (v2 instanceof ConfigObject) { - key = KEYWORDS.contains(key) ? InvokerHelper.inspect(key) : key; - writeConfig(prefix + key, (ConfigObject) v2, out, tab, false); - } else { - writeValue(key + "." + k2, space, prefix, v2, out); - } - } - } - } else { - writeNode(key, space, tab, value, out); - } - } - } else { - writeValue(key, space, prefix, v, out); - } - } - } - - private static void writeValue(String key, String space, String prefix, Object value, BufferedWriter out) throws IOException { -// key = key.indexOf('.') > -1 ? InvokerHelper.inspect(key) : key; - boolean isKeyword = KEYWORDS.contains(key); - key = isKeyword ? InvokerHelper.inspect(key) : key; - - if (!StringGroovyMethods.asBoolean(prefix) && isKeyword) prefix = "this."; - out.append(space).append(prefix).append(key).append('=').append(InvokerHelper.inspect(value)); - out.newLine(); - } - - private void writeNode(String key, String space, int tab, ConfigObject value, BufferedWriter out) throws IOException { - key = KEYWORDS.contains(key) ? InvokerHelper.inspect(key) : key; - out.append(space).append(key).append(" {"); - out.newLine(); - writeConfig("", value, out, tab + 1, true); - out.append(space).append('}'); - out.newLine(); - } - - private static Properties convertValuesToString(Map props) { - Properties newProps = new Properties(); - - for (Object o : props.entrySet()) { - Map.Entry next = (Map.Entry) o; - Object key = next.getKey(); - Object value = next.getValue(); - - newProps.put(key, value != null ? value.toString() : null); - } - - return newProps; - } - - private void populate(String suffix, Map config, Map map) { - for (Object o : map.entrySet()) { - Map.Entry next = (Map.Entry) o; - Object key = next.getKey(); - Object value = next.getValue(); - - if (value instanceof Map) { - populate(suffix + key + ".", config, (Map) value); - } else { - try { - config.put(suffix + key, value); - } catch (NullPointerException e) { - // it is idiotic story but if config map doesn't allow null values (like Hashtable) - // we can't do too much - } - } - } - } - - public int size() { - return delegateMap.size(); - } - - public boolean isEmpty() { - return delegateMap.isEmpty(); - } - - public boolean containsKey(Object key) { - return delegateMap.containsKey(key); - } - - public boolean containsValue(Object value) { - return delegateMap.containsValue(value); - } - - public Object get(Object key) { - return delegateMap.get(key); - } - - public Object put(Object key, Object value) { - return delegateMap.put(key, value); - } - - public Object remove(Object key) { - return delegateMap.remove(key); - } - - public void putAll(Map m) { - delegateMap.putAll(m); - } - - public void clear() { - delegateMap.clear(); - } - - public Set keySet() { - return delegateMap.keySet(); - } - - public Collection values() { - return delegateMap.values(); - } - - public Set entrySet() { - return delegateMap.entrySet(); - } - - /** - * Returns a shallow copy of this ConfigObject, keys and configuration entries are not cloned. - * @return a shallow copy of this ConfigObject - */ - public ConfigObject clone() { - try { - ConfigObject clone = (ConfigObject) super.clone(); - clone.configFile = configFile; - clone.delegateMap = (LinkedHashMap) delegateMap.clone(); - return clone; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } - - /** - * Checks if a config option is set. Example usage: - * <pre class="groovyTestCase"> - * def config = new ConfigSlurper().parse("foo { password='' }") - * assert config.foo.isSet('password') - * assert config.foo.isSet('username') == false - * </pre> - * - * The check works <b>only</v> for options <b>one</b> block below the current block. - * E.g. <code>config.isSet('foo.password')</code> will always return false. - * - * @param option The name of the option - * @return <code>true</code> if the option is set <code>false</code> otherwise - * @since 2.3.0 - */ - public Boolean isSet(String option) { - if (delegateMap.containsKey(option)) { - Object entry = delegateMap.get(option); - if (!(entry instanceof ConfigObject) || !((ConfigObject) entry).isEmpty()) { - return Boolean.TRUE; - } - } - return Boolean.FALSE; - } - - public String prettyPrint() { - StringWriter sw = new StringWriter(); - try { - writeTo(sw); - } catch (IOException e) { - throw new GroovyRuntimeException(e); - } - - return sw.toString(); - } - - @Override - public String toString() { - StringWriter sw = new StringWriter(); - try { - InvokerHelper.write(sw, this); - } catch (IOException e) { - throw new GroovyRuntimeException(e); - } - - return sw.toString(); - } -} http://git-wip-us.apache.org/repos/asf/groovy/blob/d638ca43/src/main/groovy/util/ConfigSlurper.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/util/ConfigSlurper.groovy b/src/main/groovy/util/ConfigSlurper.groovy deleted file mode 100644 index 20d0723..0000000 --- a/src/main/groovy/util/ConfigSlurper.groovy +++ /dev/null @@ -1,309 +0,0 @@ -/* - * 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.InvokerHelper - -/** - * ConfigSlurper is a utility class for reading configuration files defined in the form of Groovy - * scripts. Configuration settings can be defined using dot notation or scoped using closures: - * - * <pre><code> - * grails.webflow.stateless = true - * smtp { - * mail.host = 'smtp.myisp.com' - * mail.auth.user = 'server' - * } - * resources.URL = "http://localhost:80/resources" - * </code></pre> - * - * Settings can either be bound into nested maps or onto a specified JavaBean instance. - * In the latter case, an error will be thrown if a property cannot be bound. - * - * @author Graeme Rocher - * @author Andres Almiray - * @since 1.5 - */ -class ConfigSlurper { - private static final ENVIRONMENTS_METHOD = 'environments' - GroovyClassLoader classLoader = new GroovyClassLoader() - private Map bindingVars = [:] - - private final Map<String, String> conditionValues = [:] - private final Stack<Map<String, ConfigObject>> conditionalBlocks = new Stack<Map<String,ConfigObject>>() - - ConfigSlurper() { - this('') - } - - /** - * Constructs a new ConfigSlurper instance using the given environment - * - * @param env The Environment to use - */ - ConfigSlurper(String env) { - conditionValues[ENVIRONMENTS_METHOD] = env - } - - void registerConditionalBlock(String blockName, String blockValue) { - if (blockName) { - if (!blockValue) { - conditionValues.remove(blockName) - } else { - conditionValues[blockName] = blockValue - } - } - } - - Map<String, String> getConditionalBlockValues() { - Collections.unmodifiableMap(conditionValues) - } - - String getEnvironment() { - return conditionValues[ENVIRONMENTS_METHOD] - } - - void setEnvironment(String environment) { - conditionValues[ENVIRONMENTS_METHOD] = environment - } - - /** - * Sets any additional variables that should be placed into the binding when evaluating Config scripts - */ - void setBinding(Map vars) { - this.bindingVars = vars - } - - /** - * Parses a ConfigObject instances from an instance of java.util.Properties - * - * @param The java.util.Properties instance - */ - ConfigObject parse(Properties properties) { - ConfigObject config = new ConfigObject() - for (key in properties.keySet()) { - def tokens = key.split(/\./) - - def current = config - def last - def lastToken - def foundBase = false - for (token in tokens) { - if (foundBase) { - // handle not properly nested tokens by ignoring - // hierarchy below this point - lastToken += "." + token - current = last - } else { - last = current - lastToken = token - current = current."${token}" - if (!(current instanceof ConfigObject)) foundBase = true - } - } - - if (current instanceof ConfigObject) { - if (last[lastToken]) { - def flattened = last.flatten() - last.clear() - flattened.each { k2, v2 -> last[k2] = v2 } - last[lastToken] = properties.get(key) - } - else { - last[lastToken] = properties.get(key) - } - } - current = config - } - return config - } - /** - * Parse the given script as a string and return the configuration object - * - * @see ConfigSlurper#parse(groovy.lang.Script) - */ - ConfigObject parse(String script) { - return parse(classLoader.parseClass(script)) - } - - /** - * Create a new instance of the given script class and parse a configuration object from it - * - * @see ConfigSlurper#parse(groovy.lang.Script) - */ - ConfigObject parse(Class scriptClass) { - return parse(scriptClass.newInstance()) - } - - /** - * Parse the given script into a configuration object (a Map) - * (This method creates a new class to parse the script each time it is called.) - * - * @param script The script to parse - * @return A Map of maps that can be navigating with dot de-referencing syntax to obtain configuration entries - */ - ConfigObject parse(Script script) { - return parse(script, null) - } - - /** - * Parses a Script represented by the given URL into a ConfigObject - * - * @param scriptLocation The location of the script to parse - * @return The ConfigObject instance - */ - ConfigObject parse(URL scriptLocation) { - return parse(classLoader.parseClass(scriptLocation.text).newInstance(), scriptLocation) - } - - /** - * Parses the passed groovy.lang.Script instance using the second argument to allow the ConfigObject - * to retain an reference to the original location other Groovy script - * - * @param script The groovy.lang.Script instance - * @param location The original location of the Script as a URL - * @return The ConfigObject instance - */ - ConfigObject parse(Script script, URL location) { - Stack<String> currentConditionalBlock = new Stack<String>() - def config = location ? new ConfigObject(location) : new ConfigObject() - GroovySystem.metaClassRegistry.removeMetaClass(script.class) - def mc = script.class.metaClass - def prefix = "" - LinkedList stack = new LinkedList() - stack << [config: config, scope: [:]] - def pushStack = { co -> - stack << [config: co, scope: stack.last.scope.clone()] - } - def assignName = { name, co -> - def current = stack.last - current.config[name] = co - current.scope[name] = co - } - mc.getProperty = { String name -> - def current = stack.last - def result - if (current.config.get(name)) { - result = current.config.get(name) - } else if (current.scope[name]) { - result = current.scope[name] - } else { - try { - result = InvokerHelper.getProperty(this, name) - } catch (GroovyRuntimeException e) { - result = new ConfigObject() - assignName.call(name, result) - } - } - result - } - - ConfigObject overrides = new ConfigObject() - mc.invokeMethod = { String name, args -> - def result - if (args.length == 1 && args[0] instanceof Closure) { - if (name in conditionValues.keySet()) { - try { - currentConditionalBlock.push(name) - conditionalBlocks.push([:]) - args[0].call() - } finally { - currentConditionalBlock.pop() - for (entry in conditionalBlocks.pop().entrySet()) { - def c = stack.last.config - (c != config? c : overrides).merge(entry.value) - } - } - } else if (currentConditionalBlock.size() > 0) { - String conditionalBlockKey = currentConditionalBlock.peek() - if (name == conditionValues[conditionalBlockKey]) { - def co = new ConfigObject() - conditionalBlocks.peek()[conditionalBlockKey] = co - - pushStack.call(co) - try { - currentConditionalBlock.pop() - args[0].call() - } finally { - currentConditionalBlock.push(conditionalBlockKey) - } - stack.removeLast() - } - } else { - def co - if (stack.last.config.get(name) instanceof ConfigObject) { - co = stack.last.config.get(name) - } else { - co = new ConfigObject() - } - - assignName.call(name, co) - pushStack.call(co) - args[0].call() - stack.removeLast() - } - } else if (args.length == 2 && args[1] instanceof Closure) { - try { - prefix = name + '.' - assignName.call(name, args[0]) - args[1].call() - } finally { prefix = "" } - } else { - MetaMethod mm = mc.getMetaMethod(name, args) - if (mm) { - result = mm.invoke(delegate, args) - } else { - throw new MissingMethodException(name, getClass(), args) - } - } - result - } - script.metaClass = mc - - def setProperty = { String name, value -> - assignName.call(prefix + name, value) - } - def binding = new ConfigBinding(setProperty) - if (this.bindingVars) { - binding.getVariables().putAll(this.bindingVars) - } - script.binding = binding - - script.run() - - config.merge(overrides) - - return config - } -} - -/** - * Since Groovy Script doesn't support overriding setProperty, we use a trick with the Binding to provide this - * functionality - */ -class ConfigBinding extends Binding { - def callable - ConfigBinding(Closure c) { - this.callable = c - } - - void setVariable(String name, Object value) { - callable(name, value) - } -}
