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

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

There is a newer version: 1.5.8
Show newest version
/*
 * Copyright 2003-2007 the original author or authors.
 *
 * Licensed 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 java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.expr.AnnotationConstantExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.ListExpression;
import org.codehaus.groovy.ast.expr.PropertyExpression;
import org.codehaus.groovy.control.ErrorCollector;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.codehaus.groovy.syntax.SyntaxException;


/**
 * An Annotation visitor responsible with:
 * - reading annotation metadata (@Retention, @Target, attribute types)
 * - verify that an AnnotationNode conforms to annotation meta
 * - enhancing an AnnotationNode AST to reflect real annotation meta
 * 
 * @author Alex Popescu
 */
public class AnnotationVisitor {
    private static final Class[] EMPTY_ARG_TYPES = new Class[0];
    private static final Object[] EMPTY_ARGS = new Object[0];
    
    private final Class annotationRootClass;
    private SourceUnit source;
    private ErrorCollector errorCollector;
    
    private AnnotationNode annotation;
    private Class annotationClass;
    private Map requiredAttrTypes = new HashMap(); // Map
    private Map defaultAttrTypes = new HashMap();  // Map
    
    public AnnotationVisitor(SourceUnit source, ErrorCollector errorCollector) {
        this.source = source;
        this.errorCollector = errorCollector;
        this.annotationRootClass = loadAnnotationRootClass();
    }
    
    public AnnotationNode visit(AnnotationNode node) {
        if(!isValidAnnotationClass(node)) {
            node.setValid(false);
            return node;
        }
        
        this.annotation = node;
        if(!node.getClassNode().isResolved()) {
            addError("Current type was not yet resolved. Cannot introspect it.");
            node.setValid(false);
            return node;
        }
        this.annotationClass = node.getClassNode().getTypeClass();
        
        extractAnnotationMeta(this.annotationClass);
        
        if(this.errorCollector.hasErrors()) {
            this.annotation.setValid(false);
            return this.annotation;
        }
        
        Map attributes = this.annotation.getMembers();
        for(Iterator it = attributes.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = (Map.Entry) it.next();
            String attrName = (String) entry.getKey();
            Expression attrExpr = (Expression) entry.getValue();
            Class attrType = getAttributeType(attrName);
            if(attrType == null) {
                addError("Unknown attribute '" + attrName + "'", attrExpr);
                break;
            }
            visitExpression(attrName, attrExpr, attrType);
        }
        
        if(!this.requiredAttrTypes.isEmpty()) {
            addError("Required attributes " + this.requiredAttrTypes.keySet() + " not found",
                    this.annotation);
        }
        
        this.annotation.setValid(!this.errorCollector.hasErrors());
        return this.annotation;
    }
    
    /**
     * @param node
     * @return
     */
    private boolean isValidAnnotationClass(AnnotationNode node) {
        return node.getClassNode().implementsInterface("java.lang.annotation.Annotation");
    }

    protected void visitExpression(String attrName, Expression attrAst, Class attrType) {
        if(attrType.isArray()) {
            // check needed as @Test(attr = {"elem"}) passes through the parser
            if(attrAst instanceof ListExpression) {
                visitListExpression(attrName, (ListExpression) attrAst, attrType.getComponentType());
            }
            else {
                addError("Annotation list attributes must use Groovy notation [el1, el2]", attrAst);
            }
        }
        if(attrType.isPrimitive()) {
            visitConstantExpression(attrName, (ConstantExpression) attrAst, ClassHelper.getWrapper(ClassHelper.make(attrType)));
        }
        else if(String.class.equals(attrType)) {
            visitConstantExpression(attrName, (ConstantExpression) attrAst, ClassHelper.make(String.class));
        }
        else if(Class.class.equals(attrType)) {
            // there is nothing to check about ClassExpressions
        }
        else if(isEnum(attrType)) {
            visitEnumExpression(attrName, (PropertyExpression) attrAst, ClassHelper.make(attrType));
        }
        else if(isAnnotation(attrType)) {
            visitAnnotationExpression(attrName, (AnnotationConstantExpression) attrAst, attrType);
        }
    }
    
