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

org.glassfish.apf.impl.AnnotationProcessorImpl Maven / Gradle / Ivy

There is a newer version: 7.2024.1.Alpha1
Show newest version
/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2016-2021] [Payara Foundation and/or affiliates]

package org.glassfish.apf.impl;

import fish.payara.nucleus.hotdeploy.AnnotationProcessorState;
import fish.payara.nucleus.hotdeploy.ApplicationState;
import java.util.EmptyStackException;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;
import java.util.Stack;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.Optional;
import java.util.logging.Logger;

import org.glassfish.apf.ProcessingContext;
import org.glassfish.apf.AnnotationProcessor;
import org.glassfish.apf.AnnotationInfo;
import org.glassfish.apf.AnnotationProcessorException;
import org.glassfish.apf.AnnotationHandler;
import org.glassfish.apf.AnnotatedElementHandler;
import org.glassfish.apf.ComponentInfo;
import org.glassfish.apf.ResultType;
import org.glassfish.apf.HandlerProcessingResult;
import org.glassfish.apf.ProcessingResult;
import org.glassfish.apf.Scanner;
import java.util.logging.Level;

/**
 *
 * @author dochez
 */
public class AnnotationProcessorImpl implements AnnotationProcessor {
    
    private AnnotationProcessorImpl delegate;
    private Map> handlers = new HashMap>();
    
    private int errorCount;
    private Logger logger;
    private Stack annotatedElements = new Stack();
    private Set visitedPackages = new HashSet();
    
    /** Creates a new instance of AnnotationProcessorImpl */
    public AnnotationProcessorImpl() {
        logger = AnnotationUtils.getLogger();
    }
    
    public void setDelegate(AnnotationProcessorImpl delegate) {
        this.delegate = delegate;
    }
    
    @Override
    public ProcessingContext createContext() {
        ProcessingContext ctx = new ProcessingContextImpl(this);
        ctx.setErrorHandler(new DefaultErrorHandler());
        return ctx;
    }
    
    /**
     * Log a message on the default logger
     * @param level
     * @param locator
     * @param localizedMessage
     */
    @Override
    public void log(Level level, AnnotationInfo locator, String localizedMessage){
        if (logger!=null && logger.isLoggable(level)){
            if (locator!=null){
                logger.log(level, AnnotationUtils.getLocalString("enterprise.deployment.annotation.error", "{2}\n symbol: {0}\n location: {1}",
                     locator.getAnnotation().annotationType().getName(), locator.getAnnotatedElement(), localizedMessage));
            } else{
                logger.log(level, localizedMessage);
            }
        }
    }
    
    /**
     * Starts the annotation processing tool passing the processing context which 
     * encapsulate all information necessary for the configuration of the tool. 
     * @param ctx is the initialised processing context
     * @return the result of the annotations processing
     * @throws AnnotationProcessorException
     */    
    @Override
    public ProcessingResult process(ProcessingContext ctx)
        throws AnnotationProcessorException
    {
        ApplicationState state = ctx.getArchive().getExtraData(ApplicationState.class);
        Optional processorState = Optional.empty();
        if(state != null) {
            processorState = state.getProcessingState(ctx);
        }
        
        Scanner scanner = ctx.getProcessingInput();
        ProcessingResultImpl result;
        errorCount=0;

        if (state == null) {
            result = new ProcessingResultImpl();
            for (Class c : scanner.getElements()) {
                result.add(process(ctx, c));
            }
        } else if (state.isInactive()) {
            result = new ProcessingResultImpl();
            for (Class c : scanner.getElements()) {
                result.add(process(ctx, c));
            }
            processorState.ifPresent(s -> s.setProcessingResult(result));
        } else {
            result = processorState.get().getProcessingResult(ProcessingResultImpl.class);
            for (Class modifiedClass : scanner.getElements(state.getClassesChanged().keySet())) {
                result.add(process(ctx, modifiedClass));
            }
        }

        return result;
    }
    
