/** * Iterate over everything that has a {@link Validated} annotation and check for invalid usages. * * @param types classes annotated with the {@link Validated} annotation. * @return {@link RaveIR} with initial parameters set. */ private RaveIR verify(List<TypeElement> types) { AnnotationVerifier verifier = new AnnotationVerifier(messager, elementUtils, typesUtils); for (TypeElement type : types) { verifier.verify(type); } factoryTypeMirror = verifier.getSeenFactoryTypeMirror(); TypeElement element = (TypeElement) typesUtils.asElement(factoryTypeMirror); Validator strategy = element.getAnnotation(Validator.class); return new RaveIR( CompilerUtils.packageNameOf(element), element.getSimpleName().toString(), strategy == null ? DEFAULT : strategy.mode()); }
Validated validatedAnnotation = type.getAnnotation(Validated.class); if (validatedAnnotation == null) { abortWithError("Annotation processor for @" + Validated.class.getSimpleName() + " was invoked with a type that does not have the annotation.", type); return; abortWithError("Class is private. It must be at least package private", type); TypeMirror factoryMirror = getTypeMirrorFromAnnotation(validatedAnnotation, type); if (type.getModifiers().contains(Modifier.PROTECTED) || type.getModifiers().contains(Modifier.DEFAULT)) { abortWithError(type.getSimpleName() + " is not visible to " + factoryMirror.toString(), type); if (implementsAnnotation(type)) { abortWithError("@" + Validated.class.getSimpleName() + " may not be used to implement an " + "annotation interface.", type); checkFactoryClass(factoryMirror, type); verifyAnnotationConflicts(type); verifyAnnotations(type);
/** * Check if given type implements {@link java.lang.annotation.Annotation}. * * @param type The type. * @return {@code true} if given type implements {@link java.lang.annotation.Annotation}, {@code false} otherwise. */ private boolean implementsAnnotation(TypeElement type) { return types.isAssignable(type.asType(), getTypeMirror(Annotation.class)); }
@Test(expected = AbortProcessingException.class) public void verify_whenTypeElementDoesNotHaveAnnotation_shouldAbortWithError() { Messager messager = mock(Messager.class); Elements element = mock(Elements.class); Types types = mock(Types.class); AnnotationVerifier verifier = new AnnotationVerifier(messager, element, types); TypeElement typeElement = mock(TypeElement.class); verifier.verify(typeElement); verify(messager).printMessage(eq(Diagnostic.Kind.ERROR), anyString(), eq(typeElement)); }
/** * Check to make sure there is only one factory class referenced. * * @param factoryTypeMirror the {@link TypeMirror} of the {@link ValidatorFactory}. * @param type the {@link TypeElement} of the class that referenced the {@link ValidatorFactory}. */ private void checkFactoryClass(TypeMirror factoryTypeMirror, TypeElement type) { if (seenFactoryTypeMirror == null) { seenFactoryTypeMirror = factoryTypeMirror; return; } if (!types.isSameType(factoryTypeMirror, seenFactoryTypeMirror)) { String errorMsg = "More than one factory class referenced by models " + seenFactoryTypeMirror.toString() + " and " + factoryTypeMirror.toString(); abortWithError(errorMsg, type); } }
/** * Issue a compilation error and abandon the processing of this class. This does not prevent * the processing of other classes. * * @param msg The error message. * @param e The element at which the error occurred. */ private void abortWithError(String msg, Element e) { reportError(msg, e); throw new AbortProcessingException(); }
@Test(expected = AbortProcessingException.class) public void verify_whenTypeElementIsPackagePrivate_shouldAbortWithError() { Set<Modifier> modSet = new HashSet<>(); modSet.add(Modifier.PROTECTED); Messager messager = mock(Messager.class); Elements element = mock(Elements.class); Types types = mock(Types.class); AnnotationVerifier verifier = new AnnotationVerifier(messager, element, types); PackageElement packageElement = mock(PackageElement.class); Name name = mock(Name.class); when(packageElement.getQualifiedName()).thenReturn(name); when(name.toString()).thenReturn("Nonmatching.package"); TypeElement typeElement = mock(TypeElement.class); when(typeElement.getModifiers()).thenReturn(modSet); when(typeElement.getEnclosingElement()).thenReturn(packageElement); verifier.verify(typeElement); verify(messager).printMessage(eq(Diagnostic.Kind.ERROR), anyString(), eq(typeElement)); } }
/** * Verify that the annotations on methods in a model are not conflicting. * * @param typeElement the {@link TypeElement} of the model being verified. */ private void verifyAnnotationConflicts(TypeElement typeElement) { List<String> annotationList = new LinkedList<>(); List<ExecutableElement> methodElements = new ImmutableList.Builder<ExecutableElement>() .addAll(ElementFilter.methodsIn(typeElement.getEnclosedElements())).build(); for (ExecutableElement executableElement : methodElements) { annotationList.clear(); for (AnnotationMirror mirror : elements.getAllAnnotationMirrors(executableElement)) { String annotationName = mirror.getAnnotationType().toString(); if (CompilerUtils.isSupportedAnnotation(mirror.getAnnotationType().toString())) { for (String a : annotationList) { if (CompilerUtils.areConflicting(a, annotationName)) { abortWithError("Annotations " + annotationName + " cannot be used with " + a, typeElement); } } annotationList.add(annotationName); } } } }
/** * This method retrieves the {@link TypeMirror} from an annotation. This is a bit tricky in annotation * processing because the class are not loaded so you can't retrieve class objects from the annotation directly. * See: http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/ * * @param validated the {@link Validated} annotation. * @param type the TypeElement which is used only for error logging. * @return the TypeMirror. */ private TypeMirror getTypeMirrorFromAnnotation(Validated validated, TypeElement type) { try { // This should always throw an exception validated.factory(); } catch (MirroredTypeException e) { return e.getTypeMirror(); } reportError("Retrieving class information from annotation did not throw an exception something is wrong.", type); throw new AbortProcessingException(); } }
if (size != null) { if (size.multiple() < 1) { abortWithError("Multiple value is less than 1 with value:" + size.multiple() + " on method " + executableElement.getSimpleName(), type); TypeKind kind = executableElement.getReturnType().getKind(); if (!(kind == TypeKind.FLOAT || kind == TypeKind.DOUBLE)) { abortWithError(executableElement.getSimpleName().toString() + FLOAT_RANGE_BAD_RETURN_TYPE_ERROR, type); TypeKind kind = executableElement.getReturnType().getKind(); if (!(kind == TypeKind.INT || kind == TypeKind.LONG)) { abortWithError(executableElement.getSimpleName().toString() + INT_RANGE_BAD_RETURN_TYPE_ERROR, type); TypeKind kind = executableElement.getReturnType().getKind(); if (annotation != null && !(kind == TypeKind.INT)) { abortWithError(executableElement.getSimpleName().toString() + INTDEF_BAD_RETURN_TYPE_ERROR, type); TypeKind kind = executableElement.getReturnType().getKind(); if (annotation != null && !(kind == TypeKind.LONG)) { abortWithError(executableElement.getSimpleName().toString() + LONGDEF_BAD_RETURN_TYPE_ERROR, type);