    /**
     * @param attrName
     * @param expression
     * @param attrType
     */
    protected void visitAnnotationExpression(String attrName, AnnotationConstantExpression expression, Class attrType) {
        AnnotationNode annotationNode = (AnnotationNode) expression.getValue();
        AnnotationVisitor visitor = new AnnotationVisitor(this.source, this.errorCollector);
        visitor.visit(annotationNode);
    }

    protected void visitListExpression(String attrName, ListExpression listExpr, Class elementType) {
        List expressions = listExpr.getExpressions();
        for (int i = 0; i < expressions.size(); i++) {
            visitExpression(attrName, (Expression) expressions.get(i), elementType);
        }
    }
    
    protected void visitConstantExpression(String attrName, ConstantExpression constExpr, ClassNode attrType) {
        if(!constExpr.getType().isDerivedFrom(attrType)) {
            addError("Attribute '" + attrName + "' should have type '" + attrType.getName() + "'; "
                    + "but found type '" + constExpr.getType().getName() + "'",
                    constExpr);
        }
    }
    
    protected void visitEnumExpression(String attrName, PropertyExpression propExpr, ClassNode attrType) {
        if(!propExpr.getObjectExpression().getType().isDerivedFrom(attrType)) {
            addError("Attribute '" + attrName + "' should have type '" + attrType.getName() +"' (Enum), but found "
                    + propExpr.getObjectExpression().getType().getName(), 
                    propExpr);
        }
    }
    
    private boolean isAnnotation(Class clazz) {
        Boolean result = (Boolean) invoke(clazz.getClass(), "isAnnotation", EMPTY_ARG_TYPES, clazz, EMPTY_ARGS);
        return result.booleanValue();
    }
    
    private boolean isEnum(Class clazz) {
        Boolean result = (Boolean) invoke(clazz.getClass(), "isEnum", EMPTY_ARG_TYPES, clazz, EMPTY_ARGS);
        return result.booleanValue();
    }
    
    private void extractAnnotationMeta(Class annotationClass) {
        initializeAnnotationMeta(annotationClass);
        initializeAttributeTypes(annotationClass);
    }
    
    private void initializeAnnotationMeta(Class annotationClass) {
        Object[] annotations = (Object[]) invoke(annotationClass.getClass(), 
                "getAnnotations", EMPTY_ARG_TYPES, annotationClass, EMPTY_ARGS);
        if (annotations == null) {
            addError("Cannot retrieve annotation meta information. " 
                    + ExtendedVerifier.JVM_ERROR_MESSAGE);
            return;
        }
        
        for(int i = 0; i < annotations.length; i++) {
            Class annotationType = (Class) invoke(this.annotationRootClass, 
                    "annotationType", EMPTY_ARG_TYPES, annotations[i], EMPTY_ARGS);
            if (annotationType == null) continue;
            
            if ("java.lang.annotation.Retention".equals(annotationType.getName())) {
                initializeRetention(annotationClass, annotationType, annotations[i]);
            }
            else if("java.lang.annotation.Target".equals(annotationType.getName())) {
                initializeTarget(annotationClass, annotationType, annotations[i]);
            }
        }
    }
    
    private void initializeAttributeTypes(Class annotationClass) {
        Method[] methods = annotationClass.getDeclaredMethods();
        for(int i = 0; i < methods.length; i++) {
            Object defaultValue = invoke(Method.class, "getDefaultValue", EMPTY_ARG_TYPES, methods[i], EMPTY_ARGS);
            if (defaultValue != null) { 
                // by now we know JDK1.5 API is available so a null means no default value
                defaultAttrTypes.put(methods[i].getName(), methods[i].getReturnType());
            }
            else {
                requiredAttrTypes.put(methods[i].getName(), methods[i].getReturnType());
            }
        }
    }
    
