/** * Copyright (C) 2006-2017 INRIA and contributors * Spoon - http://spoon.gforge.inria.fr/ * * This software is governed by the CeCILL-C License under French law and * abiding by the rules of distribution of free software. You can use, modify * and/or redistribute the software under the terms of the CeCILL-C license as * circulated by CEA, CNRS and INRIA at http://www.cecill.info. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details. * * The fact that you are presently reading this means that you have had * knowledge of the CeCILL-C license and that you accept its terms. */ package spoon.processing; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.TreeMap; import spoon.Launcher; import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtElement; /** * This class defines an abstract annotation processor to be subclassed by the * user for defining new annotation processors including Java 8 annotations. */ public abstract class AbstractAnnotationProcessor<A extends Annotation, E extends CtElement> extends AbstractProcessor<E> implements AnnotationProcessor<A, E> { Map<String, Class<? extends A>> consumedAnnotationTypes = new TreeMap<>(); Map<String, Class<? extends A>> processedAnnotationTypes = new TreeMap<>(); /** * Empty constructor only for all processors (invoked by Spoon). */ @SuppressWarnings("unchecked") public AbstractAnnotationProcessor() { super(); clearProcessedElementType(); for (Method m : getClass().getMethods()) { if ("process".equals(m.getName()) && (m.getParameterTypes().length == 2)) { Class<?> c = m.getParameterTypes()[0]; if (inferConsumedAnnotationType() && (Annotation.class != c)) { addConsumedAnnotationType((Class<A>) c); } c = m.getParameterTypes()[1]; if (CtElement.class != c) { addProcessedElementType((Class<E>) c); } } } if (inferConsumedAnnotationType() && processedAnnotationTypes.isEmpty()) { addProcessedAnnotationType((Class<? extends A>) Annotation.class); } if (processedElementTypes.isEmpty()) { addProcessedElementType(CtElement.class); } } /** * Adds a consumed annotation type (to be used in subclasses constructors). * A consumed annotation type is also part of the processed annotation * types. */ protected final void addConsumedAnnotationType(Class<? extends A> annotationType) { addProcessedAnnotationType(annotationType); consumedAnnotationTypes.put(annotationType.getName(), annotationType); } /** * Adds a processed annotation type (to be used in subclasses constructors). */ protected final void addProcessedAnnotationType(Class<? extends A> annotationType) { processedAnnotationTypes.put(annotationType.getName(), annotationType); } /** * Removes a processed annotation type. */ protected final void removeProcessedAnnotationType(Class<? extends A> annotationType) { processedAnnotationTypes.remove(annotationType.getName()); } /** * Clears the processed annotation types. */ protected final void clearProcessedAnnotationTypes() { processedAnnotationTypes.clear(); } /** * Clears the consumed annotation types. */ protected final void clearConsumedAnnotationTypes() { consumedAnnotationTypes.clear(); } /** * Removes a processed annotation type. */ protected final void removeConsumedAnnotationType(Class<? extends A> annotationType) { consumedAnnotationTypes.remove(annotationType.getName()); } public final Set<Class<? extends A>> getConsumedAnnotationTypes() { return new HashSet<>(consumedAnnotationTypes.values()); } public final Set<Class<? extends A>> getProcessedAnnotationTypes() { return new HashSet<>(processedAnnotationTypes.values()); } public boolean inferConsumedAnnotationType() { return true; } /** * Returns true if the element is annotated with an annotation whose type is * processed. */ @Override public final boolean isToBeProcessed(E element) { if ((element != null) && (element.getAnnotations() != null)) { for (CtAnnotation<? extends Annotation> a : element.getAnnotations()) { if (shoudBeProcessed(a)) { return true; } } } return false; } @SuppressWarnings("unchecked") public final void process(E element) { for (CtAnnotation<? extends Annotation> annotation : new ArrayList<>(element.getAnnotations())) { if (shoudBeProcessed(annotation)) { try { process((A) annotation.getActualAnnotation(), element); } catch (Exception e) { Launcher.LOGGER.error(e.getMessage(), e); } if (shoudBeConsumed(annotation)) { element.removeAnnotation(annotation); } } } } /** * {@inheritDoc} * * Removes all annotations A on elements E. */ @Override public boolean shoudBeConsumed(CtAnnotation<? extends Annotation> annotation) { return consumedAnnotationTypes.containsKey(annotation.getAnnotationType().getQualifiedName()); } private boolean shoudBeProcessed(CtAnnotation<? extends Annotation> annotation) { return processedAnnotationTypes.containsKey(annotation.getAnnotationType().getQualifiedName()); } }