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.defaultvalue; 16 17 import com.google.api.generator.engine.ast.AnonymousClassExpr; 18 import com.google.api.generator.engine.ast.AssignmentExpr; 19 import com.google.api.generator.engine.ast.ConcreteReference; 20 import com.google.api.generator.engine.ast.Expr; 21 import com.google.api.generator.engine.ast.ExprStatement; 22 import com.google.api.generator.engine.ast.MethodDefinition; 23 import com.google.api.generator.engine.ast.MethodInvocationExpr; 24 import com.google.api.generator.engine.ast.NewObjectExpr; 25 import com.google.api.generator.engine.ast.PrimitiveValue; 26 import com.google.api.generator.engine.ast.ScopeNode; 27 import com.google.api.generator.engine.ast.StringObjectValue; 28 import com.google.api.generator.engine.ast.TypeNode; 29 import com.google.api.generator.engine.ast.ValueExpr; 30 import com.google.api.generator.engine.ast.VaporReference; 31 import com.google.api.generator.engine.ast.Variable; 32 import com.google.api.generator.engine.ast.VariableExpr; 33 import com.google.api.generator.engine.lexicon.Keyword; 34 import com.google.api.generator.gapic.composer.resourcename.ResourceNameTokenizer; 35 import com.google.api.generator.gapic.model.Field; 36 import com.google.api.generator.gapic.model.HttpBindings; 37 import com.google.api.generator.gapic.model.Message; 38 import com.google.api.generator.gapic.model.MethodArgument; 39 import com.google.api.generator.gapic.model.ResourceName; 40 import com.google.api.generator.gapic.utils.JavaStyle; 41 import com.google.api.generator.gapic.utils.ResourceNameConstants; 42 import com.google.api.generator.gapic.utils.ResourceReferenceUtils; 43 import com.google.common.annotations.VisibleForTesting; 44 import com.google.common.base.Preconditions; 45 import com.google.longrunning.Operation; 46 import com.google.protobuf.Any; 47 import java.util.ArrayList; 48 import java.util.Arrays; 49 import java.util.Collections; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.List; 53 import java.util.Map; 54 import java.util.Optional; 55 import java.util.regex.Pattern; 56 import java.util.stream.Collectors; 57 58 public class DefaultValueComposer { 59 private static final TypeNode OPERATION_TYPE = 60 TypeNode.withReference(ConcreteReference.withClazz(Operation.class)); 61 private static final TypeNode ANY_TYPE = 62 TypeNode.withReference(ConcreteReference.withClazz(Any.class)); 63 64 private static final Pattern REPLACER_PATTERN = Pattern.compile("(\\w*)(\\w/|-\\d+/)\\*"); 65 createMethodArgValue( MethodArgument methodArg, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes, Map<String, String> valuePatterns, HttpBindings bindings)66 public static Expr createMethodArgValue( 67 MethodArgument methodArg, 68 Map<String, ResourceName> resourceNames, 69 Map<String, Message> messageTypes, 70 Map<String, String> valuePatterns, 71 HttpBindings bindings) { 72 if (methodArg.isResourceNameHelper()) { 73 Preconditions.checkState( 74 methodArg.field().hasResourceReference(), 75 String.format( 76 "No corresponding resource reference for argument %s found on field %s %s", 77 methodArg.name(), methodArg.field().type(), methodArg.field().name())); 78 ResourceName resourceName = 79 resourceNames.get(methodArg.field().resourceReference().resourceTypeString()); 80 Preconditions.checkNotNull( 81 resourceName, 82 String.format( 83 "No resource name found for reference %s", 84 methodArg.field().resourceReference().resourceTypeString())); 85 Expr defValue = 86 createResourceHelperValue( 87 resourceName, 88 methodArg.field().resourceReference().isChildType(), 89 resourceNames.values().stream().collect(Collectors.toList()), 90 methodArg.field().name(), 91 bindings); 92 93 if (!methodArg.isResourceNameHelper() && methodArg.field().hasResourceReference()) { 94 defValue = 95 MethodInvocationExpr.builder() 96 .setExprReferenceExpr(defValue) 97 .setMethodName("toString") 98 .setReturnType(TypeNode.STRING) 99 .build(); 100 } 101 return defValue; 102 } 103 104 if (methodArg.type().equals(methodArg.field().type())) { 105 return createValue(methodArg.field(), false, resourceNames, messageTypes, valuePatterns); 106 } 107 108 return createValue(Field.builder().setName(methodArg.name()).setType(methodArg.type()).build()); 109 } 110 createValue(Field field)111 public static Expr createValue(Field field) { 112 return createValue( 113 field, false, Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap()); 114 } 115 createValue( Field field, boolean useExplicitInitTypeInGenerics, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes, Map<String, String> valuePatterns)116 public static Expr createValue( 117 Field field, 118 boolean useExplicitInitTypeInGenerics, 119 Map<String, ResourceName> resourceNames, 120 Map<String, Message> messageTypes, 121 Map<String, String> valuePatterns) { 122 if (field.isRepeated()) { 123 ConcreteReference.Builder refBuilder = 124 ConcreteReference.builder().setClazz(field.isMap() ? HashMap.class : ArrayList.class); 125 if (useExplicitInitTypeInGenerics) { 126 if (field.isMap()) { 127 refBuilder = refBuilder.setGenerics(field.type().reference().generics().subList(0, 2)); 128 } else { 129 refBuilder = refBuilder.setGenerics(field.type().reference().generics().get(0)); 130 } 131 } 132 133 TypeNode newType = TypeNode.withReference(refBuilder.build()); 134 return NewObjectExpr.builder().setType(newType).setIsGeneric(true).build(); 135 } 136 137 if (field.isEnum()) { 138 return MethodInvocationExpr.builder() 139 .setStaticReferenceType(field.type()) 140 .setMethodName("forNumber") 141 .setArguments( 142 ValueExpr.withValue( 143 PrimitiveValue.builder().setType(TypeNode.INT).setValue("0").build())) 144 .setReturnType(field.type()) 145 .build(); 146 } 147 148 if (field.isMessage()) { 149 String nestedFieldName = field.name(); 150 Map<String, String> nestedValuePatterns = new HashMap<>(); 151 for (Map.Entry<String, String> entry : valuePatterns.entrySet()) { 152 String lowerCamelNestedFieldName = JavaStyle.toLowerCamelCase(nestedFieldName); 153 lowerCamelNestedFieldName = Keyword.unescapeKeyword(lowerCamelNestedFieldName); 154 if (entry.getKey().startsWith(lowerCamelNestedFieldName + '.')) { 155 nestedValuePatterns.put( 156 entry.getKey().substring(lowerCamelNestedFieldName.length() + 1), entry.getValue()); 157 } 158 } 159 160 if (!nestedValuePatterns.isEmpty()) { 161 Message nestedMessage = messageTypes.get(field.type().reference().fullName()); 162 if (nestedMessage != null) { 163 return createSimpleMessageBuilderValue( 164 nestedMessage, resourceNames, messageTypes, nestedValuePatterns, null); 165 } 166 } 167 168 MethodInvocationExpr newBuilderExpr = 169 MethodInvocationExpr.builder() 170 .setStaticReferenceType(field.type()) 171 .setMethodName("newBuilder") 172 .build(); 173 if (field.type().equals(TypeNode.VALUE)) { 174 newBuilderExpr = 175 MethodInvocationExpr.builder() 176 .setExprReferenceExpr(newBuilderExpr) 177 .setMethodName("setBoolValue") 178 .setArguments( 179 ValueExpr.withValue( 180 PrimitiveValue.builder() 181 .setType(TypeNode.BOOLEAN) 182 .setValue("true") 183 .build())) 184 .build(); 185 } 186 187 return MethodInvocationExpr.builder() 188 .setExprReferenceExpr(newBuilderExpr) 189 .setMethodName("build") 190 .setReturnType(field.type()) 191 .build(); 192 } 193 194 if (field.type().equals(TypeNode.STRING)) { 195 String javaFieldName = JavaStyle.toLowerCamelCase(field.name()); 196 return ValueExpr.withValue( 197 StringObjectValue.withValue( 198 constructValueMatchingPattern(javaFieldName, valuePatterns.get(javaFieldName)))); 199 } 200 201 if (TypeNode.isNumericType(field.type())) { 202 return ValueExpr.withValue( 203 PrimitiveValue.builder() 204 .setType(field.type()) 205 .setValue(String.format("%s", field.name().hashCode())) 206 .build()); 207 } 208 209 if (field.type().equals(TypeNode.BOOLEAN)) { 210 return ValueExpr.withValue( 211 PrimitiveValue.builder().setType(field.type()).setValue("true").build()); 212 } 213 214 if (field.type().equals(TypeNode.BYTESTRING)) { 215 return VariableExpr.builder() 216 .setStaticReferenceType(TypeNode.BYTESTRING) 217 .setVariable(Variable.builder().setName("EMPTY").setType(TypeNode.BYTESTRING).build()) 218 .build(); 219 } 220 221 throw new UnsupportedOperationException( 222 String.format( 223 "Default value for field %s with type %s not implemented yet.", 224 field.name(), field.type())); 225 } 226 createResourceHelperValue( ResourceName resourceName, boolean isChildType, List<ResourceName> resnames, String fieldOrMessageName, HttpBindings bindings)227 public static Expr createResourceHelperValue( 228 ResourceName resourceName, 229 boolean isChildType, 230 List<ResourceName> resnames, 231 String fieldOrMessageName, 232 HttpBindings bindings) { 233 return createResourceHelperValue( 234 resourceName, isChildType, resnames, fieldOrMessageName, true, bindings); 235 } 236 findParentResource( ResourceName childResource, List<ResourceName> resourceNames)237 private static Optional<ResourceName> findParentResource( 238 ResourceName childResource, List<ResourceName> resourceNames) { 239 Map<String, ResourceName> patternToResourceName = new HashMap<>(); 240 241 for (ResourceName resourceName : resourceNames) { 242 for (String parentPattern : resourceName.patterns()) { 243 patternToResourceName.put(parentPattern, resourceName); 244 } 245 } 246 247 for (String childPattern : childResource.patterns()) { 248 Optional<String> parentPattern = ResourceReferenceUtils.parseParentPattern(childPattern); 249 if (parentPattern.isPresent() && patternToResourceName.containsKey(parentPattern.get())) { 250 return Optional.of(patternToResourceName.get(parentPattern.get())); 251 } 252 } 253 254 return Optional.empty(); 255 } 256 257 @VisibleForTesting createResourceHelperValue( ResourceName resourceName, boolean isChildType, List<ResourceName> resnames, String fieldOrMessageName, boolean allowAnonResourceNameClass, HttpBindings bindings)258 static Expr createResourceHelperValue( 259 ResourceName resourceName, 260 boolean isChildType, 261 List<ResourceName> resnames, 262 String fieldOrMessageName, 263 boolean allowAnonResourceNameClass, 264 HttpBindings bindings) { 265 266 if (isChildType) { 267 resourceName = findParentResource(resourceName, resnames).orElse(resourceName); 268 } 269 270 if (resourceName.isOnlyWildcard()) { 271 List<ResourceName> unexaminedResnames = new ArrayList<>(resnames); 272 for (ResourceName resname : resnames) { 273 unexaminedResnames.remove(resname); 274 if (!resname.isOnlyWildcard() 275 && (bindings == null || resname.getMatchingPattern(bindings) != null)) { 276 return createResourceHelperValue( 277 resname, false, unexaminedResnames, fieldOrMessageName, null); 278 } 279 } 280 281 return allowAnonResourceNameClass 282 ? createAnonymousResourceNameClassValue(fieldOrMessageName, bindings) 283 : ValueExpr.withValue( 284 StringObjectValue.withValue( 285 String.format("%s%s", fieldOrMessageName, fieldOrMessageName.hashCode()))); 286 } 287 288 // The cost tradeoffs of new ctors versus distinct() don't really matter here, since this list 289 // will usually have a very small number of elements. 290 List<String> patterns = new ArrayList<>(new HashSet<>(resourceName.patterns())); 291 boolean containsOnlyDeletedTopic = 292 patterns.size() == 1 && patterns.get(0).equals(ResourceNameConstants.DELETED_TOPIC_LITERAL); 293 String ofMethodName = "of"; 294 List<String> patternPlaceholderTokens = new ArrayList<>(); 295 296 if (containsOnlyDeletedTopic) { 297 ofMethodName = "ofDeletedTopic"; 298 } else { 299 String matchingPattern = null; 300 if (bindings != null) { 301 matchingPattern = resourceName.getMatchingPattern(bindings); 302 } 303 if (matchingPattern == null) { 304 for (String pattern : resourceName.patterns()) { 305 if (pattern.equals(ResourceNameConstants.WILDCARD_PATTERN) 306 || pattern.equals(ResourceNameConstants.DELETED_TOPIC_LITERAL)) { 307 continue; 308 } 309 matchingPattern = pattern; 310 break; 311 } 312 } 313 patternPlaceholderTokens.addAll( 314 ResourceNameTokenizer.parseTokenHierarchy(Arrays.asList(matchingPattern)).get(0)); 315 } 316 317 boolean hasOnePattern = resourceName.patterns().size() == 1; 318 if (!hasOnePattern) { 319 ofMethodName = 320 String.format( 321 "of%sName", 322 String.join( 323 "", 324 patternPlaceholderTokens.stream() 325 .map(s -> JavaStyle.toUpperCamelCase(s)) 326 .collect(Collectors.toList()))); 327 } 328 329 TypeNode resourceNameJavaType = resourceName.type(); 330 List<Expr> argExprs = 331 patternPlaceholderTokens.stream() 332 .map( 333 s -> 334 ValueExpr.withValue( 335 StringObjectValue.withValue(String.format("[%s]", s.toUpperCase())))) 336 .collect(Collectors.toList()); 337 return MethodInvocationExpr.builder() 338 .setStaticReferenceType(resourceNameJavaType) 339 .setMethodName(ofMethodName) 340 .setArguments(argExprs) 341 .setReturnType(resourceNameJavaType) 342 .build(); 343 } 344 createSimpleMessageBuilderValue( Message message, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes, HttpBindings bindings)345 public static Expr createSimpleMessageBuilderValue( 346 Message message, 347 Map<String, ResourceName> resourceNames, 348 Map<String, Message> messageTypes, 349 HttpBindings bindings) { 350 return createSimpleMessageBuilderValue( 351 message, resourceNames, messageTypes, Collections.emptyMap(), bindings); 352 } 353 createSimpleMessageBuilderValue( Message message, Map<String, ResourceName> resourceNames, Map<String, Message> messageTypes, Map<String, String> valuePatterns, HttpBindings bindings)354 public static Expr createSimpleMessageBuilderValue( 355 Message message, 356 Map<String, ResourceName> resourceNames, 357 Map<String, Message> messageTypes, 358 Map<String, String> valuePatterns, 359 HttpBindings bindings) { 360 MethodInvocationExpr builderExpr = 361 MethodInvocationExpr.builder() 362 .setStaticReferenceType(message.type()) 363 .setMethodName("newBuilder") 364 .build(); 365 366 for (Field field : message.fields()) { 367 if (field.isContainedInOneof() // Avoid colliding fields. 368 || ((field.isMessage() 369 || (field.isEnum() 370 && message.operationResponse() == null)) // Avoid importing unparsed messages. 371 && !field.isRepeated() 372 && !messageTypes.containsKey(field.type().reference().fullName()))) { 373 continue; 374 } 375 String setterMethodNamePattern = "set%s"; 376 if (field.isRepeated()) { 377 setterMethodNamePattern = field.isMap() ? "putAll%s" : "addAll%s"; 378 } 379 Expr defaultExpr = null; 380 if (field.hasResourceReference() 381 && resourceNames.get(field.resourceReference().resourceTypeString()) != null) { 382 defaultExpr = 383 createResourceHelperValue( 384 resourceNames.get(field.resourceReference().resourceTypeString()), 385 field.resourceReference().isChildType(), 386 resourceNames.values().stream().collect(Collectors.toList()), 387 message.name(), 388 /* allowAnonResourceNameClass = */ false, 389 bindings); 390 defaultExpr = 391 MethodInvocationExpr.builder() 392 .setExprReferenceExpr(defaultExpr) 393 .setMethodName("toString") 394 .setReturnType(TypeNode.STRING) 395 .build(); 396 } else { 397 if (message.operationResponse() != null) { 398 if (field.name().equals(message.operationResponse().statusFieldName())) { 399 String statusTypeName = message.operationResponse().statusFieldTypeName(); 400 String statusClassName = statusTypeName.substring(statusTypeName.lastIndexOf('.') + 1); 401 402 TypeNode statusType = 403 TypeNode.withReference( 404 VaporReference.builder() 405 .setName(statusClassName) 406 .setPakkage(message.type().reference().fullName()) 407 .setIsStaticImport(false) 408 .build()); 409 defaultExpr = 410 VariableExpr.builder() 411 .setVariable(Variable.builder().setName("DONE").setType(statusType).build()) 412 .setStaticReferenceType(statusType) 413 .build(); 414 415 } else if (field.name().equals(message.operationResponse().errorCodeFieldName())) { 416 defaultExpr = 417 ValueExpr.withValue( 418 PrimitiveValue.builder().setType(field.type()).setValue("0").build()); 419 } 420 } 421 422 if (defaultExpr == null) { 423 defaultExpr = createValue(field, true, resourceNames, messageTypes, valuePatterns); 424 } 425 } 426 builderExpr = 427 MethodInvocationExpr.builder() 428 .setExprReferenceExpr(builderExpr) 429 .setMethodName( 430 String.format(setterMethodNamePattern, JavaStyle.toUpperCamelCase(field.name()))) 431 .setArguments(defaultExpr) 432 .build(); 433 } 434 435 return MethodInvocationExpr.builder() 436 .setExprReferenceExpr(builderExpr) 437 .setMethodName("build") 438 .setReturnType(message.type()) 439 .build(); 440 } 441 createSimpleOperationBuilderValue(String name, VariableExpr responseExpr)442 public static Expr createSimpleOperationBuilderValue(String name, VariableExpr responseExpr) { 443 Expr operationExpr = 444 MethodInvocationExpr.builder() 445 .setStaticReferenceType(OPERATION_TYPE) 446 .setMethodName("newBuilder") 447 .build(); 448 operationExpr = 449 MethodInvocationExpr.builder() 450 .setExprReferenceExpr(operationExpr) 451 .setMethodName("setName") 452 .setArguments(ValueExpr.withValue(StringObjectValue.withValue(name))) 453 .build(); 454 operationExpr = 455 MethodInvocationExpr.builder() 456 .setExprReferenceExpr(operationExpr) 457 .setMethodName("setDone") 458 .setArguments( 459 ValueExpr.withValue( 460 PrimitiveValue.builder().setType(TypeNode.BOOLEAN).setValue("true").build())) 461 .build(); 462 operationExpr = 463 MethodInvocationExpr.builder() 464 .setExprReferenceExpr(operationExpr) 465 .setMethodName("setResponse") 466 .setArguments( 467 MethodInvocationExpr.builder() 468 .setStaticReferenceType(ANY_TYPE) 469 .setMethodName("pack") 470 .setArguments(responseExpr) 471 .build()) 472 .build(); 473 return MethodInvocationExpr.builder() 474 .setExprReferenceExpr(operationExpr) 475 .setMethodName("build") 476 .setReturnType(OPERATION_TYPE) 477 .build(); 478 } 479 createSimplePagedResponseValue( TypeNode responseType, String repeatedFieldName, Expr responseElementVarExpr, boolean isMap)480 public static Expr createSimplePagedResponseValue( 481 TypeNode responseType, String repeatedFieldName, Expr responseElementVarExpr, boolean isMap) { 482 // Code for paginated maps: 483 // AggregatedMessageList.newBuilder() 484 // .setNextPageToken("") 485 // .putAllItems(Collections.singletonMap("items", responsesElement)) 486 // .build(); 487 // 488 // Code for paginated arrays: 489 // MessageList expectedResponse = 490 // AddressList.newBuilder() 491 // .setNextPageToken("") 492 // .addAllItems(Arrays.asList(responsesElement)) 493 // .build(); 494 Expr pagedResponseExpr = 495 MethodInvocationExpr.builder() 496 .setStaticReferenceType(responseType) 497 .setMethodName("newBuilder") 498 .build(); 499 pagedResponseExpr = 500 MethodInvocationExpr.builder() 501 .setExprReferenceExpr(pagedResponseExpr) 502 .setMethodName("setNextPageToken") 503 .setArguments(ValueExpr.withValue(StringObjectValue.withValue(""))) 504 .build(); 505 if (isMap) { 506 pagedResponseExpr = 507 MethodInvocationExpr.builder() 508 .setExprReferenceExpr(pagedResponseExpr) 509 .setMethodName( 510 String.format("putAll%s", JavaStyle.toUpperCamelCase(repeatedFieldName))) 511 .setArguments( 512 MethodInvocationExpr.builder() 513 .setStaticReferenceType( 514 TypeNode.withReference(ConcreteReference.withClazz(Collections.class))) 515 .setMethodName("singletonMap") 516 .setArguments( 517 ValueExpr.withValue( 518 StringObjectValue.withValue( 519 JavaStyle.toLowerCamelCase(repeatedFieldName))), 520 responseElementVarExpr) 521 .build()) 522 .build(); 523 } else { 524 pagedResponseExpr = 525 MethodInvocationExpr.builder() 526 .setExprReferenceExpr(pagedResponseExpr) 527 .setMethodName( 528 String.format("addAll%s", JavaStyle.toUpperCamelCase(repeatedFieldName))) 529 .setArguments( 530 MethodInvocationExpr.builder() 531 .setStaticReferenceType( 532 TypeNode.withReference(ConcreteReference.withClazz(Arrays.class))) 533 .setMethodName("asList") 534 .setArguments(responseElementVarExpr) 535 .build()) 536 .build(); 537 } 538 return MethodInvocationExpr.builder() 539 .setExprReferenceExpr(pagedResponseExpr) 540 .setMethodName("build") 541 .setReturnType(responseType) 542 .build(); 543 } 544 545 @VisibleForTesting createAnonymousResourceNameClassValue( String fieldOrMessageName, HttpBindings matchedBindings)546 static AnonymousClassExpr createAnonymousResourceNameClassValue( 547 String fieldOrMessageName, HttpBindings matchedBindings) { 548 TypeNode stringMapType = 549 TypeNode.withReference( 550 ConcreteReference.builder() 551 .setClazz(Map.class) 552 .setGenerics( 553 Arrays.asList( 554 ConcreteReference.withClazz(String.class), 555 ConcreteReference.withClazz(String.class))) 556 .build()); 557 558 // Method code: 559 // @Override 560 // public Map<String, String> getFieldValuesMap() { 561 // Map<String, String> fieldValuesMap = new HashMap<>(); 562 // fieldValuesMap.put("resource", "resource-12345"); 563 // return fieldValuesMap; 564 // } 565 566 String toStringValue = String.format("%s%s", fieldOrMessageName, fieldOrMessageName.hashCode()); 567 if (matchedBindings != null) { 568 Map<String, String> valuePatterns = matchedBindings.getPathParametersValuePatterns(); 569 toStringValue = 570 constructValueMatchingPattern(fieldOrMessageName, valuePatterns.get(fieldOrMessageName)); 571 } 572 573 VariableExpr fieldValuesMapVarExpr = 574 VariableExpr.withVariable( 575 Variable.builder().setType(stringMapType).setName("fieldValuesMap").build()); 576 StringObjectValue fieldOrMessageStringValue = StringObjectValue.withValue(toStringValue); 577 578 List<Expr> bodyExprs = 579 Arrays.asList( 580 AssignmentExpr.builder() 581 .setVariableExpr(fieldValuesMapVarExpr.toBuilder().setIsDecl(true).build()) 582 .setValueExpr( 583 NewObjectExpr.builder() 584 .setType(TypeNode.withReference(ConcreteReference.withClazz(HashMap.class))) 585 .setIsGeneric(true) 586 .build()) 587 .build(), 588 MethodInvocationExpr.builder() 589 .setExprReferenceExpr(fieldValuesMapVarExpr) 590 .setMethodName("put") 591 .setArguments( 592 ValueExpr.withValue(StringObjectValue.withValue(fieldOrMessageName)), 593 ValueExpr.withValue(fieldOrMessageStringValue)) 594 .build()); 595 596 MethodDefinition getFieldValuesMapMethod = 597 MethodDefinition.builder() 598 .setIsOverride(true) 599 .setScope(ScopeNode.PUBLIC) 600 .setReturnType(stringMapType) 601 .setName("getFieldValuesMap") 602 .setBody( 603 bodyExprs.stream().map(e -> ExprStatement.withExpr(e)).collect(Collectors.toList())) 604 .setReturnExpr(fieldValuesMapVarExpr) 605 .build(); 606 607 // Method code: 608 // @Override 609 // public String getFieldValue(String fieldName) { 610 // return getFieldValuesMap().get(fieldName); 611 // } 612 VariableExpr fieldNameVarExpr = 613 VariableExpr.withVariable( 614 Variable.builder().setType(TypeNode.STRING).setName("fieldName").build()); 615 MethodDefinition getFieldValueMethod = 616 MethodDefinition.builder() 617 .setIsOverride(true) 618 .setScope(ScopeNode.PUBLIC) 619 .setReturnType(TypeNode.STRING) 620 .setName("getFieldValue") 621 .setArguments(fieldNameVarExpr.toBuilder().setIsDecl(true).build()) 622 .setReturnExpr( 623 MethodInvocationExpr.builder() 624 .setExprReferenceExpr( 625 MethodInvocationExpr.builder().setMethodName("getFieldValuesMap").build()) 626 .setMethodName("get") 627 .setArguments(fieldNameVarExpr) 628 .setReturnType(TypeNode.STRING) 629 .build()) 630 .build(); 631 632 MethodDefinition toStringMethod = 633 MethodDefinition.builder() 634 .setIsOverride(true) 635 .setScope(ScopeNode.PUBLIC) 636 .setReturnType(TypeNode.STRING) 637 .setName("toString") 638 .setReturnExpr(ValueExpr.withValue(StringObjectValue.withValue(toStringValue))) 639 .build(); 640 641 return AnonymousClassExpr.builder() 642 .setType( 643 TypeNode.withReference( 644 ConcreteReference.withClazz(com.google.api.resourcenames.ResourceName.class))) 645 .setMethods(Arrays.asList(getFieldValuesMapMethod, getFieldValueMethod, toStringMethod)) 646 .build(); 647 } 648 constructValueMatchingPattern(String fieldName, String pattern)649 public static String constructValueMatchingPattern(String fieldName, String pattern) { 650 if (pattern == null || pattern.isEmpty()) { 651 return fieldName + fieldName.hashCode(); 652 } 653 654 final String suffix = "-" + (Math.abs((fieldName + pattern).hashCode()) % 10000); 655 656 String value = pattern.replace("**", "*"); 657 658 String prevTempl = null; 659 while (!value.equals(prevTempl)) { 660 prevTempl = value; 661 value = REPLACER_PATTERN.matcher(value).replaceFirst("$1$2$1" + suffix); 662 } 663 664 return value.replace("*", fieldName + suffix); 665 } 666 } 667