    private void initializeRetention(Class annotationClass, Class retentionClass, Object retentionAnnotation) {
        Object retentionPolicyEnum = 
            invoke(retentionClass, "value", EMPTY_ARG_TYPES, retentionAnnotation, EMPTY_ARGS);
        if (retentionPolicyEnum == null) {
            addError("Cannot read @RetentionPolicy on the @" + annotationClass.getName() 
                    + ExtendedVerifier.JVM_ERROR_MESSAGE);
            return;
        }
        
        if("RUNTIME".equals(retentionPolicyEnum.toString())) {
            this.annotation.setRuntimeRetention(true);
        }
        else if("SOURCE".equals(retentionPolicyEnum.toString())) {
            this.annotation.setSourceRetention(true);
        }
    }
    
    private void initializeTarget(Class annotationClass, Class targetClass, Object targetAnnotation) {
        Object[] elementTypeEnum =
            (Object[]) invoke(targetClass, "value", EMPTY_ARG_TYPES, targetAnnotation, EMPTY_ARGS);
        if (elementTypeEnum == null) {
            addError("Cannot read @Target on the @" + annotationClass.getName() 
                    + ExtendedVerifier.JVM_ERROR_MESSAGE);
            return;
        }
        int bitmap = 0;
        for (int i = 0; i < elementTypeEnum.length; i++) {
            String targetName = elementTypeEnum[i].toString();
            if("TYPE".equals(targetName)) {
                bitmap |= AnnotationNode.TYPE_TARGET;
            }
            else if("CONSTRUCTOR".equals(targetName)) {
                bitmap |= AnnotationNode.CONSTRUCTOR_TARGET;
            }
            else if("METHOD".equals(targetName)) {
                bitmap |= AnnotationNode.METHOD_TARGET;
            }
            else if("FIELD".equals(targetName)) {
                bitmap |= AnnotationNode.FIELD_TARGET;
            }
            else if("PARAMETER".equals(targetName)) {
                bitmap |= AnnotationNode.PARAMETER_TARGET;
            }
            else if("LOCAL_VARIABLE".equals(targetName)) {
                bitmap |= AnnotationNode.LOCAL_VARIABLE_TARGET;
            }
            else if("ANNOTATION".equals(targetName)) {
                bitmap |= AnnotationNode.ANNOTATION_TARGET;
            }
        }
        this.annotation.setAllowedTargets(bitmap);
    }
    
    protected void addError(String msg) {
        this.errorCollector.addErrorAndContinue(
          new SyntaxErrorMessage(new SyntaxException(msg 
                  + " in @" + this.annotationClass.getName() + '\n', 
                  this.annotation.getLineNumber(), 
                  this.annotation.getColumnNumber()), this.source)
        );
    }
    
    protected void addError(String msg, ASTNode expr) {
        this.errorCollector.addErrorAndContinue(
          new SyntaxErrorMessage(new SyntaxException(msg 
                  + " in @" + this.annotationClass.getName() + '\n', 
                  expr.getLineNumber(), 
                  expr.getColumnNumber()), this.source)
        );
    }
    
    private Class getAttributeType(String attr) {
        if(this.requiredAttrTypes.containsKey(attr)) {
            return (Class) this.requiredAttrTypes.remove(attr);
        }
        
        return (Class) this.defaultAttrTypes.remove(attr);
    }
    
    private Object invoke(Class clazz, String methodName, Class[] argTypes, Object target, Object[] args) {
        try {
            Method m = clazz.getMethod(methodName, argTypes);
            return m.invoke(target, args);
        }
        catch(Throwable cause) {
            // we report an error on called side
        }
     
        return null;
    }
    
    private Class loadAnnotationRootClass() {
        try {
            return Class.forName("java.lang.annotation.Annotation");
        }
        catch(Throwable cause) {
            // report the error later
        }
        
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy