/* * Grapht, an open source dependency injector. * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt) * Copyright 2010-2014 Regents of the University of Minnesota * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the * License, or (at your option) any later version. * * 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 GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.grouplens.grapht.annotation; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; import javax.annotation.processing.RoundEnvironment; import javax.inject.Qualifier; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.tools.Diagnostic; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; /** * Annotation processor that checks and validates DI annotations. */ public class AnnotationValidator extends AbstractProcessor { private static final String DEFAULT_ANNOT_PREFIX = "org.grouplens.grapht.annotation.Default"; private static final Pattern DEFAULT_PATTERN = Pattern.compile("^" + Pattern.quote(DEFAULT_ANNOT_PREFIX)); @Override public SourceVersion getSupportedSourceVersion() { // support version 6 or 7 // we can't compile against RELEASE_7 and maintain Java 6 compatibility, but the // processor is Java 7-compatible. We have not tested against Java 8, however. SourceVersion[] versions = SourceVersion.values(); SourceVersion v6 = SourceVersion.RELEASE_6; assert v6.ordinal() < versions.length; // we support up through Java 8 return versions[Math.min(v6.ordinal() + 2, versions.length - 1)]; } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> atypes = new HashSet<String>(); atypes.add(Qualifier.class.getName()); atypes.add(Attribute.class.getName()); atypes.add(AliasFor.class.getName()); return atypes; } private void warning(Element e, String fmt, Object... args) { Messager log = processingEnv.getMessager(); String msg = String.format(fmt, args); log.printMessage(Diagnostic.Kind.MANDATORY_WARNING, msg, e); } private void error(Element e, String fmt, Object... args) { Messager log = processingEnv.getMessager(); String msg = String.format(fmt, args); log.printMessage(Diagnostic.Kind.ERROR, msg, e); } @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { analyzeQualifiers(roundEnv); analyzeAttributes(roundEnv); analyzeAliases(roundEnv); return false; // let other processors work too } private void analyzeQualifiers(RoundEnvironment re) { analyzeAnnotations(re, Qualifier.class, "qualifier"); } private void analyzeAttributes(RoundEnvironment re) { analyzeAnnotations(re, Attribute.class, "attribute"); } private void analyzeAnnotations(RoundEnvironment re, Class<? extends Annotation> annotation, String annotationName){ Set<? extends Element> elts = re.getElementsAnnotatedWith(annotation); for (Element elt: elts) { if (elt.getAnnotation(Documented.class) == null) { warning(elt, String.format("%s annotation should be @Documented", annotationName)); } Retention ret = elt.getAnnotation(Retention.class); if (ret == null || !ret.value().equals(RetentionPolicy.RUNTIME)) { error(elt, String.format("%s annotation must have RUNTIME retention", annotationName)); } } } private void analyzeAliases(RoundEnvironment re) { Set<? extends Element> elts = re.getElementsAnnotatedWith(AliasFor.class); for (Element elt : elts) { if (elt.getAnnotation(Qualifier.class) == null) { error(elt, "alias annotation must also be a qualifier"); } if (elt.getAnnotation(AllowUnqualifiedMatch.class) != null) { error(elt, "alias annotation has @AllowUnqualifiedMatch (should be on alias target)"); } if (elt.getAnnotation(AllowDefaultMatch.class) != null) { error(elt, "alias annotation has @AllowDefaultMatch (should be on alias target)"); } for (AnnotationMirror mirror: elt.getAnnotationMirrors()) { Element element = mirror.getAnnotationType().asElement(); if (element instanceof TypeElement) { TypeElement type = (TypeElement) element; Name name = type.getQualifiedName(); if (DEFAULT_PATTERN.matcher(name).matches()) { warning(elt, "alias annotation has %s, defaults should be on alias target", type.getSimpleName()); } } } } } }