Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

Commit a21ffe8

Browse files
authored
fix: [gapic-generator-java] align writer behavior for nested types (#1709)
* Removes duplicate handling of enclosing classes in JavaWriterVisitor and ImportWriterVisitor - change to only import outermost class, e.g. com.example.Outer), while type references will be written as Outer.Middle.Inner * Fixes issue in ConcreteReference where multiple levels of enclosed classes are written in reversed order
1 parent dea8426 commit a21ffe8

7 files changed

Lines changed: 340 additions & 3 deletions

File tree

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,14 +103,15 @@ public ImmutableList<String> enclosingClassNames() {
103103
if (!hasEnclosingClass()) {
104104
return ImmutableList.of();
105105
}
106-
// The innermost type will be the last element in the list.
106+
// Builds list in order of inner to outer.
107+
// Return the reversed list, since the outermost type is expected to lie at index 0.
107108
ImmutableList.Builder<String> listBuilder = new ImmutableList.Builder<>();
108109
Class<?> currentClz = clazz();
109110
while (currentClz.getEnclosingClass() != null) {
110111
listBuilder.add(currentClz.getEnclosingClass().getSimpleName());
111112
currentClz = currentClz.getEnclosingClass();
112113
}
113-
return listBuilder.build();
114+
return listBuilder.build().reverse();
114115
}
115116

116117
@Override

gapic-generator-java/src/main/java/com/google/api/generator/engine/writer/ImportWriterVisitor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,9 +508,11 @@ private void handleReference(Reference reference) {
508508
staticImports.add(reference.fullName());
509509
} else {
510510
if (reference.hasEnclosingClass()) {
511+
// Only import outermost enclosing class, e.g. import com.foo.bar.Outer
511512
addImport(
512513
String.format(
513-
"%s.%s", reference.pakkage(), String.join(DOT, reference.enclosingClassNames())));
514+
"%s.%s",
515+
reference.pakkage(), String.join(DOT, reference.enclosingClassNames().get(0))));
514516
} else {
515517
addImport(reference.fullName());
516518
}

gapic-generator-java/src/test/java/com/google/api/generator/engine/writer/ImportWriterVisitorTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import com.google.api.generator.test.utils.LineFormatter;
5959
import com.google.common.base.Function;
6060
import com.google.common.base.Strings;
61+
import com.google.testgapic.v1beta1.Outer;
6162
import java.io.File;
6263
import java.io.FileNotFoundException;
6364
import java.io.FileOutputStream;
@@ -428,6 +429,27 @@ public void writeVariableExprImports_annotationsWithDescription() {
428429
writerVisitor.write());
429430
}
430431

