Skip to content

Commit 5d3ff75

Browse files
authored
feat(ast): extend support for annotation named parameters (#1012)
This change is to extend annotation arguments support. **Before**: Only one argument that consist of a single `String` can be added to annotation description. **After**: For single unnamed parameter/description, add support for `ValueExpr` and `VariableExpr` in addition to `String`. Also allow adding multiple named parameters of type `VariableExpr`. So that annotations like below can be generated: ``` @ConditionalOnClass(VisionServiceClient.class) @ConditionalOnProperty(value = "spring.cloud.gcp.vision.enabled", matchIfMissing = true) ``` this improvement is an addition and does not affect existing usage of `setDescription(String description)`. Added tests in [JavaWriterVisitorTest.java](https://github.com/googleapis/gapic-generator-java/pull/1012/files#diff-60163f71e845b4cc97f9ccd7101646aa243fa5317a77cd326a7f61143618f62f) also shows the new feature usages. Adding a small enhancement to this PR since it's related. In addition to previous changes, I added an optional field to VariableExpr allowing Annotations. Prior to this, only annotations on ClassDefinition and MethodDefinition are supported. With this addition, I will be able to generate code like: ``` @NestedConfigurationProperty private final Credentials credentials = new Credentials(); // or @Autowired private LanguageServiceClient autoClient; ``` Field annotations are pretty common in Spring syntax, this enhancement is needed to generate Spring autoconfig code as I planned. Note: Added a todo note in `VariableExpr` only as a nice to have feature for future, it's not blocking any use cases for now. But it seems reasonable to have target info in `AnnotationNode` and apply checks on it when adding annotation nodes.
1 parent 66782d1 commit 5d3ff75

File tree

5 files changed

+315
-13
lines changed

5 files changed

+315
-13
lines changed

src/main/java/com/google/api/generator/engine/ast/AnnotationNode.java

+78-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 Google LLC
1+
// Copyright 2022 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
1616

1717
import com.google.auto.value.AutoValue;
1818
import com.google.common.base.Preconditions;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
1922
import javax.annotation.Nullable;
2023

2124
@AutoValue
@@ -31,10 +34,8 @@ private static TypeNode annotationType(Class<?> clazz) {
3134

3235
public abstract TypeNode type();
3336

34-
// TODO(unsupported): Any args that do not consist of a single string. However, this can easily be
35-
// extended to enable such support.
3637
@Nullable
37-
public abstract Expr descriptionExpr();
38+
public abstract List<Expr> descriptionExprs();
3839

3940
@Override
4041
public void accept(AstNodeVisitor visitor) {
@@ -45,6 +46,10 @@ public static AnnotationNode withTypeAndDescription(TypeNode type, String descri
4546
return AnnotationNode.builder().setType(type).setDescription(description).build();
4647
}
4748

49+
public static AnnotationNode withTypeAndDescription(TypeNode type, List<Expr> exprList) {
50+
return AnnotationNode.builder().setType(type).setDescriptions(exprList).build();
51+
}
52+
4853
public static AnnotationNode withSuppressWarnings(String description) {
4954
return withTypeAndDescription(annotationType(SuppressWarnings.class), description);
5055
}
@@ -59,15 +64,80 @@ public static Builder builder() {
5964

6065
@AutoValue.Builder
6166
public abstract static class Builder {
67+
private static final String REPEAT_SINGLE_EXCEPTION_MESSAGE =
68+
"Single parameter with no name cannot be set multiple times";
69+
70+
private static final String MULTIPLE_AFTER_SINGLE_EXCEPTION_MESSAGE =
71+
"Multiple parameters must have names";
72+
73+
abstract List<Expr> descriptionExprs();
74+
6275
public abstract Builder setType(TypeNode type);
6376

77+
/**
78+
* To set single String as description.
79+
*
80+
* @param description
81+
* @return Builder
82+
*/
6483
public Builder setDescription(String description) {
65-
return setDescriptionExpr(ValueExpr.withValue(StringObjectValue.withValue(description)));
84+
Preconditions.checkState(descriptionExprs() == null, REPEAT_SINGLE_EXCEPTION_MESSAGE);
85+
return setDescriptionExprs(
86+
Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(description))));
87+
}
88+
89+
/**
90+
* To set single ValueExpr as description.
91+
*
92+
* @param valueExpr
93+
* @return Builder
94+
*/
95+
public Builder setDescription(ValueExpr valueExpr) {
96+
Preconditions.checkState(descriptionExprs() == null, REPEAT_SINGLE_EXCEPTION_MESSAGE);
97+
return setDescriptionExprs(Arrays.asList(valueExpr));
98+
}
99+
100+
/**
101+
* To set single VariableExpr as description.
102+
*
103+
* @param variableExpr
104+
* @return Builder
105+
*/
106+
public Builder setDescription(VariableExpr variableExpr) {
107+
Preconditions.checkState(descriptionExprs() == null, REPEAT_SINGLE_EXCEPTION_MESSAGE);
108+
return setDescriptionExprs(Arrays.asList(variableExpr));
109+
}
110+
111+
/**
112+
* To add an AssignmentExpr as parameter. Can be used repeatedly to add multiple parameters.
113+
*
114+
* @param assignmentExpr
115+
* @return Builder
116+
*/
117+
public Builder addDescription(AssignmentExpr assignmentExpr) {
118+
return addDescriptionToList(assignmentExpr);
119+
}
120+
121+
private Builder setDescriptions(List<Expr> exprList) {
122+
return setDescriptionExprs(exprList);
123+
}
124+
125+
// this method is private, and called only by addDescription(AssignmentExpr expr)
126+
private Builder addDescriptionToList(Expr expr) {
127+
List<Expr> exprList = descriptionExprs();
128+
// avoid when single parameter is already set.
129+
Preconditions.checkState(
130+
exprList == null || exprList instanceof ArrayList,
131+
MULTIPLE_AFTER_SINGLE_EXCEPTION_MESSAGE);
132+
if (exprList == null) {
133+
exprList = new ArrayList<>();
134+
}
135+
exprList.add(expr);
136+
return setDescriptions(exprList);
66137
}
67138

68-
// This will never be anything other than a ValueExpr-wrapped StringObjectValue because
69-
// this setter is private, and called only by setDescription above.
70-
abstract Builder setDescriptionExpr(Expr descriptionExpr);
139+
// this setter is private, and called only by setDescription() and setDescriptions() above.
140+
abstract Builder setDescriptionExprs(List<Expr> descriptionExprs);
71141

72142
abstract AnnotationNode autoBuild();
73143

src/main/java/com/google/api/generator/engine/ast/VariableExpr.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
import com.google.auto.value.AutoValue;
1919
import com.google.common.base.Preconditions;
2020
import com.google.common.collect.ImmutableList;
21+
import java.util.Collections;
22+
import java.util.LinkedHashSet;
2123
import java.util.List;
2224
import java.util.stream.Collectors;
2325
import javax.annotation.Nullable;
@@ -43,6 +45,9 @@ public abstract class VariableExpr implements Expr {
4345

4446
public abstract boolean isVolatile();
4547

48+
// Optional
49+
public abstract ImmutableList<AnnotationNode> annotations();
50+
4651
// Please use this only in conjunction with methods.
4752
// Supports only parameterized types like Map<K, V>.
4853
// TODO(unsupported): Fully generic arguments, e.g. foobar(K key, V value).
@@ -77,7 +82,8 @@ public static Builder builder() {
7782
.setIsStatic(false)
7883
.setIsVolatile(false)
7984
.setScope(ScopeNode.LOCAL)
80-
.setTemplateObjects(ImmutableList.of());
85+
.setTemplateObjects(ImmutableList.of())
86+
.setAnnotations(Collections.emptyList());
8187
}
8288

8389
public abstract Builder toBuilder();
@@ -102,6 +108,10 @@ public abstract static class Builder {
102108

103109
public abstract Builder setIsVolatile(boolean isVolatile);
104110

111+
public abstract Builder setAnnotations(List<AnnotationNode> annotations);
112+
113+
abstract ImmutableList<AnnotationNode> annotations();
114+
105115
// This should be used only for method arguments.
106116
public abstract Builder setTemplateObjects(List<Object> objects);
107117

@@ -133,7 +143,20 @@ public VariableExpr build() {
133143
})
134144
.collect(Collectors.toList()));
135145

146+
// Remove duplicates while maintaining insertion order.
147+
ImmutableList<AnnotationNode> processedAnnotations = annotations();
148+
setAnnotations(
149+
new LinkedHashSet<>(processedAnnotations).stream().collect(Collectors.toList()));
150+
136151
VariableExpr variableExpr = autoBuild();
152+
153+
// TODO: should match on AnnotationNode @Target of ElementType.FIELD
154+
if (!variableExpr.isDecl()) {
155+
Preconditions.checkState(
156+
variableExpr.annotations().isEmpty(),
157+
"Annotation can only be added to variable declaration.");
158+
}
159+
137160
if (variableExpr.isDecl() || variableExpr.exprReferenceExpr() != null) {
138161
Preconditions.checkState(
139162
variableExpr.isDecl() ^ (variableExpr.exprReferenceExpr() != null),

src/main/java/com/google/api/generator/engine/writer/JavaWriterVisitor.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2020 Google LLC
1+
// Copyright 2022 Google LLC
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License");
44
// you may not use this file except in compliance with the License.
@@ -169,9 +169,15 @@ public void visit(ScopeNode scope) {
169169
public void visit(AnnotationNode annotation) {
170170
buffer.append(AT);
171171
annotation.type().accept(this);
172-
if (annotation.descriptionExpr() != null) {
172+
if (annotation.descriptionExprs() != null) {
173173
leftParen();
174-
annotation.descriptionExpr().accept(this);
174+
for (int i = 0; i < annotation.descriptionExprs().size(); i++) {
175+
annotation.descriptionExprs().get(i).accept(this);
176+
if (i < annotation.descriptionExprs().size() - 1) {
177+
buffer.append(COMMA);
178+
buffer.append(SPACE);
179+
}
180+
}
175181
rightParen();
176182
}
177183
newline();
@@ -253,6 +259,9 @@ public void visit(VariableExpr variableExpr) {
253259

254260
// VariableExpr will handle isDecl and exprReferenceExpr edge cases.
255261
if (variableExpr.isDecl()) {
262+
// Annotations, if any.
263+
annotations(variableExpr.annotations());
264+
256265
if (!scope.equals(ScopeNode.LOCAL)) {
257266
scope.accept(this);
258267
space();

src/test/java/com/google/api/generator/engine/ast/VariableExprTest.java

+31
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,26 @@ public void validVariableExpr_templatedArgNameAndTypeInMethod() {
173173
.containsExactly(IdentifierNode.withName("RequestT"), TypeNode.STRING);
174174
}
175175

176+
@Test
177+
public void validVariableExpr_declarationWithAnnotations() {
178+
Variable variable = Variable.builder().setName("x").setType(TypeNode.BOOLEAN).build();
179+
VariableExpr variableExpr =
180+
VariableExpr.builder()
181+
.setVariable(variable)
182+
.setIsDecl(true)
183+
.setAnnotations(
184+
Arrays.asList(
185+
AnnotationNode.withSuppressWarnings("all"),
186+
AnnotationNode.DEPRECATED,
187+
AnnotationNode.DEPRECATED))
188+
.build();
189+
assertThat(variableExpr.variable()).isEqualTo(variable);
190+
assertThat(variableExpr.type()).isEqualTo(TypeNode.VOID);
191+
assertThat(variableExpr.isDecl()).isTrue();
192+
assertThat(variableExpr.annotations())
193+
.containsExactly(AnnotationNode.withSuppressWarnings("all"), AnnotationNode.DEPRECATED);
194+
}
195+
176196
@Test
177197
public void invalidVariableExpr_templatedArgInMethodHasNonStringNonTypeNodeObject() {
178198
Variable variable =
@@ -288,4 +308,15 @@ public void invalidVariableExpr_classFieldOnPrimitiveType() {
288308
.build())
289309
.build());
290310
}
311+
312+
@Test
313+
public void invalidVariableExpr_annotationNoDeclaration() {
314+
Variable variable = Variable.builder().setName("x").setType(TypeNode.BOOLEAN).build();
315+
VariableExpr.Builder variableExprBuilder =
316+
VariableExpr.builder()
317+
.setVariable(variable)
318+
.setIsDecl(false)
319+
.setAnnotations(Arrays.asList(AnnotationNode.DEPRECATED));
320+
assertThrows(IllegalStateException.class, () -> variableExprBuilder.build());
321+
}
291322
}

0 commit comments

Comments
 (0)