Skip to content

Commit

Permalink
Update SuperficialValidation to validate supertypes and superinterfac…
Browse files Browse the repository at this point in the history
…es recursively.
  • Loading branch information
Leland Takamine committed May 16, 2019
1 parent 9f36c57 commit 56bf699
Show file tree
Hide file tree
Showing 2 changed files with 191 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
*/
package com.google.auto.common;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.AnnotationValueVisitor;
Expand Down Expand Up @@ -46,27 +49,43 @@
* @author Gregory Kick
*/
public final class SuperficialValidation {

private final Set<Element> visited = new HashSet<>();

private SuperficialValidation() {}

public static boolean validateElements(Iterable<? extends Element> elements) {
return new SuperficialValidation().validateElementsInternal(elements);
}

public static boolean validateElement(Element element) {
return new SuperficialValidation().validateElementInternal(element);
}

private boolean validateElementsInternal(Iterable<? extends Element> elements) {
for (Element element : elements) {
if (!validateElement(element)) {
if (!validateElementInternal(element)) {
return false;
}
}
return true;
}

private static final ElementVisitor<Boolean, Void> ELEMENT_VALIDATING_VISITOR =
private final ElementVisitor<Boolean, Void> ELEMENT_VALIDATING_VISITOR =
new AbstractElementVisitor6<Boolean, Void>() {
@Override public Boolean visitPackage(PackageElement e, Void p) {
// don't validate enclosed elements because it will return types in the package
return validateAnnotations(e.getAnnotationMirrors());
}

@Override public Boolean visitType(TypeElement e, Void p) {
TypeMirror superclass = e.getSuperclass();
return isValidBaseElement(e)
&& validateElements(e.getTypeParameters())
&& validateElementsInternal(e.getTypeParameters())
&& validateTypes(e.getInterfaces())
&& validateType(e.getSuperclass());
&& validateType(superclass)
&& e.getInterfaces().stream().map(MoreTypes::asElement).allMatch(i -> validateElementInternal(i))
&& (superclass.getKind() == TypeKind.NONE || validateElementInternal(MoreTypes.asElement(superclass)));
}

@Override public Boolean visitVariable(VariableElement e, Void p) {
Expand All @@ -79,8 +98,8 @@ && validateTypes(e.getInterfaces())
&& (defaultValue == null || validateAnnotationValue(defaultValue, e.getReturnType()))
&& validateType(e.getReturnType())
&& validateTypes(e.getThrownTypes())
&& validateElements(e.getTypeParameters())
&& validateElements(e.getParameters());
&& validateElementsInternal(e.getTypeParameters())
&& validateElementsInternal(e.getParameters());
}

@Override public Boolean visitTypeParameter(TypeParameterElement e, Void p) {
Expand All @@ -94,17 +113,21 @@ && validateElements(e.getTypeParameters())
}
};

public static boolean validateElement(Element element) {
return element.accept(ELEMENT_VALIDATING_VISITOR, null);
private boolean validateElementInternal(Element element) {
if (visited.add(element)) {
return element.accept(ELEMENT_VALIDATING_VISITOR, null);
} else {
return true;
}
}

private static boolean isValidBaseElement(Element e) {
private boolean isValidBaseElement(Element e) {
return validateType(e.asType())
&& validateAnnotations(e.getAnnotationMirrors())
&& validateElements(e.getEnclosedElements());
&& validateElementsInternal(e.getEnclosedElements());
}

private static boolean validateTypes(Iterable<? extends TypeMirror> types) {
private boolean validateTypes(Iterable<? extends TypeMirror> types) {
for (TypeMirror type : types) {
if (!validateType(type)) {
return false;
Expand All @@ -118,7 +141,7 @@ private static boolean validateTypes(Iterable<? extends TypeMirror> types) {
* an issue. Javac turns the whole type parameter into an error type if it can't figure out the
* bounds.
*/
private static final TypeVisitor<Boolean, Void> TYPE_VALIDATING_VISITOR =
private final TypeVisitor<Boolean, Void> TYPE_VALIDATING_VISITOR =
new SimpleTypeVisitor6<Boolean, Void>() {
@Override
protected Boolean defaultAction(TypeMirror t, Void p) {
Expand Down Expand Up @@ -163,11 +186,11 @@ && validateTypes(t.getThrownTypes())
}
};

private static boolean validateType(TypeMirror type) {
private boolean validateType(TypeMirror type) {
return type.accept(TYPE_VALIDATING_VISITOR, null);
}

private static boolean validateAnnotations(
private boolean validateAnnotations(
Iterable<? extends AnnotationMirror> annotationMirrors) {
for (AnnotationMirror annotationMirror : annotationMirrors) {
if (!validateAnnotation(annotationMirror)) {
Expand All @@ -177,13 +200,13 @@ private static boolean validateAnnotations(
return true;
}

private static boolean validateAnnotation(AnnotationMirror annotationMirror) {
private boolean validateAnnotation(AnnotationMirror annotationMirror) {
return validateType(annotationMirror.getAnnotationType())
&& validateAnnotationValues(annotationMirror.getElementValues());
}

@SuppressWarnings("unused")
private static boolean validateAnnotationValues(
private boolean validateAnnotationValues(
Map<? extends ExecutableElement, ? extends AnnotationValue> valueMap) {
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> valueEntry :
valueMap.entrySet()) {
Expand All @@ -195,7 +218,7 @@ private static boolean validateAnnotationValues(
return true;
}

private static final AnnotationValueVisitor<Boolean, TypeMirror> VALUE_VALIDATING_VISITOR =
private final AnnotationValueVisitor<Boolean, TypeMirror> VALUE_VALIDATING_VISITOR =
new SimpleAnnotationValueVisitor6<Boolean, TypeMirror>() {
@Override protected Boolean defaultAction(Object o, TypeMirror expectedType) {
return MoreTypes.isTypeOf(o.getClass(), expectedType);
Expand Down Expand Up @@ -232,7 +255,7 @@ public Boolean visitArray(List<? extends AnnotationValue> values, TypeMirror exp
@Override
public Boolean visitEnumConstant(VariableElement enumConstant, TypeMirror expectedType) {
return MoreTypes.equivalence().equivalent(enumConstant.asType(), expectedType)
&& validateElement(enumConstant);
&& validateElementInternal(enumConstant);
}

@Override public Boolean visitType(TypeMirror type, TypeMirror ignored) {
Expand Down Expand Up @@ -276,7 +299,7 @@ public Boolean visitEnumConstant(VariableElement enumConstant, TypeMirror expect
}
};

private static boolean validateAnnotationValue(
private boolean validateAnnotationValue(
AnnotationValue annotationValue, TypeMirror expectedType) {
return annotationValue.accept(VALUE_VALIDATING_VISITOR, expectedType);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.testing.compile.JavaFileObjects;
import java.util.Set;
Expand Down Expand Up @@ -199,6 +201,63 @@ public void handlesRecursiveType() {
.compilesWithoutError();
}

@Test
public void handlesInnerSubclass() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"abstract class TestClass {",
" class InnerTestClass extends TestClass {}",
"}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AssertingProcessor() {
@Override void runAssertions() {
TypeElement testClassElement =
processingEnv.getElementUtils().getTypeElement("test.TestClass");
assertThat(SuperficialValidation.validateElement(testClassElement)).isTrue();
}
})
.compilesWithoutError();
}

@Test
public void handlesRecursiveSuperinterface() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"interface TestClass implements TestClass {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
assertWithMessage("Should not reach annotation processing.").fail();
}
})
.failsToCompile();
}

@Test
public void handlesRecursiveSuperclass() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"class TestClass extends TestClass {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
assertWithMessage("Should not reach annotation processing.").fail();
}
})
.failsToCompile();
}

@Test
public void missingWildcardBound() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
Expand Down Expand Up @@ -276,6 +335,96 @@ void runAssertions() {
.failsToCompile();
}

@Test
public void missingSuperclass() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"class TestClass extends Missing {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
TypeElement testClassElement =
processingEnv.getElementUtils().getTypeElement("test.TestClass");
assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
}
})
.failsToCompile();
}

@Test
public void missingSuperinterface() {
JavaFileObject javaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"class TestClass implements Missing {}");
assertAbout(javaSource())
.that(javaFileObject)
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
TypeElement testClassElement =
processingEnv.getElementUtils().getTypeElement("test.TestClass");
assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
}
})
.failsToCompile();
}

@Test
public void missingGrandparentSuperclass() {
JavaFileObject parentJavaFileObject = JavaFileObjects.forSourceLines(
"test.Parent",
"package test;",
"",
"class Parent extends Missing {}");
JavaFileObject testClassJavaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"class TestClass extends Parent {}");
assertAbout(javaSources())
.that(ImmutableList.of(parentJavaFileObject, testClassJavaFileObject))
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
TypeElement testClassElement =
processingEnv.getElementUtils().getTypeElement("test.TestClass");
assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
}
})
.failsToCompile();
}

@Test
public void missingGrandparentSuperinterface() {
JavaFileObject parentJavaFileObject = JavaFileObjects.forSourceLines(
"test.Parent",
"package test;",
"",
"interface Parent extends Missing {}");
JavaFileObject testClassJavaFileObject = JavaFileObjects.forSourceLines(
"test.TestClass",
"package test;",
"",
"class TestClass implements Parent {}");
assertAbout(javaSources())
.that(ImmutableList.of(parentJavaFileObject, testClassJavaFileObject))
.processedWith(new AssertingProcessor() {
@Override
void runAssertions() {
TypeElement testClassElement =
processingEnv.getElementUtils().getTypeElement("test.TestClass");
assertThat(SuperficialValidation.validateElement(testClassElement)).isFalse();
}
})
.failsToCompile();
}

private abstract static class AssertingProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Expand Down

0 comments on commit 56bf699

Please sign in to comment.