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