You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2017/12/17 14:01:39 UTC
[28/62] [abbrv] [partial] groovy git commit: Move Java source set
into `src/main/java`
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/BytecodeExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/BytecodeExpression.java b/src/main/java/org/codehaus/groovy/classgen/BytecodeExpression.java
new file mode 100644
index 0000000..ca8030f
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/BytecodeExpression.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.ExpressionTransformer;
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Represents some custom bytecode generation by the compiler
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public abstract class BytecodeExpression extends Expression {
+ public static final BytecodeExpression NOP = new BytecodeExpression() {
+ public void visit(MethodVisitor visitor) {
+ //do nothing
+ }
+ };
+
+ public BytecodeExpression() {
+ }
+
+ public BytecodeExpression(ClassNode type) {
+ super.setType(type);
+ }
+
+ public void visit(GroovyCodeVisitor visitor) {
+ visitor.visitBytecodeExpression(this);
+ }
+
+ public abstract void visit(MethodVisitor mv);
+
+ public Expression transformExpression(ExpressionTransformer transformer) {
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/BytecodeInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/BytecodeInstruction.java b/src/main/java/org/codehaus/groovy/classgen/BytecodeInstruction.java
new file mode 100644
index 0000000..e390654
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/BytecodeInstruction.java
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import org.objectweb.asm.MethodVisitor;
+
+/**
+ * Helper class used by the class generator. Usually
+ * an inner class is produced, that contains bytecode
+ * creation code in the visit method.
+ *
+ * @author Jochen Theodorou
+ */
+public abstract class BytecodeInstruction {
+ public abstract void visit(MethodVisitor mv);
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/BytecodeSequence.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/BytecodeSequence.java b/src/main/java/org/codehaus/groovy/classgen/BytecodeSequence.java
new file mode 100644
index 0000000..c5a6645
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/BytecodeSequence.java
@@ -0,0 +1,81 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.GroovyCodeVisitor;
+import org.codehaus.groovy.ast.stmt.Statement;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * This class represents a sequence of BytecodeInstructions
+ * or ASTNodes. The evaluation is depending on the type of
+ * the visitor.
+ *
+ * @see BytecodeInstruction
+ * @see ASTNode
+ */
+
+public class BytecodeSequence extends Statement {
+ private final List<BytecodeInstruction> instructions;
+
+ public BytecodeSequence(List instructions) {
+ this.instructions = instructions;
+ }
+
+ public BytecodeSequence(BytecodeInstruction instruction) {
+ this.instructions = new ArrayList(1);
+ this.instructions.add(instruction);
+ }
+
+ /**
+ * Delegates to the visit method used for this class.
+ * If the visitor is a ClassGenerator, then
+ * {@link ClassGenerator#visitBytecodeSequence(BytecodeSequence)}
+ * is called with this instance. If the visitor is no
+ * ClassGenerator, then this method will call visit on
+ * each ASTNode element sorted by this class. If one
+ * element is a BytecodeInstruction, then it will be skipped
+ * as it is no ASTNode.
+ *
+ * @param visitor the visitor
+ * @see ClassGenerator
+ */
+ public void visit(GroovyCodeVisitor visitor) {
+ if (visitor instanceof ClassGenerator) {
+ ClassGenerator gen = (ClassGenerator) visitor;
+ gen.visitBytecodeSequence(this);
+ return;
+ }
+ for (Iterator iterator = instructions.iterator(); iterator.hasNext();) {
+ Object part = (Object) iterator.next();
+ if (part instanceof ASTNode) {
+ ((ASTNode)part).visit(visitor);
+ }
+ }
+ }
+
+ public List getInstructions() {
+ return instructions;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
new file mode 100644
index 0000000..b05f6bd
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/ClassCompletionVerifier.java
@@ -0,0 +1,746 @@
+/*
+ * 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;
+
+import org.apache.groovy.ast.tools.ClassNodeUtils;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.ast.Variable;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.GStringExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.CatchStatement;
+import org.codehaus.groovy.ast.tools.GeneralUtils;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.runtime.MetaClassHelper;
+import org.codehaus.groovy.syntax.Types;
+import org.codehaus.groovy.transform.trait.Traits;
+
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import static java.lang.reflect.Modifier.isAbstract;
+import static java.lang.reflect.Modifier.isFinal;
+import static java.lang.reflect.Modifier.isInterface;
+import static java.lang.reflect.Modifier.isNative;
+import static java.lang.reflect.Modifier.isPrivate;
+import static java.lang.reflect.Modifier.isStatic;
+import static java.lang.reflect.Modifier.isStrict;
+import static java.lang.reflect.Modifier.isSynchronized;
+import static java.lang.reflect.Modifier.isTransient;
+import static java.lang.reflect.Modifier.isVolatile;
+import static org.codehaus.groovy.ast.ClassHelper.VOID_TYPE;
+import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
+import static org.objectweb.asm.Opcodes.ACC_FINAL;
+import static org.objectweb.asm.Opcodes.ACC_INTERFACE;
+import static org.objectweb.asm.Opcodes.ACC_NATIVE;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_STRICT;
+import static org.objectweb.asm.Opcodes.ACC_SYNCHRONIZED;
+import static org.objectweb.asm.Opcodes.ACC_TRANSIENT;
+import static org.objectweb.asm.Opcodes.ACC_VOLATILE;
+/**
+ * Checks that a class satisfies various conditions including:
+ * <ul>
+ * <li>Incorrect class or method access modifiers</li>
+ * <li>No abstract methods appear in a non-abstract class</li>
+ * <li>Existence and correct visibility for inherited members</li>
+ * <li>Invalid attempts to override final members</li>
+ * </ul>
+ */
+public class ClassCompletionVerifier extends ClassCodeVisitorSupport {
+ private static final String[] INVALID_NAME_CHARS = {".", ":", "/", ";", "[", "<", ">"};
+ // the groovy.compiler.strictNames system property is experimental and may change default value or be removed in a future version of Groovy
+ private final boolean strictNames = Boolean.parseBoolean(System.getProperty("groovy.compiler.strictNames", "false"));
+ private ClassNode currentClass;
+ private final SourceUnit source;
+ private boolean inConstructor = false;
+ private boolean inStaticConstructor = false;
+
+ public ClassCompletionVerifier(SourceUnit source) {
+ this.source = source;
+ }
+
+ public ClassNode getClassNode() {
+ return currentClass;
+ }
+
+ public void visitClass(ClassNode node) {
+ ClassNode oldClass = currentClass;
+ currentClass = node;
+ checkImplementsAndExtends(node);
+ if (source != null && !source.getErrorCollector().hasErrors()) {
+ checkClassForIncorrectModifiers(node);
+ checkInterfaceMethodVisibility(node);
+ checkAbstractMethodVisibility(node);
+ checkClassForOverwritingFinal(node);
+ checkMethodsForIncorrectModifiers(node);
+ checkMethodsForIncorrectName(node);
+ checkMethodsForWeakerAccess(node);
+ checkMethodsForOverridingFinal(node);
+ checkNoAbstractMethodsNonabstractClass(node);
+ checkClassExtendsAllSelfTypes(node);
+ checkNoStaticMethodWithSameSignatureAsNonStatic(node);
+ checkGenericsUsage(node, node.getUnresolvedInterfaces());
+ checkGenericsUsage(node, node.getUnresolvedSuperClass());
+ }
+ super.visitClass(node);
+ currentClass = oldClass;
+ }
+
+ private void checkNoStaticMethodWithSameSignatureAsNonStatic(final ClassNode node) {
+ ClassNode parent = node.getSuperClass();
+ Map<String, MethodNode> result;
+ // start with methods from the parent if any
+ if (parent != null) {
+ result = parent.getDeclaredMethodsMap();
+ } else {
+ result = new HashMap<String, MethodNode>();
+ }
+ // add in unimplemented abstract methods from the interfaces
+ ClassNodeUtils.addDeclaredMethodsFromInterfaces(node, result);
+ for (MethodNode methodNode : node.getMethods()) {
+ MethodNode mn = result.get(methodNode.getTypeDescriptor());
+ if (mn != null && (mn.isStatic() ^ methodNode.isStatic()) && !methodNode.isStaticConstructor()) {
+ if (!mn.isAbstract()) continue;
+ ClassNode declaringClass = mn.getDeclaringClass();
+ ClassNode cn = declaringClass.getOuterClass();
+ if (cn == null && declaringClass.isResolved()) {
+ // in case of a precompiled class, the outerclass is unknown
+ Class typeClass = declaringClass.getTypeClass();
+ typeClass = typeClass.getEnclosingClass();
+ if (typeClass != null) {
+ cn = ClassHelper.make(typeClass);
+ }
+ }
+ if (cn == null || !Traits.isTrait(cn)) {
+ ASTNode errorNode = methodNode;
+ String name = mn.getName();
+ if (errorNode.getLineNumber() == -1) {
+ // try to get a better error message location based on the property
+ for (PropertyNode propertyNode : node.getProperties()) {
+ if (name.startsWith("set") || name.startsWith("get") || name.startsWith("is")) {
+ String propName = Verifier.capitalize(propertyNode.getField().getName());
+ String shortName = name.substring(name.startsWith("is") ? 2 : 3);
+ if (propName.equals(shortName)) {
+ errorNode = propertyNode;
+ break;
+ }
+ }
+ }
+ }
+ addError("The " + getDescription(methodNode) + " is already defined in " + getDescription(node) +
+ ". You cannot have both a static and an instance method with the same signature", errorNode);
+ }
+ }
+ result.put(methodNode.getTypeDescriptor(), methodNode);
+ }
+ }
+
+ private void checkInterfaceMethodVisibility(ClassNode node) {
+ if (!node.isInterface()) return;
+ for (MethodNode method : node.getMethods()) {
+ if (method.isPrivate()) {
+ addError("Method '" + method.getName() + "' is private but should be public in " + getDescription(currentClass) + ".", method);
+ } else if (method.isProtected()) {
+ addError("Method '" + method.getName() + "' is protected but should be public in " + getDescription(currentClass) + ".", method);
+ }
+ }
+ }
+
+ private void checkAbstractMethodVisibility(ClassNode node) {
+ // we only do check abstract classes (including enums), no interfaces or non-abstract classes
+ if (!isAbstract(node.getModifiers()) || isInterface(node.getModifiers())) return;
+
+ List<MethodNode> abstractMethods = node.getAbstractMethods();
+ if (abstractMethods == null || abstractMethods.isEmpty()) return;
+
+ for (MethodNode method : abstractMethods) {
+ if (method.isPrivate()) {
+ addError("Method '" + method.getName() + "' from " + getDescription(node) +
+ " must not be private as it is declared as an abstract method.", method);
+ }
+ }
+ }
+
+ private void checkNoAbstractMethodsNonabstractClass(ClassNode node) {
+ if (isAbstract(node.getModifiers())) return;
+ List<MethodNode> abstractMethods = node.getAbstractMethods();
+ if (abstractMethods == null) return;
+ for (MethodNode method : abstractMethods) {
+ MethodNode sameArgsMethod = node.getMethod(method.getName(), method.getParameters());
+ if (sameArgsMethod==null || method.getReturnType().equals(sameArgsMethod.getReturnType())) {
+ addError("Can't have an abstract method in a non-abstract class." +
+ " The " + getDescription(node) + " must be declared abstract or" +
+ " the " + getDescription(method) + " must be implemented.", node);
+ } else {
+ addError("Abstract "+getDescription(method)+" is not implemented but a " +
+ "method of the same name but different return type is defined: "+
+ (sameArgsMethod.isStatic()?"static ":"")+
+ getDescription(sameArgsMethod), method
+ );
+ }
+ }
+ }
+
+ private void checkClassExtendsAllSelfTypes(ClassNode node) {
+ int modifiers = node.getModifiers();
+ if (!isInterface(modifiers)) {
+ for (ClassNode anInterface : GeneralUtils.getInterfacesAndSuperInterfaces(node)) {
+ if (Traits.isTrait(anInterface)) {
+ LinkedHashSet<ClassNode> selfTypes = new LinkedHashSet<ClassNode>();
+ for (ClassNode type : Traits.collectSelfTypes(anInterface, selfTypes, true, false)) {
+ if (type.isInterface() && !node.implementsInterface(type)) {
+ addError(getDescription(node)
+ + " implements " + getDescription(anInterface)
+ + " but does not implement self type " + getDescription(type),
+ anInterface);
+ } else if (!type.isInterface() && !node.isDerivedFrom(type)) {
+ addError(getDescription(node)
+ + " implements " + getDescription(anInterface)
+ + " but does not extend self type " + getDescription(type),
+ anInterface);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void checkClassForIncorrectModifiers(ClassNode node) {
+ checkClassForAbstractAndFinal(node);
+ checkClassForOtherModifiers(node);
+ }
+
+ private void checkClassForAbstractAndFinal(ClassNode node) {
+ if (!isAbstract(node.getModifiers())) return;
+ if (!isFinal(node.getModifiers())) return;
+ if (node.isInterface()) {
+ addError("The " + getDescription(node) + " must not be final. It is by definition abstract.", node);
+ } else {
+ addError("The " + getDescription(node) + " must not be both final and abstract.", node);
+ }
+ }
+
+ private void checkClassForOtherModifiers(ClassNode node) {
+ checkClassForModifier(node, isTransient(node.getModifiers()), "transient");
+ checkClassForModifier(node, isVolatile(node.getModifiers()), "volatile");
+ checkClassForModifier(node, isNative(node.getModifiers()), "native");
+ if (!(node instanceof InnerClassNode)) {
+ checkClassForModifier(node, isStatic(node.getModifiers()), "static");
+ checkClassForModifier(node, isPrivate(node.getModifiers()), "private");
+ }
+ // don't check synchronized here as it overlaps with ACC_SUPER
+ }
+
+ private void checkMethodForModifier(MethodNode node, boolean condition, String modifierName) {
+ if (!condition) return;
+ addError("The " + getDescription(node) + " has an incorrect modifier " + modifierName + ".", node);
+ }
+
+ private void checkClassForModifier(ClassNode node, boolean condition, String modifierName) {
+ if (!condition) return;
+ addError("The " + getDescription(node) + " has an incorrect modifier " + modifierName + ".", node);
+ }
+
+ private static String getDescription(ClassNode node) {
+ return (node.isInterface() ? (Traits.isTrait(node)?"trait":"interface") : "class") + " '" + node.getName() + "'";
+ }
+
+ private static String getDescription(MethodNode node) {
+ return "method '" + node.getTypeDescriptor() + "'";
+ }
+
+ private static String getDescription(FieldNode node) {
+ return "field '" + node.getName() + "'";
+ }
+
+ private static String getDescription(Parameter node) {
+ return "parameter '" + node.getName() + "'";
+ }
+
+ private void checkAbstractDeclaration(MethodNode methodNode) {
+ if (!methodNode.isAbstract()) return;
+ if (isAbstract(currentClass.getModifiers())) return;
+ addError("Can't have an abstract method in a non-abstract class." +
+ " The " + getDescription(currentClass) + " must be declared abstract or the method '" +
+ methodNode.getTypeDescriptor() + "' must not be abstract.", methodNode);
+ }
+
+ private void checkClassForOverwritingFinal(ClassNode cn) {
+ ClassNode superCN = cn.getSuperClass();
+ if (superCN == null) return;
+ if (!isFinal(superCN.getModifiers())) return;
+ String msg = "You are not allowed to overwrite the final " + getDescription(superCN) + ".";
+ addError(msg, cn);
+ }
+
+ private void checkImplementsAndExtends(ClassNode node) {
+ ClassNode cn = node.getSuperClass();
+ if (cn.isInterface() && !node.isInterface()) {
+ addError("You are not allowed to extend the " + getDescription(cn) + ", use implements instead.", node);
+ }
+ for (ClassNode anInterface : node.getInterfaces()) {
+ cn = anInterface;
+ if (!cn.isInterface()) {
+ addError("You are not allowed to implement the " + getDescription(cn) + ", use extends instead.", node);
+ }
+ }
+ }
+
+ private void checkMethodsForIncorrectName(ClassNode cn) {
+ if (!strictNames) return;
+ List<MethodNode> methods = cn.getAllDeclaredMethods();
+ for (MethodNode mNode : methods) {
+ String name = mNode.getName();
+ if (name.equals("<init>") || name.equals("<clinit>")) continue;
+ // Groovy allows more characters than Character.isValidJavaIdentifier() would allow
+ // if we find a good way to encode special chars we could remove (some of) these checks
+ for (String ch : INVALID_NAME_CHARS) {
+ if (name.contains(ch)) {
+ addError("You are not allowed to have '" + ch + "' in a method name", mNode);
+ }
+ }
+ }
+ }
+
+ private void checkMethodsForIncorrectModifiers(ClassNode cn) {
+ if (!cn.isInterface()) return;
+ for (MethodNode method : cn.getMethods()) {
+ if (method.isFinal()) {
+ addError("The " + getDescription(method) + " from " + getDescription(cn) +
+ " must not be final. It is by definition abstract.", method);
+ }
+ if (method.isStatic() && !isConstructor(method)) {
+ addError("The " + getDescription(method) + " from " + getDescription(cn) +
+ " must not be static. Only fields may be static in an interface.", method);
+ }
+ }
+ }
+
+ private void checkMethodsForWeakerAccess(ClassNode cn) {
+ for (MethodNode method : cn.getMethods()) {
+ checkMethodForWeakerAccessPrivileges(method, cn);
+ }
+ }
+
+ private static boolean isConstructor(MethodNode method) {
+ return method.getName().equals("<clinit>");
+ }
+
+ private void checkMethodsForOverridingFinal(ClassNode cn) {
+ for (MethodNode method : cn.getMethods()) {
+ Parameter[] params = method.getParameters();
+ for (MethodNode superMethod : cn.getSuperClass().getMethods(method.getName())) {
+ Parameter[] superParams = superMethod.getParameters();
+ if (!hasEqualParameterTypes(params, superParams)) continue;
+ if (!superMethod.isFinal()) break;
+ addInvalidUseOfFinalError(method, params, superMethod.getDeclaringClass());
+ return;
+ }
+ }
+ }
+
+ private void addInvalidUseOfFinalError(MethodNode method, Parameter[] parameters, ClassNode superCN) {
+ StringBuilder msg = new StringBuilder();
+ msg.append("You are not allowed to override the final method ").append(method.getName());
+ appendParamsDescription(parameters, msg);
+ msg.append(" from ").append(getDescription(superCN));
+ msg.append(".");
+ addError(msg.toString(), method);
+ }
+
+ private void appendParamsDescription(Parameter[] parameters, StringBuilder msg) {
+ msg.append("(");
+ boolean needsComma = false;
+ for (Parameter parameter : parameters) {
+ if (needsComma) {
+ msg.append(",");
+ } else {
+ needsComma = true;
+ }
+ msg.append(parameter.getType());
+ }
+ msg.append(")");
+ }
+
+ private void addWeakerAccessError(ClassNode cn, MethodNode method, Parameter[] parameters, MethodNode superMethod) {
+ StringBuilder msg = new StringBuilder();
+ msg.append(method.getName());
+ appendParamsDescription(parameters, msg);
+ msg.append(" in ");
+ msg.append(cn.getName());
+ msg.append(" cannot override ");
+ msg.append(superMethod.getName());
+ msg.append(" in ");
+ msg.append(superMethod.getDeclaringClass().getName());
+ msg.append("; attempting to assign weaker access privileges; was ");
+ msg.append(superMethod.isPublic() ? "public" : "protected");
+ addError(msg.toString(), method);
+ }
+
+ private static boolean hasEqualParameterTypes(Parameter[] first, Parameter[] second) {
+ if (first.length != second.length) return false;
+ for (int i = 0; i < first.length; i++) {
+ String ft = first[i].getType().getName();
+ String st = second[i].getType().getName();
+ if (ft.equals(st)) continue;
+ return false;
+ }
+ return true;
+ }
+
+ protected SourceUnit getSourceUnit() {
+ return source;
+ }
+
+ public void visitMethod(MethodNode node) {
+ inConstructor = false;
+ inStaticConstructor = node.isStaticConstructor();
+ checkAbstractDeclaration(node);
+ checkRepetitiveMethod(node);
+ checkOverloadingPrivateAndPublic(node);
+ checkMethodModifiers(node);
+ checkGenericsUsage(node, node.getParameters());
+ checkGenericsUsage(node, node.getReturnType());
+ for (Parameter param : node.getParameters()) {
+ if (param.getType().equals(VOID_TYPE)) {
+ addError("The " + getDescription(param) + " in " + getDescription(node) + " has invalid type void", param);
+ }
+ }
+ super.visitMethod(node);
+ }
+
+ private void checkMethodModifiers(MethodNode node) {
+ // don't check volatile here as it overlaps with ACC_BRIDGE
+ // additional modifiers not allowed for interfaces
+ if ((this.currentClass.getModifiers() & ACC_INTERFACE) != 0) {
+ checkMethodForModifier(node, isStrict(node.getModifiers()), "strictfp");
+ checkMethodForModifier(node, isSynchronized(node.getModifiers()), "synchronized");
+ checkMethodForModifier(node, isNative(node.getModifiers()), "native");
+ }
+ }
+
+ private void checkMethodForWeakerAccessPrivileges(MethodNode mn, ClassNode cn) {
+ if (mn.isPublic()) return;
+ Parameter[] params = mn.getParameters();
+ for (MethodNode superMethod : cn.getSuperClass().getMethods(mn.getName())) {
+ Parameter[] superParams = superMethod.getParameters();
+ if (!hasEqualParameterTypes(params, superParams)) continue;
+ if ((mn.isPrivate() && !superMethod.isPrivate()) ||
+ (mn.isProtected() && superMethod.isPublic())) {
+ addWeakerAccessError(cn, mn, params, superMethod);
+ return;
+ }
+ }
+ }
+
+ private void checkOverloadingPrivateAndPublic(MethodNode node) {
+ if (isConstructor(node)) return;
+ boolean hasPrivate = node.isPrivate();
+ boolean hasPublic = node.isPublic();
+ for (MethodNode method : currentClass.getMethods(node.getName())) {
+ if (method == node) continue;
+ if (!method.getDeclaringClass().equals(node.getDeclaringClass())) continue;
+ if (method.isPublic() || method.isProtected()) {
+ hasPublic = true;
+ } else {
+ hasPrivate = true;
+ }
+ if (hasPrivate && hasPublic) break;
+ }
+ if (hasPrivate && hasPublic) {
+ addError("Mixing private and public/protected methods of the same name causes multimethods to be disabled and is forbidden to avoid surprising behaviour. Renaming the private methods will solve the problem.", node);
+ }
+ }
+
+ private void checkRepetitiveMethod(MethodNode node) {
+ if (isConstructor(node)) return;
+ for (MethodNode method : currentClass.getMethods(node.getName())) {
+ if (method == node) continue;
+ if (!method.getDeclaringClass().equals(node.getDeclaringClass())) continue;
+ Parameter[] p1 = node.getParameters();
+ Parameter[] p2 = method.getParameters();
+ if (p1.length != p2.length) continue;
+ addErrorIfParamsAndReturnTypeEqual(p2, p1, node, method);
+ }
+ }
+
+ private void addErrorIfParamsAndReturnTypeEqual(Parameter[] p2, Parameter[] p1,
+ MethodNode node, MethodNode element) {
+ boolean isEqual = true;
+ for (int i = 0; i < p2.length; i++) {
+ isEqual &= p1[i].getType().equals(p2[i].getType());
+ if (!isEqual) break;
+ }
+ isEqual &= node.getReturnType().equals(element.getReturnType());
+ if (isEqual) {
+ addError("Repetitive method name/signature for " + getDescription(node) +
+ " in " + getDescription(currentClass) + ".", node);
+ }
+ }
+
+ public void visitField(FieldNode node) {
+ if (currentClass.getDeclaredField(node.getName()) != node) {
+ addError("The " + getDescription(node) + " is declared multiple times.", node);
+ }
+ checkInterfaceFieldModifiers(node);
+ checkGenericsUsage(node, node.getType());
+ if (node.getType().equals(VOID_TYPE)) {
+ addError("The " + getDescription(node) + " has invalid type void", node);
+ }
+ super.visitField(node);
+ }
+
+ public void visitProperty(PropertyNode node) {
+ checkDuplicateProperties(node);
+ checkGenericsUsage(node, node.getType());
+ super.visitProperty(node);
+ }
+
+ private void checkDuplicateProperties(PropertyNode node) {
+ ClassNode cn = node.getDeclaringClass();
+ String name = node.getName();
+ String getterName = "get" + MetaClassHelper.capitalize(name);
+ if (Character.isUpperCase(name.charAt(0))) {
+ for (PropertyNode propNode : cn.getProperties()) {
+ String otherName = propNode.getField().getName();
+ String otherGetterName = "get" + MetaClassHelper.capitalize(otherName);
+ if (node != propNode && getterName.equals(otherGetterName)) {
+ String msg = "The field " + name + " and " + otherName + " on the class " +
+ cn.getName() + " will result in duplicate JavaBean properties, which is not allowed";
+ addError(msg, node);
+ }
+ }
+ }
+ }
+
+ private void checkInterfaceFieldModifiers(FieldNode node) {
+ if (!currentClass.isInterface()) return;
+ if ((node.getModifiers() & (ACC_PUBLIC | ACC_STATIC | ACC_FINAL)) == 0 ||
+ (node.getModifiers() & (ACC_PRIVATE | ACC_PROTECTED)) != 0) {
+ addError("The " + getDescription(node) + " is not 'public static final' but is defined in " +
+ getDescription(currentClass) + ".", node);
+ }
+ }
+
+ public void visitBinaryExpression(BinaryExpression expression) {
+ if (expression.getOperation().getType() == Types.LEFT_SQUARE_BRACKET &&
+ expression.getRightExpression() instanceof MapEntryExpression) {
+ addError("You tried to use a map entry for an index operation, this is not allowed. " +
+ "Maybe something should be set in parentheses or a comma is missing?",
+ expression.getRightExpression());
+ }
+ super.visitBinaryExpression(expression);
+
+ if (Types.isAssignment(expression.getOperation().getType())) {
+ checkFinalFieldAccess(expression.getLeftExpression());
+ checkSuperOrThisOnLHS(expression.getLeftExpression());
+ }
+ }
+
+ private void checkSuperOrThisOnLHS(Expression expression) {
+ if (!(expression instanceof VariableExpression)) return;
+ VariableExpression ve = (VariableExpression) expression;
+ if (ve.isThisExpression()) {
+ addError("cannot have 'this' as LHS of an assignment", expression);
+ } else if (ve.isSuperExpression()) {
+ addError("cannot have 'super' as LHS of an assignment", expression);
+ }
+ }
+
+ private void checkFinalFieldAccess(Expression expression) {
+ if (!(expression instanceof VariableExpression) && !(expression instanceof PropertyExpression)) return;
+ Variable v = null;
+ if (expression instanceof VariableExpression) {
+ VariableExpression ve = (VariableExpression) expression;
+ v = ve.getAccessedVariable();
+ } else {
+ PropertyExpression propExp = ((PropertyExpression) expression);
+ Expression objectExpression = propExp.getObjectExpression();
+ if (objectExpression instanceof VariableExpression) {
+ VariableExpression varExp = (VariableExpression) objectExpression;
+ if (varExp.isThisExpression()) {
+ v = currentClass.getDeclaredField(propExp.getPropertyAsString());
+ }
+ }
+ }
+ if (v instanceof FieldNode) {
+ FieldNode fn = (FieldNode) v;
+
+ /*
+ * if it is static final but not accessed inside a static constructor, or,
+ * if it is an instance final but not accessed inside a instance constructor, it is an error
+ */
+ boolean isFinal = fn.isFinal();
+ boolean isStatic = fn.isStatic();
+ boolean error = isFinal && ((isStatic && !inStaticConstructor) || (!isStatic && !inConstructor));
+
+ if (error) addError("cannot modify" + (isStatic ? " static" : "") + " final field '" + fn.getName() +
+ "' outside of " + (isStatic ? "static initialization block." : "constructor."), expression);
+ }
+ }
+
+ public void visitConstructor(ConstructorNode node) {
+ inConstructor = true;
+ inStaticConstructor = node.isStaticConstructor();
+ checkGenericsUsage(node, node.getParameters());
+ super.visitConstructor(node);
+ }
+
+ public void visitCatchStatement(CatchStatement cs) {
+ if (!(cs.getExceptionType().isDerivedFrom(ClassHelper.make(Throwable.class)))) {
+ addError("Catch statement parameter type is not a subclass of Throwable.", cs);
+ }
+ super.visitCatchStatement(cs);
+ }
+
+ public void visitMethodCallExpression(MethodCallExpression mce) {
+ super.visitMethodCallExpression(mce);
+ Expression aexp = mce.getArguments();
+ if (aexp instanceof TupleExpression) {
+ TupleExpression arguments = (TupleExpression) aexp;
+ for (Expression e : arguments.getExpressions()) {
+ checkForInvalidDeclaration(e);
+ }
+ } else {
+ checkForInvalidDeclaration(aexp);
+ }
+ }
+
+ @Override
+ public void visitDeclarationExpression(DeclarationExpression expression) {
+ super.visitDeclarationExpression(expression);
+ if (expression.isMultipleAssignmentDeclaration()) return;
+ checkInvalidDeclarationModifier(expression, ACC_ABSTRACT, "abstract");
+ checkInvalidDeclarationModifier(expression, ACC_NATIVE, "native");
+ checkInvalidDeclarationModifier(expression, ACC_PRIVATE, "private");
+ checkInvalidDeclarationModifier(expression, ACC_PROTECTED, "protected");
+ checkInvalidDeclarationModifier(expression, ACC_PUBLIC, "public");
+ checkInvalidDeclarationModifier(expression, ACC_STATIC, "static");
+ checkInvalidDeclarationModifier(expression, ACC_STRICT, "strictfp");
+ checkInvalidDeclarationModifier(expression, ACC_SYNCHRONIZED, "synchronized");
+ checkInvalidDeclarationModifier(expression, ACC_TRANSIENT, "transient");
+ checkInvalidDeclarationModifier(expression, ACC_VOLATILE, "volatile");
+ if (expression.getVariableExpression().getOriginType().equals(VOID_TYPE)) {
+ addError("The variable '" + expression.getVariableExpression().getName() + "' has invalid type void", expression);
+ }
+ }
+
+ private void checkInvalidDeclarationModifier(DeclarationExpression expression, int modifier, String modName) {
+ if ((expression.getVariableExpression().getModifiers() & modifier) != 0) {
+ addError("Modifier '" + modName + "' not allowed here.", expression);
+ }
+ }
+
+ private void checkForInvalidDeclaration(Expression exp) {
+ if (!(exp instanceof DeclarationExpression)) return;
+ addError("Invalid use of declaration inside method call.", exp);
+ }
+
+ public void visitConstantExpression(ConstantExpression expression) {
+ super.visitConstantExpression(expression);
+ checkStringExceedingMaximumLength(expression);
+ }
+
+ public void visitGStringExpression(GStringExpression expression) {
+ super.visitGStringExpression(expression);
+ for (ConstantExpression ce : expression.getStrings()) {
+ checkStringExceedingMaximumLength(ce);
+ }
+ }
+
+ private void checkStringExceedingMaximumLength(ConstantExpression expression) {
+ Object value = expression.getValue();
+ if (value instanceof String) {
+ String s = (String) value;
+ if (s.length() > 65535) {
+ addError("String too long. The given string is " + s.length() + " Unicode code units long, but only a maximum of 65535 is allowed.", expression);
+ }
+ }
+ }
+
+ private void checkGenericsUsage(ASTNode ref, ClassNode[] nodes) {
+ for (ClassNode node : nodes) {
+ checkGenericsUsage(ref, node);
+ }
+ }
+
+ private void checkGenericsUsage(ASTNode ref, Parameter[] params) {
+ for (Parameter p : params) {
+ checkGenericsUsage(ref, p.getType());
+ }
+ }
+
+ private void checkGenericsUsage(ASTNode ref, ClassNode node) {
+ if (node.isArray()) {
+ checkGenericsUsage(ref, node.getComponentType());
+ } else if (!node.isRedirectNode() && node.isUsingGenerics()) {
+ addError(
+ "A transform used a generics containing ClassNode "+ node + " " +
+ "for "+getRefDescriptor(ref) +
+ "directly. You are not supposed to do this. " +
+ "Please create a new ClassNode referring to the old ClassNode " +
+ "and use the new ClassNode instead of the old one. Otherwise " +
+ "the compiler will create wrong descriptors and a potential " +
+ "NullPointerException in TypeResolver in the OpenJDK. If this is " +
+ "not your own doing, please report this bug to the writer of the " +
+ "transform.",
+ ref);
+ }
+ }
+
+ private static String getRefDescriptor(ASTNode ref) {
+ if (ref instanceof FieldNode) {
+ FieldNode f = (FieldNode) ref;
+ return "the field "+f.getName()+" ";
+ } else if (ref instanceof PropertyNode) {
+ PropertyNode p = (PropertyNode) ref;
+ return "the property "+p.getName()+" ";
+ } else if (ref instanceof ConstructorNode) {
+ return "the constructor "+ref.getText()+" ";
+ } else if (ref instanceof MethodNode) {
+ return "the method "+ref.getText()+" ";
+ } else if (ref instanceof ClassNode) {
+ return "the super class "+ref+" ";
+ }
+ return "<unknown with class "+ref.getClass()+"> ";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/ClassGenerator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/ClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/ClassGenerator.java
new file mode 100644
index 0000000..2ed8661
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/ClassGenerator.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.control.SourceUnit;
+import org.objectweb.asm.Opcodes;
+
+import java.util.LinkedList;
+
+/**
+ * Abstract base class for generator of Java class versions of Groovy AST classes
+ *
+ * @author <a href="mailto:jstrachan@protique.com">James Strachan</a>
+ * @author Russel Winder
+ */
+public abstract class ClassGenerator extends ClassCodeVisitorSupport implements Opcodes {
+ // inner classes created while generating bytecode
+ protected LinkedList<ClassNode> innerClasses = new LinkedList<ClassNode>();
+
+ public LinkedList<ClassNode> getInnerClasses() {
+ return innerClasses;
+ }
+
+ protected SourceUnit getSourceUnit() {
+ return null;
+ }
+
+ public void visitBytecodeSequence(BytecodeSequence bytecodeSequence) {
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/ClassGeneratorException.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/ClassGeneratorException.java b/src/main/java/org/codehaus/groovy/classgen/ClassGeneratorException.java
new file mode 100644
index 0000000..bce3448
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/ClassGeneratorException.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+/**
+ * An exception thrown by the class generator
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ */
+public class ClassGeneratorException extends RuntimeException {
+
+ public ClassGeneratorException(String message) {
+ super(message);
+ }
+
+ public ClassGeneratorException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/DummyClassGenerator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/DummyClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/DummyClassGenerator.java
new file mode 100644
index 0000000..15ade40
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/DummyClassGenerator.java
@@ -0,0 +1,180 @@
+/*
+ * 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;
+
+import groovy.lang.GroovyRuntimeException;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CompileUnit;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.PropertyNode;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Iterator;
+
+/**
+ * To generate a class that has all the fields and methods, except that fields are not initialized
+ * and methods are empty. It's intended for being used as a place holder during code generation
+ * of reference to the "this" class itself.
+ *
+ * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
+ * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
+ */
+public class DummyClassGenerator extends ClassGenerator {
+
+ private final ClassVisitor cv;
+ private MethodVisitor mv;
+ private final GeneratorContext context;
+
+ // current class details
+ private ClassNode classNode;
+ private String internalClassName;
+ private String internalBaseClassName;
+
+
+ public DummyClassGenerator(
+ GeneratorContext context,
+ ClassVisitor classVisitor,
+ ClassLoader classLoader,
+ String sourceFile) {
+ this.context = context;
+ this.cv = classVisitor;
+ }
+
+ // GroovyClassVisitor interface
+ //-------------------------------------------------------------------------
+ public void visitClass(ClassNode classNode) {
+ try {
+ this.classNode = classNode;
+ this.internalClassName = BytecodeHelper.getClassInternalName(classNode);
+
+ //System.out.println("Generating class: " + classNode.getName());
+
+ this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass());
+
+ cv.visit(
+ Opcodes.V1_3,
+ classNode.getModifiers(),
+ internalClassName,
+ (String) null,
+ internalBaseClassName,
+ BytecodeHelper.getClassInternalNames(classNode.getInterfaces())
+ );
+
+ classNode.visitContents(this);
+
+ for (Iterator iter = innerClasses.iterator(); iter.hasNext();) {
+ ClassNode innerClass = (ClassNode) iter.next();
+ ClassNode innerClassType = innerClass;
+ String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassType);
+ String outerClassName = internalClassName; // default for inner classes
+ MethodNode enclosingMethod = innerClass.getEnclosingMethod();
+ if (enclosingMethod != null) {
+ // local inner classes do not specify the outer class name
+ outerClassName = null;
+ }
+ cv.visitInnerClass(
+ innerClassInternalName,
+ outerClassName,
+ innerClassType.getName(),
+ innerClass.getModifiers());
+ }
+ cv.visitEnd();
+ }
+ catch (GroovyRuntimeException e) {
+ e.setModule(classNode.getModule());
+ throw e;
+ }
+ }
+
+ public void visitConstructor(ConstructorNode node) {
+
+ visitParameters(node, node.getParameters());
+
+ String methodType = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, node.getParameters());
+ mv = cv.visitMethod(node.getModifiers(), "<init>", methodType, null, null);
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("not intended for execution");
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+ mv.visitMaxs(0, 0);
+ }
+
+ public void visitMethod(MethodNode node) {
+
+ visitParameters(node, node.getParameters());
+
+ String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), node.getParameters());
+ mv = cv.visitMethod(node.getModifiers(), node.getName(), methodType, null, null);
+
+ mv.visitTypeInsn(NEW, "java/lang/RuntimeException");
+ mv.visitInsn(DUP);
+ mv.visitLdcInsn("not intended for execution");
+ mv.visitMethodInsn(INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false);
+ mv.visitInsn(ATHROW);
+
+ mv.visitMaxs(0, 0);
+ }
+
+ public void visitField(FieldNode fieldNode) {
+
+ cv.visitField(
+ fieldNode.getModifiers(),
+ fieldNode.getName(),
+ BytecodeHelper.getTypeDescription(fieldNode.getType()),
+ null, //fieldValue, //br all the sudden that one cannot init the field here. init is done in static initializer and instance initializer.
+ null);
+ }
+
+ /**
+ * Creates a getter, setter and field
+ */
+ public void visitProperty(PropertyNode statement) {
+ }
+
+ protected CompileUnit getCompileUnit() {
+ CompileUnit answer = classNode.getCompileUnit();
+ if (answer == null) {
+ answer = context.getCompileUnit();
+ }
+ return answer;
+ }
+
+ protected void visitParameters(ASTNode node, Parameter[] parameters) {
+ for (int i = 0, size = parameters.length; i < size; i++) {
+ visitParameter(node, parameters[i]);
+ }
+ }
+
+ protected void visitParameter(ASTNode node, Parameter parameter) {
+ }
+
+
+ public void visitAnnotations(AnnotatedNode node) {
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java b/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
new file mode 100644
index 0000000..30c6fab
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/EnumCompletionVisitor.java
@@ -0,0 +1,169 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.CodeVisitorSupport;
+import org.codehaus.groovy.ast.ConstructorNode;
+import org.codehaus.groovy.ast.EnumConstantClassNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.transform.TupleConstructorASTTransformation;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Enums have a parent constructor with two arguments from java.lang.Enum.
+ * This visitor adds those two arguments into manually created constructors
+ * and performs the necessary super call.
+ */
+public class EnumCompletionVisitor extends ClassCodeVisitorSupport {
+ private final SourceUnit sourceUnit;
+
+ public EnumCompletionVisitor(CompilationUnit cu, SourceUnit su) {
+ sourceUnit = su;
+ }
+
+ public void visitClass(ClassNode node) {
+ if (!node.isEnum()) return;
+ completeEnum(node);
+ }
+
+ protected SourceUnit getSourceUnit() {
+ return sourceUnit;
+ }
+
+ private void completeEnum(ClassNode enumClass) {
+ boolean isAic = isAnonymousInnerClass(enumClass);
+ if (enumClass.getDeclaredConstructors().isEmpty()) {
+ addImplicitConstructors(enumClass, isAic);
+ }
+
+ for (ConstructorNode ctor : enumClass.getDeclaredConstructors()) {
+ transformConstructor(ctor, isAic);
+ }
+ }
+
+ /**
+ * Add map and no-arg constructor or mirror those of the superclass (i.e. base enum).
+ */
+ private static void addImplicitConstructors(ClassNode enumClass, boolean aic) {
+ if (aic) {
+ ClassNode sn = enumClass.getSuperClass();
+ List<ConstructorNode> sctors = new ArrayList<ConstructorNode>(sn.getDeclaredConstructors());
+ if (sctors.isEmpty()) {
+ addMapConstructors(enumClass, false);
+ } else {
+ for (ConstructorNode constructorNode : sctors) {
+ ConstructorNode init = new ConstructorNode(Opcodes.ACC_PUBLIC, constructorNode.getParameters(), ClassNode.EMPTY_ARRAY, new BlockStatement());
+ enumClass.addConstructor(init);
+ }
+ }
+ } else {
+ addMapConstructors(enumClass, false);
+ }
+ }
+
+ /**
+ * If constructor does not define a call to super, then transform constructor
+ * to get String,int parameters at beginning and add call super(String,int).
+ */
+ private void transformConstructor(ConstructorNode ctor, boolean isAic) {
+ boolean chainedThisConstructorCall = false;
+ ConstructorCallExpression cce = null;
+ if (ctor.firstStatementIsSpecialConstructorCall()) {
+ Statement code = ctor.getFirstStatement();
+ cce = (ConstructorCallExpression) ((ExpressionStatement) code).getExpression();
+ if (cce.isSuperCall()) return;
+ // must be call to this(...)
+ chainedThisConstructorCall = true;
+ }
+ // we need to add parameters
+ Parameter[] oldP = ctor.getParameters();
+ Parameter[] newP = new Parameter[oldP.length + 2];
+ String stringParameterName = getUniqueVariableName("__str", ctor.getCode());
+ newP[0] = new Parameter(ClassHelper.STRING_TYPE, stringParameterName);
+ String intParameterName = getUniqueVariableName("__int", ctor.getCode());
+ newP[1] = new Parameter(ClassHelper.int_TYPE, intParameterName);
+ System.arraycopy(oldP, 0, newP, 2, oldP.length);
+ ctor.setParameters(newP);
+ VariableExpression stringVariable = new VariableExpression(newP[0]);
+ VariableExpression intVariable = new VariableExpression(newP[1]);
+ if (chainedThisConstructorCall) {
+ TupleExpression args = (TupleExpression) cce.getArguments();
+ List<Expression> argsExprs = args.getExpressions();
+ argsExprs.add(0, stringVariable);
+ argsExprs.add(1, intVariable);
+ } else {
+ // add a super call
+ List<Expression> args = new ArrayList<Expression>();
+ args.add(stringVariable);
+ args.add(intVariable);
+ if (isAic) {
+ for (Parameter parameter : oldP) {
+ args.add(new VariableExpression(parameter.getName()));
+ }
+ }
+ cce = new ConstructorCallExpression(ClassNode.SUPER, new ArgumentListExpression(args));
+ BlockStatement code = new BlockStatement();
+ code.addStatement(new ExpressionStatement(cce));
+ Statement oldCode = ctor.getCode();
+ if (oldCode != null) code.addStatement(oldCode);
+ ctor.setCode(code);
+ }
+ }
+
+ public static void addMapConstructors(ClassNode enumClass, boolean hasNoArg) {
+ TupleConstructorASTTransformation.addMapConstructors(enumClass, hasNoArg, "One of the enum constants for enum " + enumClass.getName() +
+ " was initialized with null. Please use a non-null value or define your own constructor.");
+ }
+
+ private String getUniqueVariableName(final String name, Statement code) {
+ if (code == null) return name;
+ final Object[] found = new Object[1];
+ CodeVisitorSupport cv = new CodeVisitorSupport() {
+ public void visitVariableExpression(VariableExpression expression) {
+ if (expression.getName().equals(name)) found[0] = Boolean.TRUE;
+ }
+ };
+ code.visit(cv);
+ if (found[0] != null) return getUniqueVariableName("_" + name, code);
+ return name;
+ }
+
+ private static boolean isAnonymousInnerClass(ClassNode enumClass) {
+ if (!(enumClass instanceof EnumConstantClassNode)) return false;
+ InnerClassNode ic = (InnerClassNode) enumClass;
+ return ic.getVariableScope() == null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java b/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
new file mode 100644
index 0000000..743801f
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/EnumVisitor.java
@@ -0,0 +1,444 @@
+/*
+ * 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;
+
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.EnumConstantClassNode;
+import org.codehaus.groovy.ast.FieldNode;
+import org.codehaus.groovy.ast.InnerClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ArgumentListExpression;
+import org.codehaus.groovy.ast.expr.ArrayExpression;
+import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.BooleanExpression;
+import org.codehaus.groovy.ast.expr.ClassExpression;
+import org.codehaus.groovy.ast.expr.ConstantExpression;
+import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
+import org.codehaus.groovy.ast.expr.DeclarationExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.FieldExpression;
+import org.codehaus.groovy.ast.expr.ListExpression;
+import org.codehaus.groovy.ast.expr.MapEntryExpression;
+import org.codehaus.groovy.ast.expr.MapExpression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.SpreadExpression;
+import org.codehaus.groovy.ast.expr.StaticMethodCallExpression;
+import org.codehaus.groovy.ast.expr.VariableExpression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.stmt.ExpressionStatement;
+import org.codehaus.groovy.ast.stmt.IfStatement;
+import org.codehaus.groovy.ast.stmt.ReturnStatement;
+import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.control.CompilationUnit;
+import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
+import org.codehaus.groovy.syntax.SyntaxException;
+import org.codehaus.groovy.syntax.Token;
+import org.codehaus.groovy.syntax.Types;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnumVisitor extends ClassCodeVisitorSupport {
+ // some constants for modifiers
+ private static final int FS = Opcodes.ACC_FINAL | Opcodes.ACC_STATIC;
+ private static final int PS = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
+ private static final int PUBLIC_FS = Opcodes.ACC_PUBLIC | FS;
+ private static final int PRIVATE_FS = Opcodes.ACC_PRIVATE | FS;
+
+ private final SourceUnit sourceUnit;
+
+
+ public EnumVisitor(CompilationUnit cu, SourceUnit su) {
+ sourceUnit = su;
+ }
+
+ public void visitClass(ClassNode node) {
+ if (!node.isEnum()) return;
+ completeEnum(node);
+ }
+
+ protected SourceUnit getSourceUnit() {
+ return sourceUnit;
+ }
+
+ private void completeEnum(ClassNode enumClass) {
+ boolean isAic = isAnonymousInnerClass(enumClass);
+ // create MIN_VALUE and MAX_VALUE fields
+ FieldNode minValue = null, maxValue = null, values = null;
+
+ if (!isAic) {
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+
+ // create values field
+ values = new FieldNode("$VALUES", PRIVATE_FS | Opcodes.ACC_SYNTHETIC, enumRef.makeArray(), enumClass, null);
+ values.setSynthetic(true);
+
+ addMethods(enumClass, values);
+ checkForAbstractMethods(enumClass);
+
+ // create MIN_VALUE and MAX_VALUE fields
+ minValue = new FieldNode("MIN_VALUE", PUBLIC_FS, enumRef, enumClass, null);
+ maxValue = new FieldNode("MAX_VALUE", PUBLIC_FS, enumRef, enumClass, null);
+ }
+ addInit(enumClass, minValue, maxValue, values, isAic);
+ }
+
+ private static void checkForAbstractMethods(ClassNode enumClass) {
+ List<MethodNode> methods = enumClass.getMethods();
+ for (MethodNode m : methods) {
+ if (m.isAbstract()) {
+ // make the class abstract also see Effective Java p.152
+ enumClass.setModifiers(enumClass.getModifiers() | Opcodes.ACC_ABSTRACT);
+ break;
+ }
+ }
+ }
+
+ private static void addMethods(ClassNode enumClass, FieldNode values) {
+ List<MethodNode> methods = enumClass.getMethods();
+
+ boolean hasNext = false;
+ boolean hasPrevious = false;
+ for (MethodNode m : methods) {
+ if (m.getName().equals("next") && m.getParameters().length == 0) hasNext = true;
+ if (m.getName().equals("previous") && m.getParameters().length == 0) hasPrevious = true;
+ if (hasNext && hasPrevious) break;
+ }
+
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+
+ {
+ // create values() method
+ MethodNode valuesMethod = new MethodNode("values", PUBLIC_FS, enumRef.makeArray(), Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ valuesMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ MethodCallExpression cloneCall = new MethodCallExpression(new FieldExpression(values), "clone", MethodCallExpression.NO_ARGUMENTS);
+ cloneCall.setMethodTarget(values.getType().getMethod("clone", Parameter.EMPTY_ARRAY));
+ code.addStatement(new ReturnStatement(cloneCall));
+ valuesMethod.setCode(code);
+ enumClass.addMethod(valuesMethod);
+ }
+
+ if (!hasNext) {
+ // create next() method, code:
+ // Day next() {
+ // int ordinal = ordinal().next()
+ // if (ordinal >= values().size()) ordinal = 0
+ // return values()[ordinal]
+ // }
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ Token ge = Token.newSymbol(Types.COMPARE_GREATER_THAN_EQUAL, -1, -1);
+ MethodNode nextMethod = new MethodNode("next", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ nextMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ BlockStatement ifStatement = new BlockStatement();
+ ifStatement.addStatement(
+ new ExpressionStatement(
+ new BinaryExpression(new VariableExpression("ordinal"), assign, new ConstantExpression(0))
+ )
+ );
+
+ code.addStatement(
+ new ExpressionStatement(
+ new DeclarationExpression(
+ new VariableExpression("ordinal"),
+ assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ VariableExpression.THIS_EXPRESSION,
+ "ordinal",
+ MethodCallExpression.NO_ARGUMENTS),
+ "next",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )
+ )
+ );
+ code.addStatement(
+ new IfStatement(
+ new BooleanExpression(new BinaryExpression(
+ new VariableExpression("ordinal"),
+ ge,
+ new MethodCallExpression(
+ new FieldExpression(values),
+ "size",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )),
+ ifStatement,
+ EmptyStatement.INSTANCE
+ )
+ );
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
+ )
+ );
+ nextMethod.setCode(code);
+ enumClass.addMethod(nextMethod);
+ }
+
+ if (!hasPrevious) {
+ // create previous() method, code:
+ // Day previous() {
+ // int ordinal = ordinal().previous()
+ // if (ordinal < 0) ordinal = values().size() - 1
+ // return values()[ordinal]
+ // }
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ Token lt = Token.newSymbol(Types.COMPARE_LESS_THAN, -1, -1);
+ MethodNode nextMethod = new MethodNode("previous", Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
+ nextMethod.setSynthetic(true);
+ BlockStatement code = new BlockStatement();
+ BlockStatement ifStatement = new BlockStatement();
+ ifStatement.addStatement(
+ new ExpressionStatement(
+ new BinaryExpression(new VariableExpression("ordinal"), assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ new FieldExpression(values),
+ "size",
+ MethodCallExpression.NO_ARGUMENTS
+ ),
+ "minus",
+ new ConstantExpression(1)
+ )
+ )
+ )
+ );
+
+ code.addStatement(
+ new ExpressionStatement(
+ new DeclarationExpression(
+ new VariableExpression("ordinal"),
+ assign,
+ new MethodCallExpression(
+ new MethodCallExpression(
+ VariableExpression.THIS_EXPRESSION,
+ "ordinal",
+ MethodCallExpression.NO_ARGUMENTS),
+ "previous",
+ MethodCallExpression.NO_ARGUMENTS
+ )
+ )
+ )
+ );
+ code.addStatement(
+ new IfStatement(
+ new BooleanExpression(new BinaryExpression(
+ new VariableExpression("ordinal"),
+ lt,
+ new ConstantExpression(0)
+ )),
+ ifStatement,
+ EmptyStatement.INSTANCE
+ )
+ );
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new FieldExpression(values), "getAt", new VariableExpression("ordinal"))
+ )
+ );
+ nextMethod.setCode(code);
+ enumClass.addMethod(nextMethod);
+ }
+
+ {
+ // create valueOf
+ Parameter stringParameter = new Parameter(ClassHelper.STRING_TYPE, "name");
+ MethodNode valueOfMethod = new MethodNode("valueOf", PS, enumRef, new Parameter[]{stringParameter}, ClassNode.EMPTY_ARRAY, null);
+ ArgumentListExpression callArguments = new ArgumentListExpression();
+ callArguments.addExpression(new ClassExpression(enumClass));
+ callArguments.addExpression(new VariableExpression("name"));
+
+ BlockStatement code = new BlockStatement();
+ code.addStatement(
+ new ReturnStatement(
+ new MethodCallExpression(new ClassExpression(ClassHelper.Enum_Type), "valueOf", callArguments)
+ )
+ );
+ valueOfMethod.setCode(code);
+ valueOfMethod.setSynthetic(true);
+ enumClass.addMethod(valueOfMethod);
+ }
+ }
+
+ private void addInit(ClassNode enumClass, FieldNode minValue,
+ FieldNode maxValue, FieldNode values,
+ boolean isAic) {
+ // constructor helper
+ // This method is used instead of calling the constructor as
+ // calling the constructor may require a table with MetaClass
+ // selecting the constructor for each enum value. So instead we
+ // use this method to have a central point for constructor selection
+ // and only one table. The whole construction is needed because
+ // Reflection forbids access to the enum constructor.
+ // code:
+ // def $INIT(Object[] para) {
+ // return this(*para)
+ // }
+ ClassNode enumRef = enumClass.getPlainNodeReference();
+ Parameter[] parameter = new Parameter[]{new Parameter(ClassHelper.OBJECT_TYPE.makeArray(), "para")};
+ MethodNode initMethod = new MethodNode("$INIT", PUBLIC_FS | Opcodes.ACC_SYNTHETIC, enumRef, parameter, ClassNode.EMPTY_ARRAY, null);
+ initMethod.setSynthetic(true);
+ ConstructorCallExpression cce = new ConstructorCallExpression(
+ ClassNode.THIS,
+ new ArgumentListExpression(
+ new SpreadExpression(new VariableExpression("para"))
+ )
+ );
+ BlockStatement code = new BlockStatement();
+ code.addStatement(new ReturnStatement(cce));
+ initMethod.setCode(code);
+ enumClass.addMethod(initMethod);
+
+ // static init
+ List<FieldNode> fields = enumClass.getFields();
+ List<Expression> arrayInit = new ArrayList<Expression>();
+ int value = -1;
+ Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
+ List<Statement> block = new ArrayList<Statement>();
+ FieldNode tempMin = null;
+ FieldNode tempMax = null;
+ for (FieldNode field : fields) {
+ if ((field.getModifiers() & Opcodes.ACC_ENUM) == 0) continue;
+ value++;
+ if (tempMin == null) tempMin = field;
+ tempMax = field;
+
+ ClassNode enumBase = enumClass;
+ ArgumentListExpression args = new ArgumentListExpression();
+ args.addExpression(new ConstantExpression(field.getName()));
+ args.addExpression(new ConstantExpression(value));
+ if (field.getInitialExpression() == null) {
+ if ((enumClass.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
+ addError(field, "The enum constant " + field.getName() + " must override abstract methods from " + enumBase.getName() + ".");
+ continue;
+ }
+ } else {
+ ListExpression oldArgs = (ListExpression) field.getInitialExpression();
+ List<MapEntryExpression> savedMapEntries = new ArrayList<MapEntryExpression>();
+ for (Expression exp : oldArgs.getExpressions()) {
+ if (exp instanceof MapEntryExpression) {
+ savedMapEntries.add((MapEntryExpression) exp);
+ continue;
+ }
+
+ InnerClassNode inner = null;
+ if (exp instanceof ClassExpression) {
+ ClassExpression clazzExp = (ClassExpression) exp;
+ ClassNode ref = clazzExp.getType();
+ if (ref instanceof EnumConstantClassNode) {
+ inner = (InnerClassNode) ref;
+ }
+ }
+ if (inner != null) {
+ List<MethodNode> baseMethods = enumBase.getMethods();
+ for (MethodNode methodNode : baseMethods) {
+ if (!methodNode.isAbstract()) continue;
+ MethodNode enumConstMethod = inner.getMethod(methodNode.getName(), methodNode.getParameters());
+ if (enumConstMethod == null || (enumConstMethod.getModifiers() & Opcodes.ACC_ABSTRACT) != 0) {
+ addError(field, "Can't have an abstract method in enum constant " + field.getName() + ". Implement method '" + methodNode.getTypeDescriptor() + "'.");
+ }
+ }
+ if (inner.getVariableScope() == null) {
+ enumBase = inner;
+ /*
+ * GROOVY-3985: Remove the final modifier from $INIT method in this case
+ * so that subclasses of enum generated for enum constants (aic) can provide
+ * their own $INIT method
+ */
+ initMethod.setModifiers(initMethod.getModifiers() & ~Opcodes.ACC_FINAL);
+ continue;
+ }
+ }
+ args.addExpression(exp);
+ }
+ if (!savedMapEntries.isEmpty()) {
+ args.getExpressions().add(2, new MapExpression(savedMapEntries));
+ }
+ }
+ field.setInitialValueExpression(null);
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(field),
+ assign,
+ new StaticMethodCallExpression(enumBase, "$INIT", args)
+ )
+ )
+ );
+ arrayInit.add(new FieldExpression(field));
+ }
+
+ if (!isAic) {
+ if (tempMin != null) {
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(minValue),
+ assign,
+ new FieldExpression(tempMin)
+ )
+ )
+ );
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(
+ new FieldExpression(maxValue),
+ assign,
+ new FieldExpression(tempMax)
+ )
+ )
+ );
+ enumClass.addField(minValue);
+ enumClass.addField(maxValue);
+ }
+
+ block.add(
+ new ExpressionStatement(
+ new BinaryExpression(new FieldExpression(values), assign, new ArrayExpression(enumClass, arrayInit))
+ )
+ );
+ enumClass.addField(values);
+ }
+ enumClass.addStaticInitializerStatements(block, true);
+ }
+
+ private void addError(AnnotatedNode exp, String msg) {
+ sourceUnit.getErrorCollector().addErrorAndContinue(
+ new SyntaxErrorMessage(
+ new SyntaxException(msg + '\n', exp.getLineNumber(), exp.getColumnNumber(), exp.getLastLineNumber(), exp.getLastColumnNumber()), sourceUnit)
+ );
+ }
+
+ private static boolean isAnonymousInnerClass(ClassNode enumClass) {
+ if (!(enumClass instanceof EnumConstantClassNode)) return false;
+ InnerClassNode ic = (InnerClassNode) enumClass;
+ return ic.getVariableScope() == null;
+ }
+
+}