Skip to content

Commit b2a1c08

Browse files
eamonnmcmanusGoogle Java Core Libraries
authored andcommitted
Provide a way to control annotations on generated AutoFactory classes.
The meta-annotation `@AutoFactory.AnnotationsToApply` specifies an annotation that can be applied alongside `@AutoFactory` to specify the annotations to be applied to the generated class. Its javadoc has more details. RELNOTES=There is now a way to add annotations to generated AutoFactory classes. See the javadoc for `@AutoFactory.AnnotationsToApply`. PiperOrigin-RevId: 539648904
1 parent b8a8560 commit b2a1c08

13 files changed

+352
-2
lines changed

factory/src/main/java/com/google/auto/factory/AutoFactory.java

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.google.auto.factory;
1717

18+
import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
1819
import static java.lang.annotation.ElementType.CONSTRUCTOR;
1920
import static java.lang.annotation.ElementType.TYPE;
2021

@@ -62,4 +63,44 @@
6263
* Defaults to disallowing subclasses (generating the factory as final).
6364
*/
6465
boolean allowSubclasses() default false;
66+
67+
/**
68+
* Specifies that an annotation should be used to determine how to annotate generated AutoFactory
69+
* classes. For example, suppose you have this annotation:
70+
* <pre>{@code
71+
* @AutoFactory.AnnotationsToApply
72+
* @interface ApplyImmutableAndSuppressWarnings {
73+
* Immutable immutable() default @Immutable;
74+
* SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
75+
* }
76+
* }</pre>
77+
*
78+
* And suppose you use it like this:
79+
* <pre>{@code
80+
* @ApplyImmutableAndSuppressWarnings
81+
* @AutoFactory
82+
* public class Foo {...}
83+
* }</pre>
84+
*
85+
* Then the generated {@code FooFactory} would look like this:
86+
* <pre>{@code
87+
* @Immutable
88+
* @SuppressWarnings("Immutable")
89+
* public class FooFactory {...}
90+
* }</pre>
91+
*
92+
* The same would be true if you used it like this:
93+
* <pre>{@code
94+
* @ApplyImmutableAndSuppressWarnings(
95+
* immutable = @Immutable,
96+
* suppressWarnings = @SuppressWarnings("Immutable"))
97+
* @AutoFactory
98+
* public class Foo {...}
99+
* }</pre>
100+
*
101+
* You could also have {@code suppressWarnings = @SuppressWarnings({"Immutable", "unchecked"})},
102+
* etc, to specify a value different from the default.
103+
*/
104+
@Target(ANNOTATION_TYPE)
105+
@interface AnnotationsToApply {}
65106
}

factory/src/main/java/com/google/auto/factory/processor/AutoFactoryDeclaration.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.google.auto.factory.processor;
1717

1818
import static com.google.auto.common.MoreElements.getPackage;
19+
import static com.google.auto.common.MoreStreams.toImmutableSet;
1920
import static com.google.auto.factory.processor.Elements2.isValidSupertypeForClass;
2021
import static com.google.common.base.Preconditions.checkArgument;
2122
import static com.google.common.base.Preconditions.checkNotNull;
@@ -25,7 +26,9 @@
2526
import static javax.lang.model.util.ElementFilter.typesIn;
2627
import static javax.tools.Diagnostic.Kind.ERROR;
2728

