Skip to content

Commit e5eee2d

Browse files
cindy-penglqiu96
authored andcommitted
feat: Selective gapic generation phase II (#3730)
## Implement Selective GAPIC Generation (Phase II) This PR implements Phase II of selective GAPIC generation within the `gapic-generator-java` project. This allows for finer control over the intended usage of generated client methods (public, internal, or hidden) by providing selective gapic generation configuration in service yaml. ### Key Changes: #### 1. Model Updates * Added a `isInternalApi` attribute to the internal representation of methods to track their intended visibility (e.g., public, internal). #### 2. Parser Logic * Introduced the `getMethodSelectiveGapicType()` method responsible for parsing the selective generation configuration for each method. * Modified service filtering logic: Service classes will not be generated if the service definition contains no methods or includes only methods marked as **`HIDDEN`**. * Enhanced `parseService()` to determine and assign the appropriate `SelectiveGapicType` to each service method and its corresponding generated variants (e.g., overloaded methods). #### 3. Composer (Code Generation) Updates * **Method Annotations:** For all method variants designated as `INTERNAL`, generate an `@InternalApi` annotation accompanied by a warning message discouraging external use. * **Method Header Comments:** For methods marked as `INTERNAL`, generate a specific comment in the method's header indicating its intended internal-only usage. * **Sample Generation:** Adjusted the logic for generating `package-info.java` samples to prevent the usage of any methods marked as `INTERNAL`. #### 4. Tests * Added **unit tests** covering the new parser logic and comment generation changes related to selective generation types. * Added/updated **golden unit/integration tests** to verify the correct code output for various selective generation scenarios, including services with: * All public methods. * A mix of public, `INTERNAL`, and/or `HIDDEN` methods. * No public methods (verifying that the service class is not generated).
1 parent d93ea37 commit e5eee2d

File tree

53 files changed

+3340
-234
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+3340
-234
lines changed

gapic-generator-java/src/main/java/com/google/api/generator/engine/ast/JavaDocComment.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ public abstract static class Builder {
5151
String throwsType = null;
5252
String throwsDescription = null;
5353
String deprecated = null;
54+
String internalOnly = null;
5455
String returnDescription = null;
5556
List<String> paramsList = new ArrayList<>();
5657
List<String> componentsList = new ArrayList<>();
@@ -71,6 +72,11 @@ public Builder setDeprecated(String deprecatedText) {
7172
return this;
7273
}
7374

75+
public Builder setInternalOnly(String internalOnlyText) {
76+
internalOnly = internalOnlyText;
77+
return this;
78+
}
79+
7480
public Builder setReturn(String returnText) {
7581
returnDescription = returnText;
7682
return this;
@@ -136,13 +142,20 @@ public boolean emptyComments() {
136142
return Strings.isNullOrEmpty(throwsType)
137143
&& Strings.isNullOrEmpty(throwsDescription)
138144
&& Strings.isNullOrEmpty(deprecated)
145+
&& Strings.isNullOrEmpty(internalOnly)
139146
&& Strings.isNullOrEmpty(returnDescription)
140147
&& paramsList.isEmpty()
141148
&& componentsList.isEmpty();
142149
}
143150

144151
public JavaDocComment build() {
145-
// @param, @throws, @return, and @deprecated should always get printed at the end.
152+
// Add additional descriptive text before block tags.
153+
if (!Strings.isNullOrEmpty(internalOnly)) {
154+
componentsList.add(
155+
String.format("<p> <b>Warning: </b>%s", HtmlEscaper.process(internalOnly)));
156+
}
157+
// @param, @throws, @return and @deprecated should always get printed at the
158+
// end.
146159
componentsList.addAll(paramsList);
147160
if (!Strings.isNullOrEmpty(throwsType)) {
148161
componentsList.add(

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/CommentComposer.java

+3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ public class CommentComposer {
4949
static final String DEPRECATED_METHOD_STRING =
5050
"This method is deprecated and will be removed in the next major version update.";
5151

52+
static final String INTERNAL_ONLY_METHOD_STRING =
53+
"This method is for internal use only. Please do not use it directly.";
54+
5255
public static final CommentStatement APACHE_LICENSE_COMMENT =
5356
CommentStatement.withComment(BlockComment.withComment(APACHE_LICENSE_STRING));
5457

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/ServiceClientCommentComposer.java

+8
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ public static List<CommentStatement> createRpcMethodHeaderComment(
202202
methodJavadocBuilder.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING);
203203
}
204204

205+
if (method.isInternalApi()) {
206+
methodJavadocBuilder.setInternalOnly(CommentComposer.INTERNAL_ONLY_METHOD_STRING);
207+
}
208+
205209
List<CommentStatement> comments = new ArrayList<>();
206210
comments.add(CommentComposer.AUTO_GENERATED_METHOD_COMMENT);
207211
if (!methodJavadocBuilder.emptyComments()) {
@@ -345,6 +349,10 @@ public static List<CommentStatement> createRpcCallableMethodHeaderComment(
345349
methodJavadocBuilder.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING);
346350
}
347351

352+
if (method.isInternalApi()) {
353+
methodJavadocBuilder.setInternalOnly(CommentComposer.INTERNAL_ONLY_METHOD_STRING);
354+
}
355+
348356
return Arrays.asList(
349357
CommentComposer.AUTO_GENERATED_METHOD_COMMENT,
350358
CommentStatement.withComment(methodJavadocBuilder.build()));

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/comment/SettingsCommentComposer.java

+35-30
Original file line numberDiff line numberDiff line change
@@ -59,31 +59,31 @@ public class SettingsCommentComposer {
5959
"Retries are configured for idempotent methods but not for non-idempotent methods.";
6060

6161
public static final CommentStatement DEFAULT_SCOPES_COMMENT =
62-
toSimpleComment("The default scopes of the service.");
62+
toCommentStatement("The default scopes of the service.");
6363

6464
public static final CommentStatement DEFAULT_EXECUTOR_PROVIDER_BUILDER_METHOD_COMMENT =
65-
toSimpleComment("Returns a builder for the default ExecutorProvider for this service.");
65+
toCommentStatement("Returns a builder for the default ExecutorProvider for this service.");
6666

6767
public static final CommentStatement DEFAULT_SERVICE_NAME_METHOD_COMMENT =
68-
toSimpleComment("Returns the default service name.");
68+
toCommentStatement("Returns the default service name.");
6969
public static final CommentStatement DEFAULT_SERVICE_ENDPOINT_METHOD_COMMENT =
70-
toSimpleComment("Returns the default service endpoint.");
70+
toCommentStatement("Returns the default service endpoint.");
7171
public static final CommentStatement DEFAULT_SERVICE_MTLS_ENDPOINT_METHOD_COMMENT =
72-
toSimpleComment("Returns the default mTLS service endpoint.");
72+
toCommentStatement("Returns the default mTLS service endpoint.");
7373
public static final CommentStatement DEFAULT_SERVICE_SCOPES_METHOD_COMMENT =
74-
toSimpleComment("Returns the default service scopes.");
74+
toCommentStatement("Returns the default service scopes.");
7575

7676
public static final CommentStatement DEFAULT_CREDENTIALS_PROVIDER_BUILDER_METHOD_COMMENT =
77-
toSimpleComment("Returns a builder for the default credentials for this service.");
77+
toCommentStatement("Returns a builder for the default credentials for this service.");
7878

7979
public static final CommentStatement DEFAULT_TRANSPORT_PROVIDER_BUILDER_METHOD_COMMENT =
80-
toSimpleComment("Returns a builder for the default ChannelProvider for this service.");
80+
toCommentStatement("Returns a builder for the default ChannelProvider for this service.");
8181

8282
public static final CommentStatement NEW_BUILDER_METHOD_COMMENT =
83-
toSimpleComment("Returns a new builder for this class.");
83+
toCommentStatement("Returns a new builder for this class.");
8484

8585
public static final CommentStatement TO_BUILDER_METHOD_COMMENT =
86-
toSimpleComment("Returns a builder containing all the values of this settings class.");
86+
toCommentStatement("Returns a builder containing all the values of this settings class.");
8787

8888
public static final List<CommentStatement> APPLY_TO_ALL_UNARY_METHODS_METHOD_COMMENTS =
8989
Arrays.asList(
@@ -103,9 +103,10 @@ public class SettingsCommentComposer {
103103

104104
public SettingsCommentComposer(String transportPrefix) {
105105
this.newTransportBuilderMethodComment =
106-
toSimpleComment(String.format("Returns a new %s builder for this class.", transportPrefix));
106+
toCommentStatement(
107+
String.format("Returns a new %s builder for this class.", transportPrefix));
107108
this.transportProviderBuilderMethodComment =
108-
toSimpleComment(
109+
toCommentStatement(
109110
String.format(
110111
"Returns a builder for the default %s ChannelProvider for this service.",
111112
transportPrefix));
@@ -120,23 +121,21 @@ public CommentStatement getTransportProviderBuilderMethodComment() {
120121
}
121122

122123
public static CommentStatement createCallSettingsGetterComment(
123-
String javaMethodName, boolean isMethodDeprecated) {
124-
String methodComment = String.format(CALL_SETTINGS_METHOD_DOC_PATTERN, javaMethodName);
125-
return isMethodDeprecated
126-
? toDeprecatedSimpleComment(methodComment)
127-
: toSimpleComment(methodComment);
124+
String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) {
125+
return toCommentStatement(
126+
String.format(CALL_SETTINGS_METHOD_DOC_PATTERN, javaMethodName),
127+
isMethodDeprecated,
128+
isMethodInternal);
128129
}
129130

130131
public static CommentStatement createBuilderClassComment(String outerClassName) {
131-
return toSimpleComment(String.format(BUILDER_CLASS_DOC_PATTERN, outerClassName));
132+
return toCommentStatement(String.format(BUILDER_CLASS_DOC_PATTERN, outerClassName));
132133
}
133134

134135
public static CommentStatement createCallSettingsBuilderGetterComment(
135-
String javaMethodName, boolean isMethodDeprecated) {
136+
String javaMethodName, boolean isMethodDeprecated, boolean isMethodInternal) {
136137
String methodComment = String.format(CALL_SETTINGS_BUILDER_METHOD_DOC_PATTERN, javaMethodName);
137-
return isMethodDeprecated
138-
? toDeprecatedSimpleComment(methodComment)
139-
: toSimpleComment(methodComment);
138+
return toCommentStatement(methodComment, isMethodDeprecated, isMethodInternal);
140139
}
141140

142141
public static List<CommentStatement> createClassHeaderComments(
@@ -201,15 +200,21 @@ public static List<CommentStatement> createClassHeaderComments(
201200
CommentStatement.withComment(javaDocCommentBuilder.build()));
202201
}
203202

204-
private static CommentStatement toSimpleComment(String comment) {
205-
return CommentStatement.withComment(JavaDocComment.withComment(comment));
203+
private static CommentStatement toCommentStatement(String comment) {
204+
return toCommentStatement(comment, false, false);
206205
}
207206

208-
private static CommentStatement toDeprecatedSimpleComment(String comment) {
209-
return CommentStatement.withComment(
210-
JavaDocComment.builder()
211-
.addComment(comment)
212-
.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING)
213-
.build());
207+
private static CommentStatement toCommentStatement(
208+
String comment, boolean isDeprecated, boolean isInternal) {
209+
JavaDocComment.Builder docBuilder = JavaDocComment.builder().addComment(comment);
210+
docBuilder =
211+
isDeprecated
212+
? docBuilder.setDeprecated(CommentComposer.DEPRECATED_METHOD_STRING)
213+
: docBuilder;
214+
docBuilder =
215+
isInternal
216+
? docBuilder.setInternalOnly(CommentComposer.INTERNAL_ONLY_METHOD_STRING)
217+
: docBuilder;
218+
return CommentStatement.withComment(docBuilder.build());
214219
}
215220
}

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientClassComposer.java

+29-23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import com.google.api.core.ApiFuture;
1919
import com.google.api.core.ApiFutures;
2020
import com.google.api.core.BetaApi;
21+
import com.google.api.core.InternalApi;
2122
import com.google.api.gax.core.BackgroundResource;
2223
import com.google.api.gax.longrunning.OperationFuture;
2324
import com.google.api.gax.paging.AbstractFixedSizeCollection;
@@ -62,6 +63,7 @@
6263
import com.google.api.generator.gapic.composer.samplecode.ServiceClientMethodSampleComposer;
6364
import com.google.api.generator.gapic.composer.store.TypeStore;
6465
import com.google.api.generator.gapic.composer.utils.ClassNames;
66+
import com.google.api.generator.gapic.composer.utils.CommonStrings;
6567
import com.google.api.generator.gapic.composer.utils.PackageChecker;
6668
import com.google.api.generator.gapic.model.Field;
6769
import com.google.api.generator.gapic.model.GapicClass;
@@ -103,7 +105,6 @@
103105
import javax.annotation.Generated;
104106

105107
public abstract class AbstractServiceClientClassComposer implements ClassComposer {
106-
private static final String PAGED_RESPONSE_TYPE_NAME_PATTERN = "%sPagedResponse";
107108
private static final String CALLABLE_NAME_PATTERN = "%sCallable";
108109
private static final String PAGED_CALLABLE_NAME_PATTERN = "%sPagedCallable";
109110
private static final String OPERATION_CALLABLE_NAME_PATTERN = "%sOperationCallable";
@@ -127,6 +128,21 @@ protected TransportContext getTransportContext() {
127128
return transportContext;
128129
}
129130

131+
private static List<AnnotationNode> createMethodAnnotations(Method method, TypeStore typeStore) {
132+
List<AnnotationNode> annotations = new ArrayList<>();
133+
if (method.isDeprecated()) {
134+
annotations.add(AnnotationNode.withType(TypeNode.DEPRECATED));
135+
}
136+
137+
if (method.isInternalApi()) {
138+
annotations.add(
139+
AnnotationNode.withTypeAndDescription(
140+
typeStore.get("InternalApi"), CommonStrings.INTERNAL_API_WARNING));
141+
}
142+
143+
return annotations;
144+
}
145+
130146
@Override
131147
public GapicClass generate(GapicContext context, Service service) {
132148
Map<String, ResourceName> resourceNames = context.helperResourceNames();
@@ -136,7 +152,6 @@ public GapicClass generate(GapicContext context, Service service) {
136152
GapicClass.Kind kind = Kind.MAIN;
137153
String pakkage = service.pakkage();
138154
boolean hasLroClient = service.hasStandardLroMethods();
139-
140155
List<Sample> samples = new ArrayList<>();
141156
Map<String, List<String>> grpcRpcsToJavaMethodNames = new HashMap<>();
142157
Map<String, List<String>> methodVariantsForClientHeader = new HashMap<>();
@@ -713,7 +728,8 @@ private static List<MethodDefinition> createMethodVariants(
713728
TypeNode methodInputType = method.inputType();
714729
TypeNode methodOutputType =
715730
method.isPaged()
716-
? typeStore.get(String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
731+
? typeStore.get(
732+
String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
717733
: method.outputType();
718734
if (method.hasLro()) {
719735
LongrunningOperation lro = method.lro();
@@ -802,11 +818,8 @@ private static List<MethodDefinition> createMethodVariants(
802818
methodVariantBuilder.setReturnType(methodOutputType).setReturnExpr(rpcInvocationExpr);
803819
}
804820

805-
if (method.isDeprecated()) {
806-
methodVariantBuilder =
807-
methodVariantBuilder.setAnnotations(
808-
Arrays.asList(AnnotationNode.withType(TypeNode.DEPRECATED)));
809-
}
821+
methodVariantBuilder =
822+
methodVariantBuilder.setAnnotations(createMethodAnnotations(method, typeStore));
810823
methodVariantBuilder = methodVariantBuilder.setBody(statements);
811824
javaMethods.add(methodVariantBuilder.build());
812825
}
@@ -826,9 +839,9 @@ private static MethodDefinition createMethodDefaultMethod(
826839
TypeNode methodInputType = method.inputType();
827840
TypeNode methodOutputType =
828841
method.isPaged()
829-
? typeStore.get(String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
842+
? typeStore.get(
843+
String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
830844
: method.outputType();
831-
List<AnnotationNode> annotations = new ArrayList<>();
832845
if (method.hasLro()) {
833846
LongrunningOperation lro = method.lro();
834847
methodOutputType =
@@ -885,10 +898,6 @@ private static MethodDefinition createMethodDefaultMethod(
885898
.setName(String.format(method.hasLro() ? "%sAsync" : "%s", methodName))
886899
.setArguments(Arrays.asList(requestArgVarExpr));
887900

888-
if (method.isDeprecated()) {
889-
annotations.add(AnnotationNode.withType(TypeNode.DEPRECATED));
890-
}
891-
892901
if (isProtoEmptyType(methodOutputType)) {
893902
methodBuilder =
894903
methodBuilder
@@ -899,8 +908,7 @@ private static MethodDefinition createMethodDefaultMethod(
899908
methodBuilder.setReturnExpr(callableMethodExpr).setReturnType(methodOutputType);
900909
}
901910

902-
methodBuilder.setAnnotations(annotations);
903-
911+
methodBuilder.setAnnotations(createMethodAnnotations(method, typeStore));
904912
return methodBuilder.build();
905913
}
906914

@@ -1039,11 +1047,8 @@ private static MethodDefinition createCallableMethod(
10391047
}
10401048

10411049
MethodDefinition.Builder methodDefBuilder = MethodDefinition.builder();
1042-
if (method.isDeprecated()) {
1043-
methodDefBuilder =
1044-
methodDefBuilder.setAnnotations(
1045-
Arrays.asList(AnnotationNode.withType(TypeNode.DEPRECATED)));
1046-
}
1050+
1051+
methodDefBuilder = methodDefBuilder.setAnnotations(createMethodAnnotations(method, typeStore));
10471052

10481053
return methodDefBuilder
10491054
.setHeaderCommentStatements(
@@ -1774,6 +1779,7 @@ private static TypeStore createTypes(Service service, Map<String, Message> messa
17741779
ApiFutures.class,
17751780
BackgroundResource.class,
17761781
BetaApi.class,
1782+
InternalApi.class,
17771783
BidiStreamingCallable.class,
17781784
ClientStreamingCallable.class,
17791785
Generated.class,
@@ -1828,7 +1834,7 @@ private static void createVaporTypes(Service service, TypeStore typeStore) {
18281834
service.pakkage(),
18291835
service.methods().stream()
18301836
.filter(m -> m.isPaged())
1831-
.map(m -> String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, m.name()))
1837+
.map(m -> String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, m.name()))
18321838
.collect(Collectors.toList()),
18331839
true,
18341840
ClassNames.getServiceClientClassName(service));
@@ -1846,7 +1852,7 @@ private static List<Reference> getGenericsForCallable(
18461852
return Arrays.asList(
18471853
method.inputType().reference(),
18481854
typeStore
1849-
.get(String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
1855+
.get(String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
18501856
.reference());
18511857
}
18521858
return Arrays.asList(method.inputType().reference(), method.outputType().reference());

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/common/AbstractServiceClientTestClassComposer.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.google.api.generator.gapic.composer.defaultvalue.DefaultValueComposer;
4848
import com.google.api.generator.gapic.composer.store.TypeStore;
4949
import com.google.api.generator.gapic.composer.utils.ClassNames;
50+
import com.google.api.generator.gapic.composer.utils.CommonStrings;
5051
import com.google.api.generator.gapic.model.Field;
5152
import com.google.api.generator.gapic.model.GapicClass;
5253
import com.google.api.generator.gapic.model.GapicClass.Kind;
@@ -87,7 +88,6 @@ public abstract class AbstractServiceClientTestClassComposer implements ClassCom
8788

8889
protected static final String CLIENT_VAR_NAME = "client";
8990
private static final String MOCK_SERVICE_VAR_NAME_PATTERN = "mock%s";
90-
private static final String PAGED_RESPONSE_TYPE_NAME_PATTERN = "%sPagedResponse";
9191

9292
protected static final TypeStore FIXED_TYPESTORE = createStaticTypes();
9393
protected static final AnnotationNode TEST_ANNOTATION =
@@ -944,7 +944,7 @@ private void addDynamicTypes(GapicContext context, Service service, TypeStore ty
944944
service.pakkage(),
945945
service.methods().stream()
946946
.filter(m -> m.isPaged())
947-
.map(m -> String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, m.name()))
947+
.map(m -> String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, m.name()))
948948
.collect(Collectors.toList()),
949949
true,
950950
ClassNames.getServiceClientClassName(service));
@@ -956,7 +956,7 @@ private void addDynamicTypes(GapicContext context, Service service, TypeStore ty
956956
}
957957
typeStore.put(
958958
service.pakkage(),
959-
String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, mixinMethod.name()),
959+
String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, mixinMethod.name()),
960960
true,
961961
ClassNames.getServiceClientClassName(service));
962962
}
@@ -995,7 +995,7 @@ protected static TypeNode getCallableType(Method protoMethod) {
995995
private static TypeNode getPagedResponseType(Method method, Service service) {
996996
return TypeNode.withReference(
997997
VaporReference.builder()
998-
.setName(String.format(PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
998+
.setName(String.format(CommonStrings.PAGED_RESPONSE_TYPE_NAME_PATTERN, method.name()))
999999
.setPakkage(service.pakkage())
10001000
.setEnclosingClassNames(ClassNames.getServiceClientClassName(service))
10011001
.setIsStaticImport(true)

0 commit comments

Comments
 (0)