    /**
     * Process a set of classes from the parameter list rather than from the 
     * processing context. This allow the annotation handlers to call be the
     * annotation processing tool when classes need to be processed in a 
     * particular context rather than when they are picked up by the scanner.
     * 
     * @param ctx the processing context
     * @param classes the list of classes to process
     * @return the processing result for such classes
     * @throws AnnotationProcessorException if handlers fail to process 
     * an annotation
     */    
    @Override
    public ProcessingResult process(ProcessingContext ctx, Class[] classes)
        throws AnnotationProcessorException {
        
        ProcessingResultImpl result = new ProcessingResultImpl();
        for (Class c : classes) {
            result.add(process(ctx, c));
        }
        return result;
    }
    
    private ProcessingResult process(ProcessingContext ctx, Class c) 
        throws AnnotationProcessorException {
        
        Scanner scanner = ctx.getProcessingInput();
        ProcessingResultImpl result = new ProcessingResultImpl();
        
        // let's see first if this package is new to us and annotated.
        Package classPackage = c.getPackage();
        if (classPackage != null && visitedPackages.add(classPackage)) {
            // new package
            result.add(classPackage, 
                    processAnnotations(ctx, ElementType.PACKAGE, classPackage));
        }

        ComponentInfo info = null;
        try {
            info = scanner.getComponentInfo(c);
        } catch (NoClassDefFoundError err) {
            // issue 456: allow verifier to report this issue
            AnnotationProcessorException ape = new AnnotationProcessorException(AnnotationUtils.getLocalString(
                    "enterprise.deployment.annotation.classnotfounderror", "Class [ {0} ] not found. Error while loading [ {1} ]", err.getMessage(), c));
            ctx.getErrorHandler().error(ape);
            // let's continue to the next class instead of aborting the whole 
            // annotation processing
            return result;
        }

        // process the class itself.
        AnnotatedElementHandler handler= ctx.getHandler();
        logStart(handler, ElementType.TYPE,c);
        result.add(c, processAnnotations(ctx, c));
        
        // now dive into the fields.
        for (Field field : info.getFields()) {
            result.add(field,processAnnotations(ctx, ElementType.FIELD, field));
        }
        
        // constructors...
        for (Constructor constructor : info.getConstructors()) {
            logStart(ctx.getHandler(), ElementType.CONSTRUCTOR, constructor);
            result.add(constructor, processAnnotations(ctx, constructor));
            
            // parameters
            processParameters(ctx, constructor.getParameterAnnotations());
            
            logEnd(ctx.getHandler(), ElementType.CONSTRUCTOR, constructor);
            
        }
        
        // methods...
        for (Method method : info.getMethods()) {
            logStart(ctx.getHandler(), ElementType.METHOD, method);
            result.add(method, processAnnotations(ctx, method));
            
            // parameters
            processParameters(ctx, method.getParameterAnnotations());
            
            logEnd(ctx.getHandler(), ElementType.METHOD, method);
        }
        
        // Because of annotation inheritance, we need to to travel to
        // the superclasses to ensure that annotations defined at the
        // TYPE level are processed at this component level.
        // Note : so far, I am ignoring the implemented interfaces
        Class currentClass = c.getSuperclass();
        while (currentClass!=null && !currentClass.equals(Object.class)) {
            // the trick is to add the results for this class, not
            // for the ones they are defined in...
            result.add(c, processAnnotations(ctx, currentClass));
            currentClass = currentClass.getSuperclass();
        }
        
        // end of class processing, we need to get the top handler
        // since it may have changed during the annotation processing
        logEnd(ctx.getHandler(), ElementType.TYPE, c);  
        
        return result;
    }
    
    private HandlerProcessingResult processParameters(ProcessingContext ctx, Annotation[][] parametersAnnotations)
    throws AnnotationProcessorException
    {

        HandlerProcessingResultImpl result = new HandlerProcessingResultImpl();
        
        // process the method parameters...
        for (Annotation[] parameterAnnotations : parametersAnnotations) {
            logStart(ctx.getHandler(), ElementType.PARAMETER, null);
            if (parameterAnnotations!=null) {
                for (Annotation annotation : parameterAnnotations) {
                    AnnotationInfo info = new AnnotationInfo(ctx, null, annotation, ElementType.PARAMETER);
                    process(ctx, info, result);
                    dumpProcessingResult(result);
                }
            }
            logEnd(ctx.getHandler(), ElementType.PARAMETER, null);
        }
        return result;        
    }
    
