http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/inspect/Inspector.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/inspect/Inspector.java b/src/main/groovy/groovy/inspect/Inspector.java new file mode 100644 index 0000000..f0c94a7 --- /dev/null +++ b/src/main/groovy/groovy/inspect/Inspector.java @@ -0,0 +1,348 @@ +/* + * 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.inspect; + +import groovy.lang.GroovyObject; +import groovy.lang.MetaClass; +import groovy.lang.MetaMethod; +import groovy.lang.PropertyValue; +import org.codehaus.groovy.reflection.CachedClass; +import org.codehaus.groovy.runtime.DefaultGroovyMethods; +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.io.PrintStream; +import java.io.Serializable; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; + +/** + * The Inspector provides a unified access to an object's + * information that can be determined by introspection. + * + * @author Dierk Koenig + */ +public class Inspector { + protected Object objectUnderInspection; + + // Indexes to retrieve Class Property information + public static final int CLASS_PACKAGE_IDX = 0; + public static final int CLASS_CLASS_IDX = 1; + public static final int CLASS_INTERFACE_IDX = 2; + public static final int CLASS_SUPERCLASS_IDX = 3; + public static final int CLASS_OTHER_IDX = 4; + + // Indexes to retrieve field and method information + public static final int MEMBER_ORIGIN_IDX = 0; + public static final int MEMBER_MODIFIER_IDX = 1; + public static final int MEMBER_DECLARER_IDX = 2; + public static final int MEMBER_TYPE_IDX = 3; + public static final int MEMBER_NAME_IDX = 4; + public static final int MEMBER_PARAMS_IDX = 5; + public static final int MEMBER_VALUE_IDX = 5; + public static final int MEMBER_EXCEPTIONS_IDX = 6; + + public static final String NOT_APPLICABLE = "n/a"; + public static final String GROOVY = "GROOVY"; + public static final String JAVA = "JAVA"; + + /** + * @param objectUnderInspection must not be null + */ + public Inspector(Object objectUnderInspection) { + if (null == objectUnderInspection) { + throw new IllegalArgumentException("argument must not be null"); + } + this.objectUnderInspection = objectUnderInspection; + } + + /** + * Get the Class Properties of the object under inspection. + * + * @return String array to be indexed by the CLASS_xxx_IDX constants + */ + public String[] getClassProps() { + String[] result = new String[CLASS_OTHER_IDX + 1]; + Package pack = getClassUnderInspection().getPackage(); + result[CLASS_PACKAGE_IDX] = "package " + ((pack == null) ? NOT_APPLICABLE : pack.getName()); + String modifiers = Modifier.toString(getClassUnderInspection().getModifiers()); + result[CLASS_CLASS_IDX] = modifiers + " class " + shortName(getClassUnderInspection()); + result[CLASS_INTERFACE_IDX] = "implements "; + Class[] interfaces = getClassUnderInspection().getInterfaces(); + for (Class anInterface : interfaces) { + result[CLASS_INTERFACE_IDX] += shortName(anInterface) + " "; + } + result[CLASS_SUPERCLASS_IDX] = "extends " + shortName(getClassUnderInspection().getSuperclass()); + result[CLASS_OTHER_IDX] = "is Primitive: " + getClassUnderInspection().isPrimitive() + + ", is Array: " + getClassUnderInspection().isArray() + + ", is Groovy: " + isGroovy(); + return result; + } + + public boolean isGroovy() { + return GroovyObject.class.isAssignableFrom(getClassUnderInspection()); + } + + /** + * Gets the object being inspected. + * + * @return the object + */ + public Object getObject() { + return objectUnderInspection; + } + + /** + * Get info about usual Java instance and class Methods as well as Constructors. + * + * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants + */ + public Object[] getMethods() { + Method[] methods = getClassUnderInspection().getMethods(); + Constructor[] ctors = getClassUnderInspection().getConstructors(); + Object[] result = new Object[methods.length + ctors.length]; + int resultIndex = 0; + for (; resultIndex < methods.length; resultIndex++) { + Method method = methods[resultIndex]; + result[resultIndex] = methodInfo(method); + } + for (int i = 0; i < ctors.length; i++, resultIndex++) { + Constructor ctor = ctors[i]; + result[resultIndex] = methodInfo(ctor); + } + return result; + } + + /** + * Get info about instance and class Methods that are dynamically added through Groovy. + * + * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants + */ + public Object[] getMetaMethods() { + MetaClass metaClass = InvokerHelper.getMetaClass(objectUnderInspection); + List metaMethods = metaClass.getMetaMethods(); + Object[] result = new Object[metaMethods.size()]; + int i = 0; + for (Iterator iter = metaMethods.iterator(); iter.hasNext(); i++) { + MetaMethod metaMethod = (MetaMethod) iter.next(); + result[i] = methodInfo(metaMethod); + } + return result; + } + + /** + * Get info about usual Java public fields incl. constants. + * + * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants + */ + public Object[] getPublicFields() { + Field[] fields = getClassUnderInspection().getFields(); + Object[] result = new Object[fields.length]; + for (int i = 0; i < fields.length; i++) { + Field field = fields[i]; + result[i] = fieldInfo(field); + } + return result; + } + + /** + * Get info about Properties (Java and Groovy alike). + * + * @return Array of StringArrays that can be indexed with the MEMBER_xxx_IDX constants + */ + public Object[] getPropertyInfo() { + List props = DefaultGroovyMethods.getMetaPropertyValues(objectUnderInspection); + Object[] result = new Object[props.size()]; + int i = 0; + for (Iterator iter = props.iterator(); iter.hasNext(); i++) { + PropertyValue pv = (PropertyValue) iter.next(); + result[i] = fieldInfo(pv); + } + return result; + } + + protected String[] fieldInfo(Field field) { + String[] result = new String[MEMBER_VALUE_IDX + 1]; + result[MEMBER_ORIGIN_IDX] = JAVA; + result[MEMBER_MODIFIER_IDX] = Modifier.toString(field.getModifiers()); + result[MEMBER_DECLARER_IDX] = shortName(field.getDeclaringClass()); + result[MEMBER_TYPE_IDX] = shortName(field.getType()); + result[MEMBER_NAME_IDX] = field.getName(); + try { + result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(field.get(objectUnderInspection)); + } catch (IllegalAccessException e) { + result[MEMBER_VALUE_IDX] = NOT_APPLICABLE; + } + return withoutNulls(result); + } + + protected String[] fieldInfo(PropertyValue pv) { + String[] result = new String[MEMBER_VALUE_IDX + 1]; + result[MEMBER_ORIGIN_IDX] = GROOVY; + result[MEMBER_MODIFIER_IDX] = "public"; + result[MEMBER_DECLARER_IDX] = NOT_APPLICABLE; + result[MEMBER_TYPE_IDX] = shortName(pv.getType()); + result[MEMBER_NAME_IDX] = pv.getName(); + try { + result[MEMBER_VALUE_IDX] = InvokerHelper.inspect(pv.getValue()); + } catch (Exception e) { + result[MEMBER_VALUE_IDX] = NOT_APPLICABLE; + } + return withoutNulls(result); + } + + protected Class getClassUnderInspection() { + return objectUnderInspection.getClass(); + } + + public static String shortName(Class clazz) { + if (null == clazz) return NOT_APPLICABLE; + String className = clazz.getName(); + if (null == clazz.getPackage()) return className; + String packageName = clazz.getPackage().getName(); + int offset = packageName.length(); + if (offset > 0) offset++; + className = className.substring(offset); + return className; + } + + protected String[] methodInfo(Method method) { + String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; + int mod = method.getModifiers(); + result[MEMBER_ORIGIN_IDX] = JAVA; + result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass()); + result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); + result[MEMBER_NAME_IDX] = method.getName(); + result[MEMBER_TYPE_IDX] = shortName(method.getReturnType()); + Class[] params = method.getParameterTypes(); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < params.length; j++) { + sb.append(shortName(params[j])); + if (j < (params.length - 1)) sb.append(", "); + } + result[MEMBER_PARAMS_IDX] = sb.toString(); + sb.setLength(0); + Class[] exceptions = method.getExceptionTypes(); + for (int k = 0; k < exceptions.length; k++) { + sb.append(shortName(exceptions[k])); + if (k < (exceptions.length - 1)) sb.append(", "); + } + result[MEMBER_EXCEPTIONS_IDX] = sb.toString(); + return withoutNulls(result); + } + + protected String[] methodInfo(Constructor ctor) { + String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; + int mod = ctor.getModifiers(); + result[MEMBER_ORIGIN_IDX] = JAVA; + result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); + result[MEMBER_DECLARER_IDX] = shortName(ctor.getDeclaringClass()); + result[MEMBER_TYPE_IDX] = shortName(ctor.getDeclaringClass()); + result[MEMBER_NAME_IDX] = ctor.getName(); + Class[] params = ctor.getParameterTypes(); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < params.length; j++) { + sb.append(shortName(params[j])); + if (j < (params.length - 1)) sb.append(", "); + } + result[MEMBER_PARAMS_IDX] = sb.toString(); + sb.setLength(0); + Class[] exceptions = ctor.getExceptionTypes(); + for (int k = 0; k < exceptions.length; k++) { + sb.append(shortName(exceptions[k])); + if (k < (exceptions.length - 1)) sb.append(", "); + } + result[MEMBER_EXCEPTIONS_IDX] = sb.toString(); + return withoutNulls(result); + } + + protected String[] methodInfo(MetaMethod method) { + String[] result = new String[MEMBER_EXCEPTIONS_IDX + 1]; + int mod = method.getModifiers(); + result[MEMBER_ORIGIN_IDX] = GROOVY; + result[MEMBER_MODIFIER_IDX] = Modifier.toString(mod); + result[MEMBER_DECLARER_IDX] = shortName(method.getDeclaringClass().getTheClass()); + result[MEMBER_TYPE_IDX] = shortName(method.getReturnType()); + result[MEMBER_NAME_IDX] = method.getName(); + CachedClass[] params = method.getParameterTypes(); + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < params.length; j++) { + sb.append(shortName(params[j].getTheClass())); + if (j < (params.length - 1)) sb.append(", "); + } + result[MEMBER_PARAMS_IDX] = sb.toString(); + result[MEMBER_EXCEPTIONS_IDX] = NOT_APPLICABLE; // no exception info for Groovy MetaMethods + return withoutNulls(result); + } + + protected String[] withoutNulls(String[] toNormalize) { + for (int i = 0; i < toNormalize.length; i++) { + String s = toNormalize[i]; + if (null == s) toNormalize[i] = NOT_APPLICABLE; + } + return toNormalize; + } + + public static void print(Object[] memberInfo) { + print(System.out, memberInfo); + } + + static void print(final PrintStream out, Object[] memberInfo) { + for (int i = 0; i < memberInfo.length; i++) { + String[] metaMethod = (String[]) memberInfo[i]; + out.print(i + ":\t"); + for (String s : metaMethod) { + out.print(s + " "); + } + out.println(""); + } + } + + public static Collection sort(List<Object> memberInfo) { + Collections.sort(memberInfo, new MemberComparator()); + return memberInfo; + } + + public static class MemberComparator implements Comparator<Object>, Serializable { + private static final long serialVersionUID = -7691851726606749541L; + + public int compare(Object a, Object b) { + String[] aStr = (String[]) a; + String[] bStr = (String[]) b; + int result = aStr[Inspector.MEMBER_NAME_IDX].compareTo(bStr[Inspector.MEMBER_NAME_IDX]); + if (0 != result) return result; + result = aStr[Inspector.MEMBER_TYPE_IDX].compareTo(bStr[Inspector.MEMBER_TYPE_IDX]); + if (0 != result) return result; + result = aStr[Inspector.MEMBER_PARAMS_IDX].compareTo(bStr[Inspector.MEMBER_PARAMS_IDX]); + if (0 != result) return result; + result = aStr[Inspector.MEMBER_DECLARER_IDX].compareTo(bStr[Inspector.MEMBER_DECLARER_IDX]); + if (0 != result) return result; + result = aStr[Inspector.MEMBER_MODIFIER_IDX].compareTo(bStr[Inspector.MEMBER_MODIFIER_IDX]); + if (0 != result) return result; + result = aStr[Inspector.MEMBER_ORIGIN_IDX].compareTo(bStr[Inspector.MEMBER_ORIGIN_IDX]); + return result; + } + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/inspect/package.html ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/inspect/package.html b/src/main/groovy/groovy/inspect/package.html new file mode 100644 index 0000000..6d80c2b --- /dev/null +++ b/src/main/groovy/groovy/inspect/package.html @@ -0,0 +1,28 @@ +<!-- + + 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. + +--> +<html> + <head> + <title>package groovy.inspect.*</title> + </head> + <body> + <p>Classes for inspecting object properties through introspection.</p> + </body> +</html> http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/EncodingAwareBufferedWriter.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/EncodingAwareBufferedWriter.java b/src/main/groovy/groovy/io/EncodingAwareBufferedWriter.java new file mode 100644 index 0000000..2faaaeb --- /dev/null +++ b/src/main/groovy/groovy/io/EncodingAwareBufferedWriter.java @@ -0,0 +1,58 @@ +/* + * 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.io; + +import java.io.BufferedWriter; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * A buffered writer only for OutputStreamWriter that is aware of + * the encoding of the OutputStreamWriter. + * + * @author Paul King + */ + +public class EncodingAwareBufferedWriter extends BufferedWriter { + private final OutputStreamWriter out; + public EncodingAwareBufferedWriter(OutputStreamWriter out) { + super(out); + this.out = out; + } + + /** + * The encoding as returned by the underlying OutputStreamWriter. Can be the historical name. + * + * @return the encoding + * @see java.io.OutputStreamWriter#getEncoding() + */ + public String getEncoding() { + return out.getEncoding(); + } + + /** + * The encoding as returned by the underlying OutputStreamWriter. Will be the preferred name. + * + * @return the encoding + * @see java.io.OutputStreamWriter#getEncoding() + */ + public String getNormalizedEncoding() { + return Charset.forName(getEncoding()).name(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/FileType.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/FileType.java b/src/main/groovy/groovy/io/FileType.java new file mode 100644 index 0000000..945b9ae --- /dev/null +++ b/src/main/groovy/groovy/io/FileType.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.io; + +/** + * Represents particular files of interest. + */ +public enum FileType { + /** Represents normal files */ + FILES, + /** Represents directories */ + DIRECTORIES, + /** Represents both normal files and directories */ + ANY +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/FileVisitResult.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/FileVisitResult.java b/src/main/groovy/groovy/io/FileVisitResult.java new file mode 100644 index 0000000..55ecb4a --- /dev/null +++ b/src/main/groovy/groovy/io/FileVisitResult.java @@ -0,0 +1,35 @@ +/* + * 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.io; + +/** + * Represents special return values for the 'preDir', 'postDir' and 'visit'/supplied Closures used with + * {@link org.codehaus.groovy.runtime.DefaultGroovyMethods#traverse(java.io.File, java.util.Map, groovy.lang.Closure)} + * and related methods to control subsequent traversal behavior. + */ +public enum FileVisitResult { + /** Continue processing; the default */ + CONTINUE, + /** Skip processing sibling files/directories within the current directory being processed */ + SKIP_SIBLINGS, + /** Do not process the child files/subdirectories within the current directory being processed */ + SKIP_SUBTREE, + /** Do not process any more files */ + TERMINATE +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/GroovyPrintStream.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/GroovyPrintStream.java b/src/main/groovy/groovy/io/GroovyPrintStream.java new file mode 100644 index 0000000..d77c5aa --- /dev/null +++ b/src/main/groovy/groovy/io/GroovyPrintStream.java @@ -0,0 +1,130 @@ +/* + * 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.io; + +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.UnsupportedEncodingException; + +/** + * A PrintStream that outputs objects in Groovy style. + * That means print(Object) uses InvokerHelper.toString(Object) + * to produce the same results as Writer.print(Object). + * + * @author Jim White + * @since 1.6 + */ +public class GroovyPrintStream extends PrintStream +{ + /** + * Creates a new print stream. This stream will not flush automatically. + * + * @see java.io.PrintStream#PrintStream(java.io.OutputStream) + */ + public GroovyPrintStream(OutputStream out) { + super(out, false); + } + + /** + * Creates a new print stream. + * + * @see java.io.PrintStream#PrintStream(java.io.OutputStream, boolean) + */ + public GroovyPrintStream(OutputStream out, boolean autoFlush) { + super(out, autoFlush); + } + + /** + * Creates a new print stream. + * + * @see java.io.PrintStream#PrintStream(java.io.OutputStream, boolean, String) + */ + public GroovyPrintStream(OutputStream out, boolean autoFlush, String encoding) + throws UnsupportedEncodingException + { + super(out, autoFlush, encoding); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file name. + * + * @see java.io.PrintStream#PrintStream(String) + */ + public GroovyPrintStream(String fileName) throws FileNotFoundException { + super(fileName); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file name and charset. + * + * @see java.io.PrintStream#PrintStream(String, String) + */ + public GroovyPrintStream(String fileName, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(fileName, csn); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file. + * + * @see java.io.PrintStream#PrintStream(File) + */ + public GroovyPrintStream(File file) throws FileNotFoundException { + super(file); + } + + /** + * Creates a new print stream, without automatic line flushing, with the + * specified file and charset. + * + * @see java.io.PrintStream#PrintStream(File, String) + */ + public GroovyPrintStream(File file, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(file, csn); + } + + /** + * Prints an object Groovy style. + * + * @param obj The <code>Object</code> to be printed + */ + public void print(Object obj) { + print(InvokerHelper.toString(obj)); + } + + /** + * Prints an object Groovy style followed by a newline. + * + * @param obj The <code>Object</code> to be printed + */ + public void println(Object obj) { + println(InvokerHelper.toString(obj)); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/GroovyPrintWriter.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/GroovyPrintWriter.java b/src/main/groovy/groovy/io/GroovyPrintWriter.java new file mode 100644 index 0000000..0a076b9 --- /dev/null +++ b/src/main/groovy/groovy/io/GroovyPrintWriter.java @@ -0,0 +1,101 @@ +/* + * 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.io; + +import org.codehaus.groovy.runtime.InvokerHelper; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; + +/** + * A PrintWriter that outputs objects in Groovy style. + * That means print(Object) uses InvokerHelper.toString(Object) + * to produce the same results as Writer.print(Object). + * + * @author Jim White + * @since 1.6 + */ +public class GroovyPrintWriter extends PrintWriter +{ + public GroovyPrintWriter(File file) throws FileNotFoundException + { + super(file); + } + + public GroovyPrintWriter(File file, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(file, csn); + } + + public GroovyPrintWriter(Writer out) + { + super(out); + } + + public GroovyPrintWriter(Writer out, boolean autoflush) + { + super(out, autoflush); + } + + public GroovyPrintWriter(OutputStream out) + { + super(out); + } + + public GroovyPrintWriter(OutputStream out, boolean autoflush) + { + super(out, autoflush); + } + + public GroovyPrintWriter(String filename) throws FileNotFoundException + { + super(filename); + } + + public GroovyPrintWriter(String filename, String csn) + throws FileNotFoundException, UnsupportedEncodingException + { + super(filename, csn); + } + +// Don't need to do this if Groovy is going to print char[] like a string. +// public void print(char[] x) +// { +// write(InvokerHelper.toString(x)); +// } + + public void print(Object x) + { + write(InvokerHelper.toString(x)); + } + + public void println(Object x) + { + // JDK 1.6 has changed the implementation to do a + // String.valueOf(x) rather than call print(x). + // Probably to improve performance by doing the conversion outside the lock. + // This will do the same thing for us, and we don't have to have access to the lock. + println(InvokerHelper.toString(x)); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/LineColumnReader.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/LineColumnReader.java b/src/main/groovy/groovy/io/LineColumnReader.java new file mode 100644 index 0000000..49b7c94 --- /dev/null +++ b/src/main/groovy/groovy/io/LineColumnReader.java @@ -0,0 +1,252 @@ +/* + * 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.io; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.nio.CharBuffer; + +/** + * The <code>LineColumnReader</code> is an extension to <code>BufferedReader</code> + * that keeps track of the line and column information of where the cursor is. + * + * @author Guillaume Laforge + * @since 1.8.0 + */ +public class LineColumnReader extends BufferedReader { + + /** + * The current line position + */ + private long line = 1; + + /** + * The current column position + */ + private long column = 1; + + /** + * The latest marked line position + */ + private long lineMark = 1; + + /** + * The latest marked line position + */ + private long columnMark = 1; + + private boolean newLineWasRead = false; + + /** + * Constructor wrapping a <code>Reader</code> + * (<code>FileReader</code>, <code>FileReader</code>, <code>InputStreamReader</code>, etc.) + * + * @param reader the reader to wrap + */ + public LineColumnReader(Reader reader) { + super(reader); + } + + /** + * Marks the present position in the stream. Subsequent calls to reset() will attempt to reposition the stream to this point. + * + * @param readAheadLimit Limit on the number of characters that may be read while still preserving the mark. + * An attempt to reset the stream after reading characters up to this limit or beyond may fail. + * A limit value larger than the size of the input buffer will cause a new buffer to be allocated whose size is no smaller than limit. + * Therefore large values should be used with care. + */ + @Override + public void mark(int readAheadLimit) throws IOException { + lineMark = line; + columnMark = column; + super.mark(readAheadLimit); + } + + /** + * Resets the stream to the most recent mark. + */ + @Override + public void reset() throws IOException { + line = lineMark; + column = columnMark; + super.reset(); + } + + /** + * Reads a single character. + * + * @return The character read, as an integer in the range 0 to 65535 (0x00-0xffff), + * or -1 if the end of the stream has been reached + */ + @Override + public int read() throws IOException { + if (newLineWasRead) { + line += 1; + column = 1; + newLineWasRead = false; + } + + int charRead = super.read(); + if (charRead > -1) { + char c = (char)charRead; + // found a \r or \n, like on Mac or Unix + // could also be Windows' \r\n + if (c == '\r' || c == '\n') { + newLineWasRead = true; + if (c == '\r') { + mark(1); + c = (char)super.read(); + // check if we have \r\n like on Windows + // if it's not \r\n we reset, otherwise, the \n is just consummed + if (c != '\n') { + reset(); + } + } + } else { + column += 1; + } + } + + return charRead; + } + + /** + * Reads characters into a portion of an array. + * + * @param chars Destination array of char + * @param startOffset Offset at which to start storing characters + * @param length Maximum number of characters to read + * @return an exception if an error occurs + */ + @Override + public int read(char[] chars, int startOffset, int length) throws IOException { + for (int i = startOffset; i <= startOffset + length; i++) { + int readInt = read(); + if (readInt == -1) return i - startOffset; + chars[i] = (char)readInt; + } + return length; + } + + /** + * Reads a line of text. A line is considered to be terminated by any one of a line feed ('\n'), + * a carriage return ('\r'), or a carriage return followed immediately by a linefeed. + * + * @return A String containing the contents of the line, not including any line-termination characters, + * or null if the end of the stream has been reached + */ + @Override + public String readLine() throws IOException { + StringBuilder result = new StringBuilder(); + for (;;) { + int intRead = read(); + if (intRead == -1) { + return result.length() == 0 ? null : result.toString(); + } + + char c = (char)intRead; + if (c == '\n' || c == '\r') break; + result.append(c); + } + return result.toString(); + } + + /** + * Skips characters. + * + * @param toSkip the number of characters to skip + * @return The number of characters actually skipped + */ + @Override + public long skip(long toSkip) throws IOException { + for (long i = 0; i < toSkip; i++) { + int intRead = read(); + if (intRead == -1) return i; + } + return toSkip; + } + + /** + * Reads characters into an array. + * This method will block until some input is available, an I/O error occurs, + * or the end of the stream is reached. + * + * @param chars Destination buffer + * @return The number of characters read, or -1 if the end of the stream has been reached + */ + @Override + public int read(char[] chars) throws IOException { + return read(chars, 0, chars.length - 1); + } + + /** + * Not implemented. + * + * @param buffer Destination buffer + * @return The number of characters read, or -1 if the end of the stream has been reached + * @throws UnsupportedOperationException as the method is not implemented + */ + @Override + public int read(CharBuffer buffer) { + throw new UnsupportedOperationException("read(CharBuffer) not yet implemented"); + } + + /** + * Closes the stream and releases any system resources associated with it. + * Once the stream has been closed, further read(), ready(), mark(), reset(), or skip() invocations + * will throw an IOException. Closing a previously closed stream has no effect. + */ + @Override + public void close() throws IOException { + super.close(); + } + + public long getColumn() { + return column; + } + + public void setColumn(long column) { + this.column = column; + } + + public long getColumnMark() { + return columnMark; + } + + public void setColumnMark(long columnMark) { + this.columnMark = columnMark; + } + + public long getLine() { + return line; + } + + public void setLine(long line) { + this.line = line; + } + + public long getLineMark() { + return lineMark; + } + + public void setLineMark(long lineMark) { + this.lineMark = lineMark; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/PlatformLineWriter.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/PlatformLineWriter.java b/src/main/groovy/groovy/io/PlatformLineWriter.java new file mode 100644 index 0000000..0071e0f --- /dev/null +++ b/src/main/groovy/groovy/io/PlatformLineWriter.java @@ -0,0 +1,64 @@ +/* + * 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.io; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.Writer; + +/** + * A buffered writer that gobbles any \r characters + * and replaces every \n with a platform specific newline. + * In many places Groovy normalises streams to only have \n + * characters but when creating files that must be used + * by other platform-aware tools, you sometimes want the + * newlines to match what the platform expects. + * + * @author Paul King + */ +public class PlatformLineWriter extends Writer { + private final BufferedWriter writer; + + public PlatformLineWriter(Writer out) { + writer = new BufferedWriter(out); + } + + public PlatformLineWriter(Writer out, int sz) { + writer = new BufferedWriter(out, sz); + } + + public void write(char cbuf[], int off, int len) throws IOException { + for (; len > 0; len--) { + char c = cbuf[off++]; + if (c == '\n') { + writer.newLine(); + } else if (c != '\r') { + writer.write(c); + } + } + } + + public void flush() throws IOException { + writer.flush(); + } + + public void close() throws IOException { + writer.close(); + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/io/package.html ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/io/package.html b/src/main/groovy/groovy/io/package.html new file mode 100644 index 0000000..fef0d09 --- /dev/null +++ b/src/main/groovy/groovy/io/package.html @@ -0,0 +1,28 @@ +<!-- + + 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. + +--> +<html> + <head> + <title>package groovy.io.*</title> + </head> + <body> + <p>Classes for Groovier Input/Output.</p> + </body> +</html> http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/AdaptingMetaClass.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/AdaptingMetaClass.java b/src/main/groovy/groovy/lang/AdaptingMetaClass.java new file mode 100644 index 0000000..81afc26 --- /dev/null +++ b/src/main/groovy/groovy/lang/AdaptingMetaClass.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.lang; + +/** + * An interface for MetaClass instances that "adapt" other MetaClass instances such as a proxy or + * delegating MetaClass. + * + * @author Graeme Rocher + * @since 1.5 + */ +public interface AdaptingMetaClass extends MetaClass { + + /** + * Returns the MetaClass that this adapter adapts + * + * @return The MetaClass instance + */ + MetaClass getAdaptee(); + + /** + * Sets the MetaClass adapted by this MetaClass + * + * @param metaClass The MetaClass to adapt + */ + void setAdaptee(MetaClass metaClass); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/BenchmarkInterceptor.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/BenchmarkInterceptor.java b/src/main/groovy/groovy/lang/BenchmarkInterceptor.java new file mode 100644 index 0000000..77bcb64 --- /dev/null +++ b/src/main/groovy/groovy/lang/BenchmarkInterceptor.java @@ -0,0 +1,126 @@ +/* + * 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.lang; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * Interceptor that registers the timestamp of each method call + * before and after invocation. The timestamps are stored internally + * and can be retrieved through the with the <pre>getCalls()</pre> + * and <pre>statistic()</pre> API. + * <p> + * Example usage: + * <pre> + * def proxy = ProxyMetaClass.getInstance(ArrayList.class) + * proxy.interceptor = new BenchmarkInterceptor() + * proxy.use { + * def list = (0..10000).collect{ it } + * 4.times { list.size() } + * 4000.times { list.set(it, it+1) } + * } + * proxy.interceptor.statistic() + * </pre> + * Which produces the following output: + * <pre> + * [[size, 4, 0], [set, 4000, 21]] + * </pre> + */ +public class BenchmarkInterceptor implements Interceptor { + + protected Map calls = new LinkedHashMap(); // keys to list of invocation times and before and after + + /** + * Returns the raw data associated with the current benchmark run. + */ + public Map getCalls() { + return calls; + } + + /** + * Resets all the benchmark data on this object. + */ + public void reset() { + calls = new HashMap(); + } + /** + * This code is executed before the method is called. + * @param object receiver object for the method call + * @param methodName name of the method to call + * @param arguments arguments to the method call + * @return null + * relays this result. + */ + public Object beforeInvoke(Object object, String methodName, Object[] arguments) { + if (!calls.containsKey(methodName)) calls.put(methodName, new LinkedList()); + ((List) calls.get(methodName)).add(Long.valueOf(System.currentTimeMillis())); + + return null; + } + /** + * This code is executed after the method is called. + * @param object receiver object for the called method + * @param methodName name of the called method + * @param arguments arguments to the called method + * @param result result of the executed method call or result of beforeInvoke if method was not called + * @return result + */ + public Object afterInvoke(Object object, String methodName, Object[] arguments, Object result) { + ((List) calls.get(methodName)).add(Long.valueOf(System.currentTimeMillis())); + return result; + } + + /** + * The call should be invoked separately + * @return true + */ + public boolean doInvoke() { + return true; + } + + /** + * Returns benchmark statistics as a List<Object[]>. + * AccumulateTime is measured in milliseconds and is as accurate as + * System.currentTimeMillis() allows it to be. + * @return a list of lines, each item is [methodname, numberOfCalls, accumulatedTime] + */ + public List statistic() { + List result = new LinkedList(); + for (Iterator iter = calls.keySet().iterator(); iter.hasNext();) { + Object[] line = new Object[3]; + result.add(line); + line[0] = iter.next(); + List times = (List) calls.get(line[0]); + line[1] = Integer.valueOf(times.size() / 2); + int accTime = 0; + for (Iterator it = times.iterator(); it.hasNext();) { + Long start = (Long) it.next(); + Long end = (Long) it.next(); + accTime += end.longValue() - start.longValue(); + } + line[2] = Long.valueOf(accTime); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Binding.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Binding.java b/src/main/groovy/groovy/lang/Binding.java new file mode 100644 index 0000000..6505ea5 --- /dev/null +++ b/src/main/groovy/groovy/lang/Binding.java @@ -0,0 +1,122 @@ +/* + * 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.lang; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Represents the variable bindings of a script which can be altered + * from outside the script object or created outside of a script and passed + * into it. + * <p> Binding instances are not supposed to be used in a multi-threaded context. + * + * @author <a href="mailto:[email protected]">James Strachan</a> + */ +public class Binding extends GroovyObjectSupport { + private Map variables; + + public Binding() { + } + + public Binding(Map variables) { + this.variables = variables; + } + + /** + * A helper constructor used in main(String[]) method calls + * + * @param args are the command line arguments from a main() + */ + public Binding(String[] args) { + this(); + setVariable("args", args); + } + + /** + * @param name the name of the variable to lookup + * @return the variable value + */ + public Object getVariable(String name) { + if (variables == null) + throw new MissingPropertyException(name, this.getClass()); + + Object result = variables.get(name); + + if (result == null && !variables.containsKey(name)) { + throw new MissingPropertyException(name, this.getClass()); + } + + return result; + } + + /** + * Sets the value of the given variable + * + * @param name the name of the variable to set + * @param value the new value for the given variable + */ + public void setVariable(String name, Object value) { + if (variables == null) + variables = new LinkedHashMap(); + variables.put(name, value); + } + + /** + * Simple check for whether the binding contains a particular variable or not. + * + * @param name the name of the variable to check for + */ + public boolean hasVariable(String name) { + return variables != null && variables.containsKey(name); + } + + public Map getVariables() { + if (variables == null) + variables = new LinkedHashMap(); + return variables; + } + + /** + * Overloaded to make variables appear as bean properties or via the subscript operator + */ + public Object getProperty(String property) { + /** @todo we should check if we have the property with the metaClass instead of try/catch */ + try { + return super.getProperty(property); + } + catch (MissingPropertyException e) { + return getVariable(property); + } + } + + /** + * Overloaded to make variables appear as bean properties or via the subscript operator + */ + public void setProperty(String property, Object newValue) { + /** @todo we should check if we have the property with the metaClass instead of try/catch */ + try { + super.setProperty(property, newValue); + } + catch (MissingPropertyException e) { + setVariable(property, newValue); + } + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Buildable.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Buildable.java b/src/main/groovy/groovy/lang/Buildable.java new file mode 100644 index 0000000..af22958 --- /dev/null +++ b/src/main/groovy/groovy/lang/Buildable.java @@ -0,0 +1,24 @@ +/* + * 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.lang; + + +public interface Buildable { + void build(GroovyObject builder); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/10110145/src/main/groovy/groovy/lang/Category.java ---------------------------------------------------------------------- diff --git a/src/main/groovy/groovy/lang/Category.java b/src/main/groovy/groovy/lang/Category.java new file mode 100644 index 0000000..a7d3336 --- /dev/null +++ b/src/main/groovy/groovy/lang/Category.java @@ -0,0 +1,105 @@ +/* + * 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.lang; + +import org.codehaus.groovy.transform.GroovyASTTransformationClass; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Transforms an instance-style Groovy class or interface to become a static-style + * conventional Groovy category. + * <p> + * Groovy categories are the original mechanism used + * by Groovy when augmenting classes with new methods. Writing categories required + * using a class writing style where all methods were static and an additional + * self parameter was defined. The self parameter and static nature of the methods + * disappeared once applied by Groovy's metaclass framework but some regarded + * the writing style as a little noisy. This transformation allows you to write + * your categories without the "apparent noise" but adds it back in during + * compilation so that the classes appear as normal categories. + * <p> + * It might seem strange writing your class/object enhancements using a succinct + * notation, then having "noise" added, then having the noise removed during + * category application. If this worries you, then you may also like to consider + * using Groovy's {@code ExpandoMetaClass} mechanism which avoids + * the category definition altogether. If you already have an investment in + * categories or like some of the other features which categories currently give you, + * then read on. + * <p> + * The mechanics: during compilation, all methods are transformed to static ones with an additional + * self parameter of the type you supply as the annotation parameter (the default type + * for the self parameters is {@code Object} which might be more broad reaching than + * you like so it is usually wise to specify a type). + * Properties invoked using 'this' references are transformed so that + * they are instead invoked on the additional self parameter and not on + * the Category instance. (Remember that once the category is applied, the reverse + * will occur and we will be back to conceptually having methods on the {@code this} + * references again!) + * <p> + * Classes conforming to the conventional Groovy category conventions can be used + * within {@code use} statements or mixed in at runtime with the {@code mixin} method on classes. + * <p> + * An example showing a {@code use} statement (allowing fine-grained application of + * the category methods): + * <pre class="groovyTestCase"> + * {@code @Category}(Integer) + * class IntegerOps { + * def triple() { + * this * 3 + * } + * } + * + * use (IntegerOps) { + * assert 25.triple() == 75 + * } + * </pre> + * Or, "mixing in" your methods at runtime: + * <pre class="groovyTestCase"> + * {@code @Category}(List) + * class Shuffler { + * def shuffle() { + * def result = new ArrayList(this) + * Collections.shuffle(result) + * result + * } + * } + * + * class Sentence extends ArrayList { + * Sentence(Collection initial) { super(initial) } + * } + * Sentence.mixin Shuffler + * + * def words = ["The", "quick", "brown", "fox"] + * println new Sentence(words).shuffle() + * // => [quick, fox, The, brown] (order will vary) + * </pre> + * + * @author Alex Tkachman + */ [email protected] +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +@GroovyASTTransformationClass("org.codehaus.groovy.transform.CategoryASTTransformation") +public @interface Category { + Class value () default Object.class; +}
