| /* |
| * Copyright 2015 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.truth.Truth.assertThat; |
| import static com.google.testing.compile.CompilationSubject.assertThat; |
| import static com.google.testing.compile.Compiler.javac; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.reflect.Reflection; |
| import com.google.testing.compile.Compilation; |
| import com.google.testing.compile.JavaFileObjects; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.util.Collection; |
| import java.util.Set; |
| import java.util.concurrent.ConcurrentHashMap; |
| import javax.annotation.processing.ProcessingEnvironment; |
| import javax.annotation.processing.Processor; |
| import javax.lang.model.SourceVersion; |
| import javax.lang.model.element.TypeElement; |
| import javax.lang.model.util.Elements; |
| import javax.tools.JavaFileObject; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| /** |
| * Tests that {@link AutoValueProcessor} works even if run in a context where the {@code @Generated} |
| * annotation does not exist. |
| * |
| * @author emcmanus@google.com (Éamonn McManus) |
| */ |
| @RunWith(Parameterized.class) |
| public class GeneratedDoesNotExistTest { |
| |
| @Parameters(name = "{0}") |
| public static Collection<Object[]> data() { |
| ImmutableList.Builder<Object[]> params = ImmutableList.builder(); |
| if (SourceVersion.latestSupported().compareTo(SourceVersion.RELEASE_8) > 0) { |
| // use default options when running on JDK > 8 |
| // TODO(b/72513371): use --release 8 once compile-testing supports that |
| params.add( |
| new Object[] { |
| ImmutableList.of(), "javax.annotation.processing.Generated", |
| }); |
| } |
| params.add( |
| new Object[] { |
| ImmutableList.of("-source", "8", "-target", "8"), "javax.annotation.Generated", |
| }); |
| return params.build(); |
| } |
| |
| private final ImmutableList<String> javacOptions; |
| private final String expectedAnnotation; |
| |
| public GeneratedDoesNotExistTest(ImmutableList<String> javacOptions, String expectedAnnotation) { |
| this.javacOptions = javacOptions; |
| this.expectedAnnotation = expectedAnnotation; |
| } |
| |
| // The classes here are basically just rigmarole to ensure that |
| // Types.getTypeElement("javax.annotation.Generated") returns null, and to check that something |
| // called that. We want a Processor that forwards everything to AutoValueProcessor, except that |
| // the init(ProcessingEnvironment) method should forward a ProcessingEnvironment that filters |
| // out the Generated class. So that ProcessingEnvironment forwards everything to the real |
| // ProcessingEnvironment, except the ProcessingEnvironment.getElementUtils() method. That method |
| // returns an Elements object that forwards everything to the real Elements except |
| // getTypeElement("javax.annotation.Generated") and |
| // getTypeElement("javax.annotation.processing.Generated"). |
| |
| private static final ImmutableSet<String> GENERATED_ANNOTATIONS = |
| ImmutableSet.of("javax.annotation.Generated", "javax.annotation.processing.Generated"); |
| |
| /** |
| * InvocationHandler that forwards every method to an original object, except methods where there |
| * is an implementation in this class with the same signature. So for example in the subclass |
| * {@link ElementsHandler} there is a method {@link ElementsHandler#getTypeElement(CharSequence)}, |
| * which means that a call of {@link Elements#getTypeElement(CharSequence)} on the proxy with this |
| * invocation handler will end up calling that method, but a call of any of the other methods of |
| * {@code Elements} will end up calling the method on the original object. |
| */ |
| private abstract static class OverridableInvocationHandler<T> implements InvocationHandler { |
| final T original; |
| |
| OverridableInvocationHandler(T original) { |
| this.original = original; |
| } |
| |
| @Override |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| try { |
| Method override = getClass().getMethod(method.getName(), method.getParameterTypes()); |
| if (override.getDeclaringClass() == getClass()) { |
| return override.invoke(this, args); |
| } |
| } catch (NoSuchMethodException ignored) { |
| // OK: we don't have an override for this method, so just invoke the original method. |
| } |
| return method.invoke(original, args); |
| } |
| } |
| |
| private static <T> T partialProxy(Class<T> type, OverridableInvocationHandler<T> handler) { |
| return Reflection.newProxy(type, handler); |
| } |
| |
| private static class ElementsHandler extends OverridableInvocationHandler<Elements> { |
| |
| private final Set<String> ignoredGenerated; |
| |
| ElementsHandler(Elements original, Set<String> ignoredGenerated) { |
| super(original); |
| this.ignoredGenerated = ignoredGenerated; |
| } |
| |
| public TypeElement getTypeElement(CharSequence name) { |
| if (GENERATED_ANNOTATIONS.contains(name.toString())) { |
| ignoredGenerated.add(name.toString()); |
| return null; |
| } else { |
| return original.getTypeElement(name); |
| } |
| } |
| } |
| |
| private static class ProcessingEnvironmentHandler |
| extends OverridableInvocationHandler<ProcessingEnvironment> { |
| private final Elements noGeneratedElements; |
| |
| ProcessingEnvironmentHandler(ProcessingEnvironment original, Set<String> ignoredGenerated) { |
| super(original); |
| ElementsHandler elementsHandler = |
| new ElementsHandler(original.getElementUtils(), ignoredGenerated); |
| this.noGeneratedElements = partialProxy(Elements.class, elementsHandler); |
| } |
| |
| public Elements getElementUtils() { |
| return noGeneratedElements; |
| } |
| } |
| |
| private static class ProcessorHandler extends OverridableInvocationHandler<Processor> { |
| private final Set<String> ignoredGenerated; |
| |
| ProcessorHandler(Processor original, Set<String> ignoredGenerated) { |
| super(original); |
| this.ignoredGenerated = ignoredGenerated; |
| } |
| |
| public void init(ProcessingEnvironment processingEnv) { |
| ProcessingEnvironmentHandler processingEnvironmentHandler = |
| new ProcessingEnvironmentHandler(processingEnv, ignoredGenerated); |
| ProcessingEnvironment noGeneratedProcessingEnvironment = |
| partialProxy(ProcessingEnvironment.class, processingEnvironmentHandler); |
| original.init(noGeneratedProcessingEnvironment); |
| } |
| } |
| |
| @Test |
| public void test() { |
| JavaFileObject javaFileObject = |
| JavaFileObjects.forSourceLines( |
| "foo.bar.Baz", |
| "package foo.bar;", |
| "", |
| "import com.google.auto.value.AutoValue;", |
| "", |
| "@AutoValue", |
| "public abstract class Baz {", |
| " public static Baz create() {", |
| " return new AutoValue_Baz();", |
| " }", |
| "}"); |
| JavaFileObject expectedOutput = |
| JavaFileObjects.forSourceLines( |
| "foo.bar.AutoValue_Baz", |
| "package foo.bar;", |
| "", |
| "final class AutoValue_Baz extends Baz {", |
| " AutoValue_Baz() {", |
| " }", |
| "", |
| " @Override public String toString() {", |
| " return \"Baz{\"", |
| " + \"}\";", |
| " }", |
| "", |
| " @Override public boolean equals(Object o) {", |
| " if (o == this) {", |
| " return true;", |
| " }", |
| " if (o instanceof Baz) {", |
| " return true;", |
| " }", |
| " return false;", |
| " }", |
| "", |
| " @Override public int hashCode() {", |
| " int h$ = 1;", |
| " return h$;", |
| " }", |
| "}"); |
| Set<String> ignoredGenerated = ConcurrentHashMap.newKeySet(); |
| Processor autoValueProcessor = new AutoValueProcessor(); |
| ProcessorHandler handler = new ProcessorHandler(autoValueProcessor, ignoredGenerated); |
| Processor noGeneratedProcessor = partialProxy(Processor.class, handler); |
| Compilation compilation = |
| javac() |
| .withOptions(javacOptions) |
| .withProcessors(noGeneratedProcessor) |
| .compile(javaFileObject); |
| assertThat(compilation).succeededWithoutWarnings(); |
| assertThat(compilation) |
| .generatedSourceFile("foo.bar.AutoValue_Baz") |
| .hasSourceEquivalentTo(expectedOutput); |
| assertThat(ignoredGenerated).containsExactly(expectedAnnotation); |
| } |
| } |