| /* |
| * Copyright 2017 Google LLC |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.google.auto.value.processor; |
| |
| import static com.google.common.collect.MoreCollectors.onlyElement; |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.testing.compile.CompilationSubject.assertThat; |
| import static com.google.testing.compile.Compiler.javac; |
| import static javax.lang.model.util.ElementFilter.fieldsIn; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.truth.Expect; |
| import com.google.testing.compile.JavaFileObjects; |
| import java.util.Set; |
| import javax.annotation.processing.AbstractProcessor; |
| import javax.annotation.processing.RoundEnvironment; |
| import javax.annotation.processing.SupportedAnnotationTypes; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.ExecutableElement; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.element.VariableElement; |
| import javax.lang.model.type.TypeMirror; |
| import javax.lang.model.util.ElementFilter; |
| import javax.tools.JavaFileObject; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** |
| * This test verifies the method {@link TypeEncoder#encodeWithAnnotations(TypeMirror). |
| * It takes a list of "type spellings", like {@code @Nullable String}, and compiles a class |
| * with one field for each spelling. So there might be a field {@code @Nullable String x2;}. |
| * Then it examines each compiled field to extract its {@code TypeMirror}, and uses the |
| * {@code TypeSimplifier} method to reconvert that into a string. It should get back the same |
| * type spelling in each case. |
| * |
| * <p>I originally tried to write a less convoluted test using compile-testing. In my test, |
| * each type to be tested was an actual type in the test class (the type of a field, or the |
| * return type of a method). However, I found that if I examined these types by looking up a class |
| * with {@link javax.lang.model.util.Elements#getTypeElement} and following through to the type |
| * of interest, it never had any type annotations. |
| * |
| * @author emcmanus@google.com (Éamonn McManus) |
| */ |
| @RunWith(JUnit4.class) |
| public class SimplifyWithAnnotationsTest { |
| @Rule public final Expect expect = Expect.create(); |
| |
| /** |
| * The types that we will compile and then recreate. They are referenced in a context where {@code |
| * Set} is unambiguous but not {@code List}, which allows us to test the placement of annotations |
| * in unqualified types like {@code Set<T>} and qualified types like {@code java.util.List<T>}. |
| */ |
| private static final ImmutableList<String> TYPE_SPELLINGS = |
| ImmutableList.of( |
| "Object", |
| "Set", |
| "String", |
| "Nullable", |
| "@Nullable String", |
| "String[]", |
| "@Nullable String[]", |
| "String @Nullable []", |
| "String @Nullable [] @Nullable []", |
| "java.awt.List", |
| "java.util.List<String>", |
| "Set<@Nullable String>", |
| "@Nullable Set<String>", |
| "int", |
| "@Nullable int", // whatever that might mean |
| "@Nullable int[]", |
| "int @Nullable []", |
| "T", |
| "@Nullable T", |
| "Set<@Nullable T>", |
| "Set<? extends @Nullable T>", |
| "Set<? extends @Nullable String>", |
| "Set<? extends @Nullable String @Nullable []>", |
| "java.util.@Nullable List<@Nullable T>", |
| "java.util.@Nullable List<java.util.@Nullable List<T>>"); |
| |
| private static final JavaFileObject NULLABLE_FILE_OBJECT = |
| JavaFileObjects.forSourceLines( |
| "pkg.Nullable", |
| "package pkg;", |
| "", |
| "import java.lang.annotation.ElementType;", |
| "import java.lang.annotation.Target;", |
| "", |
| "@Target(ElementType.TYPE_USE)", |
| "public @interface Nullable {}"); |
| |
| private static final JavaFileObject TEST_CLASS_FILE_OBJECT = |
| JavaFileObjects.forSourceLines("pkg.TestClass", buildTestClass()); |
| |
| private static ImmutableList<String> buildTestClass() { |
| // Some older versions of javac don't handle type annotations at all well in annotation |
| // processors. The `witness` method in the generated class is there to detect that, and |
| // skip the test if it is the case. |
| ImmutableList.Builder<String> builder = ImmutableList.builder(); |
| builder.add( |
| "package pkg;", |
| "", |
| "import java.util.Set;", |
| "", |
| "public abstract class TestClass<T> {", |
| " abstract @Nullable T witness();"); |
| int i = 0; |
| for (String typeSpelling : TYPE_SPELLINGS) { |
| builder.add(String.format(" %s x%d;\n", typeSpelling, i++)); |
| } |
| builder.add("}"); |
| return builder.build(); |
| } |
| |
| @Test |
| public void testSimplifyWithAnnotations() { |
| // The real test happens inside the .compile(...), by virtue of TestProcessor. |
| assertThat( |
| javac() |
| .withOptions("-proc:only") |
| .withProcessors(new TestProcessor()) |
| .compile(NULLABLE_FILE_OBJECT, TEST_CLASS_FILE_OBJECT)) |
| .succeededWithoutWarnings(); |
| } |
| |
| @SupportedAnnotationTypes("*") |
| private static class TestProcessor extends AbstractProcessor { |
| @Override |
| public SourceVersion getSupportedSourceVersion() { |
| return SourceVersion.latest(); |
| } |
| |
| @Override |
| public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { |
| if (roundEnv.processingOver()) { |
| TypeElement testClass = processingEnv.getElementUtils().getTypeElement("pkg.TestClass"); |
| testTypeSpellings(testClass); |
| } |
| return false; |
| } |
| |
| void testTypeSpellings(TypeElement testClass) { |
| ExecutableElement witness = |
| ElementFilter.methodsIn(testClass.getEnclosedElements()) |
| .stream() |
| .filter(m -> m.getSimpleName().contentEquals("witness")) |
| .collect(onlyElement()); |
| if (witness.getReturnType().getAnnotationMirrors().isEmpty()) { |
| System.err.println("SKIPPING TEST BECAUSE OF BUGGY COMPILER"); |
| return; |
| } |
| ImmutableMap<String, TypeMirror> typeSpellingToType = typesFromTestClass(testClass); |
| assertThat(typeSpellingToType).isNotEmpty(); |
| StringBuilder text = new StringBuilder(); |
| StringBuilder expected = new StringBuilder(); |
| // Build up a fake source text with the encodings for the types in it, and decode it to |
| // ensure the type spellings are what we expect. |
| typeSpellingToType.forEach( |
| (typeSpelling, type) -> { |
| text.append("{").append(TypeEncoder.encodeWithAnnotations(type)).append("}"); |
| expected.append("{").append(typeSpelling).append("}"); |
| }); |
| String decoded = TypeEncoder.decode(text.toString(), processingEnv, "pkg", null); |
| assertThat(decoded).isEqualTo(expected.toString()); |
| } |
| |
| private static ImmutableMap<String, TypeMirror> typesFromTestClass(TypeElement type) { |
| // Reads the types of the fields from the compiled TestClass and uses them to produce |
| // a map from type spellings to types. This method depends on type.getEnclosedElements() |
| // returning the fields in source order, which it is specified to do. |
| ImmutableMap.Builder<String, TypeMirror> typeSpellingToTypeBuilder = ImmutableMap.builder(); |
| int i = 0; |
| for (VariableElement field : fieldsIn(type.getEnclosedElements())) { |
| String spelling = TYPE_SPELLINGS.get(i); |
| typeSpellingToTypeBuilder.put(spelling, field.asType()); |
| i++; |
| } |
| return typeSpellingToTypeBuilder.build(); |
| } |
| } |
| } |