    private HandlerProcessingResult processAnnotations(ProcessingContext ctx, ElementType type, AnnotatedElement element) 
        throws AnnotationProcessorException
    {
        
        AnnotatedElementHandler handler = ctx.getHandler();
        logStart(handler, type, element);
        HandlerProcessingResult result = processAnnotations(ctx, element);
        logEnd(handler, type, element);

        dumpProcessingResult(result);                    
        
        return result;
    }
    
    private HandlerProcessingResult processAnnotations(ProcessingContext ctx, AnnotatedElement element)
        throws AnnotationProcessorException
    {
    
        HandlerProcessingResultImpl result= new HandlerProcessingResultImpl();

        try {
            for (Annotation annotation : element.getAnnotations()) {
                // initialize the result...
                AnnotationInfo subElement = new AnnotationInfo(ctx, element, annotation, getTopElementType());
                if (!result.processedAnnotations().containsKey(annotation.annotationType())) {
                    process(ctx, subElement, result);
                } else {
                    if (AnnotationUtils.shouldLog("annotation")) { 
                        logger.finer("Annotation " + annotation.annotationType() + " already processed");
                    }
                }       
            }
	} catch (ArrayStoreException e) {
	    logger.info("Exception " + e.toString()
		    + " encountered while processing annotaton for element "
		    + element + ". Message is: " + e.getMessage()
		    + ". Ignoring annotations and proceeding.");
	}
        return result;
    }
    
    private void process(ProcessingContext ctx, AnnotationInfo element, HandlerProcessingResultImpl result) 
        throws AnnotationProcessorException 
    {
        
        
        Annotation annotation = element.getAnnotation();
        if (AnnotationUtils.shouldLog("annotation")) {
            logger.log(Level.FINER, "Annotation : {0} delegate = {1}", new Object[]{annotation.annotationType().getName(), delegate});
        }
        result.addResult(annotation.annotationType(), ResultType.UNPROCESSED);
        
        // we ignore all java.* annotations
        Package annPackage = annotation.annotationType().getPackage();
        if (annPackage != null && annPackage.getName().startsWith("java.lang"))
            return;
        
        List annotationHandlers = handlers.get(annotation.annotationType().getName());
        if (annotationHandlers!=null) {
            for (AnnotationHandler handler : annotationHandlers) {
                
                // here we need to be careful, we are ready to invoke a handler
                // to process a particular annotation type. However, this handler 
                // may have defined a list of annotations that should be processed 
                // (if present on the annotated element) before itself. 
                // do this check and process those annotations first.
                Class[] dependencies = handler.getTypeDependencies();
                if (dependencies!=null) {
                    AnnotatedElement ae = element.getAnnotatedElement();
                    for (Class annotationType : dependencies) {
                        Annotation depAnnotation = ae.getAnnotation(annotationType);
                        if (depAnnotation!=null) {                        
                            ResultType resultType = result.processedAnnotations().get(annotationType);
                            if (resultType==null || resultType==ResultType.UNPROCESSED){
                                // annotation is present, process it.
                                AnnotationInfo info = new AnnotationInfo(ctx, ae, depAnnotation, getTopElementType());
                                process(ctx, info, result);
                            }
                        }
                    }
                }
                
                // at this point, all annotation that I declared depending on
                // are processed
                HandlerProcessingResult processingResult = null;
                try {
                    processingResult = handler.processAnnotation(element);
                } catch(AnnotationProcessorException ape) {
                    // I am logging this exception
                    log(Level.SEVERE, ape.getLocator(), ape.getMessage());
                    
                    // I am not throwing the exception unless it is fatal so annotation
                    // processing can continue and we have a chance to report all 
                    // errors. 
                    if (ape.isFatal()) {
                        throw ape;
                    } 
                    
                    if (++errorCount>100){
                        throw new AnnotationProcessorException(AnnotationUtils.getLocalString("enterprise.deployment.annotation.toomanyerror", 
                                "Too many errors, annotation processing abandoned."));
                    }
                    
                    processingResult = HandlerProcessingResultImpl.getDefaultResult(annotation.annotationType(), ResultType.FAILED);
                    
                } catch(Throwable e){
                    
                    AnnotationProcessorException ape = new AnnotationProcessorException(e.getMessage(), element);
                    ape.initCause(e);
                    throw ape;
                }
                result.addAll(processingResult);
            }
        } else {
            if (delegate!=null) {
                delegate.process(ctx, element, result);
            } else {           
                ctx.getErrorHandler().fine(new AnnotationProcessorException("No handler defined for " + annotation.annotationType()));
            }
        }
    }
    
