All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.classgen.EnumVisitor Maven / Gradle / Ivy

There is a newer version: 1.5.8
Show newest version
package org.codehaus.groovy.classgen;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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.FieldNode;
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.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
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.MethodCallExpression;
import org.codehaus.groovy.ast.expr.SpreadExpression;
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.ReturnStatement;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.objectweb.asm.Opcodes;

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 CompilationUnit compilationUnit;
    private final SourceUnit sourceUnit;
    
    
    public EnumVisitor(CompilationUnit cu, SourceUnit su) {
        compilationUnit = cu;
        sourceUnit = su;
    }    
    
    public void visitClass(ClassNode node) {
        if (!isEnum(node)) return;
        completeEnum(node);
    }

    protected SourceUnit getSourceUnit() {
        return sourceUnit;
    }
    
    private boolean isEnum(ClassNode node) {
       return (node.getModifiers()&Opcodes.ACC_ENUM) != 0;
    }

    private void completeEnum(final ClassNode enumClass) {
        ClassNode enumArray = enumClass.makeArray();

        // create values field
        FieldNode values = new FieldNode("$VALUES",PRIVATE_FS,enumArray,enumClass,null);
        values.setSynthetic(true);      
        {
            // create values() method
            MethodNode valuesMethod = new MethodNode("values",PUBLIC_FS,enumArray,new Parameter[0],ClassNode.EMPTY_ARRAY,null);
            valuesMethod.setSynthetic(true);
            BlockStatement code = new BlockStatement();
            code.addStatement(
                    new ReturnStatement(
                            new MethodCallExpression(new FieldExpression(values),"clone",MethodCallExpression.NO_ARGUMENTS)
                    )
            );
            valuesMethod.setCode(code);
            enumClass.addMethod(valuesMethod);
        }
        
        {        
            // create valueOf
            Parameter stringParameter = new Parameter(ClassHelper.STRING_TYPE,"name");
            MethodNode valueOfMethod = new MethodNode("valueOf",PS,enumClass,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);
        }
        addConstructor(enumClass);
        {
            // constructor helper
            // This method is used instead of calling the constructor as
            // calling the constrcutor 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)
            // }            
            Parameter[] parameter = new Parameter[]{new Parameter(ClassHelper.OBJECT_TYPE.makeArray(), "para")};
            MethodNode initMethod = new MethodNode("$INIT",PRIVATE_FS,enumClass,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 fields = enumClass.getFields();
            List arrayInit = new ArrayList();
            int value = -1;
            Token assign = Token.newSymbol(Types.ASSIGN, -1, -1);
            List block = new ArrayList();
            for (Iterator iterator = fields.iterator(); iterator.hasNext();) {
                FieldNode field = (FieldNode) iterator.next();
                if ((field.getModifiers()&Opcodes.ACC_ENUM) == 0) continue;
                value++;
                
                ArgumentListExpression args = new ArgumentListExpression();
                args.addExpression(new ConstantExpression(field.getName()));
                args.addExpression(new ConstantExpression(new Integer(value)));
                if (field.getInitialExpression()!=null) {
                    ListExpression oldArgs = (ListExpression) field.getInitialExpression();
                    for (Iterator oldArgsIterator = oldArgs.getExpressions().iterator(); oldArgsIterator.hasNext();) {
                        Expression exp = (Expression) oldArgsIterator.next();
                        args.addExpression(exp);
                    }
                }
                field.setInitialValueExpression(null);
                block.add(
                        new ExpressionStatement(
                                new BinaryExpression(
                                        new FieldExpression(field),
                                        assign,
                                        new MethodCallExpression(new ClassExpression(enumClass),"$INIT",args)
                                )
                        )
                );
                arrayInit.add(new FieldExpression(field));
            }
            
            block.add(
                    new ExpressionStatement(
                            new BinaryExpression(new FieldExpression(values),assign,new ArrayExpression(enumClass,arrayInit))
                    )
            );
            enumClass.addStaticInitializerStatements(block, true);
            enumClass.addField(values);
        }
        
        
    }

    private void addConstructor(ClassNode enumClass) {
        // first look if there are declared constructors
        List ctors = new ArrayList(enumClass.getDeclaredConstructors());
        if (ctors.size()==0) {
            // add default constructor
            ConstructorNode init = new ConstructorNode(Opcodes.ACC_PRIVATE,new Parameter[0],ClassNode.EMPTY_ARRAY,new BlockStatement());
            enumClass.addConstructor(init);
            ctors.add(init);
        } 
        
        // for each constructor:
        // if constructordoes not define a call to super, then transform constructor
        // to get String,int parameters at beginning and add call super(String,int)  
        
        for (Iterator iterator = ctors.iterator(); iterator.hasNext();) {
            ConstructorNode ctor = (ConstructorNode) iterator.next();
            if (ctor.firstStatementIsSpecialConstructorCall()) continue;
            // 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);
            // and a super call
            ConstructorCallExpression cce = new ConstructorCallExpression(
                    ClassNode.SUPER,
                    new ArgumentListExpression(
                           new VariableExpression(stringParameterName),
                           new VariableExpression(intParameterName)
                    )
            );
            BlockStatement code = new BlockStatement();
            code.addStatement(new ExpressionStatement(cce));
            Statement oldCode = ctor.getCode();
            if (oldCode!=null) code.addStatement(oldCode);
            ctor.setCode(code);
        }
    }

    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;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy