• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 package com.google.api.generator.gapic.composer.resourcename;
16 
17 import com.google.api.core.BetaApi;
18 import com.google.api.generator.engine.ast.AnnotationNode;
19 import com.google.api.generator.engine.ast.AssignmentExpr;
20 import com.google.api.generator.engine.ast.AssignmentOperationExpr;
21 import com.google.api.generator.engine.ast.CastExpr;
22 import com.google.api.generator.engine.ast.ClassDefinition;
23 import com.google.api.generator.engine.ast.CommentStatement;
24 import com.google.api.generator.engine.ast.ConcreteReference;
25 import com.google.api.generator.engine.ast.Expr;
26 import com.google.api.generator.engine.ast.ExprStatement;
27 import com.google.api.generator.engine.ast.ForStatement;
28 import com.google.api.generator.engine.ast.IfStatement;
29 import com.google.api.generator.engine.ast.JavaDocComment;
30 import com.google.api.generator.engine.ast.LogicalOperationExpr;
31 import com.google.api.generator.engine.ast.MethodDefinition;
32 import com.google.api.generator.engine.ast.MethodInvocationExpr;
33 import com.google.api.generator.engine.ast.NewObjectExpr;
34 import com.google.api.generator.engine.ast.PrimitiveValue;
35 import com.google.api.generator.engine.ast.Reference;
36 import com.google.api.generator.engine.ast.RelationalOperationExpr;
37 import com.google.api.generator.engine.ast.ReturnExpr;
38 import com.google.api.generator.engine.ast.ScopeNode;
39 import com.google.api.generator.engine.ast.Statement;
40 import com.google.api.generator.engine.ast.StringObjectValue;
41 import com.google.api.generator.engine.ast.SynchronizedStatement;
42 import com.google.api.generator.engine.ast.TernaryExpr;
43 import com.google.api.generator.engine.ast.ThisObjectValue;
44 import com.google.api.generator.engine.ast.ThrowExpr;
45 import com.google.api.generator.engine.ast.TypeNode;
46 import com.google.api.generator.engine.ast.ValueExpr;
47 import com.google.api.generator.engine.ast.Variable;
48 import com.google.api.generator.engine.ast.VariableExpr;
49 import com.google.api.generator.gapic.composer.comment.CommentComposer;
50 import com.google.api.generator.gapic.composer.store.TypeStore;
51 import com.google.api.generator.gapic.model.GapicClass;
52 import com.google.api.generator.gapic.model.GapicContext;
53 import com.google.api.generator.gapic.model.ResourceName;
54 import com.google.api.generator.gapic.utils.JavaStyle;
55 import com.google.api.generator.gapic.utils.ResourceNameConstants;
56 import com.google.api.pathtemplate.PathTemplate;
57 import com.google.api.pathtemplate.ValidationException;
58 import com.google.common.annotations.VisibleForTesting;
59 import com.google.common.base.Preconditions;
60 import com.google.common.collect.ImmutableMap;
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.Collections;
64 import java.util.HashMap;
65 import java.util.Iterator;
66 import java.util.LinkedHashSet;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Objects;
70 import java.util.Set;
71 import java.util.function.Function;
72 import java.util.stream.Collectors;
73 import javax.annotation.Generated;
74 
75 public class ResourceNameHelperClassComposer {
76   private static final String CLASS_NAME_PATTERN = "%sName";
77   private static final String BUILDER_CLASS_HEADER_PATTERN = "Builder for %s.";
78 
79   private static final ResourceNameHelperClassComposer INSTANCE =
80       new ResourceNameHelperClassComposer();
81 
82   private static final TypeStore FIXED_TYPESTORE = createStaticTypes();
83   private static final Map<String, VariableExpr> FIXED_CLASS_VARS =
84       createFixedClassMemberVariables();
85 
86   private static Reference javaObjectReference = ConcreteReference.withClazz(Object.class);
87 
ResourceNameHelperClassComposer()88   private ResourceNameHelperClassComposer() {}
89 
instance()90   public static ResourceNameHelperClassComposer instance() {
91     return INSTANCE;
92   }
93 
generate(ResourceName resourceName, GapicContext context)94   public GapicClass generate(ResourceName resourceName, GapicContext context) {
95     // Set up types.
96     List<List<String>> tokenHierarchies =
97         ResourceNameTokenizer.parseTokenHierarchy(resourceName.patterns());
98     TypeStore typeStore = createDynamicTypes(resourceName, tokenHierarchies);
99     // Use the full name java.lang.Object if there is a proto message that is also named "Object".
100     // Affects GCS.
101     if (context.messages().keySet().stream()
102         .anyMatch(s -> s.equals("Object") || s.endsWith(".Object"))) {
103       javaObjectReference =
104           ConcreteReference.builder().setClazz(Object.class).setUseFullName(true).build();
105     }
106 
107     // Set up variables.
108     List<VariableExpr> templateFinalVarExprs = createTemplateClassMembers(tokenHierarchies);
109     Map<String, VariableExpr> patternTokenVarExprs =
110         createPatternTokenClassMembers(tokenHierarchies);
111 
112     // Check invariants.
113     Preconditions.checkState(
114         patternTokenVarExprs.size() > 0,
115         String.format("No patterns found for resource name %s", resourceName.resourceTypeString()));
116     Preconditions.checkState(
117         templateFinalVarExprs.size() > 0 && tokenHierarchies.size() == templateFinalVarExprs.size(),
118         String.format(
119             "Cardinalities of patterns (%d) and associated variables (%d) do not match for"
120                 + " resource name %s ",
121             templateFinalVarExprs.size(),
122             tokenHierarchies.size(),
123             resourceName.resourceTypeString()));
124 
125     String className = getThisClassName(resourceName);
126 
127     ClassDefinition classDef =
128         ClassDefinition.builder()
129             .setPackageString(resourceName.pakkage())
130             .setHeaderCommentStatements(CommentComposer.AUTO_GENERATED_CLASS_COMMENT)
131             .setAnnotations(createClassAnnotations())
132             .setScope(ScopeNode.PUBLIC)
133             .setName(className)
134             .setImplementsTypes(createImplementsTypes())
135             .setStatements(
136                 createClassStatements(
137                     templateFinalVarExprs,
138                     patternTokenVarExprs,
139                     resourceName.patterns(),
140                     tokenHierarchies))
141             .setMethods(
142                 createClassMethods(
143                     resourceName,
144                     templateFinalVarExprs,
145                     patternTokenVarExprs,
146                     tokenHierarchies,
147                     typeStore))
148             .setNestedClasses(
149                 createNestedBuilderClasses(
150                     resourceName, tokenHierarchies, templateFinalVarExprs, typeStore))
151             .build();
152     return GapicClass.create(GapicClass.Kind.PROTO, classDef);
153   }
154 
createClassAnnotations()155   private static List<AnnotationNode> createClassAnnotations() {
156     return Arrays.asList(
157         AnnotationNode.builder()
158             .setType(FIXED_TYPESTORE.get("Generated"))
159             .setDescription("by gapic-generator-java")
160             .build());
161   }
162 
createImplementsTypes()163   private static List<TypeNode> createImplementsTypes() {
164     return Arrays.asList(FIXED_TYPESTORE.get("ResourceName"));
165   }
166 
createTemplateClassMembers( List<List<String>> tokenHierarchies)167   private static List<VariableExpr> createTemplateClassMembers(
168       List<List<String>> tokenHierarchies) {
169     return tokenHierarchies.stream()
170         .map(
171             ts ->
172                 VariableExpr.withVariable(
173                     Variable.builder()
174                         .setName(concatToUpperSnakeCaseName(ts))
175                         .setType(
176                             // PubSub special-case handling for the _deleted-topic_ pattern.
177                             ts.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL)
178                                 ? TypeNode.STRING
179                                 : FIXED_TYPESTORE.get("PathTemplate"))
180                         .build()))
181         .collect(Collectors.toList());
182   }
183 
createPatternTokenClassMembers( List<List<String>> tokenHierarchies)184   private static Map<String, VariableExpr> createPatternTokenClassMembers(
185       List<List<String>> tokenHierarchies) {
186     // PubSub special-case handling - exclude _deleted-topic_.
187     List<List<String>> processedTokenHierarchies =
188         tokenHierarchies.stream()
189             .filter(ts -> !ts.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
190             .collect(Collectors.toList());
191     Set<String> tokenSet = getTokenSet(processedTokenHierarchies);
192     return tokenSet.stream()
193         .collect(
194             Collectors.toMap(
195                 t -> t,
196                 t ->
197                     VariableExpr.withVariable(
198                         Variable.builder()
199                             .setName(JavaStyle.toLowerCamelCase(t))
200                             .setType(TypeNode.STRING)
201                             .build())));
202   }
203 
createClassStatements( List<VariableExpr> templateFinalVarExprs, Map<String, VariableExpr> patternTokenVarExprs, List<String> patterns, List<List<String>> tokenHierarchies)204   private static List<Statement> createClassStatements(
205       List<VariableExpr> templateFinalVarExprs,
206       Map<String, VariableExpr> patternTokenVarExprs,
207       List<String> patterns,
208       List<List<String>> tokenHierarchies) {
209     List<Expr> memberVars = new ArrayList<>();
210     // Pattern string variables.
211     // Example:
212     // private static final PathTemplate PROJECT_LOCATION_AUTOSCALING_POLICY_PATH_TEMPLATE =
213     //     PathTemplate.createWithoutUrlEncoding(
214     //         "projects/{project}/locations/{location}/autoscalingPolicies/{autoscaling_policy}");
215     for (int i = 0; i < patterns.size(); i++) {
216       VariableExpr varExpr =
217           templateFinalVarExprs
218               .get(i)
219               .toBuilder()
220               .setIsDecl(true)
221               .setScope(ScopeNode.PRIVATE)
222               .setIsStatic(true)
223               .setIsFinal(true)
224               .build();
225       String pattern = patterns.get(i);
226       Expr valueExpr =
227           MethodInvocationExpr.builder()
228               .setStaticReferenceType(FIXED_TYPESTORE.get("PathTemplate"))
229               .setMethodName("createWithoutUrlEncoding")
230               .setArguments(
231                   Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(pattern))))
232               .setReturnType(FIXED_TYPESTORE.get("PathTemplate"))
233               .build();
234       // PubSub special-case handling for _deleted-topic_.
235       if (pattern.equals(ResourceNameConstants.DELETED_TOPIC_LITERAL)) {
236         valueExpr = ValueExpr.withValue(StringObjectValue.withValue(pattern));
237       }
238       memberVars.add(
239           AssignmentExpr.builder().setVariableExpr(varExpr).setValueExpr(valueExpr).build());
240     }
241 
242     memberVars.add(
243         FIXED_CLASS_VARS
244             .get("fieldValuesMap")
245             .toBuilder()
246             .setIsDecl(true)
247             .setScope(ScopeNode.PRIVATE)
248             .setIsVolatile(true)
249             .build());
250 
251     boolean hasVariants = tokenHierarchies.size() > 1;
252     if (hasVariants) {
253       Function<VariableExpr, VariableExpr> toDeclFn =
254           v -> v.toBuilder().setIsDecl(true).setScope(ScopeNode.PRIVATE).build();
255       memberVars.add(toDeclFn.apply(FIXED_CLASS_VARS.get("pathTemplate")));
256       memberVars.add(toDeclFn.apply(FIXED_CLASS_VARS.get("fixedValue")));
257     }
258 
259     // Private per-token string variables.
260     // Use the token set as a key to maintain ordering (for consistency).
261     Function<VariableExpr, VariableExpr> toFinalDeclFn =
262         v -> v.toBuilder().setIsDecl(true).setScope(ScopeNode.PRIVATE).setIsFinal(true).build();
263     // Special-cased PubSub handling.
264     List<List<String>> processedTokenHierarchies =
265         tokenHierarchies.stream()
266             .filter(tokens -> !tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
267             .collect(Collectors.toList());
268     memberVars.addAll(
269         getTokenSet(processedTokenHierarchies).stream()
270             .map(t -> toFinalDeclFn.apply(patternTokenVarExprs.get(t)))
271             .collect(Collectors.toList()));
272     return memberVars.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList());
273   }
274 
createClassMethods( ResourceName resourceName, List<VariableExpr> templateFinalVarExprs, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)275   private static List<MethodDefinition> createClassMethods(
276       ResourceName resourceName,
277       List<VariableExpr> templateFinalVarExprs,
278       Map<String, VariableExpr> patternTokenVarExprs,
279       List<List<String>> tokenHierarchies,
280       TypeStore typeStore) {
281     List<MethodDefinition> javaMethods = new ArrayList<>();
282 
283     javaMethods.addAll(
284         createConstructorMethods(
285             resourceName,
286             templateFinalVarExprs,
287             patternTokenVarExprs,
288             tokenHierarchies,
289             typeStore));
290 
291     javaMethods.addAll(createTokenGetterMethods(patternTokenVarExprs, tokenHierarchies));
292 
293     javaMethods.addAll(createBuilderCreatorMethods(resourceName, tokenHierarchies, typeStore));
294     javaMethods.addAll(
295         createOfCreatorMethods(resourceName, patternTokenVarExprs, tokenHierarchies, typeStore));
296 
297     javaMethods.addAll(
298         createFormatCreatorMethods(
299             resourceName, patternTokenVarExprs, tokenHierarchies, typeStore));
300 
301     javaMethods.addAll(
302         createParsingAndSplittingMethods(
303             resourceName, templateFinalVarExprs, tokenHierarchies, typeStore));
304 
305     javaMethods.addAll(
306         createFieldValueGetterMethods(
307             resourceName, patternTokenVarExprs, tokenHierarchies, typeStore));
308     javaMethods.add(
309         createToStringMethod(templateFinalVarExprs, patternTokenVarExprs, tokenHierarchies));
310     javaMethods.add(createEqualsMethod(resourceName, tokenHierarchies, typeStore));
311     javaMethods.add(createHashCodeMethod(tokenHierarchies));
312     return javaMethods;
313   }
314 
createConstructorMethods( ResourceName resourceName, List<VariableExpr> templateFinalVarExprs, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)315   private static List<MethodDefinition> createConstructorMethods(
316       ResourceName resourceName,
317       List<VariableExpr> templateFinalVarExprs,
318       Map<String, VariableExpr> patternTokenVarExprs,
319       List<List<String>> tokenHierarchies,
320       TypeStore typeStore) {
321     String thisClassName = getThisClassName(resourceName);
322     TypeNode thisClassType = typeStore.get(thisClassName);
323     boolean hasVariants = tokenHierarchies.size() > 1;
324 
325     List<MethodDefinition> javaMethods = new ArrayList<>();
326     final ValueExpr nullExpr = ValueExpr.createNullExpr();
327     Function<String, AssignmentExpr> assignTokenToNullExpr =
328         t ->
329             AssignmentExpr.builder()
330                 .setVariableExpr(patternTokenVarExprs.get(t))
331                 .setValueExpr(nullExpr)
332                 .build();
333 
334     // Special-cased PubSub handling.
335     List<List<String>> processedTokenHierarchies =
336         tokenHierarchies.stream()
337             .filter(tokens -> !tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
338             .collect(Collectors.toList());
339     boolean hasDeletedTopicPattern = tokenHierarchies.size() > processedTokenHierarchies.size();
340 
341     // First deprecated constructor.
342     javaMethods.add(
343         MethodDefinition.constructorBuilder()
344             .setAnnotations(
345                 Arrays.asList(
346                     AnnotationNode.withType(
347                         TypeNode.withReference(ConcreteReference.withClazz(Deprecated.class)))))
348             .setScope(ScopeNode.PROTECTED)
349             .setReturnType(thisClassType)
350             .setBody(
351                 getTokenSet(processedTokenHierarchies).stream()
352                     .map(t -> ExprStatement.withExpr(assignTokenToNullExpr.apply(t)))
353                     .collect(Collectors.toList()))
354             .build());
355 
356     for (int i = 0; i < processedTokenHierarchies.size(); i++) {
357       List<String> tokens = processedTokenHierarchies.get(i);
358 
359       List<Expr> bodyExprs = new ArrayList<>();
360       TypeNode argType = getBuilderType(typeStore, processedTokenHierarchies, i);
361       VariableExpr builderArgExpr =
362           VariableExpr.withVariable(Variable.builder().setName("builder").setType(argType).build());
363       for (String token : tokens) {
364         MethodInvocationExpr checkNotNullExpr =
365             MethodInvocationExpr.builder()
366                 .setStaticReferenceType(FIXED_TYPESTORE.get("Preconditions"))
367                 .setMethodName("checkNotNull")
368                 .setReturnType(TypeNode.STRING)
369                 .setArguments(
370                     Arrays.asList(
371                         MethodInvocationExpr.builder()
372                             .setExprReferenceExpr(builderArgExpr)
373                             .setMethodName(
374                                 String.format("get%s", JavaStyle.toUpperCamelCase(token)))
375                             .build()))
376                 .build();
377         bodyExprs.add(
378             AssignmentExpr.builder()
379                 .setVariableExpr(patternTokenVarExprs.get(token))
380                 .setValueExpr(checkNotNullExpr)
381                 .build());
382       }
383       // Initialize the rest to null.
384       for (String token : getTokenSet(processedTokenHierarchies)) {
385         if (tokens.contains(token)) {
386           continue;
387         }
388         bodyExprs.add(assignTokenToNullExpr.apply(token));
389       }
390 
391       if (hasVariants) {
392         AssignmentExpr pathTemplateAssignExpr =
393             AssignmentExpr.builder()
394                 .setVariableExpr(FIXED_CLASS_VARS.get("pathTemplate"))
395                 .setValueExpr(templateFinalVarExprs.get(i))
396                 .build();
397         bodyExprs.add(pathTemplateAssignExpr);
398       }
399 
400       // Private constructor.
401       javaMethods.add(
402           MethodDefinition.constructorBuilder()
403               .setScope(ScopeNode.PRIVATE)
404               .setReturnType(thisClassType)
405               .setArguments(Arrays.asList(builderArgExpr.toBuilder().setIsDecl(true).build()))
406               .setBody(
407                   bodyExprs.stream()
408                       .map(e -> ExprStatement.withExpr(e))
409                       .collect(Collectors.toList()))
410               .build());
411     }
412 
413     if (hasDeletedTopicPattern) {
414       Expr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType));
415       // PubSub special-case handling for the _deleted-topic_ pattern - add an extra constructor.
416       //  private TopicName(String fixedValue) {
417       //    this.fixedValue = fixedValue;
418       //    fieldValuesMap = ImmutableMap.of("", fixedValue);
419       //  }
420       VariableExpr fixedValueVarExpr =
421           VariableExpr.withVariable(
422               Variable.builder().setName("fixedValue").setType(TypeNode.STRING).build());
423       List<Expr> specialCtorBodyExprs = new ArrayList<>();
424       specialCtorBodyExprs.add(
425           AssignmentExpr.builder()
426               .setVariableExpr(
427                   VariableExpr.builder()
428                       .setExprReferenceExpr(thisExpr)
429                       .setVariable(fixedValueVarExpr.variable())
430                       .build())
431               .setValueExpr(fixedValueVarExpr)
432               .build());
433       specialCtorBodyExprs.add(
434           AssignmentExpr.builder()
435               .setVariableExpr(FIXED_CLASS_VARS.get("fieldValuesMap"))
436               .setValueExpr(
437                   MethodInvocationExpr.builder()
438                       .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap"))
439                       .setMethodName("of")
440                       .setArguments(
441                           ValueExpr.withValue(StringObjectValue.withValue("")), fixedValueVarExpr)
442                       .setReturnType(FIXED_TYPESTORE.get("ImmutableMap"))
443                       .build())
444               .build());
445       specialCtorBodyExprs.addAll(
446           getTokenSet(processedTokenHierarchies).stream()
447               .map(t -> assignTokenToNullExpr.apply(t))
448               .collect(Collectors.toList()));
449 
450       javaMethods.add(
451           MethodDefinition.constructorBuilder()
452               .setScope(ScopeNode.PRIVATE)
453               .setReturnType(thisClassType)
454               .setArguments(Arrays.asList(fixedValueVarExpr.toBuilder().setIsDecl(true).build()))
455               .setBody(
456                   specialCtorBodyExprs.stream()
457                       .map(e -> ExprStatement.withExpr(e))
458                       .collect(Collectors.toList()))
459               .build());
460     }
461 
462     return javaMethods;
463   }
464 
createTokenGetterMethods( Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies)465   private static List<MethodDefinition> createTokenGetterMethods(
466       Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies) {
467     // PubSub special-case handling.
468     List<List<String>> processedTokenHierarchies =
469         tokenHierarchies.stream()
470             .filter(ts -> !ts.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
471             .collect(Collectors.toList());
472     return getTokenSet(processedTokenHierarchies).stream()
473         .map(
474             t ->
475                 MethodDefinition.builder()
476                     .setScope(ScopeNode.PUBLIC)
477                     .setReturnType(TypeNode.STRING)
478                     .setName(String.format("get%s", JavaStyle.toUpperCamelCase(t)))
479                     .setReturnExpr(patternTokenVarExprs.get(t))
480                     .build())
481         .collect(Collectors.toList());
482   }
483 
createBuilderCreatorMethods( ResourceName resourceName, List<List<String>> tokenHierarchies, TypeStore typeStore)484   private static List<MethodDefinition> createBuilderCreatorMethods(
485       ResourceName resourceName, List<List<String>> tokenHierarchies, TypeStore typeStore) {
486     List<MethodDefinition> javaMethods = new ArrayList<>();
487     String newMethodNameFormat = "new%s";
488     AnnotationNode betaAnnotation =
489         AnnotationNode.builder()
490             .setType(FIXED_TYPESTORE.get("BetaApi"))
491             .setDescription(
492                 "The per-pattern Builders are not stable yet and may be changed in the future.")
493             .build();
494     List<AnnotationNode> annotations = Arrays.asList(betaAnnotation);
495 
496     // Create the newBuilder and variation methods here.
497     // Variation example: newProjectLocationAutoscalingPolicyBuilder().
498     for (int i = 0; i < tokenHierarchies.size(); i++) {
499       // PubSub special-case handling.
500       if (tokenHierarchies.get(i).contains(ResourceNameConstants.DELETED_TOPIC_LITERAL)) {
501         continue;
502       }
503 
504       final TypeNode returnType = getBuilderType(typeStore, tokenHierarchies, i);
505       final Expr returnExpr = NewObjectExpr.withType(returnType);
506 
507       Function<String, MethodDefinition.Builder> methodDefStarterFn =
508           methodName ->
509               MethodDefinition.builder()
510                   .setScope(ScopeNode.PUBLIC)
511                   .setIsStatic(true)
512                   .setReturnType(returnType)
513                   .setName(methodName)
514                   .setReturnExpr(returnExpr);
515 
516       String variantName = getBuilderTypeName(tokenHierarchies, i);
517       javaMethods.add(
518           methodDefStarterFn
519               .apply(String.format(newMethodNameFormat, variantName))
520               .setAnnotations(i == 0 ? Collections.emptyList() : annotations)
521               .build());
522       if (i == 0 && tokenHierarchies.size() > 1) {
523         // Create another builder creator method, but with the per-variant name.
524         javaMethods.add(
525             methodDefStarterFn
526                 .apply(
527                     String.format(newMethodNameFormat, getBuilderTypeName(tokenHierarchies.get(i))))
528                 .setAnnotations(annotations)
529                 .build());
530       }
531     }
532 
533     // TODO(miraleung, v2): It seems weird that we currently generate a toBuilder method only for
534     // the default class, and none for the Builder variants.
535     TypeNode toBuilderReturnType = getBuilderType(typeStore, tokenHierarchies, 0);
536     TypeNode thisClassType = typeStore.get(getThisClassName(resourceName));
537     javaMethods.add(
538         MethodDefinition.builder()
539             .setScope(ScopeNode.PUBLIC)
540             .setReturnType(toBuilderReturnType)
541             .setName("toBuilder")
542             .setReturnExpr(
543                 NewObjectExpr.builder()
544                     .setType(toBuilderReturnType)
545                     .setArguments(
546                         Arrays.asList(ValueExpr.withValue(ThisObjectValue.withType(thisClassType))))
547                     .build())
548             .build());
549     return javaMethods;
550   }
551 
createOfCreatorMethods( ResourceName resourceName, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)552   private static List<MethodDefinition> createOfCreatorMethods(
553       ResourceName resourceName,
554       Map<String, VariableExpr> patternTokenVarExprs,
555       List<List<String>> tokenHierarchies,
556       TypeStore typeStore) {
557     return createOfOrFormatMethodHelper(
558         resourceName, patternTokenVarExprs, tokenHierarchies, typeStore, /*isFormatMethod=*/ false);
559   }
560 
createFormatCreatorMethods( ResourceName resourceName, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)561   private static List<MethodDefinition> createFormatCreatorMethods(
562       ResourceName resourceName,
563       Map<String, VariableExpr> patternTokenVarExprs,
564       List<List<String>> tokenHierarchies,
565       TypeStore typeStore) {
566     return createOfOrFormatMethodHelper(
567         resourceName, patternTokenVarExprs, tokenHierarchies, typeStore, /*isFormatMethod=*/ true);
568   }
569 
createOfOrFormatMethodHelper( ResourceName resourceName, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore, boolean isFormatMethod)570   private static List<MethodDefinition> createOfOrFormatMethodHelper(
571       ResourceName resourceName,
572       Map<String, VariableExpr> patternTokenVarExprs,
573       List<List<String>> tokenHierarchies,
574       TypeStore typeStore,
575       boolean isFormatMethod) {
576     List<MethodDefinition> javaMethods = new ArrayList<>();
577     String methodNameFormat = isFormatMethod ? "format%s" : "of%s";
578     String newBuilderMethodNameFormat = "new%s";
579     String setMethodNameFormat = "set%s";
580     String buildMethodName = "build";
581     String toStringMethodName = "toString";
582     AnnotationNode betaAnnotation =
583         AnnotationNode.builder()
584             .setType(FIXED_TYPESTORE.get("BetaApi"))
585             .setDescription(
586                 String.format(
587                     "The static %s methods are not stable yet and may be changed in the future.",
588                     isFormatMethod ? "format" : "create"))
589             .build();
590     List<AnnotationNode> annotations = Arrays.asList(betaAnnotation);
591 
592     TypeNode thisClassType = typeStore.get(getThisClassName(resourceName));
593     TypeNode returnType = isFormatMethod ? TypeNode.STRING : thisClassType;
594     // Create the newBuilder and variation methods here.
595     // Variation example: newProjectLocationAutoscalingPolicyBuilder().
596     boolean hasVariants = tokenHierarchies.size() > 1;
597     for (int i = 0; i < tokenHierarchies.size(); i++) {
598       List<String> tokens = tokenHierarchies.get(i);
599       // PubSub special-case handling.
600       if (tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL)) {
601         Expr deletedTopicStringValExpr =
602             ValueExpr.withValue(
603                 StringObjectValue.withValue(ResourceNameConstants.DELETED_TOPIC_LITERAL));
604         // Simply return `new TopicName("_deleted-topic_")` or the string value itself.
605         javaMethods.add(
606             MethodDefinition.builder()
607                 .setScope(ScopeNode.PUBLIC)
608                 .setIsStatic(true)
609                 .setAnnotations(annotations)
610                 .setReturnType(returnType)
611                 .setName(
612                     String.format(methodNameFormat, concatToUpperCamelCaseName(tokens) + "Name"))
613                 .setReturnExpr(
614                     returnType.equals(TypeNode.STRING)
615                         ? deletedTopicStringValExpr
616                         : NewObjectExpr.builder()
617                             .setType(returnType)
618                             .setArguments(deletedTopicStringValExpr)
619                             .build())
620                 .build());
621         continue;
622       }
623 
624       String builderMethodName =
625           String.format(newBuilderMethodNameFormat, getBuilderTypeName(tokenHierarchies, i));
626 
627       MethodInvocationExpr returnExpr =
628           MethodInvocationExpr.builder().setMethodName(builderMethodName).build();
629       for (String token : tokens) {
630         String javaTokenVarName = JavaStyle.toLowerCamelCase(token);
631         returnExpr =
632             MethodInvocationExpr.builder()
633                 .setExprReferenceExpr(returnExpr)
634                 .setMethodName(
635                     String.format(setMethodNameFormat, JavaStyle.toUpperCamelCase(token)))
636                 .setArguments(
637                     Arrays.asList(
638                         VariableExpr.withVariable(
639                             Variable.builder()
640                                 .setName(javaTokenVarName)
641                                 .setType(TypeNode.STRING)
642                                 .build())))
643                 .build();
644       }
645       returnExpr =
646           MethodInvocationExpr.builder()
647               .setExprReferenceExpr(returnExpr)
648               .setMethodName(buildMethodName)
649               .setReturnType(thisClassType)
650               .build();
651       if (isFormatMethod) {
652         returnExpr =
653             MethodInvocationExpr.builder()
654                 .setExprReferenceExpr(returnExpr)
655                 .setMethodName(toStringMethodName)
656                 .setReturnType(TypeNode.STRING)
657                 .build();
658       }
659       List<VariableExpr> methodArgs =
660           tokens.stream()
661               .map(t -> patternTokenVarExprs.get(t).toBuilder().setIsDecl(true).build())
662               .collect(Collectors.toList());
663       javaMethods.add(
664           MethodDefinition.builder()
665               .setScope(ScopeNode.PUBLIC)
666               .setIsStatic(true)
667               .setAnnotations(i == 0 ? Collections.emptyList() : annotations)
668               .setReturnType(returnType)
669               .setName(
670                   String.format(
671                       methodNameFormat, i == 0 ? "" : concatToUpperCamelCaseName(tokens) + "Name"))
672               .setArguments(methodArgs)
673               .setReturnExpr(returnExpr)
674               .build());
675 
676       if (i == 0 && hasVariants) {
677         javaMethods.add(
678             MethodDefinition.builder()
679                 .setScope(ScopeNode.PUBLIC)
680                 .setIsStatic(true)
681                 .setAnnotations(annotations)
682                 .setReturnType(returnType)
683                 .setName(
684                     String.format(
685                         methodNameFormat,
686                         concatToUpperCamelCaseName(tokenHierarchies.get(i)) + "Name"))
687                 .setArguments(methodArgs)
688                 .setReturnExpr(returnExpr)
689                 .build());
690       }
691     }
692     return javaMethods;
693   }
694 
createParsingAndSplittingMethods( ResourceName resourceName, List<VariableExpr> templateFinalVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)695   private static List<MethodDefinition> createParsingAndSplittingMethods(
696       ResourceName resourceName,
697       List<VariableExpr> templateFinalVarExprs,
698       List<List<String>> tokenHierarchies,
699       TypeStore typeStore) {
700     List<MethodDefinition> javaMethods = new ArrayList<>();
701     TypeNode thisClassType = typeStore.get(getThisClassName(resourceName));
702     javaMethods.add(
703         createParseMethod(thisClassType, templateFinalVarExprs, tokenHierarchies, typeStore));
704     javaMethods.add(createParseListMethod(thisClassType));
705     javaMethods.add(createToStringListMethod(thisClassType));
706     javaMethods.add(createIsParseableFromMethod(templateFinalVarExprs));
707 
708     return javaMethods;
709   }
710 
createParseMethod( TypeNode thisClassType, List<VariableExpr> templateFinalVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)711   private static MethodDefinition createParseMethod(
712       TypeNode thisClassType,
713       List<VariableExpr> templateFinalVarExprs,
714       List<List<String>> tokenHierarchies,
715       TypeStore typeStore) {
716     String formattedStringArgName = "formattedString";
717     VariableExpr formattedStringArgExpr =
718         VariableExpr.withVariable(
719             Variable.builder().setName(formattedStringArgName).setType(TypeNode.STRING).build());
720     String exceptionMessageString =
721         String.format(
722             "%s.parse: %s not in valid format",
723             thisClassType.reference().name(), formattedStringArgName);
724 
725     ValueExpr exceptionMessageExpr =
726         ValueExpr.withValue(StringObjectValue.withValue(exceptionMessageString));
727     TypeNode mapStringType =
728         TypeNode.withReference(
729             ConcreteReference.builder()
730                 .setClazz(Map.class)
731                 .setGenerics(
732                     Arrays.asList(
733                         ConcreteReference.withClazz(String.class),
734                         ConcreteReference.withClazz(String.class)))
735                 .build());
736     VariableExpr matchMapVarExpr =
737         VariableExpr.withVariable(
738             Variable.builder().setName("matchMap").setType(mapStringType).build());
739 
740     List<Statement> body = new ArrayList<>();
741     body.add(
742         IfStatement.builder()
743             .setConditionExpr(
744                 MethodInvocationExpr.builder()
745                     .setExprReferenceExpr(formattedStringArgExpr)
746                     .setMethodName("isEmpty")
747                     .setReturnType(TypeNode.BOOLEAN)
748                     .build())
749             .setBody(
750                 Arrays.asList(
751                     ExprStatement.withExpr(ReturnExpr.withExpr(ValueExpr.createNullExpr()))))
752             .build());
753 
754     List<Expr> formattedStringArgList = Arrays.asList(formattedStringArgExpr);
755     List<VariableExpr> formattedStringArgDeclList =
756         Arrays.asList(formattedStringArgExpr.toBuilder().setIsDecl(true).build());
757     boolean hasVariants = tokenHierarchies.size() > 1;
758     if (!hasVariants) {
759       List<Expr> methodArgs = Arrays.asList(formattedStringArgExpr, exceptionMessageExpr);
760       MethodInvocationExpr validatedMatchExpr =
761           MethodInvocationExpr.builder()
762               .setExprReferenceExpr(templateFinalVarExprs.get(0))
763               .setMethodName("validatedMatch")
764               .setArguments(methodArgs)
765               .setReturnType(mapStringType)
766               .build();
767 
768       AssignmentExpr matchMapAssignExpr =
769           AssignmentExpr.builder()
770               .setVariableExpr(matchMapVarExpr.toBuilder().setIsDecl(true).build())
771               .setValueExpr(validatedMatchExpr)
772               .build();
773       body.add(ExprStatement.withExpr(matchMapAssignExpr));
774 
775       List<Expr> ofMethodArgExprs =
776           tokenHierarchies.get(0).stream()
777               .map(
778                   t ->
779                       MethodInvocationExpr.builder()
780                           .setExprReferenceExpr(matchMapVarExpr)
781                           .setMethodName("get")
782                           .setArguments(
783                               Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(t))))
784                           .build())
785               .collect(Collectors.toList());
786 
787       MethodInvocationExpr ofMethodExpr =
788           MethodInvocationExpr.builder()
789               .setMethodName("of")
790               .setArguments(ofMethodArgExprs)
791               .setReturnType(thisClassType)
792               .build();
793       return MethodDefinition.builder()
794           .setScope(ScopeNode.PUBLIC)
795           .setIsStatic(true)
796           .setReturnType(thisClassType)
797           .setName("parse")
798           .setArguments(formattedStringArgDeclList)
799           .setBody(body)
800           .setReturnExpr(ofMethodExpr)
801           .build();
802     }
803 
804     IfStatement.Builder ifStatementBuilder = IfStatement.builder();
805     String ofMethodNamePattern = "of%sName";
806     for (int i = 0; i < tokenHierarchies.size(); i++) {
807       // PubSub special-case handling for the "_deleted-topic_" pattern.
808       boolean isDeletedTopicPattern =
809           tokenHierarchies.get(i).contains(ResourceNameConstants.DELETED_TOPIC_LITERAL);
810 
811       VariableExpr templateVarExpr = templateFinalVarExprs.get(i);
812       MethodInvocationExpr conditionExpr =
813           MethodInvocationExpr.builder()
814               .setExprReferenceExpr(templateVarExpr)
815               .setMethodName(isDeletedTopicPattern ? "equals" : "matches")
816               .setArguments(formattedStringArgList)
817               .setReturnType(TypeNode.BOOLEAN)
818               .build();
819 
820       MethodInvocationExpr matchValueExpr =
821           MethodInvocationExpr.builder()
822               .setExprReferenceExpr(templateVarExpr)
823               .setMethodName("match")
824               .setArguments(formattedStringArgList)
825               .setReturnType(mapStringType)
826               .build();
827       AssignmentExpr matchMapAssignExpr =
828           AssignmentExpr.builder()
829               .setVariableExpr(matchMapVarExpr.toBuilder().setIsDecl(true).build())
830               .setValueExpr(matchValueExpr)
831               .build();
832 
833       List<String> tokens = tokenHierarchies.get(i);
834       MethodInvocationExpr ofMethodExpr =
835           MethodInvocationExpr.builder()
836               .setMethodName(String.format(ofMethodNamePattern, concatToUpperCamelCaseName(tokens)))
837               .setArguments(
838                   tokens.stream()
839                       .map(
840                           t ->
841                               MethodInvocationExpr.builder()
842                                   .setExprReferenceExpr(matchMapVarExpr)
843                                   .setMethodName("get")
844                                   .setArguments(
845                                       Arrays.asList(
846                                           ValueExpr.withValue(StringObjectValue.withValue(t))))
847                                   .build())
848                       .collect(Collectors.toList()))
849               .setReturnType(thisClassType)
850               .build();
851 
852       ReturnExpr subReturnExpr = ReturnExpr.withExpr(ofMethodExpr);
853 
854       List<Statement> ifStatements =
855           Arrays.asList(matchMapAssignExpr, subReturnExpr).stream()
856               .map(e -> ExprStatement.withExpr(e))
857               .collect(Collectors.toList());
858       if (i == 0) {
859         ifStatementBuilder =
860             ifStatementBuilder.setConditionExpr(conditionExpr).setBody(ifStatements);
861       } else {
862         // PubSub special-case handling - clobber ifStatements if the current pattern is
863         // _deleted-topic_.
864         if (isDeletedTopicPattern) {
865           ifStatements.clear();
866           ifStatements.add(
867               ExprStatement.withExpr(
868                   ReturnExpr.withExpr(
869                       NewObjectExpr.builder()
870                           .setType(thisClassType)
871                           .setArguments(
872                               ValueExpr.withValue(
873                                   StringObjectValue.withValue(
874                                       ResourceNameConstants.DELETED_TOPIC_LITERAL)))
875                           .build())));
876         }
877         ifStatementBuilder = ifStatementBuilder.addElseIf(conditionExpr, ifStatements);
878       }
879     }
880 
881     body.add(ifStatementBuilder.build());
882     body.add(
883         ExprStatement.withExpr(
884             ThrowExpr.builder()
885                 .setType(FIXED_TYPESTORE.get("ValidationException"))
886                 .setMessageExpr(exceptionMessageString)
887                 .build()));
888     return MethodDefinition.builder()
889         .setScope(ScopeNode.PUBLIC)
890         .setIsStatic(true)
891         .setReturnType(thisClassType)
892         .setName("parse")
893         .setArguments(formattedStringArgDeclList)
894         .setBody(body)
895         .build();
896   }
897 
createParseListMethod(TypeNode thisClassType)898   private static MethodDefinition createParseListMethod(TypeNode thisClassType) {
899     TypeNode listStringType =
900         TypeNode.withReference(
901             ConcreteReference.builder()
902                 .setClazz(List.class)
903                 .setGenerics(Arrays.asList(ConcreteReference.withClazz(String.class)))
904                 .build());
905     TypeNode returnType =
906         TypeNode.withReference(
907             ConcreteReference.builder()
908                 .setClazz(List.class)
909                 .setGenerics(Arrays.asList(thisClassType.reference()))
910                 .build());
911 
912     VariableExpr formattedStringsVarExpr =
913         VariableExpr.withVariable(
914             Variable.builder().setName("formattedStrings").setType(listStringType).build());
915     VariableExpr listVarExpr =
916         VariableExpr.withVariable(Variable.builder().setName("list").setType(returnType).build());
917 
918     AssignmentExpr listAssignExpr =
919         AssignmentExpr.builder()
920             .setVariableExpr(listVarExpr.toBuilder().setIsDecl(true).build())
921             .setValueExpr(
922                 NewObjectExpr.builder()
923                     .setType(
924                         TypeNode.withReference(
925                             ConcreteReference.builder().setClazz(ArrayList.class).build()))
926                     .setIsGeneric(true)
927                     .setArguments(
928                         Arrays.asList(
929                             MethodInvocationExpr.builder()
930                                 .setExprReferenceExpr(formattedStringsVarExpr)
931                                 .setMethodName("size")
932                                 .build()))
933                     .build())
934             .build();
935 
936     VariableExpr singleStrVarExpr =
937         VariableExpr.withVariable(
938             Variable.builder().setName("formattedString").setType(TypeNode.STRING).build());
939     ForStatement forStatement =
940         ForStatement.builder()
941             .setLocalVariableExpr(singleStrVarExpr.toBuilder().setIsDecl(true).build())
942             .setCollectionExpr(formattedStringsVarExpr)
943             .setBody(
944                 Arrays.asList(
945                     ExprStatement.withExpr(
946                         MethodInvocationExpr.builder()
947                             .setExprReferenceExpr(listVarExpr)
948                             .setMethodName("add")
949                             .setArguments(
950                                 Arrays.asList(
951                                     MethodInvocationExpr.builder()
952                                         .setMethodName("parse")
953                                         .setArguments(Arrays.asList(singleStrVarExpr))
954                                         .build()))
955                             .build())))
956             .build();
957 
958     return MethodDefinition.builder()
959         .setScope(ScopeNode.PUBLIC)
960         .setIsStatic(true)
961         .setReturnType(returnType)
962         .setName("parseList")
963         .setArguments(Arrays.asList(formattedStringsVarExpr.toBuilder().setIsDecl(true).build()))
964         .setBody(Arrays.asList(ExprStatement.withExpr(listAssignExpr), forStatement))
965         .setReturnExpr(listVarExpr)
966         .build();
967   }
968 
createToStringListMethod(TypeNode thisClassType)969   private static MethodDefinition createToStringListMethod(TypeNode thisClassType) {
970     TypeNode listClassType =
971         TypeNode.withReference(
972             ConcreteReference.builder()
973                 .setClazz(List.class)
974                 .setGenerics(Arrays.asList(thisClassType.reference()))
975                 .build());
976     VariableExpr valuesVarExpr =
977         VariableExpr.withVariable(
978             Variable.builder().setName("values").setType(listClassType).build());
979 
980     TypeNode listStringType =
981         TypeNode.withReference(
982             ConcreteReference.builder()
983                 .setClazz(List.class)
984                 .setGenerics(Arrays.asList(ConcreteReference.withClazz(String.class)))
985                 .build());
986     VariableExpr listVarExpr =
987         VariableExpr.withVariable(
988             Variable.builder().setName("list").setType(listStringType).build());
989 
990     AssignmentExpr listAssignExpr =
991         AssignmentExpr.builder()
992             .setVariableExpr(listVarExpr.toBuilder().setIsDecl(true).build())
993             .setValueExpr(
994                 NewObjectExpr.builder()
995                     .setType(TypeNode.withReference(ConcreteReference.withClazz(ArrayList.class)))
996                     .setIsGeneric(true)
997                     .setArguments(
998                         Arrays.asList(
999                             MethodInvocationExpr.builder()
1000                                 .setExprReferenceExpr(valuesVarExpr)
1001                                 .setMethodName("size")
1002                                 .build()))
1003                     .build())
1004             .build();
1005 
1006     VariableExpr valueVarExpr =
1007         VariableExpr.withVariable(
1008             Variable.builder().setName("value").setType(thisClassType).build());
1009     // We use an equality check instead of Objects.isNull() for Java 7 compatibility.
1010     Expr isNullCheck =
1011         RelationalOperationExpr.equalToWithExprs(valueVarExpr, ValueExpr.createNullExpr());
1012     Statement listAddEmptyStringStatement =
1013         ExprStatement.withExpr(
1014             MethodInvocationExpr.builder()
1015                 .setExprReferenceExpr(listVarExpr)
1016                 .setMethodName("add")
1017                 .setArguments(Arrays.asList(ValueExpr.withValue(StringObjectValue.withValue(""))))
1018                 .build());
1019 
1020     Statement listAddValueStatement =
1021         ExprStatement.withExpr(
1022             MethodInvocationExpr.builder()
1023                 .setExprReferenceExpr(listVarExpr)
1024                 .setMethodName("add")
1025                 .setArguments(
1026                     Arrays.asList(
1027                         MethodInvocationExpr.builder()
1028                             .setExprReferenceExpr(valueVarExpr)
1029                             .setMethodName("toString")
1030                             .build()))
1031                 .build());
1032 
1033     IfStatement ifStatement =
1034         IfStatement.builder()
1035             .setConditionExpr(isNullCheck)
1036             .setBody(Arrays.asList(listAddEmptyStringStatement))
1037             .setElseBody(Arrays.asList(listAddValueStatement))
1038             .build();
1039     ForStatement forStatement =
1040         ForStatement.builder()
1041             .setLocalVariableExpr(valueVarExpr.toBuilder().setIsDecl(true).build())
1042             .setCollectionExpr(valuesVarExpr)
1043             .setBody(Arrays.asList(ifStatement))
1044             .build();
1045 
1046     return MethodDefinition.builder()
1047         .setScope(ScopeNode.PUBLIC)
1048         .setIsStatic(true)
1049         .setReturnType(listStringType)
1050         .setName("toStringList")
1051         .setArguments(Arrays.asList(valuesVarExpr.toBuilder().setIsDecl(true).build()))
1052         .setBody(Arrays.asList(ExprStatement.withExpr(listAssignExpr), forStatement))
1053         .setReturnExpr(listVarExpr)
1054         .build();
1055   }
1056 
createIsParseableFromMethod( List<VariableExpr> templateFinalVarExprs)1057   private static MethodDefinition createIsParseableFromMethod(
1058       List<VariableExpr> templateFinalVarExprs) {
1059     VariableExpr formattedStringVarExpr =
1060         VariableExpr.withVariable(
1061             Variable.builder().setName("formattedString").setType(TypeNode.STRING).build());
1062     Expr returnOrExpr =
1063         MethodInvocationExpr.builder()
1064             .setExprReferenceExpr(templateFinalVarExprs.get(0))
1065             .setMethodName("matches")
1066             .setArguments(Arrays.asList(formattedStringVarExpr))
1067             .setReturnType(TypeNode.BOOLEAN)
1068             .build();
1069     for (int i = 1; i < templateFinalVarExprs.size(); i++) {
1070       VariableExpr templateVarExpr = templateFinalVarExprs.get(i);
1071       returnOrExpr =
1072           LogicalOperationExpr.logicalOrWithExprs(
1073               returnOrExpr,
1074               MethodInvocationExpr.builder()
1075                   .setExprReferenceExpr(templateVarExpr)
1076                   .setMethodName(
1077                       // PubSub special-case handling for the _deleted-topic_ pattern.
1078                       templateVarExpr.variable().identifier().name().equals("DELETED_TOPIC")
1079                           ? "equals"
1080                           : "matches")
1081                   .setArguments(Arrays.asList(formattedStringVarExpr))
1082                   .setReturnType(TypeNode.BOOLEAN)
1083                   .build());
1084     }
1085 
1086     return MethodDefinition.builder()
1087         .setScope(ScopeNode.PUBLIC)
1088         .setIsStatic(true)
1089         .setReturnType(TypeNode.BOOLEAN)
1090         .setName("isParsableFrom")
1091         .setArguments(Arrays.asList(formattedStringVarExpr.toBuilder().setIsDecl(true).build()))
1092         .setReturnExpr(returnOrExpr)
1093         .build();
1094   }
1095 
createFieldValueGetterMethods( ResourceName resourceName, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies, TypeStore typeStore)1096   private static List<MethodDefinition> createFieldValueGetterMethods(
1097       ResourceName resourceName,
1098       Map<String, VariableExpr> patternTokenVarExprs,
1099       List<List<String>> tokenHierarchies,
1100       TypeStore typeStore) {
1101     List<MethodDefinition> javaMethods = new ArrayList<>();
1102     TypeNode thisClassType = typeStore.get(getThisClassName(resourceName));
1103     javaMethods.add(
1104         createGetFieldValuesMapMethod(
1105             resourceName, thisClassType, patternTokenVarExprs, tokenHierarchies));
1106     javaMethods.add(createGetFieldValueMethod());
1107     return javaMethods;
1108   }
1109 
createGetFieldValuesMapMethod( ResourceName resourceName, TypeNode thisClassType, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies)1110   private static MethodDefinition createGetFieldValuesMapMethod(
1111       ResourceName resourceName,
1112       TypeNode thisClassType,
1113       Map<String, VariableExpr> patternTokenVarExprs,
1114       List<List<String>> tokenHierarchies) {
1115 
1116     Reference strRef = TypeNode.STRING.reference();
1117     TypeNode mapBuilderType =
1118         TypeNode.withReference(
1119             ConcreteReference.builder()
1120                 .setClazz(ImmutableMap.Builder.class)
1121                 .setGenerics(Arrays.asList(strRef, strRef))
1122                 .build());
1123     VariableExpr fieldMapBuilderVarExpr =
1124         VariableExpr.withVariable(
1125             Variable.builder().setName("fieldMapBuilder").setType(mapBuilderType).build());
1126 
1127     AssignmentExpr builderAssignExpr =
1128         AssignmentExpr.builder()
1129             .setVariableExpr(fieldMapBuilderVarExpr.toBuilder().setIsDecl(true).build())
1130             .setValueExpr(
1131                 MethodInvocationExpr.builder()
1132                     .setStaticReferenceType(FIXED_TYPESTORE.get("ImmutableMap"))
1133                     .setMethodName("builder")
1134                     .setReturnType(mapBuilderType)
1135                     .build())
1136             .build();
1137 
1138     // Special-cased PubSub handling.
1139     List<List<String>> processedTokenHierarchies =
1140         tokenHierarchies.stream()
1141             .filter(tokens -> !tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
1142             .collect(Collectors.toList());
1143 
1144     // Innermost if-blocks.
1145     List<Statement> tokenIfStatements = new ArrayList<>();
1146     for (String token : getTokenSet(processedTokenHierarchies)) {
1147       VariableExpr tokenVarExpr = patternTokenVarExprs.get(token);
1148       Preconditions.checkNotNull(
1149           tokenVarExpr,
1150           String.format("No variable found for %s among %s", token, patternTokenVarExprs.keySet()));
1151       StringObjectValue tokenStrVal = StringObjectValue.withValue(token);
1152       MethodInvocationExpr putExpr =
1153           MethodInvocationExpr.builder()
1154               .setExprReferenceExpr(fieldMapBuilderVarExpr)
1155               .setMethodName("put")
1156               .setArguments(ValueExpr.withValue(tokenStrVal), tokenVarExpr)
1157               .build();
1158       Expr notNullCheckExpr =
1159           RelationalOperationExpr.notEqualToWithExprs(tokenVarExpr, ValueExpr.createNullExpr());
1160       tokenIfStatements.add(
1161           IfStatement.builder()
1162               .setConditionExpr(notNullCheckExpr)
1163               .setBody(Arrays.asList(ExprStatement.withExpr(putExpr)))
1164               .build());
1165     }
1166 
1167     // Put the innermost if-statements and assignment expressions together.
1168     VariableExpr fieldValuesMapVarExpr = FIXED_CLASS_VARS.get("fieldValuesMap");
1169     AssignmentExpr fieldValuesMapAssignExpr =
1170         AssignmentExpr.builder()
1171             .setVariableExpr(fieldValuesMapVarExpr)
1172             .setValueExpr(
1173                 MethodInvocationExpr.builder()
1174                     .setExprReferenceExpr(fieldMapBuilderVarExpr)
1175                     .setMethodName("build")
1176                     .setReturnType(fieldValuesMapVarExpr.type())
1177                     .build())
1178             .build();
1179 
1180     List<Statement> middleIfBlockStatements = new ArrayList<>();
1181     middleIfBlockStatements.add(ExprStatement.withExpr(builderAssignExpr));
1182     middleIfBlockStatements.addAll(tokenIfStatements);
1183     middleIfBlockStatements.add(ExprStatement.withExpr(fieldValuesMapAssignExpr));
1184 
1185     // Middle if-block, i.e. `if (fieldValuesMap == null)`.
1186     Expr fieldValuesMapNullCheckExpr =
1187         RelationalOperationExpr.equalToWithExprs(fieldValuesMapVarExpr, ValueExpr.createNullExpr());
1188     IfStatement fieldValuesMapIfStatement =
1189         IfStatement.builder()
1190             .setConditionExpr(fieldValuesMapNullCheckExpr)
1191             .setBody(middleIfBlockStatements)
1192             .build();
1193 
1194     // Outer if-block.
1195     IfStatement outerIfStatement =
1196         IfStatement.builder()
1197             .setConditionExpr(fieldValuesMapNullCheckExpr)
1198             .setBody(
1199                 Arrays.asList(
1200                     SynchronizedStatement.builder()
1201                         .setLock(ThisObjectValue.withType(thisClassType))
1202                         .setBody(Arrays.asList(fieldValuesMapIfStatement))
1203                         .build()))
1204             .build();
1205 
1206     // Put the method together.
1207     TypeNode mapStringType = fieldValuesMapVarExpr.type();
1208     return MethodDefinition.builder()
1209         .setIsOverride(true)
1210         .setScope(ScopeNode.PUBLIC)
1211         .setReturnType(mapStringType)
1212         .setName("getFieldValuesMap")
1213         .setBody(Arrays.asList(outerIfStatement))
1214         .setReturnExpr(fieldValuesMapVarExpr)
1215         .build();
1216   }
1217 
createGetFieldValueMethod()1218   private static MethodDefinition createGetFieldValueMethod() {
1219     VariableExpr fieldNameVarExpr =
1220         VariableExpr.withVariable(
1221             Variable.builder().setName("fieldName").setType(TypeNode.STRING).build());
1222     MethodInvocationExpr returnExpr =
1223         MethodInvocationExpr.builder().setMethodName("getFieldValuesMap").build();
1224     returnExpr =
1225         MethodInvocationExpr.builder()
1226             .setExprReferenceExpr(returnExpr)
1227             .setMethodName("get")
1228             .setArguments(fieldNameVarExpr)
1229             .setReturnType(TypeNode.STRING)
1230             .build();
1231     return MethodDefinition.builder()
1232         .setScope(ScopeNode.PUBLIC)
1233         .setReturnType(TypeNode.STRING)
1234         .setName("getFieldValue")
1235         .setArguments(fieldNameVarExpr.toBuilder().setIsDecl(true).build())
1236         .setReturnExpr(returnExpr)
1237         .build();
1238   }
1239 
createToStringMethod( List<VariableExpr> templateFinalVarExprs, Map<String, VariableExpr> patternTokenVarExprs, List<List<String>> tokenHierarchies)1240   private static MethodDefinition createToStringMethod(
1241       List<VariableExpr> templateFinalVarExprs,
1242       Map<String, VariableExpr> patternTokenVarExprs,
1243       List<List<String>> tokenHierarchies) {
1244     boolean hasVariants = tokenHierarchies.size() > 1;
1245     if (!hasVariants) {
1246 
1247       List<Expr> instantiateArgExprs = new ArrayList<>();
1248       List<String> tokens = getTokenSet(tokenHierarchies).stream().collect(Collectors.toList());
1249       for (String token : tokens) {
1250         Preconditions.checkNotNull(
1251             patternTokenVarExprs.get(token),
1252             String.format(
1253                 "No expression found for token %s amongst values %s",
1254                 token, patternTokenVarExprs.toString()));
1255         instantiateArgExprs.add(ValueExpr.withValue(StringObjectValue.withValue(token)));
1256         instantiateArgExprs.add(patternTokenVarExprs.get(token));
1257       }
1258 
1259       MethodInvocationExpr returnInstantiateExpr =
1260           MethodInvocationExpr.builder()
1261               .setExprReferenceExpr(templateFinalVarExprs.get(0))
1262               .setMethodName("instantiate")
1263               .setArguments(instantiateArgExprs)
1264               .setReturnType(TypeNode.STRING)
1265               .build();
1266       return MethodDefinition.builder()
1267           .setIsOverride(true)
1268           .setScope(ScopeNode.PUBLIC)
1269           .setReturnType(TypeNode.STRING)
1270           .setName("toString")
1271           .setReturnExpr(returnInstantiateExpr)
1272           .build();
1273     }
1274 
1275     VariableExpr fixedValueVarExpr = FIXED_CLASS_VARS.get("fixedValue");
1276     // Code:  return fixedValue != null ? fixedValue : pathTemplate.instantiate(getFieldValuesMap())
1277     Expr fixedValueNullCheck =
1278         RelationalOperationExpr.notEqualToWithExprs(fixedValueVarExpr, ValueExpr.createNullExpr());
1279 
1280     MethodInvocationExpr instantiateExpr =
1281         MethodInvocationExpr.builder()
1282             .setExprReferenceExpr(FIXED_CLASS_VARS.get("pathTemplate"))
1283             .setMethodName("instantiate")
1284             .setArguments(MethodInvocationExpr.builder().setMethodName("getFieldValuesMap").build())
1285             .setReturnType(TypeNode.STRING)
1286             .build();
1287 
1288     TernaryExpr returnExpr =
1289         TernaryExpr.builder()
1290             .setConditionExpr(fixedValueNullCheck)
1291             .setElseExpr(instantiateExpr)
1292             .setThenExpr(fixedValueVarExpr)
1293             .build();
1294 
1295     return MethodDefinition.builder()
1296         .setIsOverride(true)
1297         .setScope(ScopeNode.PUBLIC)
1298         .setReturnType(TypeNode.STRING)
1299         .setName("toString")
1300         .setReturnExpr(returnExpr)
1301         .build();
1302   }
1303 
createEqualsMethod( ResourceName resourceName, List<List<String>> tokenHierarchies, TypeStore typeStore)1304   private static MethodDefinition createEqualsMethod(
1305       ResourceName resourceName, List<List<String>> tokenHierarchies, TypeStore typeStore) {
1306     // Create method definition variables.
1307     Variable oVariable =
1308         Variable.builder()
1309             .setType(TypeNode.withReference(javaObjectReference))
1310             .setName("o")
1311             .build();
1312     VariableExpr argVarExpr =
1313         VariableExpr.builder().setIsDecl(false).setVariable(oVariable).build();
1314     TypeNode thisClassType = typeStore.get(getThisClassName(resourceName));
1315     ValueExpr thisValueExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType));
1316     ValueExpr trueValueExpr =
1317         ValueExpr.withValue(
1318             PrimitiveValue.builder().setType(TypeNode.BOOLEAN).setValue("true").build());
1319 
1320     // Create first if statement's return expression
1321     ReturnExpr returnTrueExpr = ReturnExpr.withExpr(trueValueExpr);
1322 
1323     // Create second if statement's condition expression
1324     RelationalOperationExpr oEqualsThisExpr =
1325         RelationalOperationExpr.equalToWithExprs(argVarExpr, thisValueExpr);
1326     RelationalOperationExpr oNotEqualsNullExpr =
1327         RelationalOperationExpr.notEqualToWithExprs(argVarExpr, ValueExpr.createNullExpr());
1328     MethodInvocationExpr getClassMethodInvocationExpr =
1329         MethodInvocationExpr.builder().setMethodName("getClass").build();
1330     RelationalOperationExpr getClassEqualsExpr =
1331         RelationalOperationExpr.equalToWithExprs(
1332             getClassMethodInvocationExpr,
1333             getClassMethodInvocationExpr.toBuilder().setExprReferenceExpr(argVarExpr).build());
1334     LogicalOperationExpr orLogicalExpr =
1335         LogicalOperationExpr.logicalOrWithExprs(oNotEqualsNullExpr, getClassEqualsExpr);
1336 
1337     // Create second if statement's body assignment expression.
1338     Variable thatVariable = Variable.builder().setName("that").setType(thisClassType).build();
1339     VariableExpr thatVariableExpr =
1340         VariableExpr.builder().setIsDecl(false).setVariable(thatVariable).build();
1341     CastExpr oCastExpr = CastExpr.builder().setExpr(argVarExpr).setType(thisClassType).build();
1342     AssignmentExpr thatAssignmentExpr =
1343         AssignmentExpr.builder()
1344             .setVariableExpr(thatVariableExpr.toBuilder().setIsDecl(true).build())
1345             .setValueExpr(oCastExpr)
1346             .build();
1347 
1348     // PubSub special-case handling - exclude _deleted-topic_.
1349     List<List<String>> processedTokenHierarchies =
1350         tokenHierarchies.stream()
1351             .filter(ts -> !ts.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
1352             .collect(Collectors.toList());
1353 
1354     // Create return expression in the second if statement's body.
1355     Set<String> tokenSet = getTokenSet(processedTokenHierarchies);
1356     Iterator<String> itToken = tokenSet.iterator();
1357     Expr curTokenExpr =
1358         createObjectsEqualsForTokenMethodExpr(
1359             thisValueExpr,
1360             thatVariableExpr,
1361             Variable.builder()
1362                 .setType(TypeNode.STRING)
1363                 .setName(JavaStyle.toLowerCamelCase(itToken.next()))
1364                 .build());
1365     while (itToken.hasNext()) {
1366       Expr nextTokenExpr =
1367           createObjectsEqualsForTokenMethodExpr(
1368               thisValueExpr,
1369               thatVariableExpr,
1370               Variable.builder()
1371                   .setType(TypeNode.STRING)
1372                   .setName(JavaStyle.toLowerCamelCase(itToken.next()))
1373                   .build());
1374       curTokenExpr = LogicalOperationExpr.logicalAndWithExprs(curTokenExpr, nextTokenExpr);
1375     }
1376     ReturnExpr secondIfReturnExpr = ReturnExpr.withExpr(curTokenExpr);
1377 
1378     // Code: if (o == this) { return true;}
1379     IfStatement firstIfStatement =
1380         IfStatement.builder()
1381             .setConditionExpr(oEqualsThisExpr)
1382             .setBody(Arrays.asList(ExprStatement.withExpr(returnTrueExpr)))
1383             .build();
1384     // Code: if (o != null || getClass() == o.getClass()) { FoobarName that = ((FoobarName) o);
1385     // return ..}
1386     IfStatement secondIfStatement =
1387         IfStatement.builder()
1388             .setConditionExpr(orLogicalExpr)
1389             .setBody(
1390                 Arrays.asList(
1391                     ExprStatement.withExpr(thatAssignmentExpr),
1392                     ExprStatement.withExpr(secondIfReturnExpr)))
1393             .build();
1394 
1395     // Create method's return expression.
1396     ValueExpr falseValueExpr =
1397         ValueExpr.withValue(
1398             PrimitiveValue.builder().setType(TypeNode.BOOLEAN).setValue("false").build());
1399 
1400     return MethodDefinition.builder()
1401         .setIsOverride(true)
1402         .setScope(ScopeNode.PUBLIC)
1403         .setArguments(argVarExpr.toBuilder().setIsDecl(true).build())
1404         .setReturnType(TypeNode.BOOLEAN)
1405         .setName("equals")
1406         .setReturnExpr(falseValueExpr)
1407         .setBody(Arrays.asList(firstIfStatement, secondIfStatement))
1408         .build();
1409   }
1410 
createObjectsEqualsForTokenMethodExpr( Expr thisExpr, Expr thatExpr, Variable tokenVar)1411   private static MethodInvocationExpr createObjectsEqualsForTokenMethodExpr(
1412       Expr thisExpr, Expr thatExpr, Variable tokenVar) {
1413     VariableExpr varThisExpr =
1414         VariableExpr.builder().setVariable(tokenVar).setExprReferenceExpr(thisExpr).build();
1415     VariableExpr varThatExpr =
1416         VariableExpr.builder().setVariable(tokenVar).setExprReferenceExpr(thatExpr).build();
1417     return MethodInvocationExpr.builder()
1418         .setStaticReferenceType(FIXED_TYPESTORE.get("Objects"))
1419         .setMethodName("equals")
1420         .setArguments(Arrays.asList(varThisExpr, varThatExpr))
1421         .setReturnType(TypeNode.BOOLEAN)
1422         .build();
1423   }
1424 
createHashCodeMethod(List<List<String>> tokenHierarchies)1425   private static MethodDefinition createHashCodeMethod(List<List<String>> tokenHierarchies) {
1426     List<Statement> assignmentBody = new ArrayList<>();
1427     // code: int h = 1;
1428     Variable hVar = Variable.builder().setType(TypeNode.INT).setName("h").build();
1429     VariableExpr hVarExpr = VariableExpr.builder().setVariable(hVar).build();
1430     ValueExpr hValueExpr =
1431         ValueExpr.withValue(PrimitiveValue.builder().setType(TypeNode.INT).setValue("1").build());
1432     AssignmentExpr hAssignmentExpr =
1433         AssignmentExpr.builder()
1434             .setVariableExpr(hVarExpr.toBuilder().setIsDecl(true).build())
1435             .setValueExpr(hValueExpr)
1436             .build();
1437     assignmentBody.add(ExprStatement.withExpr(hAssignmentExpr));
1438     // code: h *= 1000003;
1439     // code: h ^= Objects.hashCode(...);
1440     ValueExpr numValueExpr =
1441         ValueExpr.withValue(
1442             PrimitiveValue.builder().setType(TypeNode.INT).setValue("1000003").build());
1443     AssignmentOperationExpr multiplyAssignmentOpExpr =
1444         AssignmentOperationExpr.multiplyAssignmentWithExprs(hVarExpr, numValueExpr);
1445 
1446     // PubSub special-case handling - exclude _deleted-topic_.
1447     List<List<String>> processedTokenHierarchies =
1448         tokenHierarchies.stream()
1449             .filter(ts -> !ts.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
1450             .collect(Collectors.toList());
1451 
1452     // If it has variants, add the multiply and xor assignment operation exprs for fixedValue.
1453     boolean hasVariants = processedTokenHierarchies.size() > 1;
1454     if (hasVariants) {
1455       VariableExpr fixedValueVarExpr = FIXED_CLASS_VARS.get("fixedValue");
1456       assignmentBody.add(ExprStatement.withExpr(multiplyAssignmentOpExpr));
1457       assignmentBody.add(
1458           ExprStatement.withExpr(
1459               AssignmentOperationExpr.xorAssignmentWithExprs(
1460                   hVarExpr, createObjectsHashCodeForVarMethod(fixedValueVarExpr))));
1461     }
1462     // Add the multiply and xor assignment operation exprs for tokens.
1463     Set<String> tokenSet = getTokenSet(processedTokenHierarchies);
1464     tokenSet.stream()
1465         .forEach(
1466             token -> {
1467               VariableExpr tokenVarExpr =
1468                   VariableExpr.withVariable(
1469                       Variable.builder()
1470                           .setName(JavaStyle.toLowerCamelCase(token))
1471                           .setType(TypeNode.STRING)
1472                           .build());
1473               assignmentBody.add(ExprStatement.withExpr(multiplyAssignmentOpExpr));
1474               assignmentBody.add(
1475                   ExprStatement.withExpr(
1476                       AssignmentOperationExpr.xorAssignmentWithExprs(
1477                           hVarExpr, createObjectsHashCodeForVarMethod(tokenVarExpr))));
1478             });
1479 
1480     return MethodDefinition.builder()
1481         .setIsOverride(true)
1482         .setScope(ScopeNode.PUBLIC)
1483         .setReturnType(TypeNode.INT)
1484         .setName("hashCode")
1485         .setBody(assignmentBody)
1486         .setReturnExpr(hVarExpr)
1487         .build();
1488   }
1489 
createObjectsHashCodeForVarMethod(VariableExpr varExpr)1490   private static MethodInvocationExpr createObjectsHashCodeForVarMethod(VariableExpr varExpr) {
1491     // code: Objects.hashCode(varExpr)
1492     return MethodInvocationExpr.builder()
1493         .setMethodName("hashCode")
1494         .setStaticReferenceType(FIXED_TYPESTORE.get("Objects"))
1495         .setArguments(varExpr)
1496         .setReturnType(TypeNode.INT)
1497         .build();
1498   }
1499 
createNestedBuilderClasses( ResourceName resourceName, List<List<String>> tokenHierarchies, List<VariableExpr> templateFinalVarExprs, TypeStore typeStore)1500   private static List<ClassDefinition> createNestedBuilderClasses(
1501       ResourceName resourceName,
1502       List<List<String>> tokenHierarchies,
1503       List<VariableExpr> templateFinalVarExprs,
1504       TypeStore typeStore) {
1505     String thisClassName = getThisClassName(resourceName);
1506     TypeNode outerThisClassType = typeStore.get(thisClassName);
1507     boolean hasVariants = tokenHierarchies.size() > 1;
1508     List<ClassDefinition> nestedClasses = new ArrayList<>();
1509     for (int i = 0; i < tokenHierarchies.size(); i++) {
1510       List<String> tokens = tokenHierarchies.get(i);
1511       // PubSub special-case handling.
1512       if (tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL)) {
1513         continue;
1514       }
1515       nestedClasses.add(
1516           createNestedBuilderClass(
1517               outerThisClassType,
1518               tokens,
1519               templateFinalVarExprs.get(i),
1520               resourceName.patterns().get(i),
1521               typeStore,
1522               hasVariants,
1523               i == 0));
1524     }
1525     return nestedClasses;
1526   }
1527 
createNestedBuilderClass( TypeNode outerClassType, List<String> tokens, VariableExpr templateFinalVarExpr, String resourceNamePattern, TypeStore typeStore, boolean hasVariants, boolean isDefaultClass)1528   private static ClassDefinition createNestedBuilderClass(
1529       TypeNode outerClassType,
1530       List<String> tokens,
1531       VariableExpr templateFinalVarExpr,
1532       String resourceNamePattern,
1533       TypeStore typeStore,
1534       boolean hasVariants,
1535       boolean isDefaultClass) {
1536     String className = isDefaultClass ? "Builder" : getBuilderTypeName(tokens);
1537     // Class member declarations.
1538     List<VariableExpr> classMemberVarExprs =
1539         tokens.stream()
1540             .map(
1541                 t ->
1542                     VariableExpr.withVariable(
1543                         Variable.builder()
1544                             .setName(JavaStyle.toLowerCamelCase(t))
1545                             .setType(TypeNode.STRING)
1546                             .build()))
1547             .collect(Collectors.toList());
1548     List<Statement> classMemberDecls =
1549         classMemberVarExprs.stream()
1550             .map(
1551                 v ->
1552                     ExprStatement.withExpr(
1553                         v.toBuilder().setIsDecl(true).setScope(ScopeNode.PRIVATE).build()))
1554             .collect(Collectors.toList());
1555 
1556     // Constructor.
1557     List<MethodDefinition> nestedClassMethods = new ArrayList<>();
1558     TypeNode thisClassType = typeStore.get(className);
1559     MethodDefinition ctor =
1560         MethodDefinition.constructorBuilder()
1561             .setScope(ScopeNode.PROTECTED)
1562             .setReturnType(thisClassType)
1563             .build();
1564     nestedClassMethods.add(ctor);
1565 
1566     // Getters and setters.
1567     List<MethodDefinition> getterMethods = new ArrayList<>();
1568     List<MethodDefinition> setterMethods = new ArrayList<>();
1569     ValueExpr thisExpr = ValueExpr.withValue(ThisObjectValue.withType(thisClassType));
1570     for (int i = 0; i < tokens.size(); i++) {
1571       String token = tokens.get(i);
1572       String upperCamelTokenName = JavaStyle.toUpperCamelCase(token);
1573       VariableExpr currClassTokenVarExpr = classMemberVarExprs.get(i);
1574 
1575       // Getter.
1576       MethodDefinition getterMethod =
1577           MethodDefinition.builder()
1578               .setScope(ScopeNode.PUBLIC)
1579               .setReturnType(TypeNode.STRING)
1580               .setName(String.format("get%s", upperCamelTokenName))
1581               .setReturnExpr(currClassTokenVarExpr)
1582               .build();
1583       getterMethods.add(getterMethod);
1584 
1585       // Setter.
1586       VariableExpr tokenArgVarExpr = currClassTokenVarExpr;
1587       AssignmentExpr fieldAssignExpr =
1588           AssignmentExpr.builder()
1589               .setVariableExpr(
1590                   currClassTokenVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build())
1591               .setValueExpr(tokenArgVarExpr)
1592               .build();
1593       MethodDefinition setterMethod =
1594           MethodDefinition.builder()
1595               .setScope(ScopeNode.PUBLIC)
1596               .setReturnType(thisClassType)
1597               .setName(String.format("set%s", upperCamelTokenName))
1598               .setArguments(classMemberVarExprs.get(i).toBuilder().setIsDecl(true).build())
1599               .setBody(Arrays.asList(ExprStatement.withExpr(fieldAssignExpr)))
1600               .setReturnExpr(thisExpr)
1601               .build();
1602       setterMethods.add(setterMethod);
1603     }
1604 
1605     nestedClassMethods.addAll(getterMethods);
1606     nestedClassMethods.addAll(setterMethods);
1607 
1608     // Private builder constructor method.
1609     if (isDefaultClass) {
1610       VariableExpr outerClassVarExpr =
1611           VariableExpr.withVariable(
1612               Variable.builder()
1613                   .setName(JavaStyle.toLowerCamelCase(outerClassType.reference().name()))
1614                   .setType(outerClassType)
1615                   .build());
1616       List<Expr> builderCtorBodyExprs = new ArrayList<>();
1617 
1618       if (hasVariants) {
1619         // TODO(miraleung): Use eq operator instead.
1620         MethodInvocationExpr equalsCheckExpr =
1621             MethodInvocationExpr.builder()
1622                 .setStaticReferenceType(FIXED_TYPESTORE.get("Objects"))
1623                 .setMethodName("equals")
1624                 .setArguments(
1625                     FIXED_CLASS_VARS
1626                         .get("pathTemplate")
1627                         .toBuilder()
1628                         .setExprReferenceExpr(outerClassVarExpr)
1629                         .build(),
1630                     templateFinalVarExpr)
1631                 .build();
1632 
1633         builderCtorBodyExprs.add(
1634             MethodInvocationExpr.builder()
1635                 .setStaticReferenceType(FIXED_TYPESTORE.get("Preconditions"))
1636                 .setMethodName("checkArgument")
1637                 .setArguments(
1638                     equalsCheckExpr,
1639                     ValueExpr.withValue(
1640                         StringObjectValue.withValue(
1641                             String.format(
1642                                 "toBuilder is only supported when %s has the pattern of %s",
1643                                 outerClassType.reference().name(), resourceNamePattern))))
1644                 .build());
1645       }
1646 
1647       for (VariableExpr memberVarExpr : classMemberVarExprs) {
1648         VariableExpr currClassTokenVarExpr =
1649             memberVarExpr.toBuilder().setExprReferenceExpr(thisExpr).build();
1650         builderCtorBodyExprs.add(
1651             AssignmentExpr.builder()
1652                 .setVariableExpr(currClassTokenVarExpr)
1653                 .setValueExpr(
1654                     currClassTokenVarExpr
1655                         .toBuilder()
1656                         .setExprReferenceExpr(outerClassVarExpr)
1657                         .build())
1658                 .build());
1659       }
1660 
1661       MethodDefinition fromOuterTypeCtor =
1662           MethodDefinition.constructorBuilder()
1663               .setScope(ScopeNode.PRIVATE)
1664               .setReturnType(thisClassType)
1665               .setArguments(outerClassVarExpr.toBuilder().setIsDecl(true).build())
1666               .setBody(
1667                   builderCtorBodyExprs.stream()
1668                       .map(e -> ExprStatement.withExpr(e))
1669                       .collect(Collectors.toList()))
1670               .build();
1671       nestedClassMethods.add(fromOuterTypeCtor);
1672     }
1673 
1674     // Last build() method.
1675     MethodDefinition buildMethod =
1676         MethodDefinition.builder()
1677             .setScope(ScopeNode.PUBLIC)
1678             .setReturnType(outerClassType)
1679             .setName("build")
1680             .setReturnExpr(
1681                 NewObjectExpr.builder().setType(outerClassType).setArguments(thisExpr).build())
1682             .build();
1683     nestedClassMethods.add(buildMethod);
1684 
1685     // Return the class.
1686     AnnotationNode betaAnnotation =
1687         AnnotationNode.builder()
1688             .setType(FIXED_TYPESTORE.get("BetaApi"))
1689             .setDescription(
1690                 "The per-pattern Builders are not stable yet and may be changed in the future.")
1691             .build();
1692     List<AnnotationNode> classAnnotations =
1693         isDefaultClass ? Collections.emptyList() : Arrays.asList(betaAnnotation);
1694 
1695     return ClassDefinition.builder()
1696         .setHeaderCommentStatements(
1697             CommentStatement.withComment(
1698                 JavaDocComment.withComment(
1699                     String.format(BUILDER_CLASS_HEADER_PATTERN, resourceNamePattern))))
1700         .setAnnotations(classAnnotations)
1701         .setIsNested(true)
1702         .setScope(ScopeNode.PUBLIC)
1703         .setIsStatic(true)
1704         .setName(className)
1705         .setStatements(classMemberDecls)
1706         .setMethods(nestedClassMethods)
1707         .build();
1708   }
1709 
createStaticTypes()1710   private static TypeStore createStaticTypes() {
1711     List<Class<?>> concreteClazzes =
1712         Arrays.asList(
1713             ArrayList.class,
1714             BetaApi.class,
1715             Generated.class,
1716             ImmutableMap.class,
1717             List.class,
1718             Map.class,
1719             Objects.class,
1720             PathTemplate.class,
1721             Preconditions.class,
1722             com.google.api.resourcenames.ResourceName.class,
1723             ValidationException.class);
1724     return new TypeStore(concreteClazzes);
1725   }
1726 
createDynamicTypes( ResourceName resourceName, List<List<String>> tokenHierarchies)1727   private static TypeStore createDynamicTypes(
1728       ResourceName resourceName, List<List<String>> tokenHierarchies) {
1729     String thisClassName = getThisClassName(resourceName);
1730     TypeStore typeStore = new TypeStore();
1731     typeStore.put(resourceName.pakkage(), thisClassName);
1732     typeStore.put(resourceName.pakkage(), "Builder", true, thisClassName);
1733 
1734     // Special-cased PubSub handling.
1735     List<List<String>> processedTokenHierarchies =
1736         tokenHierarchies.stream()
1737             .filter(tokens -> !tokens.contains(ResourceNameConstants.DELETED_TOPIC_LITERAL))
1738             .collect(Collectors.toList());
1739 
1740     if (processedTokenHierarchies.size() > 1) {
1741       typeStore.putAll(
1742           resourceName.pakkage(),
1743           tokenHierarchies.subList(1, tokenHierarchies.size()).stream()
1744               .map(ts -> getBuilderTypeName(ts))
1745               .collect(Collectors.toList()));
1746     }
1747     return typeStore;
1748   }
1749 
createFixedClassMemberVariables()1750   private static Map<String, VariableExpr> createFixedClassMemberVariables() {
1751     Map<String, TypeNode> memberVars = new HashMap<>();
1752     Reference stringRef = ConcreteReference.withClazz(String.class);
1753     memberVars.put(
1754         "fieldValuesMap",
1755         TypeNode.withReference(
1756             ConcreteReference.builder()
1757                 .setClazz(Map.class)
1758                 .setGenerics(Arrays.asList(stringRef, stringRef))
1759                 .build()));
1760     memberVars.put(
1761         "pathTemplate", TypeNode.withReference(ConcreteReference.withClazz(PathTemplate.class)));
1762     memberVars.put("fixedValue", TypeNode.STRING);
1763     return memberVars.entrySet().stream()
1764         .map(e -> Variable.builder().setName(e.getKey()).setType(e.getValue()).build())
1765         .collect(Collectors.toMap(v -> v.identifier().name(), v -> VariableExpr.withVariable(v)));
1766   }
1767 
getThisClassName(ResourceName resourceName)1768   private static String getThisClassName(ResourceName resourceName) {
1769     return String.format(
1770         CLASS_NAME_PATTERN, JavaStyle.toUpperCamelCase(resourceName.resourceTypeName()));
1771   }
1772 
getBuilderTypeName(List<List<String>> tokenHierarchies, int index)1773   private static String getBuilderTypeName(List<List<String>> tokenHierarchies, int index) {
1774     return index == 0 ? "Builder" : getBuilderTypeName(tokenHierarchies.get(index));
1775   }
1776 
getBuilderTypeName(List<String> tokens)1777   private static String getBuilderTypeName(List<String> tokens) {
1778     return String.format("%sBuilder", concatToUpperCamelCaseName(tokens));
1779   }
1780 
getBuilderType( TypeStore typeStore, List<List<String>> tokenHierarchies, int index)1781   private static TypeNode getBuilderType(
1782       TypeStore typeStore, List<List<String>> tokenHierarchies, int index) {
1783     return index == 0
1784         ? typeStore.get("Builder")
1785         : typeStore.get(getBuilderTypeName(tokenHierarchies, index));
1786   }
1787 
1788   @VisibleForTesting
getTokenSet(List<List<String>> tokenHierarchy)1789   static Set<String> getTokenSet(List<List<String>> tokenHierarchy) {
1790     return tokenHierarchy.stream()
1791         .flatMap(tokens -> tokens.stream())
1792         .collect(Collectors.toCollection(LinkedHashSet::new));
1793   }
1794 
1795   @VisibleForTesting
concatToUpperSnakeCaseName(List<String> tokens)1796   static String concatToUpperSnakeCaseName(List<String> tokens) {
1797     // Tokens are currently in lower_snake_case space.
1798     return JavaStyle.toUpperSnakeCase(tokens.stream().collect(Collectors.joining("_")));
1799   }
1800 
1801   @VisibleForTesting
concatToUpperCamelCaseName(List<String> tokens)1802   static String concatToUpperCamelCaseName(List<String> tokens) {
1803     // Tokens are currently in lower_snake_case space.
1804     return JavaStyle.toUpperCamelCase(tokens.stream().collect(Collectors.joining("_")));
1805   }
1806 }
1807