432+
@Test
433+
public void writeVaporReferenceImport_outermostForNestedClass() {
434+
VaporReference nestedVaporReference =
435+
VaporReference.builder()
436+
.setName("Inner")
437+
.setEnclosingClassNames(Arrays.asList("Outer", "Middle"))
438+
.setPakkage("com.google.testgapic.v1beta1")
439+
.build();
440+
441+
TypeNode.withReference(nestedVaporReference).accept(writerVisitor);
442+
assertEquals("import com.google.testgapic.v1beta1.Outer;\n\n", writerVisitor.write());
443+
}
444+
445+
@Test
446+
public void writeConcreteReferenceImport_outermostForNestedClass() {
447+
ConcreteReference nestedConcreteReference =
448+
ConcreteReference.withClazz(Outer.Middle.Inner.class);
449+
TypeNode.withReference(nestedConcreteReference).accept(writerVisitor);
450+
assertEquals("import com.google.testgapic.v1beta1.Outer;\n\n", writerVisitor.write());
451+
}
452+
431453
@Test
432454
public void writeAnonymousClassExprImports() {
433455
// [Constructing] Function<List<IOException>, MethodDefinition>

gapic-generator-java/src/test/java/com/google/api/generator/engine/writer/JavaWriterVisitorTest.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,16 @@
7171
import com.google.api.generator.engine.ast.Variable;
7272
import com.google.api.generator.engine.ast.VariableExpr;
7373
import com.google.api.generator.engine.ast.WhileStatement;
74+
import com.google.api.generator.gapic.composer.grpc.ServiceClientClassComposer;
75+
import com.google.api.generator.gapic.model.GapicClass;
76+
import com.google.api.generator.gapic.model.GapicContext;
77+
import com.google.api.generator.gapic.model.Service;
78+
import com.google.api.generator.test.framework.Assert;
79+
import com.google.api.generator.test.protoloader.TestProtoLoader;
7480
import com.google.api.generator.test.utils.LineFormatter;
7581
import com.google.api.generator.test.utils.TestExprBuilder;
7682
import com.google.common.base.Function;
83+
import com.google.testgapic.v1beta1.Outer;
7784
import java.io.IOException;
7885
import java.util.Arrays;
7986
import java.util.Collections;
@@ -129,6 +136,26 @@ public void writeReferenceType_basic() {
129136
assertEquals("FooBar", writerVisitor.write());
130137
}
131138

139+
@Test
140+
public void writeVaporReferenceType_nestedClasses() {
141+
VaporReference nestedVaporReference =
142+
VaporReference.builder()
143+
.setName("Inner")
144+
.setEnclosingClassNames(Arrays.asList("Outer", "Middle"))
145+
.setPakkage("com.google.testgapic.v1beta1")
146+
.build();
147+
TypeNode.withReference(nestedVaporReference).accept(writerVisitor);
148+
assertEquals("Outer.Middle.Inner", writerVisitor.write());
149+
}
150+
151+
@Test
152+
public void writeConcreteReferenceType_nestedClasses() {
153+
ConcreteReference nestedConcreteReference =
154+
ConcreteReference.withClazz(Outer.Middle.Inner.class);
155+
TypeNode.withReference(nestedConcreteReference).accept(writerVisitor);
156+
assertEquals("Outer.Middle.Inner", writerVisitor.write());
157+
}
158+
132159
@Test
133160
public void writeReferenceType_useFullName() {
134161
TypeNode.withReference(
@@ -2796,6 +2823,17 @@ public void writePackageInfoDefinition() {
27962823
writerVisitor.write());
27972824
}
27982825

2826+
/** =============================== GOLDEN TESTS =============================== */
2827+
@Test
2828+
public void writeSGrpcServiceClientWithNestedClassImport() {
2829+
GapicContext context = TestProtoLoader.instance().parseNestedMessage();
2830+
Service nestedService = context.services().get(0);
2831+
GapicClass clazz = ServiceClientClassComposer.instance().generate(context, nestedService);
2832+
2833+
Assert.assertGoldenClass(
2834+
this.getClass(), clazz, "GrpcServiceClientWithNestedClassImport.golden");
2835+
}
2836+
27992837
/** =============================== HELPERS =============================== */
28002838
private static AssignmentExpr createAssignmentExpr(
28012839
String variableName, String value, TypeNode type) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
package com.google.types.testing;
2+
3+
import com.google.api.gax.core.BackgroundResource;
4+
import com.google.api.gax.rpc.UnaryCallable;
5+
import com.google.testgapic.v1beta1.Outer;
6+
import com.google.types.testing.stub.NestedMessageServiceStub;
7+
import com.google.types.testing.stub.NestedMessageServiceStubSettings;
8+
import java.io.IOException;
9+
import java.util.concurrent.TimeUnit;
10+
import javax.annotation.Generated;
11+
12+
// AUTO-GENERATED DOCUMENTATION AND CLASS.
13+
/**
14+
* This class provides the ability to make remote calls to the backing service through method calls
15+
* that map to API methods. Sample code to get started:
16+
*
17+
* <pre>{@code
18+
* // This snippet has been automatically generated and should be regarded as a code template only.
19+
* // It will require modifications to work:
20+
* // - It may require correct/in-range values for request initialization.
21+
* // - It may require specifying regional endpoints when creating the service client as shown in
22+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
23+
* try (NestedMessageServiceClient nestedMessageServiceClient =
24+
* NestedMessageServiceClient.create()) {
25+
* Outer.Middle request = Outer.Middle.newBuilder().setX(120).build();
26+
* Outer.Middle.Inner response = nestedMessageServiceClient.nestedMessageMethod(request);
27+
* }
28+
* }</pre>
29+
*
30+
* <p>Note: close() needs to be called on the NestedMessageServiceClient object to clean up
31+
* resources such as threads. In the example above, try-with-resources is used, which automatically
32+
* calls close().
33+
*
34+
* <p>The surface of this class includes several types of Java methods for each of the API's
35+
* methods:
36+
*
37+
* <ol>
38+
* <li>A "flattened" method. With this type of method, the fields of the request type have been
39+
* converted into function parameters. It may be the case that not all fields are available as
40+
* parameters, and not every API method will have a flattened method entry point.
41+
* <li>A "request object" method. This type of method only takes one parameter, a request object,
42+
* which must be constructed before the call. Not every API method will have a request object
43+
* method.
44+
* <li>A "callable" method. This type of method takes no parameters and returns an immutable API
45+
* callable object, which can be used to initiate calls to the service.
46+
* </ol>
47+
*
48+
* <p>See the individual methods for example code.
49+
*
50+
* <p>Many parameters require resource names to be formatted in a particular way. To assist with
51+
* these names, this class includes a format method for each type of name, and additionally a parse
52+
* method to extract the individual identifiers contained within names that are returned.
53+
*
54+
* <p>This class can be customized by passing in a custom instance of NestedMessageServiceSettings
55+
* to create(). For example:
56+
*
57+
* <p>To customize credentials:
58+
*
59+
* <pre>{@code
60+
* // This snippet has been automatically generated and should be regarded as a code template only.
61+
* // It will require modifications to work:
62+
* // - It may require correct/in-range values for request initialization.
63+
* // - It may require specifying regional endpoints when creating the service client as shown in
64+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
65+
* NestedMessageServiceSettings nestedMessageServiceSettings =
66+
* NestedMessageServiceSettings.newBuilder()
67+
* .setCredentialsProvider(FixedCredentialsProvider.create(myCredentials))
68+
* .build();
69+
* NestedMessageServiceClient nestedMessageServiceClient =
70+
* NestedMessageServiceClient.create(nestedMessageServiceSettings);
71+
* }</pre>
72+
*
73+
* <p>To customize the endpoint:
74+
*
75+
* <pre>{@code
76+
* // This snippet has been automatically generated and should be regarded as a code template only.
77+
* // It will require modifications to work:
78+
* // - It may require correct/in-range values for request initialization.
79+
* // - It may require specifying regional endpoints when creating the service client as shown in
80+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
81+
* NestedMessageServiceSettings nestedMessageServiceSettings =
82+
* NestedMessageServiceSettings.newBuilder().setEndpoint(myEndpoint).build();
83+
* NestedMessageServiceClient nestedMessageServiceClient =
84+
* NestedMessageServiceClient.create(nestedMessageServiceSettings);
85+
* }</pre>
86+
*
87+
* <p>Please refer to the GitHub repository's samples for more quickstart code snippets.
88+
*/
89+
@Generated("by gapic-generator-java")
90+
public class NestedMessageServiceClient implements BackgroundResource {
91+
private final NestedMessageServiceSettings settings;
92+
private final NestedMessageServiceStub stub;
93+
94+
/** Constructs an instance of NestedMessageServiceClient with default settings. */
95+
public static final NestedMessageServiceClient create() throws IOException {
96+
return create(NestedMessageServiceSettings.newBuilder().build());
97+
}
98+
99+
/**
100+
* Constructs an instance of NestedMessageServiceClient, using the given settings. The channels
101+
* are created based on the settings passed in, or defaults for any settings that are not set.
102+
*/
103+
public static final NestedMessageServiceClient create(NestedMessageServiceSettings settings)
104+
throws IOException {
105+
return new NestedMessageServiceClient(settings);
106+
}
107+
108+
/**
109+
* Constructs an instance of NestedMessageServiceClient, using the given stub for making calls.
110+
* This is for advanced usage - prefer using create(NestedMessageServiceSettings).
111+
*/
112+
public static final NestedMessageServiceClient create(NestedMessageServiceStub stub) {
113+
return new NestedMessageServiceClient(stub);
114+
}
115+
116+
/**
117+
* Constructs an instance of NestedMessageServiceClient, using the given settings. This is
118+
* protected so that it is easy to make a subclass, but otherwise, the static factory methods
119+
* should be preferred.
120+
*/
121+
protected NestedMessageServiceClient(NestedMessageServiceSettings settings) throws IOException {
122+
this.settings = settings;
123+
this.stub = ((NestedMessageServiceStubSettings) settings.getStubSettings()).createStub();
124+
}
125+
126+
protected NestedMessageServiceClient(NestedMessageServiceStub stub) {
127+
this.settings = null;
128+
this.stub = stub;
129+
}
130+
131+
public final NestedMessageServiceSettings getSettings() {
132+
return settings;
133+
}
134+
135+
public NestedMessageServiceStub getStub() {
136+
return stub;
137+
}
138+
139+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
140+
/**
141+
* Sample code:
142+
*
143+
* <pre>{@code
144+
* // This snippet has been automatically generated and should be regarded as a code template only.
145+
* // It will require modifications to work:
146+
* // - It may require correct/in-range values for request initialization.
147+
* // - It may require specifying regional endpoints when creating the service client as shown in
148+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
149+
* try (NestedMessageServiceClient nestedMessageServiceClient =
150+
* NestedMessageServiceClient.create()) {
151+
* Outer.Middle request = Outer.Middle.newBuilder().setX(120).build();
152+
* Outer.Middle.Inner response = nestedMessageServiceClient.nestedMessageMethod(request);
153+
* }
154+
* }</pre>
155+
*
156+
* @param request The request object containing all of the parameters for the API call.
157+
* @throws com.google.api.gax.rpc.ApiException if the remote call fails
158+
*/
159+
public final Outer.Middle.Inner nestedMessageMethod(Outer.Middle request) {
160+
return nestedMessageMethodCallable().call(request);
161+
}
162+
163+
// AUTO-GENERATED DOCUMENTATION AND METHOD.
164+
/**
165+
* Sample code:
166+
*
167+
* <pre>{@code
168+
* // This snippet has been automatically generated and should be regarded as a code template only.
169+
* // It will require modifications to work:
170+
* // - It may require correct/in-range values for request initialization.
171+
* // - It may require specifying regional endpoints when creating the service client as shown in
172+
* // https://cloud.google.com/java/docs/setup#configure_endpoints_for_the_client_library
173+
* try (NestedMessageServiceClient nestedMessageServiceClient =
174+
* NestedMessageServiceClient.create()) {
175+
* Outer.Middle request = Outer.Middle.newBuilder().setX(120).build();
176+
* ApiFuture<Outer.Middle.Inner> future =
177+
* nestedMessageServiceClient.nestedMessageMethodCallable().futureCall(request);
178+
* // Do something.
179+
* Outer.Middle.Inner response = future.get();
180+
* }
181+
* }</pre>
182+
*/
183+
public final UnaryCallable<Outer.Middle, Outer.Middle.Inner> nestedMessageMethodCallable() {
184+
return stub.nestedMessageMethodCallable();
185+
}
186+
187+
@Override
188+
public final void close() {
189+
stub.close();
190+
}
191+
192+
@Override
193+
public void shutdown() {
194+
stub.shutdown();
195+
}
196+
197+
@Override
198+
public boolean isShutdown() {
199+
return stub.isShutdown();
200+
}
201+
202+
@Override
203+
public boolean isTerminated() {
204+
return stub.isTerminated();
205+
}
206+
207+
@Override
208+
public void shutdownNow() {
209+
stub.shutdownNow();
210+
}
211+
212+
@Override
213+
public boolean awaitTermination(long duration, TimeUnit unit) throws InterruptedException {
214+
return stub.awaitTermination(duration, unit);
215+
}
216+
}

gapic-generator-java/src/test/java/com/google/api/generator/test/protoloader/TestProtoLoader.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import com.google.showcase.v1beta1.MessagingOuterClass;
4242
import com.google.showcase.v1beta1.TestingOuterClass;
4343
import com.google.testdata.v1.DeprecatedServiceOuterClass;
44+
import com.google.testgapic.v1beta1.NestedMessageProto;
45+
import com.google.types.testing.TypesTestingProto;
4446
import google.cloud.CommonResources;
4547
import java.nio.file.Path;
4648
import java.nio.file.Paths;
@@ -131,6 +133,30 @@ public GapicContext parseBookshopService() {
131133
.build();
132134
}
133135

136+
public GapicContext parseNestedMessage() {
137+
FileDescriptor fileDescriptor = TypesTestingProto.getDescriptor();
138+
ServiceDescriptor serviceDescriptor = fileDescriptor.getServices().get(0);
139+
assertEquals(serviceDescriptor.getName(), "NestedMessageService");
140+
Map<String, Message> messageTypes = Parser.parseMessages(fileDescriptor);
141+
142+
FileDescriptor messageFileDescriptor = NestedMessageProto.getDescriptor();
143+
messageTypes.putAll(Parser.parseMessages(messageFileDescriptor));
144+
145+
Map<String, ResourceName> resourceNames = new HashMap<>();
146+
Set<ResourceName> outputResourceNames = new HashSet<>();
147+
List<Service> services =
148+
Parser.parseService(
149+
fileDescriptor, messageTypes, resourceNames, Optional.empty(), outputResourceNames);
150+
151+
return GapicContext.builder()
152+
.setMessages(messageTypes)
153+
.setResourceNames(resourceNames)
154+
.setServices(services)
155+
.setHelperResourceNames(outputResourceNames)
156+
.setTransport(transport)
157+
.build();
158+
}
159+
134160
public GapicContext parseShowcaseEcho() {
135161
FileDescriptor echoFileDescriptor = EchoOuterClass.getDescriptor();
136162
ServiceDescriptor echoServiceDescriptor = echoFileDescriptor.getServices().get(0);

0 commit comments

Comments
 (0)