/* * The contents of this file are subject to the terms * of the Common Development and Distribution License * (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/CDDLv1.0.html or * glassfish/bootstrap/legal/CDDLv1.0.txt. * See the License for the specific language governing * permissions and limitations under the License. * * When distributing Covered Code, include this CDDL * Header Notice in each file and include the License file * at glassfish/bootstrap/legal/CDDLv1.0.txt. * If applicable, add the following below the CDDL Header, * with the fields enclosed by brackets [] replaced by * you own identifying information: * "Portions Copyrighted [year] [name of copyright owner]" * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. */ package com.sun.enterprise.deployment.annotation.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 com.sun.enterprise.deployment.annotation.ProcessingContext; import com.sun.enterprise.deployment.annotation.AnnotationProcessor; import com.sun.enterprise.deployment.annotation.AnnotationInfo; import com.sun.enterprise.deployment.annotation.AnnotationProcessorException; import com.sun.enterprise.deployment.annotation.AnnotationHandler; import com.sun.enterprise.deployment.annotation.AnnotatedElementHandler; import com.sun.enterprise.deployment.annotation.ComponentInfo; import com.sun.enterprise.deployment.annotation.ResultType; import com.sun.enterprise.deployment.annotation.HandlerProcessingResult; import com.sun.enterprise.deployment.annotation.ProcessingResult; import com.sun.enterprise.deployment.annotation.Scanner; import java.util.logging.Level; /** * * @author dochez */ public class AnnotationProcessorImpl implements AnnotationProcessor { AnnotationProcessorImpl delegate; Map, List> handlers = new HashMap, List>(); int errorCount; Logger logger; Stack annotatedElements = new Stack(); Set visitedPackages = new HashSet(); /** 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 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 the processing context * @param 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("Class [ "+err.getMessage()+ " ] not found. Error while loading [ "+c+" ]"); ctx.getErrorHandler().error(ape); throw err; } // 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(); 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"); } } } 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 annotationHandlers = handlers.get(annotation.annotationType()); 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 (Class annotationType : annotationResults.keySet()) { logger.finer("Annotation " + annotationType + " : " + annotationResults.get(annotationType)); } } public void pushAnnotationHandler(AnnotationHandler handler) { Class type = handler.getAnnotationType(); List currentHandlers = handlers.get(type); if (currentHandlers==null) { currentHandlers = new ArrayList(); handlers.put(type, currentHandlers); } currentHandlers.add(handler); } public void popAnnotationHandler(Class type) { List currentHandlers = handlers.get(type); if (currentHandlers!=null) { currentHandlers.remove(currentHandlers.size()); } } public AnnotationHandler getAnnotationHandler(Class type) { List currentHandlers = handlers.get(type); 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 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; } } }