Skip to content

Commit 08eca7d

Browse files
fix: resource name class deduplication (#1854)
* chore: add resource name golden from collisions.proto * chore: improve deduplication logic * chore: finish test case * chore: unnecessary proto definitions * chore: format * chore: remove commented line in collisions.proto * chore: simplify deduplication logic * chore: revert changes in TypeStore.java * chore: improve javadoc of `createImplementsTypes`
1 parent c821635 commit 08eca7d

File tree

4 files changed

+351
-4
lines changed

4 files changed

+351
-4
lines changed

gapic-generator-java/src/main/java/com/google/api/generator/gapic/composer/resourcename/ResourceNameHelperClassComposer.java

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ public GapicClass generate(ResourceName resourceName, GapicContext context) {
131131
.setAnnotations(createClassAnnotations())
132132
.setScope(ScopeNode.PUBLIC)
133133
.setName(className)
134-
.setImplementsTypes(createImplementsTypes())
134+
.setImplementsTypes(createImplementsTypes(className))
135135
.setStatements(
136136
createClassStatements(
137137
templateFinalVarExprs,
@@ -160,8 +160,27 @@ private static List<AnnotationNode> createClassAnnotations() {
160160
.build());
161161
}
162162

163-
private static List<TypeNode> createImplementsTypes() {
164-
return Arrays.asList(FIXED_TYPESTORE.get("ResourceName"));
163+
/**
164+
* Returns a singleton list with {@code ResourceName} as its only member. Checks for collisions
165+
*
166+
* @param implementingClassName class that is implementing the resulting list
167+
*/
168+
private static List<TypeNode> createImplementsTypes(String implementingClassName) {
169+
// the original resource name reference has useFullName == false
170+
TypeNode originalResourceName = FIXED_TYPESTORE.get("ResourceName");
171+
if (implementingClassName.equals(originalResourceName.reference().name())) {
172+
// we create a copy with useFullName == true
173+
return Arrays.asList(
174+
TypeNode.withReference(
175+
ConcreteReference.builder()
176+
.setUseFullName(true)
177+
.setClazz(com.google.api.resourcenames.ResourceName.class)
178+
.setGenerics(originalResourceName.reference().generics())
179+
.setIsStaticImport(originalResourceName.reference().isStaticImport())
180+
.setWildcardUpperBound(originalResourceName.reference().wildcardUpperBound())
181+
.build()));
182+
}
183+
return Arrays.asList(originalResourceName);
165184
}
166185

167186
private static List<VariableExpr> createTemplateClassMembers(

gapic-generator-java/src/test/java/com/google/api/generator/gapic/composer/resourcename/ResourceNameHelperClassComposerTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import static com.google.common.truth.Truth.assertThat;
1818
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertTrue;
1920

2021
import com.google.api.generator.engine.writer.JavaWriterVisitor;
2122
import com.google.api.generator.gapic.model.GapicClass;
@@ -34,6 +35,7 @@
3435
import com.google.protobuf.Descriptors.ServiceDescriptor;
3536
import com.google.showcase.v1beta1.EchoOuterClass;
3637
import com.google.showcase.v1beta1.TestingOuterClass;
38+
import com.google.test.collisions.CollisionsOuterClass;
3739
import google.cloud.CommonResources;
3840
import java.nio.file.Path;
3941
import java.nio.file.Paths;
@@ -50,6 +52,9 @@
5052
import org.junit.Test;
5153

5254
public class ResourceNameHelperClassComposerTest {
55+
56+
private final String COLLIDING_RESOURCE_NAME_KEY = "config.googleapis.com/Resource";
57+
5358
private ServiceDescriptor echoService;
5459
private FileDescriptor echoFileDescriptor;
5560

@@ -238,4 +243,29 @@ public void generateResourceNameClass_childSingleton() {
238243
Paths.get(GoldenFileWriter.getGoldenDir(this.getClass()), "AgentName.golden");
239244
Assert.assertCodeEquals(goldenFilePath, visitor.write());
240245
}
246+
247+
@Test
248+
public void generateResourceNameClass_resourceNameCollisionIsAvoided() {
249+
ResourceName collidingResourceName =
250+
Parser.parseResourceNames(CollisionsOuterClass.getDescriptor())
251+
.get(COLLIDING_RESOURCE_NAME_KEY);
252+
253+
GapicContext irrelevantContext = TestProtoLoader.instance().parseShowcaseEcho();
254+
GapicClass clazz =
255+
ResourceNameHelperClassComposer.instance()
256+
.generate(collidingResourceName, irrelevantContext);
257+
JavaWriterVisitor visitor = new JavaWriterVisitor();
258+
clazz.classDefinition().accept(visitor);
259+
GoldenFileWriter.saveCodegenToFile(
260+
this.getClass(), "CollisionResourceName.golden", visitor.write());
261+
Path goldenFilePath =
262+
Paths.get(GoldenFileWriter.getGoldenDir(this.getClass()), "CollisionResourceName.golden");
263+
Assert.assertCodeEquals(goldenFilePath, visitor.write());
264+
265+
assertEquals(1, clazz.classDefinition().implementsTypes().size());
266+
assertTrue(clazz.classDefinition().implementsTypes().get(0).reference().useFullName());
267+
assertEquals(
268+
clazz.classDefinition().classIdentifier().name(),
269+
clazz.classDefinition().implementsTypes().get(0).reference().name());
270+
}
241271
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
package com.google.test.collisions;
2+
3+
import com.google.api.pathtemplate.PathTemplate;
4+
import com.google.common.base.Preconditions;
5+
import com.google.common.collect.ImmutableMap;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
import javax.annotation.Generated;
11+
12+
// AUTO-GENERATED DOCUMENTATION AND CLASS.
13+
@Generated("by gapic-generator-java")
14+
public class ResourceName implements com.google.api.resourcenames.ResourceName {
15+
private static final PathTemplate PROJECT_LOCATION_DEPLOYMENT_REVISION_RESOURCE =
16+
PathTemplate.createWithoutUrlEncoding(
17+
"projects/{project}/locations/{location}/deployments/{deployment}/revisions/{revision}/resources/{resource}");
18+
private volatile Map<String, String> fieldValuesMap;
19+
private final String project;
20+
private final String location;
21+
private final String deployment;
22+
private final String revision;
23+
private final String resource;
24+
25+
@Deprecated
26+
protected ResourceName() {
27+
project = null;
28+
location = null;
29+
deployment = null;
30+
revision = null;
31+
resource = null;
32+
}
33+
34+
private ResourceName(Builder builder) {
35+
project = Preconditions.checkNotNull(builder.getProject());
36+
location = Preconditions.checkNotNull(builder.getLocation());
37+
deployment = Preconditions.checkNotNull(builder.getDeployment());
38+
revision = Preconditions.checkNotNull(builder.getRevision());
39+
resource = Preconditions.checkNotNull(builder.getResource());
40+
}
41+
42+
public String getProject() {
43+
return project;
44+
}
45+
46+
public String getLocation() {
47+
return location;
48+
}
49+
50+
public String getDeployment() {
51+
return deployment;
52+
}
53+
54+
public String getRevision() {
55+
return revision;
56+
}
57+
58+
public String getResource() {
59+
return resource;
60+
}
61+
62+
public static Builder newBuilder() {
63+
return new Builder();
64+
}
65+
66+
public Builder toBuilder() {
67+
return new Builder(this);
68+
}
69+
70+
public static ResourceName of(
71+
String project, String location, String deployment, String revision, String resource) {
72+
return newBuilder()
73+
.setProject(project)
74+
.setLocation(location)
75+
.setDeployment(deployment)
76+
.setRevision(revision)
77+
.setResource(resource)
78+
.build();
79+
}
80+
81+
public static String format(
82+
String project, String location, String deployment, String revision, String resource) {
83+
return newBuilder()
84+
.setProject(project)
85+
.setLocation(location)
86+
.setDeployment(deployment)
87+
.setRevision(revision)
88+
.setResource(resource)
89+
.build()
90+
.toString();
91+
}
92+
93+
public static ResourceName parse(String formattedString) {
94+
if (formattedString.isEmpty()) {
95+
return null;
96+
}
97+
Map<String, String> matchMap =
98+
PROJECT_LOCATION_DEPLOYMENT_REVISION_RESOURCE.validatedMatch(
99+
formattedString, "ResourceName.parse: formattedString not in valid format");
100+
return of(
101+
matchMap.get("project"),
102+
matchMap.get("location"),
103+
matchMap.get("deployment"),
104+
matchMap.get("revision"),
105+
matchMap.get("resource"));
106+
}
107+
108+
public static List<ResourceName> parseList(List<String> formattedStrings) {
109+
List<ResourceName> list = new ArrayList<>(formattedStrings.size());
110+
for (String formattedString : formattedStrings) {
111+
list.add(parse(formattedString));
112+
}
113+
return list;
114+
}
115+
116+
public static List<String> toStringList(List<ResourceName> values) {
117+
List<String> list = new ArrayList<>(values.size());
118+
for (ResourceName value : values) {
119+
if (value == null) {
120+
list.add("");
121+
} else {
122+
list.add(value.toString());
123+
}
124+
}
125+
return list;
126+
}
127+
128+
public static boolean isParsableFrom(String formattedString) {
129+
return PROJECT_LOCATION_DEPLOYMENT_REVISION_RESOURCE.matches(formattedString);
130+
}
131+
132+
@Override
133+
public Map<String, String> getFieldValuesMap() {
134+
if (fieldValuesMap == null) {
135+
synchronized (this) {
136+
if (fieldValuesMap == null) {
137+
ImmutableMap.Builder<String, String> fieldMapBuilder = ImmutableMap.builder();
138+
if (project != null) {
139+
fieldMapBuilder.put("project", project);
140+
}
141+
if (location != null) {
142+
fieldMapBuilder.put("location", location);
143+
}
144+
if (deployment != null) {
145+
fieldMapBuilder.put("deployment", deployment);
146+
}
147+
if (revision != null) {
148+
fieldMapBuilder.put("revision", revision);
149+
}
150+
if (resource != null) {
151+
fieldMapBuilder.put("resource", resource);
152+
}
153+
fieldValuesMap = fieldMapBuilder.build();
154+
}
155+
}
156+
}
157+
return fieldValuesMap;
158+
}
159+
160+
public String getFieldValue(String fieldName) {
161+
return getFieldValuesMap().get(fieldName);
162+
}
163+
164+
@Override
165+
public String toString() {
166+
return PROJECT_LOCATION_DEPLOYMENT_REVISION_RESOURCE.instantiate(
167+
"project",
168+
project,
169+
"location",
170+
location,
171+
"deployment",
172+
deployment,
173+
"revision",
174+
revision,
175+
"resource",
176+
resource);
177+
}
178+
179+
@Override
180+
public boolean equals(java.lang.Object o) {
181+
if (o == this) {
182+
return true;
183+
}
184+
if (o != null || getClass() == o.getClass()) {
185+
ResourceName that = ((ResourceName) o);
186+
return Objects.equals(this.project, that.project)
187+
&& Objects.equals(this.location, that.location)
188+
&& Objects.equals(this.deployment, that.deployment)
189+
&& Objects.equals(this.revision, that.revision)
190+
&& Objects.equals(this.resource, that.resource);
191+
}
192+
return false;
193+
}
194+
195+
@Override
196+
public int hashCode() {
197+
int h = 1;
198+
h *= 1000003;
199+
h ^= Objects.hashCode(project);
200+
h *= 1000003;
201+
h ^= Objects.hashCode(location);
202+
h *= 1000003;
203+
h ^= Objects.hashCode(deployment);
204+
h *= 1000003;
205+
h ^= Objects.hashCode(revision);
206+
h *= 1000003;
207+
h ^= Objects.hashCode(resource);
208+
return h;
209+
}
210+
211+
/**
212+
* Builder for
213+
* projects/{project}/locations/{location}/deployments/{deployment}/revisions/{revision}/resources/{resource}.
214+
*/
215+
public static class Builder {
216+
private String project;
217+
private String location;
218+
private String deployment;
219+
private String revision;
220+
private String resource;
221+
222+
protected Builder() {}
223+
224+
public String getProject() {
225+
return project;
226+
}
227+
228+
public String getLocation() {
229+
return location;
230+
}
231+
232+
public String getDeployment() {
233+
return deployment;
234+
}
235+
236+
public String getRevision() {
237+
return revision;
238+
}
239+
240+
public String getResource() {
241+
return resource;
242+
}
243+
244+
public Builder setProject(String project) {
245+
this.project = project;
246+
return this;
247+
}
248+
249+
public Builder setLocation(String location) {
250+
this.location = location;
251+
return this;
252+
}
253+
254+
public Builder setDeployment(String deployment) {
255+
this.deployment = deployment;
256+
return this;
257+
}
258+
259+
public Builder setRevision(String revision) {
260+
this.revision = revision;
261+
return this;
262+
}
263+
264+
public Builder setResource(String resource) {
265+
this.resource = resource;
266+
return this;
267+
}
268+
269+
private Builder(ResourceName resourceName) {
270+
this.project = resourceName.project;
271+
this.location = resourceName.location;
272+
this.deployment = resourceName.deployment;
273+
this.revision = resourceName.revision;
274+
this.resource = resourceName.resource;
275+
}
276+
277+
public ResourceName build() {
278+
return new ResourceName(this);
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)