blob: ee5f168dc8948057510e01b9d5ff7cb91bb4be87 [file] [log] [blame]
/*
* 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 java.util.ArrayList;
import java.util.List;
import static org.apache.groovy.ast.tools.ClassNodeUtils.addGeneratedMethod;
import static org.codehaus.groovy.ast.tools.GeneralUtils.localVarX;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
public class EnumVisitor extends ClassCodeVisitorSupport {
private final SourceUnit sourceUnit;
public EnumVisitor(final CompilationUnit cu, final SourceUnit su) {
sourceUnit = su;
}
@Override
protected SourceUnit getSourceUnit() {
return sourceUnit;
}
@Override
public void visitClass(final ClassNode node) {
if (!node.isEnum()) return;
completeEnum(node);
}
private void completeEnum(final ClassNode enumClass) {
FieldNode minValue = null, maxValue = null, values = null;
boolean isAic = isAnonymousInnerClass(enumClass);
if (!isAic) {
ClassNode enumRef = enumClass.getPlainNodeReference();
// create $VALUES field
values = new FieldNode("$VALUES", ACC_FINAL | ACC_PRIVATE | ACC_STATIC | 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", ACC_FINAL | ACC_PUBLIC | ACC_STATIC, enumRef, enumClass, null);
maxValue = new FieldNode("MAX_VALUE", ACC_FINAL | ACC_PUBLIC | ACC_STATIC, enumRef, enumClass, null);
}
addInit(enumClass, minValue, maxValue, values, isAic);
}
private static void checkForAbstractMethods(final ClassNode enumClass) {
for (MethodNode method : enumClass.getMethods()) {
if (method.isAbstract()) {
// make the class abstract also; see Effective Java p.152
enumClass.setModifiers(enumClass.getModifiers() | ACC_ABSTRACT);
break;
}
}
}
private static void addMethods(final ClassNode enumClass, final 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", ACC_FINAL | ACC_PUBLIC | ACC_STATIC, 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);
addGeneratedMethod(enumClass, 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", ACC_PUBLIC, 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(
localVarX("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);
addGeneratedMethod(enumClass, 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 prevMethod = new MethodNode("previous", ACC_PUBLIC, enumRef, Parameter.EMPTY_ARRAY, ClassNode.EMPTY_ARRAY, null);
prevMethod.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(
localVarX("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"))
)
);
prevMethod.setCode(code);
addGeneratedMethod(enumClass, prevMethod);
}
{
// create valueOf
Parameter stringParameter = new Parameter(ClassHelper.STRING_TYPE, "name");
MethodNode valueOfMethod = new MethodNode("valueOf", ACC_PUBLIC | ACC_STATIC, 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);
addGeneratedMethod(enumClass, valueOfMethod);
}
}
private void addInit(final ClassNode enumClass, final FieldNode minValue, final FieldNode maxValue, final FieldNode values, final 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", ACC_FINAL | ACC_PUBLIC | ACC_STATIC | 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);
addGeneratedMethod(enumClass, initMethod);
// static init
List<FieldNode> fields = enumClass.getFields();
List<Expression> arrayInit = new ArrayList<>();
int value = -1;
Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
List<Statement> block = new ArrayList<>();
FieldNode tempMin = null;
FieldNode tempMax = null;
for (FieldNode field : fields) {
if (!field.isEnum()) continue;
value += 1;
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.isAbstract()) {
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<>();
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.isAbstract()) {
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() & ~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(final AnnotatedNode exp, final String msg) {
getSourceUnit().getErrorCollector().addErrorAndContinue(
new SyntaxErrorMessage(
new SyntaxException(msg + '\n', exp),
getSourceUnit()
)
);
}
private static boolean isAnonymousInnerClass(final ClassNode enumClass) {
if (!(enumClass instanceof EnumConstantClassNode)) return false;
return (((EnumConstantClassNode) enumClass).getVariableScope() == null);
}
}