/* * 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] [Payara Foundation] package org.glassfish.apf.impl; 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.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 { AnnotationProcessorImpl delegate; Map<String, List<AnnotationHandler>> handlers = new HashMap<String, List<AnnotationHandler>>(); int errorCount; Logger logger; Stack<StackElement> annotatedElements = new Stack<StackElement>(); Set<Package> visitedPackages = new HashSet<Package>(); /** Creates a new instance of AnnotationProcessorImpl */ public AnnotationProcessorImpl() { logger = AnnotationUtils.getLogger(); } public void setDelegate(AnnotationProcessorImpl delegate) { this.delegate = delegate; } public ProcessingContext createContext() { ProcessingContext ctx = new ProcessingContextImpl(this); ctx.setErrorHandler(new DefaultErrorHandler()); return ctx; } /** * Log a message on the default logger */ 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}", new Object[] { locator.getAnnotation().annotationType().getName(), locator.getAnnotatedElement(), localizedMessage})); } else{ logger.log(level, localizedMessage); } } } /** * Starts the annotation processing tool passing the processing context which * encapuslate all information necessary for the configuration of the tool. * @param ctx is the initialized processing context * @return the result of the annoations processing */ public ProcessingResult process(ProcessingContext ctx) throws AnnotationProcessorException { Scanner<Object> scanner = ctx.getProcessingInput(); ProcessingResultImpl result = new ProcessingResultImpl(); errorCount=0; for (Class c : scanner.getElements()) { result.add(process(ctx, c)); } 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 */ 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} ]", new Object[]{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.finer("Annotation : " + annotation.annotationType().getName() + " delegate = " + 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<AnnotationHandler> 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<? extends Annotation>[] dependencies = handler.getTypeDependencies(); if (dependencies!=null) { AnnotatedElement ae = element.getAnnotatedElement(); for (Class<? extends Annotation> 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<Class<? extends Annotation>, ResultType> annotationResults = result.processedAnnotations(); for (Map.Entry<Class<? extends Annotation>, ResultType> element : annotationResults.entrySet()) { logger.finer("Annotation " + element.getKey() + " : " + element.getValue()); } } 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<AnnotationHandler> currentHandlers = handlers.get(type); if (currentHandlers==null) { currentHandlers = new ArrayList<AnnotationHandler>(); handlers.put(type, currentHandlers); } currentHandlers.add(handler); } public void popAnnotationHandler(Class<? extends Annotation> type) { List<AnnotationHandler> currentHandlers = handlers.get(type.getName()); if (currentHandlers!=null) { currentHandlers.remove(currentHandlers.size()-1); } } public AnnotationHandler getAnnotationHandler(Class<? extends Annotation> type) { List<AnnotationHandler> currentHandlers = handlers.get(type.getName()); if (currentHandlers!=null && currentHandlers.size()>0) { return currentHandlers.get(0); } return null; } /** * @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. */ 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<StackElement> 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().finer(type + " END : " + 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; } } }