    private void dumpProcessingResult(HandlerProcessingResult result) {

        if (result==null || !AnnotationUtils.shouldLog("annotation")) {
            return;
        }
   
        Map, ResultType> annotationResults = 
                result.processedAnnotations();
        for (Map.Entry, ResultType> element : annotationResults.entrySet()) {
            logger.log(Level.FINER, "Annotation {0} : {1}", new Object[]{element.getKey(), element.getValue()});
        }
    }
    
    @Override
    public void pushAnnotationHandler(AnnotationHandler handler) {
        
        String type = handler.getAnnotationType().getName();
        pushAnnotationHandler(type, handler);
    }

    /**
     * This method is similar to {@link #pushAnnotationHandler(AnnotationHandler)} except that
     * it takes an additional String type argument which allows us to avoid extracting the information from the
     * AnnotationHandler. Calling the AnnotationHandler can lead to its instantiation where as the annotation
     * that a handler is responsible for handling is a metadata that can be statically extracted. This allows us to
     * build more lazy systems.
     *
     * @param type
     * @param handler
     */
    public void pushAnnotationHandler(String type, AnnotationHandler handler) {
        List currentHandlers = handlers.get(type);
        if (currentHandlers==null) {
            currentHandlers = new ArrayList();
            handlers.put(type, currentHandlers);
        }
        currentHandlers.add(handler);
    }

    @Override
    public void popAnnotationHandler(Class type) {
        List currentHandlers = handlers.get(type.getName());
        if (currentHandlers!=null) {
            currentHandlers.remove(currentHandlers.size()-1);
        }        
    }    

    @Override
    public AnnotationHandler getAnnotationHandler(Class type) {
        List currentHandlers = handlers.get(type.getName());
        if (currentHandlers!=null && currentHandlers.size()>0) {
            return currentHandlers.get(0);
        }        
        return null;
    }      
    
    /**
     * @param type
     * @return the last element pushed on the stack which ElementType was
     * the one passed or null if no stack element is of the given type.
     */
    @Override
    public AnnotatedElement getLastAnnotatedElement(ElementType type) {
        for (int i=annotatedElements.size();i!=0;i--) {
            StackElement e = annotatedElements.get(i - 1);
            if (e.getElementType().equals(type)) 
                return e.getAnnotatedElement();
        }
        return null;
    }    
    
    public Stack getStack() {
        return annotatedElements;
    }
    
    private void logStart(AnnotatedElementHandler handler, ElementType type, AnnotatedElement c) throws AnnotationProcessorException {
        
        if (AnnotationUtils.shouldLog("types")) {
            AnnotationUtils.getLogger().finer(type + " START : " + c);
        }
        
        // push it to our annotated element stack
        annotatedElements.push(new StackElement(type, c));
        if(delegate!=null) {
            delegate.getStack().push(new StackElement(type, c));
        }
        
        if (handler!=null) {
            handler.startElement(type, c);
        }   
    }
    
    private void logEnd(AnnotatedElementHandler handler, ElementType type, AnnotatedElement c) throws AnnotationProcessorException {
        
        if (AnnotationUtils.shouldLog("types")) {
            AnnotationUtils.getLogger().log(Level.FINER, "{0} END : {1}", new Object[]{type, c});
        }
        
        // pop it from our annotated element stack
        annotatedElements.pop();
        if(delegate!=null) {
            delegate.getStack().pop();
        }
        
        if (handler!=null) {
            handler.endElement(type, c);
        }   
    }   
    
    /** 
     * @return the top annotated elements stack element type 
     */
    private ElementType getTopElementType() {
        try {
            StackElement top = annotatedElements.peek();
            return top.getElementType();
        } catch(EmptyStackException ex) {
            return null;
        }
    }
}