/* * 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.*; 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; } }