29+
import com.google.auto.common.AnnotationMirrors;
2830
import com.google.auto.factory.AutoFactory;
31+
import com.google.auto.factory.AutoFactory.AnnotationsToApply;
2932
import com.google.auto.value.AutoValue;
3033
import com.google.common.base.Predicate;
3134
import com.google.common.collect.FluentIterable;
@@ -59,6 +62,7 @@ abstract class AutoFactoryDeclaration {
5962

6063
abstract Optional<String> className();
6164

65+
abstract ImmutableSet<AnnotationMirror> annotations();
6266

6367
abstract TypeElement extendingType();
6468

@@ -125,6 +129,7 @@ Optional<AutoFactoryDeclaration> createIfValid(Element element) {
125129
return Optional.empty();
126130
}
127131

132+
ImmutableSet<AnnotationMirror> annotations = annotationsToAdd(element);
128133
AnnotationValue extendingValue = checkNotNull(values.get("extending"));
129134
TypeElement extendingType = AnnotationValues.asType(extendingValue);
130135
if (extendingType == null) {
@@ -187,6 +192,7 @@ public boolean apply(ExecutableElement constructor) {
187192
getAnnotatedType(element),
188193
element,
189194
className.isEmpty() ? Optional.empty() : Optional.of(className),
195+
annotations,
190196
extendingType,
191197
implementingTypes,
192198
allowSubclasses,
@@ -206,5 +212,35 @@ private static TypeElement getAnnotatedType(Element element) {
206212
static boolean isValidIdentifier(String identifier) {
207213
return SourceVersion.isIdentifier(identifier) && !SourceVersion.isKeyword(identifier);
208214
}
215+
216+
private ImmutableSet<AnnotationMirror> annotationsToAdd(Element element) {
217+
ImmutableSet.Builder<AnnotationMirror> annotationsBuilder = ImmutableSet.builder();
218+
219+
ImmutableSet<? extends AnnotationMirror> containers =
220+
AnnotationMirrors.getAnnotatedAnnotations(element, AnnotationsToApply.class);
221+
switch (containers.size()) {
222+
case 1:
223+
annotationsBuilder.addAll(extractAnnotationsToApply(getOnlyElement(containers)));
224+
break;
225+
case 0:
226+
break;
227+
default:
228+
messager.printMessage(
229+
ERROR, "Multiple @AnnotationsToApply annotations are not supported", element);
230+
}
231+
232+
return annotationsBuilder.build();
233+
}
234+
235+
private ImmutableSet<AnnotationMirror> extractAnnotationsToApply(
236+
AnnotationMirror annotationsToApply) {
237+
return elements.getElementValuesWithDefaults(annotationsToApply).values().stream()
238+
.map(AnnotationValue::getValue)
239+
.filter(AnnotationMirror.class::isInstance)
240+
// Any non-annotation element should already have been flagged when processing
241+
// @AnnotationsToApply
242+
.map(AnnotationMirror.class::cast)
243+
.collect(toImmutableSet());
244+
}
209245
}
210246
}

factory/src/main/java/com/google/auto/factory/processor/AutoFactoryProcessor.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@
1515
*/
1616
package com.google.auto.factory.processor;
1717

18+
import static com.google.auto.common.MoreTypes.asElement;
19+
import static com.google.auto.common.MoreTypes.asTypeElement;
20+
import static javax.lang.model.element.ElementKind.ANNOTATION_TYPE;
21+
import static javax.lang.model.type.TypeKind.DECLARED;
22+
import static javax.lang.model.type.TypeKind.ERROR;
23+
import static javax.lang.model.util.ElementFilter.methodsIn;
24+
1825
import com.google.auto.common.MoreTypes;
1926
import com.google.auto.factory.AutoFactory;
27+
import com.google.auto.factory.AutoFactory.AnnotationsToApply;
2028
import com.google.auto.factory.Provided;
2129
import com.google.auto.service.AutoService;
2230
import com.google.common.base.Throwables;
@@ -39,6 +47,7 @@
3947
import javax.annotation.processing.Processor;
4048
import javax.annotation.processing.RoundEnvironment;
4149
import javax.lang.model.SourceVersion;
50+
import javax.lang.model.element.AnnotationMirror;
4251
import javax.lang.model.element.Element;
4352
import javax.lang.model.element.ExecutableElement;
4453
import javax.lang.model.element.Modifier;
@@ -96,6 +105,11 @@ private void doProcess(RoundEnvironment roundEnv) {
96105
providedChecker.checkProvidedParameter(element);
97106
}
98107

108+
for (Element element :
109+
roundEnv.getElementsAnnotatedWith(AnnotationsToApply.class)) {
110+
checkAnnotationsToApply(element);
111+
}
112+
99113
ImmutableListMultimap.Builder<PackageAndClass, FactoryMethodDescriptor> indexedMethodsBuilder =
100114
ImmutableListMultimap.builder();
101115
ImmutableSetMultimap.Builder<PackageAndClass, ImplementationMethodDescriptor>
@@ -138,6 +152,9 @@ private void doProcess(RoundEnvironment roundEnv) {
138152
// methodDescriptors.iterator().next() below.
139153
return;
140154
}
155+
// Sort to ensure output is deterministic.
156+
ImmutableSortedSet.Builder<AnnotationMirror> annotationsBuilder =
157+
ImmutableSortedSet.orderedBy(ANNOTATION_COMPARATOR);
141158
// The sets of classes that are mentioned in the `extending` and `implementing`
142159
// elements, respectively, of the @AutoFactory annotations for this factory.
143160
ImmutableSet.Builder<TypeMirror> extending = newTypeSetBuilder();
@@ -146,6 +163,7 @@ private void doProcess(RoundEnvironment roundEnv) {
146163
Set<Boolean> allowSubclassesSet = new HashSet<>();
147164
boolean skipCreation = false;
148165
for (FactoryMethodDescriptor methodDescriptor : methodDescriptors) {
166+
annotationsBuilder.addAll(methodDescriptor.declaration().annotations());
149167
extending.add(methodDescriptor.declaration().extendingType().asType());
150168
for (TypeElement implementingType :
151169
methodDescriptor.declaration().implementingTypes()) {
@@ -170,6 +188,7 @@ private void doProcess(RoundEnvironment roundEnv) {
170188
factoryWriter.writeFactory(
171189
FactoryDescriptor.create(
172190
factoryName,
191+
annotationsBuilder.build(),
173192
Iterables.getOnlyElement(extending.build()),
174193
implementing.build(),
175194
publicType,
@@ -183,6 +202,9 @@ private void doProcess(RoundEnvironment roundEnv) {
183202
});
184203
}
185204

205+
private static final Comparator<AnnotationMirror> ANNOTATION_COMPARATOR =
206+
Comparator.comparing(mirror -> mirror.getAnnotationType().toString());
207+
186208
private ImmutableSet<ImplementationMethodDescriptor> implementationMethods(
187209
TypeElement supertype, Element autoFactoryElement) {
188210
ImmutableSet.Builder<ImplementationMethodDescriptor> implementationMethodsBuilder =
@@ -234,9 +256,47 @@ private static ImmutableSortedSet.Builder<TypeMirror> newTypeSetBuilder() {
234256
Comparator.comparing(t -> MoreTypes.asTypeElement(t).getQualifiedName().toString()));
235257
}
236258

259+
/** Checks that {@link AnnotationsToApply} is used correctly. */
260+
private void checkAnnotationsToApply(Element annotation) {
261+
if (!annotation.getKind().equals(ANNOTATION_TYPE)) {
262+
// Should not be possible because of @Target.
263+
messager.printMessage(
264+
Kind.ERROR,
265+
"@"
266+
+ AnnotationsToApply.class.getSimpleName()
267+
+ " must be applied to an annotation type declaration.",
268+
annotation);
269+
}
270+
Set<TypeElement> seenAnnotations = new HashSet<>();
271+
for (ExecutableElement annotationMember : methodsIn(annotation.getEnclosedElements())) {
272+
TypeMirror memberType = annotationMember.getReturnType();
273+
boolean isAnnotation = memberType.getKind().equals(DECLARED) && asElement(memberType).getKind().equals(ANNOTATION_TYPE);
274+
if (!isAnnotation && !memberType.getKind().equals(ERROR)) {
275+
messager.printMessage(
276+
Kind.ERROR,
277+
"Members of an @"
278+
+ AnnotationsToApply.class.getSimpleName()
279+
+ " annotation must themselves be annotations; "
280+
+ annotationMember.getSimpleName()
281+
+ " has type "
282+
+ memberType,
283+
annotationMember);
284+
} else {
285+
TypeElement annotationElement = asTypeElement(memberType);
286+
if (!seenAnnotations.add(annotationElement)) {
287+
messager.printMessage(
288+
Kind.ERROR, "More than one @" + annotationElement + " in " + annotation, annotation);
289+
}
290+
}
291+
}
292+
}
293+
237294
@Override
238295
public ImmutableSet<String> getSupportedAnnotationTypes() {
239-
return ImmutableSet.of(AutoFactory.class.getName(), Provided.class.getName());
296+
return ImmutableSet.of(
297+
AutoFactory.class.getCanonicalName(),
298+
Provided.class.getCanonicalName(),
299+
AnnotationsToApply.class.getCanonicalName());
240300
}
241301

242302
@Override

factory/src/main/java/com/google/auto/factory/processor/FactoryDescriptor.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public boolean matches(char c) {
4747

4848
abstract PackageAndClass name();
4949

50+
abstract ImmutableSet<AnnotationMirror> annotations();
5051

5152
abstract TypeMirror extendingType();
5253

@@ -85,6 +86,7 @@ String getUniqueName(CharSequence base) {
8586

8687
static FactoryDescriptor create(
8788
PackageAndClass name,
89+
ImmutableSet<AnnotationMirror> annotations,
8890
TypeMirror extendingType,
8991
ImmutableSet<TypeMirror> implementingTypes,
9092
boolean publicType,
@@ -146,6 +148,7 @@ static FactoryDescriptor create(
146148

147149
return new AutoValue_FactoryDescriptor(
148150
name,
151+
annotations,
149152
extendingType,
150153
implementingTypes,
151154
publicType,

factory/src/main/java/com/google/auto/factory/processor/FactoryWriter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ void writeFactory(FactoryDescriptor descriptor) throws IOException {
8484
AutoFactoryProcessor.class,
8585
"https://github.com/google/auto/tree/main/factory")
8686
.ifPresent(factory::addAnnotation);
87+
descriptor.annotations().forEach(a -> factory.addAnnotation(AnnotationSpec.get(a)));
8788
if (!descriptor.allowSubclasses()) {
8889
factory.addModifiers(FINAL);
8990
}

factory/src/main/java/com/google/auto/factory/processor/Mirrors.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static boolean isProvider(TypeMirror type) {
5959
}
6060

6161
/**
62-
* Returns an annotation value map with {@link String} keys instead of {@link ExecutableElement}
62+
* Returns an annotation value map with {@link String} keys instead of {@link ExecutableElement}
6363
* instances.
6464
*/
6565
static ImmutableMap<String, AnnotationValue> simplifyAnnotationValueMap(

factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorNegativeTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,48 @@ public void factoryExtendingFinalClass() {
134134
.inFile(file)
135135
.onLineContaining("@AutoFactory");
136136
}
137+
138+
/**
139+
* We don't currently allow you to have more than one {@code @AnnotationsToApply} annotation for
140+
* any given AutoFactory class.
141+
*/
142+
@Test
143+
public void annotationsToApplyMultiple() {
144+
JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyMultiple.java");
145+
Compilation compilation = javac.compile(file);
146+
assertThat(compilation).failed();
147+
assertThat(compilation)
148+
.hadErrorContaining("Multiple @AnnotationsToApply annotations are not supported");
149+
}
150+
151+
/**
152+
* We also don't allow you to have the same annotation appear more than once inside a given
153+
* {@code @AnnotationsToApply}, even with the same values.
154+
*/
155+
@Test
156+
public void annotationsToApplyRepeated() {
157+
JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyRepeated.java");
158+
Compilation compilation = javac.compile(file);
159+
assertThat(compilation).failed();
160+
assertThat(compilation).hadErrorContaining("More than one @java.lang.SuppressWarnings");
161+
}
162+
163+
@Test
164+
public void annotationsToApplyNotAnnotations() {
165+
JavaFileObject file = JavaFileObjects.forResource("bad/AnnotationsToApplyNotAnnotations.java");
166+
Compilation compilation = javac.compile(file);
167+
assertThat(compilation).failed();
168+
assertThat(compilation)
169+
.hadErrorContaining(
170+
"Members of an @AnnotationsToApply annotation must themselves be annotations;"
171+
+ " whatIsThis has type int")
172+
.inFile(file)
173+
.onLineContaining("whatIsThis");
174+
assertThat(compilation)
175+
.hadErrorContaining(
176+
"Members of an @AnnotationsToApply annotation must themselves be annotations;"
177+
+ " andWhatIsThis has type com.google.errorprone.annotations.Immutable[]")
178+
.inFile(file)
179+
.onLineContaining("andWhatIsThis");
180+
}
137181
}

factory/src/test/java/com/google/auto/factory/processor/AutoFactoryProcessorTest.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ private enum Config {
9191
static final ImmutableList<File> COMMON_CLASSPATH =
9292
ImmutableList.of(
9393
fileForClass("com.google.auto.factory.AutoFactory"),
94+
fileForClass("com.google.errorprone.annotations.Immutable"),
9495
fileForClass("javax.annotation.Nullable"),
9596
fileForClass("org.checkerframework.checker.nullness.compatqual.NullableType"));
9697
static final File JAVAX_CLASSPATH = fileForClass("javax.inject.Provider");
@@ -640,6 +641,14 @@ public void parameterAnnotations() {
640641
"tests.ParameterAnnotationsFactory", "expected/ParameterAnnotationsFactory.java"));
641642
}
642643

644+
@Test
645+
public void customAnnotations() {
646+
goldenTest(
647+
ImmutableList.of("good/CustomAnnotations.java"),
648+
ImmutableMap.of(
649+
"tests.CustomAnnotationsFactory", "expected/CustomAnnotationsFactory.java"));
650+
}
651+
643652
private JavaFileObject loadExpectedFile(String resourceName) {
644653
try {
645654
List<String> sourceLines = Resources.readLines(Resources.getResource(resourceName), UTF_8);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2023 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package tests;
17+
18+
import com.google.auto.factory.AutoFactory;
19+
20+
@AutoFactory.AnnotationsToApply
21+
@interface This {
22+
SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
23+
}
24+
25+
@AutoFactory.AnnotationsToApply
26+
@interface That {
27+
SuppressWarnings suppressWarnings() default @SuppressWarnings("Immutable");
28+
}
29+
30+
@This
31+
@That
32+
@AutoFactory
33+
final class AnnotationsToApplyMultiple {}

0 commit comments

Comments
 (0)