http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableClassVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableClassVisitor.java b/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableClassVisitor.java new file mode 100644 index 0000000..b794235 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableClassVisitor.java @@ -0,0 +1,34 @@ +/* + * 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 org.codehaus.groovy.classgen.asm.util; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.util.TraceClassVisitor; + +/** + * A ClassVisitor proxy, which can log bytecode generation + * + * @since 2.5.0 + */ +public class LoggableClassVisitor extends ClassVisitor { + public LoggableClassVisitor(final ClassVisitor cv) { + super(Opcodes.ASM6, new TraceClassVisitor(cv, new LoggableTextifier(), null)); + } +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableTextifier.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableTextifier.java b/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableTextifier.java new file mode 100644 index 0000000..e5b0dea --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/asm/util/LoggableTextifier.java @@ -0,0 +1,438 @@ +/* + * 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 org.codehaus.groovy.classgen.asm.util; + +import org.objectweb.asm.Attribute; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.TypePath; +import org.objectweb.asm.util.Printer; +import org.objectweb.asm.util.Textifier; + +import java.util.List; + +/** + * Logging bytecode generation, which can make debugging easy + * + * @since 2.5.0 + */ +public class LoggableTextifier extends Textifier { +// private static final Logger LOGGER = Logger.getLogger(LoggableTextifier.class.getName()); + private int loggedLineCnt = 0; + + public LoggableTextifier() { + super(Opcodes.ASM6); + } + + @Override + protected Textifier createTextifier() { + return new LoggableTextifier(); + } + + protected void log() { + int textSize = text.size(); + + for (int i = loggedLineCnt; i < textSize; i++) { + Object bc = text.get(i); + + if (bc instanceof List && 0 == ((List) bc).size()) { + continue; + } + + System.out.print(bc); + } + + loggedLineCnt = textSize; + } + + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + log(); + } + + @Override + public void visitSource(String file, String debug) { + super.visitSource(file, debug); + log(); + } + + @Override + public Printer visitModule(final String name, final int access, final String version) { + Printer p = super.visitModule(name, access, version); + log(); + return p; + } + + @Override + public void visitOuterClass(String owner, String name, String desc) { + super.visitOuterClass(owner, name, desc); + log(); + } + + @Override + public Textifier visitClassAnnotation(String desc, boolean visible) { + Textifier t = super.visitClassAnnotation(desc, visible); + log(); + return t; + } + + @Override + public Printer visitClassTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Printer t = super.visitClassTypeAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public void visitClassAttribute(Attribute attr) { + super.visitClassAttribute(attr); + log(); + } + + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visitInnerClass(name, outerName, innerName, access); + log(); + } + + @Override + public Textifier visitField(int access, String name, String desc, String signature, Object value) { + Textifier t = super.visitField(access, name, desc, signature, value); + log(); + return t; + } + + @Override + public Textifier visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + Textifier t = super.visitMethod(access, name, desc, signature, exceptions); + log(); + return t; + } + + @Override + public void visitClassEnd() { + super.visitClassEnd(); + log(); + } + + @Override + public void visitRequire(String require, int access, String version) { + super.visitRequire(require, access, version); + log(); + } + + @Override + public void visitExport(String export, int access, String... modules) { + super.visitExport(export, access, modules); + log(); + } + + @Override + public void visitUse(String use) { + super.visitUse(use); + log(); + } + + @Override + public void visitProvide(String provide, String... providers) { + super.visitProvide(provide, providers); + log(); + } + + @Override + public void visitModuleEnd() { + super.visitModuleEnd(); + log(); + } + + @Override + public void visit(String name, Object value) { + super.visit(name, value); + log(); + } + + @Override + public void visitEnum(String name, String desc, String value) { + super.visitEnum(name, desc, value); + log(); + } + + @Override + public Textifier visitAnnotation(String name, String desc) { + Textifier t = super.visitAnnotation(name, desc); + log(); + return t; + } + + @Override + public Textifier visitArray(String name) { + Textifier t = super.visitArray(name); + log(); + return t; + } + + @Override + public void visitAnnotationEnd() { + super.visitAnnotationEnd(); + log(); + } + + @Override + public Textifier visitFieldAnnotation(String desc, boolean visible) { + Textifier t = super.visitFieldAnnotation(desc, visible); + log(); + return t; + } + + @Override + public Printer visitFieldTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Printer t = super.visitFieldTypeAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public void visitFieldAttribute(Attribute attr) { + super.visitFieldAttribute(attr); + log(); + } + + @Override + public void visitFieldEnd() { + super.visitFieldEnd(); + log(); + } + + @Override + public void visitParameter(String name, int access) { + super.visitParameter(name, access); + log(); + } + + @Override + public Textifier visitAnnotationDefault() { + Textifier t = super.visitAnnotationDefault(); + log(); + return t; + } + + @Override + public Textifier visitMethodAnnotation(String desc, boolean visible) { + Textifier t = super.visitMethodAnnotation(desc, visible); + log(); + return t; + } + + @Override + public Printer visitMethodTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Printer t = super.visitMethodTypeAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public Textifier visitParameterAnnotation(int parameter, String desc, boolean visible) { + Textifier t = super.visitParameterAnnotation(parameter, desc, visible); + log(); + return t; + } + + @Override + public void visitMethodAttribute(Attribute attr) { + super.visitMethodAttribute(attr); + log(); + } + + @Override + public void visitCode() { + super.visitCode(); + log(); + } + + @Override + public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { + super.visitFrame(type, nLocal, local, nStack, stack); + log(); + } + + @Override + public void visitInsn(int opcode) { + super.visitInsn(opcode); + log(); + } + + @Override + public void visitIntInsn(int opcode, int operand) { + super.visitIntInsn(opcode, operand); + log(); + } + + @Override + public void visitVarInsn(int opcode, int var) { + super.visitVarInsn(opcode, var); + log(); + } + + @Override + public void visitTypeInsn(int opcode, String type) { + super.visitTypeInsn(opcode, type); + log(); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + super.visitFieldInsn(opcode, owner, name, desc); + log(); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + super.visitMethodInsn(opcode, owner, name, desc); + log(); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + log(); + } + + @Override + public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { + super.visitInvokeDynamicInsn(name, desc, bsm, bsmArgs); + log(); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + super.visitJumpInsn(opcode, label); + log(); + } + + @Override + public void visitLabel(Label label) { + super.visitLabel(label); + log(); + } + + @Override + public void visitLdcInsn(Object cst) { + super.visitLdcInsn(cst); + log(); + } + + @Override + public void visitIincInsn(int var, int increment) { + super.visitIincInsn(var, increment); + log(); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + super.visitTableSwitchInsn(min, max, dflt, labels); + log(); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + super.visitLookupSwitchInsn(dflt, keys, labels); + log(); + } + + @Override + public void visitMultiANewArrayInsn(String desc, int dims) { + super.visitMultiANewArrayInsn(desc, dims); + log(); + } + + @Override + public Printer visitInsnAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Printer t = super.visitInsnAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + super.visitTryCatchBlock(start, end, handler, type); + log(); + } + + @Override + public Printer visitTryCatchAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Printer t = super.visitTryCatchAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + super.visitLocalVariable(name, desc, signature, start, end, index); + log(); + } + + @Override + public Printer visitLocalVariableAnnotation(int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { + Printer t = super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, desc, visible); + log(); + return t; + } + + @Override + public void visitLineNumber(int line, Label start) { + super.visitLineNumber(line, start); + log(); + } + + @Override + public void visitMaxs(int maxStack, int maxLocals) { + super.visitMaxs(maxStack, maxLocals); + log(); + } + + @Override + public void visitMethodEnd() { + super.visitMethodEnd(); + log(); + } + + @Override + public Textifier visitAnnotation(String desc, boolean visible) { + Textifier t = super.visitAnnotation(desc, visible); + log(); + return t; + } + + @Override + public Textifier visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { + Textifier t = super.visitTypeAnnotation(typeRef, typePath, desc, visible); + log(); + return t; + } + + @Override + public void visitAttribute(Attribute attr) { + super.visitAttribute(attr); + log(); + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/package.html ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/classgen/package.html b/src/main/java/org/codehaus/groovy/classgen/package.html new file mode 100644 index 0000000..a72224a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/classgen/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 org.codehaus.groovy.classgen.*</title> + </head> + <body> + <p>Generates Java classes for Groovy classes using ASM.</p> + </body> +</html> http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/cli/GroovyPosixParser.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/cli/GroovyPosixParser.java b/src/main/java/org/codehaus/groovy/cli/GroovyPosixParser.java new file mode 100644 index 0000000..e583ac9 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/cli/GroovyPosixParser.java @@ -0,0 +1,281 @@ +/* + * 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 org.codehaus.groovy.cli; + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.Parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +/** + * This is a hacked version of Commons CLI 1.2 PosixParser with some bug fixes added. + * We aren't aware of any use cases where it isn't now preferable to use the + * Commons CLI 1.3 DefaultParser but this class is retained for the time being for + * backwards compatibility if someone is relying on this class's exact functionality. + * + * @author John Keyes (john at integralsource.com) + * @author Paul King (Groovy hacks/fixes) + * @deprecated use the DefaultParser from Commons CLI + */ +@Deprecated +public class GroovyPosixParser extends Parser +{ + /** holder for flattened tokens */ + private List tokens = new ArrayList(); + + /** specifies if bursting should continue */ + private boolean eatTheRest; + + /** holder for the current option */ + private Option currentOption; + + /** the command line Options */ + private Options options; + + /** + * Resets the members to their original state i.e. remove + * all of <code>tokens</code> entries and set <code>eatTheRest</code> + * to false. + */ + private void init() + { + eatTheRest = false; + tokens.clear(); + } + + /** + * An implementation of {@link Parser}'s abstract + * {@link Parser#flatten(Options,String[],boolean) flatten} method. + * <p> + * The following are the rules used by this flatten method. + * <ol> + * <li>if <code>stopAtNonOption</code> is <b>true</b> then do not + * burst anymore of <code>arguments</code> entries, just add each + * successive entry without further processing. Otherwise, ignore + * <code>stopAtNonOption</code>.</li> + * <li>if the current <code>arguments</code> entry is "<b>--</b>" + * just add the entry to the list of processed tokens</li> + * <li>if the current <code>arguments</code> entry is "<b>-</b>" + * just add the entry to the list of processed tokens</li> + * <li>if the current <code>arguments</code> entry is two characters + * in length and the first character is "<b>-</b>" then check if this + * is a valid {@link Option} id. If it is a valid id, then add the + * entry to the list of processed tokens and set the current {@link Option} + * member. If it is not a valid id and <code>stopAtNonOption</code> + * is true, then the remaining entries are copied to the list of + * processed tokens. Otherwise, the current entry is ignored.</li> + * <li>if the current <code>arguments</code> entry is more than two + * characters in length and the first character is "<b>-</b>" then + * we need to burst the entry to determine its constituents. For more + * information on the bursting algorithm see + * {@link GroovyPosixParser#burstToken(String, boolean) burstToken}.</li> + * <li>if the current <code>arguments</code> entry is not handled + * by any of the previous rules, then the entry is added to the list + * of processed tokens.</li> + * </ol> + * + * @param options The command line {@link Options} + * @param arguments The command line arguments to be parsed + * @param stopAtNonOption Specifies whether to stop flattening when an non option is found. + * @return The flattened <code>arguments</code> String array. + */ + protected String[] flatten(Options options, String[] arguments, boolean stopAtNonOption) { + init(); + this.options = options; + + // an iterator for the command line tokens + Iterator iter = Arrays.asList(arguments).iterator(); + + // process each command line token + while (iter.hasNext()) + { + // get the next command line token + String token = (String) iter.next(); + + // handle long option --foo or --foo=bar + if (token.startsWith("--")) + { + int pos = token.indexOf('='); + String opt = pos == -1 ? token : token.substring(0, pos); // --foo + + if (!options.hasOption(opt)) + { + processNonOptionToken(token, stopAtNonOption); + } + else + { + tokens.add(opt); + if (pos != -1) + { + tokens.add(token.substring(pos + 1)); + } else { + currentOption = options.getOption(opt); + } + } + } + + // single hyphen + else if ("-".equals(token)) + { + tokens.add(token); + } + else if (token.startsWith("-")) + { + if (token.length() == 2 || options.hasOption(token)) + { + processOptionToken(token, stopAtNonOption); + } + // requires bursting + else + { + burstToken(token, stopAtNonOption); + } + } + else + { + processNonOptionToken(token, stopAtNonOption); + } + + gobble(iter); + } + + return (String[]) tokens.toArray(new String[tokens.size()]); + } + + /** + * Adds the remaining tokens to the processed tokens list. + * + * @param iter An iterator over the remaining tokens + */ + private void gobble(Iterator iter) + { + if (eatTheRest) + { + while (iter.hasNext()) + { + tokens.add(iter.next()); + } + } + } + + /** + * Add the special token "<b>--</b>" and the current <code>value</code> + * to the processed tokens list. Then add all the remaining + * <code>argument</code> values to the processed tokens list. + * + * @param value The current token + */ + private void processNonOptionToken(String value, boolean stopAtNonOption) + { + if (stopAtNonOption && (currentOption == null || !currentOption.hasArg())) + { + eatTheRest = true; + tokens.add("--"); + } + + tokens.add(value); + currentOption = null; + } + + /** + * If an {@link Option} exists for <code>token</code> then + * add the token to the processed list. + * <p> + * If an {@link Option} does not exist and <code>stopAtNonOption</code> + * is set then add the remaining tokens to the processed tokens list + * directly. + * + * @param token The current option token + * @param stopAtNonOption Specifies whether flattening should halt at the first non option. + */ + private void processOptionToken(String token, boolean stopAtNonOption) { + if (stopAtNonOption && !options.hasOption(token)) + { + eatTheRest = true; + } + + if (options.hasOption(token)) { + currentOption = options.getOption(token); + } else { + currentOption = null; + } + + tokens.add(token); + } + + /** + * Breaks <code>token</code> into its constituent parts + * using the following algorithm. + * + * <ul> + * <li>ignore the first character ("<b>-</b>")</li> + * <li>foreach remaining character check if an {@link Option} + * exists with that id.</li> + * <li>if an {@link Option} does exist then add that character + * prepended with "<b>-</b>" to the list of processed tokens.</li> + * <li>if the {@link Option} can have an argument value and there + * are remaining characters in the token then add the remaining + * characters as a token to the list of processed tokens.</li> + * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> + * <code>stopAtNonOption</code> <b>IS</b> set then add the special token + * "<b>--</b>" followed by the remaining characters and also + * the remaining tokens directly to the processed tokens list.</li> + * <li>if an {@link Option} does <b>NOT</b> exist <b>AND</b> + * <code>stopAtNonOption</code> <b>IS NOT</b> set then add that + * character prepended with "<b>-</b>".</li> + * </ul> + * + * @param token The current token to be <b>burst</b> + * @param stopAtNonOption Specifies whether to stop processing + * at the first non-Option encountered. + */ + protected void burstToken(String token, boolean stopAtNonOption) + { + for (int i = 1; i < token.length(); i++) + { + String ch = String.valueOf(token.charAt(i)); + + if (options.hasOption(ch)) + { + tokens.add("-" + ch); + currentOption = options.getOption(ch); + + if (currentOption.hasArg() && (token.length() != (i + 1))) + { + tokens.add(token.substring(i + 1)); + break; + } + } + else if (stopAtNonOption) + { + processNonOptionToken(token.substring(i), true); + break; + } + else + { + tokens.add(token); + break; + } + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/ASTTransformationsContext.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/ASTTransformationsContext.java b/src/main/java/org/codehaus/groovy/control/ASTTransformationsContext.java new file mode 100644 index 0000000..ae95275 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/ASTTransformationsContext.java @@ -0,0 +1,53 @@ +/* + * 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 org.codehaus.groovy.control; + +import groovy.lang.GroovyClassLoader; + +import java.util.HashSet; +import java.util.Set; + +/** + * Stores state information about global AST transformations applied to a compilation unit. + * + * @author Cedric Champeau +*/ +public class ASTTransformationsContext { + protected final GroovyClassLoader transformLoader; // Classloader for global and local transforms + + protected final CompilationUnit compilationUnit; // The compilation unit global AST transformations are applied on + protected final Set<String> globalTransformNames = new HashSet<String>(); // collected AST transformation names + + public ASTTransformationsContext(final CompilationUnit compilationUnit, final GroovyClassLoader transformLoader) { + this.compilationUnit = compilationUnit; + this.transformLoader = transformLoader; + } + + public CompilationUnit getCompilationUnit() { + return compilationUnit; + } + + public Set<String> getGlobalTransformNames() { + return globalTransformNames; + } + + public GroovyClassLoader getTransformLoader() { + return transformLoader; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java b/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java new file mode 100644 index 0000000..b1610df --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/AnnotationConstantsVisitor.java @@ -0,0 +1,133 @@ +/* + * 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 org.codehaus.groovy.control; + +import org.codehaus.groovy.ast.ClassCodeVisitorSupport; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.MethodNode; +import org.codehaus.groovy.ast.expr.CastExpression; +import org.codehaus.groovy.ast.expr.ConstantExpression; +import org.codehaus.groovy.ast.expr.Expression; +import org.codehaus.groovy.ast.stmt.ExpressionStatement; +import org.codehaus.groovy.ast.stmt.ReturnStatement; +import org.codehaus.groovy.ast.stmt.Statement; +import org.codehaus.groovy.classgen.Verifier; + +import java.math.BigDecimal; + +/** + * Visitor to resolve constants in annotation definitions. + * + * @author Paul King + */ +public class AnnotationConstantsVisitor extends ClassCodeVisitorSupport { + private SourceUnit source; + private boolean inAnnotationDef; + + public void visitClass(ClassNode node, SourceUnit source) { + this.source = source; + this.inAnnotationDef = node.isAnnotationDefinition(); + super.visitClass(node); + this.inAnnotationDef = false; + } + + @Override + protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) { + if (!inAnnotationDef) return; + visitStatement(node.getFirstStatement(), node.getReturnType()); + } + + private static void visitStatement(Statement statement, ClassNode returnType) { + if (statement instanceof ReturnStatement) { + // normal path + ReturnStatement rs = (ReturnStatement) statement; + rs.setExpression(transformConstantExpression(rs.getExpression(), returnType)); + } else if (statement instanceof ExpressionStatement) { + // path for JavaStubGenerator + ExpressionStatement es = (ExpressionStatement) statement; + es.setExpression(transformConstantExpression(es.getExpression(), returnType)); + } + } + + private static Expression transformConstantExpression(Expression val, ClassNode returnType) { + ClassNode returnWrapperType = ClassHelper.getWrapper(returnType); + if (val instanceof ConstantExpression) { + Expression result = revertType(val, returnWrapperType); + if (result != null) { + return result; + } + return val; + } + if (val instanceof CastExpression) { + CastExpression castExp = (CastExpression) val; + Expression castee = castExp.getExpression(); + if (castee instanceof ConstantExpression) { + if (ClassHelper.getWrapper(castee.getType()).isDerivedFrom(returnWrapperType)) { + return castee; + } + Expression result = revertType(castee, returnWrapperType); + if (result != null) { + return result; + } + return castee; + } + } + return val; + } + + private static Expression revertType(Expression val, ClassNode returnWrapperType) { + ConstantExpression ce = (ConstantExpression) val; + if (ClassHelper.Character_TYPE.equals(returnWrapperType) && ClassHelper.STRING_TYPE.equals(val.getType())) { + return configure(val, Verifier.transformToPrimitiveConstantIfPossible((ConstantExpression) val)); + } + ClassNode valWrapperType = ClassHelper.getWrapper(val.getType()); + if (ClassHelper.Integer_TYPE.equals(valWrapperType)) { + Integer i = (Integer) ce.getValue(); + if (ClassHelper.Character_TYPE.equals(returnWrapperType)) { + return configure(val, new ConstantExpression((char) i.intValue(), true)); + } + if (ClassHelper.Short_TYPE.equals(returnWrapperType)) { + return configure(val, new ConstantExpression(i.shortValue(), true)); + } + if (ClassHelper.Byte_TYPE.equals(returnWrapperType)) { + return configure(val, new ConstantExpression(i.byteValue(), true)); + } + } + if (ClassHelper.BigDecimal_TYPE.equals(valWrapperType)) { + BigDecimal bd = (BigDecimal) ce.getValue(); + if (ClassHelper.Float_TYPE.equals(returnWrapperType)) { + return configure(val, new ConstantExpression(bd.floatValue(), true)); + } + if (ClassHelper.Double_TYPE.equals(returnWrapperType)) { + return configure(val, new ConstantExpression(bd.doubleValue(), true)); + } + } + return null; + } + + private static Expression configure(Expression orig, Expression result) { + result.setSourcePosition(orig); + return result; + } + + protected SourceUnit getSourceUnit() { + return source; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/BytecodeProcessor.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/BytecodeProcessor.java b/src/main/java/org/codehaus/groovy/control/BytecodeProcessor.java new file mode 100644 index 0000000..6ef8c30 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/BytecodeProcessor.java @@ -0,0 +1,23 @@ +/* + * 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 org.codehaus.groovy.control; + +public interface BytecodeProcessor { + byte[] processBytecode(String name, byte[] original); +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/ClassNodeResolver.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/ClassNodeResolver.java b/src/main/java/org/codehaus/groovy/control/ClassNodeResolver.java new file mode 100644 index 0000000..3b64333 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/ClassNodeResolver.java @@ -0,0 +1,345 @@ +/* + * 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 org.codehaus.groovy.control; + +import groovy.lang.GroovyClassLoader; +import org.codehaus.groovy.GroovyBugError; +import org.codehaus.groovy.ast.ClassHelper; +import org.codehaus.groovy.ast.ClassNode; +import org.codehaus.groovy.ast.decompiled.AsmDecompiler; +import org.codehaus.groovy.ast.decompiled.AsmReferenceResolver; +import org.codehaus.groovy.ast.decompiled.DecompiledClassNode; +import org.codehaus.groovy.classgen.Verifier; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is used as a pluggable way to resolve class names. + * An instance of this class has to be added to {@link CompilationUnit} using + * {@link CompilationUnit#setClassNodeResolver(ClassNodeResolver)}. The + * CompilationUnit will then set the resolver on the {@link ResolveVisitor} each + * time new. The ResolveVisitor will prepare name lookup and then finally ask + * the resolver if the class exists. This resolver then can return either a + * SourceUnit or a ClassNode. In case of a SourceUnit the compiler is notified + * that a new source is to be added to the compilation queue. In case of a + * ClassNode no further action than the resolving is done. The lookup result + * is stored in the helper class {@link LookupResult}. This class provides a + * class cache to cache lookups. If you don't want this, you have to override + * the methods {@link ClassNodeResolver#cacheClass(String, ClassNode)} and + * {@link ClassNodeResolver#getFromClassCache(String)}. Custom lookup logic is + * supposed to go into the method + * {@link ClassNodeResolver#findClassNode(String, CompilationUnit)} while the + * entry method is {@link ClassNodeResolver#resolveName(String, CompilationUnit)} + * + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ +public class ClassNodeResolver { + + /** + * Helper class to return either a SourceUnit or ClassNode. + * @author <a href="mailto:[email protected]">Jochen "blackdrag" Theodorou</a> + */ + public static class LookupResult { + private final SourceUnit su; + private final ClassNode cn; + /** + * creates a new LookupResult. You are not supposed to supply + * a SourceUnit and a ClassNode at the same time + */ + public LookupResult(SourceUnit su, ClassNode cn) { + this.su = su; + this.cn = cn; + if (su==null && cn==null) throw new IllegalArgumentException("Either the SourceUnit or the ClassNode must not be null."); + if (su!=null && cn!=null) throw new IllegalArgumentException("SourceUnit and ClassNode cannot be set at the same time."); + } + /** + * returns true if a ClassNode is stored + */ + public boolean isClassNode() { return cn!=null; } + /** + * returns true if a SourecUnit is stored + */ + public boolean isSourceUnit() { return su!=null; } + /** + * returns the SourceUnit + */ + public SourceUnit getSourceUnit() { return su; } + /** + * returns the ClassNode + */ + public ClassNode getClassNode() { return cn; } + } + + // Map to store cached classes + private final Map<String,ClassNode> cachedClasses = new HashMap(); + /** + * Internal helper used to indicate a cache hit for a class that does not exist. + * This way further lookups through a slow {@link #findClassNode(String, CompilationUnit)} + * path can be avoided. + * WARNING: This class is not to be used outside of ClassNodeResolver. + */ + protected static final ClassNode NO_CLASS = new ClassNode("NO_CLASS", Opcodes.ACC_PUBLIC,ClassHelper.OBJECT_TYPE){ + public void setRedirect(ClassNode cn) { + throw new GroovyBugError("This is a dummy class node only! Never use it for real classes."); + } + }; + + /** + * Resolves the name of a class to a SourceUnit or ClassNode. If no + * class or source is found this method returns null. A lookup is done + * by first asking the cache if there is an entry for the class already available + * to then call {@link #findClassNode(String, CompilationUnit)}. The result + * of that method call will be cached if a ClassNode is found. If a SourceUnit + * is found, this method will not be asked later on again for that class, because + * ResolveVisitor will first ask the CompilationUnit for classes in the + * compilation queue and it will find the class for that SourceUnit there then. + * method return a ClassNode instead of a SourceUnit, the res + * @param name - the name of the class + * @param compilationUnit - the current CompilationUnit + * @return the LookupResult + */ + public LookupResult resolveName(String name, CompilationUnit compilationUnit) { + ClassNode res = getFromClassCache(name); + if (res==NO_CLASS) return null; + if (res!=null) return new LookupResult(null,res); + LookupResult lr = findClassNode(name, compilationUnit); + if (lr != null) { + if (lr.isClassNode()) cacheClass(name, lr.getClassNode()); + return lr; + } else { + cacheClass(name, NO_CLASS); + return null; + } + } + + /** + * caches a ClassNode + * @param name - the name of the class + * @param res - the ClassNode for that name + */ + public void cacheClass(String name, ClassNode res) { + cachedClasses.put(name, res); + } + + /** + * returns whatever is stored in the class cache for the given name + * @param name - the name of the class + * @return the result of the lookup, which may be null + */ + public ClassNode getFromClassCache(String name) { + // We use here the class cache cachedClasses to prevent + // calls to ClassLoader#loadClass. Disabling this cache will + // cause a major performance hit. + ClassNode cached = cachedClasses.get(name); + return cached; + } + + /** + * Extension point for custom lookup logic of finding ClassNodes. Per default + * this will use the CompilationUnit class loader to do a lookup on the class + * path and load the needed class using that loader. Or if a script is found + * and that script is seen as "newer", the script will be used instead of the + * class. + * + * @param name - the name of the class + * @param compilationUnit - the current compilation unit + * @return the lookup result + */ + public LookupResult findClassNode(String name, CompilationUnit compilationUnit) { + return tryAsLoaderClassOrScript(name, compilationUnit); + } + + /** + * This method is used to realize the lookup of a class using the compilation + * unit class loader. Should no class be found we fall back to a script lookup. + * If a class is found we check if there is also a script and maybe use that + * one in case it is newer.<p/> + * + * Two class search strategies are possible: by ASM decompilation or by usual Java classloading. + * The latter is slower but is unavoidable for scripts executed in dynamic environments where + * the referenced classes might only be available in the classloader, not on disk. + */ + private LookupResult tryAsLoaderClassOrScript(String name, CompilationUnit compilationUnit) { + GroovyClassLoader loader = compilationUnit.getClassLoader(); + + Map<String, Boolean> options = compilationUnit.configuration.getOptimizationOptions(); + boolean useAsm = !Boolean.FALSE.equals(options.get("asmResolving")); + boolean useClassLoader = !Boolean.FALSE.equals(options.get("classLoaderResolving")); + + LookupResult result = useAsm ? findDecompiled(name, compilationUnit, loader) : null; + if (result != null) { + return result; + } + + if (!useClassLoader) { + return tryAsScript(name, compilationUnit, null); + } + + return findByClassLoading(name, compilationUnit, loader); + } + + /** + * Search for classes using class loading + */ + private static LookupResult findByClassLoading(String name, CompilationUnit compilationUnit, GroovyClassLoader loader) { + Class cls; + try { + // NOTE: it's important to do no lookup against script files + // here since the GroovyClassLoader would create a new CompilationUnit + cls = loader.loadClass(name, false, true); + } catch (ClassNotFoundException cnfe) { + LookupResult lr = tryAsScript(name, compilationUnit, null); + return lr; + } catch (CompilationFailedException cfe) { + throw new GroovyBugError("The lookup for "+name+" caused a failed compilaton. There should not have been any compilation from this call.", cfe); + } + //TODO: the case of a NoClassDefFoundError needs a bit more research + // a simple recompilation is not possible it seems. The current class + // we are searching for is there, so we should mark that somehow. + // Basically the missing class needs to be completely compiled before + // we can again search for the current name. + /*catch (NoClassDefFoundError ncdfe) { + cachedClasses.put(name,SCRIPT); + return false; + }*/ + if (cls == null) return null; + //NOTE: we might return false here even if we found a class, + // because we want to give a possible script a chance to + // recompile. This can only be done if the loader was not + // the instance defining the class. + ClassNode cn = ClassHelper.make(cls); + if (cls.getClassLoader() != loader) { + return tryAsScript(name, compilationUnit, cn); + } + return new LookupResult(null,cn); + } + + /** + * Search for classes using ASM decompiler + */ + private LookupResult findDecompiled(String name, CompilationUnit compilationUnit, GroovyClassLoader loader) { + ClassNode node = ClassHelper.make(name); + if (node.isResolved()) { + return new LookupResult(null, node); + } + + DecompiledClassNode asmClass = null; + String fileName = name.replace('.', '/') + ".class"; + URL resource = loader.getResource(fileName); + if (resource != null) { + try { + asmClass = new DecompiledClassNode(AsmDecompiler.parseClass(resource), new AsmReferenceResolver(this, compilationUnit)); + if (!asmClass.getName().equals(name)) { + // this may happen under Windows because getResource is case insensitive under that OS! + asmClass = null; + } + } catch (IOException e) { + // fall through and attempt other search strategies + } + } + + if (asmClass != null) { + if (isFromAnotherClassLoader(loader, fileName)) { + return tryAsScript(name, compilationUnit, asmClass); + } + + return new LookupResult(null, asmClass); + } + return null; + } + + private static boolean isFromAnotherClassLoader(GroovyClassLoader loader, String fileName) { + ClassLoader parent = loader.getParent(); + return parent != null && parent.getResource(fileName) != null; + } + + /** + * try to find a script using the compilation unit class loader. + */ + private static LookupResult tryAsScript(String name, CompilationUnit compilationUnit, ClassNode oldClass) { + LookupResult lr = null; + if (oldClass!=null) { + lr = new LookupResult(null, oldClass); + } + + if (name.startsWith("java.")) return lr; + //TODO: don't ignore inner static classes completely + if (name.indexOf('$') != -1) return lr; + + // try to find a script from classpath*/ + GroovyClassLoader gcl = compilationUnit.getClassLoader(); + URL url = null; + try { + url = gcl.getResourceLoader().loadGroovySource(name); + } catch (MalformedURLException e) { + // fall through and let the URL be null + } + if (url != null && ( oldClass==null || isSourceNewer(url, oldClass))) { + SourceUnit su = compilationUnit.addSource(url); + return new LookupResult(su,null); + } + return lr; + } + + /** + * get the time stamp of a class + * NOTE: copied from GroovyClassLoader + */ + private static long getTimeStamp(ClassNode cls) { + if (!(cls instanceof DecompiledClassNode)) { + return Verifier.getTimestamp(cls.getTypeClass()); + } + + return ((DecompiledClassNode) cls).getCompilationTimeStamp(); + } + + /** + * returns true if the source in URL is newer than the class + * NOTE: copied from GroovyClassLoader + */ + private static boolean isSourceNewer(URL source, ClassNode cls) { + try { + long lastMod; + + // Special handling for file:// protocol, as getLastModified() often reports + // incorrect results (-1) + if (source.getProtocol().equals("file")) { + // Coerce the file URL to a File + String path = source.getPath().replace('/', File.separatorChar).replace('|', ':'); + File file = new File(path); + lastMod = file.lastModified(); + } else { + URLConnection conn = source.openConnection(); + lastMod = conn.getLastModified(); + conn.getInputStream().close(); + } + return lastMod > getTimeStamp(cls); + } catch (IOException e) { + // if the stream can't be opened, let's keep the old reference + return false; + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/CompilationFailedException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/CompilationFailedException.java b/src/main/java/org/codehaus/groovy/control/CompilationFailedException.java new file mode 100644 index 0000000..741b647 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/CompilationFailedException.java @@ -0,0 +1,77 @@ +/* + * 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 org.codehaus.groovy.control; + +import groovy.lang.GroovyRuntimeException; + + +/** + * Thrown when compilation fails from source errors. + * + * @author <a href="mailto:[email protected]">Chris Poirier</a> + */ + +public class CompilationFailedException extends GroovyRuntimeException { + + protected int phase; // The phase in which the failures occurred + protected ProcessingUnit unit; // The *Unit object this exception wraps + + public CompilationFailedException(int phase, ProcessingUnit unit, Throwable cause) { + super(Phases.getDescription(phase) + " failed", cause); + this.phase = phase; + this.unit = unit; + } + + + public CompilationFailedException(int phase, ProcessingUnit unit) { + super(Phases.getDescription(phase) + " failed"); + this.phase = phase; + this.unit = unit; + } + + + /** + * Formats the error data as a String. + */ + + /*public String toString() { + StringWriter data = new StringWriter(); + PrintWriter writer = new PrintWriter(data); + Janitor janitor = new Janitor(); + + try { + unit.getErrorReporter().write(writer, janitor); + } + finally { + janitor.cleanup(); + } + + return data.toString(); + }*/ + + + /** + * Returns the ProcessingUnit in which the error occurred. + */ + + public ProcessingUnit getUnit() { + return this.unit; + } + +}
