1 // Copyright 2018 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.jni_generator; 6 7 import com.google.auto.service.AutoService; 8 import com.google.common.collect.ImmutableSet; 9 import com.google.common.collect.Lists; 10 import com.google.common.collect.Maps; 11 import com.squareup.javapoet.AnnotationSpec; 12 import com.squareup.javapoet.ArrayTypeName; 13 import com.squareup.javapoet.ClassName; 14 import com.squareup.javapoet.FieldSpec; 15 import com.squareup.javapoet.JavaFile; 16 import com.squareup.javapoet.MethodSpec; 17 import com.squareup.javapoet.ParameterSpec; 18 import com.squareup.javapoet.ParameterizedTypeName; 19 import com.squareup.javapoet.TypeName; 20 import com.squareup.javapoet.TypeSpec; 21 22 import org.chromium.base.JniStaticTestMocker; 23 import org.chromium.base.NativeLibraryLoadedStatus; 24 import org.chromium.base.annotations.NativeMethods; 25 import org.chromium.build.annotations.CheckDiscard; 26 27 import java.util.ArrayList; 28 import java.util.List; 29 import java.util.Map; 30 import java.util.Set; 31 32 import javax.annotation.processing.AbstractProcessor; 33 import javax.annotation.processing.ProcessingEnvironment; 34 import javax.annotation.processing.Processor; 35 import javax.annotation.processing.RoundEnvironment; 36 import javax.annotation.processing.SupportedOptions; 37 import javax.lang.model.SourceVersion; 38 import javax.lang.model.element.Element; 39 import javax.lang.model.element.ElementKind; 40 import javax.lang.model.element.ExecutableElement; 41 import javax.lang.model.element.Modifier; 42 import javax.lang.model.element.TypeElement; 43 import javax.lang.model.element.VariableElement; 44 import javax.lang.model.type.ArrayType; 45 import javax.lang.model.type.TypeKind; 46 import javax.lang.model.type.TypeMirror; 47 import javax.tools.Diagnostic; 48 49 /** 50 * Annotation processor that finds inner interfaces annotated with 51 * {@link NativeMethods} and generates a class with native bindings 52 * (GEN_JNI) and a class specific wrapper class with name (classnameJni) 53 * 54 * NativeClass - refers to the class that contains all native declarations. 55 * NativeWrapperClass - refers to the class that is generated for each class 56 * containing an interface annotated with NativeMethods. 57 * 58 */ 59 @SupportedOptions({JniProcessor.SKIP_GEN_JNI_ARG, JniProcessor.PACKAGE_PREFIX_OPTION}) 60 @AutoService(Processor.class) 61 public class JniProcessor extends AbstractProcessor { 62 static final String SKIP_GEN_JNI_ARG = "org.chromium.chrome.skipGenJni"; 63 static final String PACKAGE_PREFIX_OPTION = "package_prefix"; 64 private static final Class<NativeMethods> JNI_STATIC_NATIVES_CLASS = NativeMethods.class; 65 private static final Class<CheckDiscard> CHECK_DISCARD_CLASS = CheckDiscard.class; 66 67 private static final String CHECK_DISCARD_CRBUG = "crbug.com/993421"; 68 private static final String NATIVE_WRAPPER_CLASS_POSTFIX = "Jni"; 69 70 private static final ClassName JNI_STATUS_CLASS_NAME = 71 ClassName.get(NativeLibraryLoadedStatus.class); 72 73 static final String NATIVE_TEST_FIELD_NAME = "TESTING_ENABLED"; 74 static final String NATIVE_REQUIRE_MOCK_FIELD_NAME = "REQUIRE_MOCK"; 75 76 // Builder for NativeClass which will hold all our native method declarations. 77 private TypeSpec.Builder mNativesBuilder; 78 79 private ClassName mGenJniClassName = ClassName.get("org.chromium.base.natives", "GEN_JNI"); 80 81 // Types that are non-primitives and should not be 82 // casted to objects in native method declarations. 83 static final ImmutableSet JNI_OBJECT_TYPE_EXCEPTIONS = 84 ImmutableSet.of("java.lang.String", "java.lang.Throwable", "java.lang.Class", "void"); 85 getNameOfWrapperClass(String containingClassName)86 static String getNameOfWrapperClass(String containingClassName) { 87 return containingClassName + NATIVE_WRAPPER_CLASS_POSTFIX; 88 } 89 90 @Override getSupportedAnnotationTypes()91 public Set<String> getSupportedAnnotationTypes() { 92 return ImmutableSet.of(JNI_STATIC_NATIVES_CLASS.getCanonicalName()); 93 } 94 95 @Override getSupportedSourceVersion()96 public SourceVersion getSupportedSourceVersion() { 97 return SourceVersion.latestSupported(); 98 } 99 100 @Override init(ProcessingEnvironment processingEnv)101 public synchronized void init(ProcessingEnvironment processingEnv) { 102 super.init(processingEnv); 103 if (processingEnv.getOptions().containsKey(PACKAGE_PREFIX_OPTION)) { 104 mGenJniClassName = ClassName.get( 105 String.format("%s.%s", processingEnv.getOptions().get(PACKAGE_PREFIX_OPTION), 106 mGenJniClassName.packageName()), 107 mGenJniClassName.simpleName()); 108 } 109 } 110 111 /** 112 * Processes annotations that match getSupportedAnnotationTypes() 113 * Called each 'round' of annotation processing, must fail gracefully if set is empty. 114 */ 115 @Override process( Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment)116 public boolean process( 117 Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) { 118 // Do nothing on an empty round. 119 if (annotations.isEmpty()) { 120 return true; 121 } 122 123 List<JavaFile> writeQueue = Lists.newArrayList(); 124 for (Element e : roundEnvironment.getElementsAnnotatedWith(JNI_STATIC_NATIVES_CLASS)) { 125 // @NativeMethods can only annotate types so this is safe. 126 TypeElement type = (TypeElement) e; 127 128 if (!e.getKind().isInterface()) { 129 printError("@NativeMethods must annotate an interface", e); 130 } 131 132 if (mNativesBuilder == null) { 133 String genJniPrefix = e.getAnnotation(JNI_STATIC_NATIVES_CLASS).value(); 134 if (!genJniPrefix.isEmpty()) { 135 mGenJniClassName = ClassName.get(mGenJniClassName.packageName(), 136 genJniPrefix + "_" + mGenJniClassName.simpleName()); 137 } 138 139 FieldSpec.Builder testingFlagBuilder = 140 FieldSpec.builder(TypeName.BOOLEAN, NATIVE_TEST_FIELD_NAME) 141 .addModifiers(Modifier.STATIC, Modifier.PUBLIC); 142 FieldSpec.Builder throwFlagBuilder = 143 FieldSpec.builder(TypeName.BOOLEAN, NATIVE_REQUIRE_MOCK_FIELD_NAME) 144 .addModifiers(Modifier.STATIC, Modifier.PUBLIC); 145 146 // State of mNativesBuilder needs to be preserved between processing rounds. 147 mNativesBuilder = TypeSpec.classBuilder(mGenJniClassName) 148 .addModifiers(Modifier.PUBLIC, Modifier.FINAL) 149 .addField(testingFlagBuilder.build()) 150 .addField(throwFlagBuilder.build()); 151 } 152 153 // Interface must be nested within a class. 154 Element outerElement = e.getEnclosingElement(); 155 if (!(outerElement instanceof TypeElement)) { 156 printError( 157 "Interface annotated with @JNIInterface must be nested within a class", e); 158 } 159 160 TypeElement outerType = (TypeElement) outerElement; 161 ClassName outerTypeName = ClassName.get(outerType); 162 String outerClassName = outerTypeName.simpleName(); 163 String packageName = outerTypeName.packageName(); 164 165 // Get all methods in annotated interface. 166 List<ExecutableElement> interfaceMethods = getMethodsFromType(type); 167 168 // Map from the current method names to the method spec for a static native 169 // method that will be in our big NativeClass. 170 // Collisions are not allowed - no overloading. 171 Map<String, MethodSpec> methodMap = 172 createNativeMethodSpecs(interfaceMethods, outerTypeName); 173 174 // Add these to our NativeClass. 175 for (MethodSpec method : methodMap.values()) { 176 mNativesBuilder.addMethod(method); 177 } 178 179 // Generate a NativeWrapperClass for outerType by implementing the 180 // annotated interface. Need to pass it the method map because each 181 // method overridden will be a wrapper that calls its 182 // native counterpart in NativeClass. 183 boolean isNativesInterfacePublic = type.getModifiers().contains(Modifier.PUBLIC); 184 185 TypeSpec nativeWrapperClassSpec = 186 createNativeWrapperClassSpec(getNameOfWrapperClass(outerClassName), 187 isNativesInterfacePublic, type, methodMap); 188 189 // Queue this file for writing. 190 // Can't write right now because the wrapper class depends on NativeClass 191 // to be written and we can't write NativeClass until all @NativeMethods 192 // interfaces are processed because each will add new native methods. 193 JavaFile file = JavaFile.builder(packageName, nativeWrapperClassSpec) 194 .addFileComment("Generated by JniProcessor.java") 195 .build(); 196 writeQueue.add(file); 197 } 198 199 // Nothing needs to be written this round. 200 if (writeQueue.size() == 0) { 201 return true; 202 } 203 204 try { 205 // Need to write NativeClass first because the wrapper classes 206 // depend on it. This step is skipped for APK targets since the final GEN_JNI class is 207 // provided elsewhere. 208 if (!processingEnv.getOptions().containsKey(SKIP_GEN_JNI_ARG)) { 209 JavaFile nativeClassFile = 210 JavaFile.builder(mGenJniClassName.packageName(), mNativesBuilder.build()) 211 .addFileComment("Generated by JniProcessor.java") 212 .build(); 213 214 nativeClassFile.writeTo(processingEnv.getFiler()); 215 } 216 217 for (JavaFile f : writeQueue) { 218 f.writeTo(processingEnv.getFiler()); 219 } 220 } catch (Exception e) { 221 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage()); 222 } 223 return true; 224 } 225 getMethodsFromType(TypeElement t)226 List<ExecutableElement> getMethodsFromType(TypeElement t) { 227 List<ExecutableElement> methods = Lists.newArrayList(); 228 for (Element e : t.getEnclosedElements()) { 229 if (e.getKind() == ElementKind.METHOD) { 230 methods.add((ExecutableElement) e); 231 } 232 } 233 return methods; 234 } 235 236 /** 237 * Gets method name for methods inside of NativeClass 238 */ getNativeMethodName(String packageName, String className, String oldMethodName)239 String getNativeMethodName(String packageName, String className, String oldMethodName) { 240 // e.g. org.chromium.base.Foo_Class.bar 241 // => org_chromium_base_Foo_1Class_bar() 242 final String packagePrefix = 243 processingEnv.getOptions().getOrDefault(PACKAGE_PREFIX_OPTION, ""); 244 return (packagePrefix.length() > 0 245 ? String.format( 246 "%s.%s.%s.%s", packagePrefix, packageName, className, oldMethodName) 247 : String.format("%s.%s.%s", packageName, className, oldMethodName)) 248 .replaceAll("_", "_1") 249 .replaceAll("\\.", "_"); 250 } 251 252 /** 253 * Creates method specs for the native methods of NativeClass given 254 * the method declarations from a {@link NativeMethods} annotated interface 255 * @param interfaceMethods method declarations from a {@link NativeMethods} annotated 256 * interface 257 * @param outerType ClassName of class that contains the annotated interface 258 * @return map from old method name to new native method specification 259 */ createNativeMethodSpecs( List<ExecutableElement> interfaceMethods, ClassName outerType)260 Map<String, MethodSpec> createNativeMethodSpecs( 261 List<ExecutableElement> interfaceMethods, ClassName outerType) { 262 Map<String, MethodSpec> methodMap = Maps.newTreeMap(); 263 for (ExecutableElement m : interfaceMethods) { 264 String oldMethodName = m.getSimpleName().toString(); 265 String newMethodName = getNativeMethodName( 266 outerType.packageName(), outerType.simpleName(), oldMethodName); 267 MethodSpec.Builder builder = MethodSpec.methodBuilder(newMethodName) 268 .addModifiers(Modifier.PUBLIC) 269 .addModifiers(Modifier.FINAL) 270 .addModifiers(Modifier.STATIC) 271 .addModifiers(Modifier.NATIVE); 272 builder.addJavadoc(createNativeMethodJavadocString(outerType, m)); 273 274 copyMethodParamsAndReturnType(builder, m, true); 275 if (methodMap.containsKey(oldMethodName)) { 276 printError("Overloading is not currently implemented with this processor ", m); 277 } 278 methodMap.put(oldMethodName, builder.build()); 279 } 280 return methodMap; 281 } 282 283 /** 284 * Creates a generated annotation that contains the name of this class. 285 */ createAnnotationWithValue(Class<?> annotationClazz, String value)286 static AnnotationSpec createAnnotationWithValue(Class<?> annotationClazz, String value) { 287 return AnnotationSpec.builder(annotationClazz) 288 .addMember("value", String.format("\"%s\"", value)) 289 .build(); 290 } 291 printError(String s)292 void printError(String s) { 293 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, s); 294 } 295 printError(String s, Element e)296 void printError(String s, Element e) { 297 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, 298 String.format("Error processing @NativeMethods interface: %s", s), e); 299 } 300 301 /** 302 * Creates a class spec for an implementation of an {@link NativeMethods} annotated interface 303 * that will wrap calls to the NativesClass which contains the actual native method 304 * declarations. 305 * 306 * This class should contain: 307 * 1. Wrappers for all GEN_JNI static native methods 308 * 2. A getter that when testing is disabled, will return the native implementation and 309 * when testing is enabled, will call the mock of the native implementation. 310 * 3. A field that holds the testNatives instance for when testing is enabled 311 * 4. A TEST_HOOKS field that implements an anonymous instance of {@link JniStaticTestMocker} 312 * which will set the testNatives implementation when called in tests 313 * 314 * @param name name of the wrapper class. 315 * @param isPublic if true, a public modifier will be added to this native wrapper. 316 * @param nativeInterface the {@link NativeMethods} annotated type that this native wrapper 317 * will implement. 318 * @param methodMap a map from the old method name to the new method spec in NativeClass. 319 * */ createNativeWrapperClassSpec(String name, boolean isPublic, TypeElement nativeInterface, Map<String, MethodSpec> methodMap)320 TypeSpec createNativeWrapperClassSpec(String name, boolean isPublic, 321 TypeElement nativeInterface, Map<String, MethodSpec> methodMap) { 322 // The wrapper class builder. 323 TypeName nativeInterfaceType = TypeName.get(nativeInterface.asType()); 324 TypeSpec.Builder builder = 325 TypeSpec.classBuilder(name).addSuperinterface(nativeInterfaceType); 326 if (isPublic) { 327 builder.addModifiers(Modifier.PUBLIC); 328 } 329 builder.addAnnotation(createAnnotationWithValue(CHECK_DISCARD_CLASS, CHECK_DISCARD_CRBUG)); 330 331 // Start by adding all the native method wrappers. 332 for (Element enclosed : nativeInterface.getEnclosedElements()) { 333 if (enclosed.getKind() != ElementKind.METHOD) { 334 printError("Cannot have a non-method in a @NativeMethods annotated interface", 335 enclosed); 336 } 337 338 // ElementKind.Method is ExecutableElement so this cast is safe. 339 // interfaceMethod will is the method we are overloading. 340 ExecutableElement interfaceMethod = (ExecutableElement) enclosed; 341 342 // Method in NativesClass that we'll be calling. 343 MethodSpec nativesMethod = methodMap.get(interfaceMethod.getSimpleName().toString()); 344 345 // Add a matching method in this class that overrides the declaration 346 // in nativeInterface. It will just call the actual natives method in 347 // NativeClass. 348 builder.addMethod(createNativeWrapperMethod(interfaceMethod, nativesMethod)); 349 } 350 351 // Add the testInstance field. 352 // Holds the test natives target if it is set. 353 FieldSpec testTarget = FieldSpec.builder(nativeInterfaceType, "testInstance") 354 .addModifiers(Modifier.PRIVATE, Modifier.STATIC) 355 .build(); 356 builder.addField(testTarget); 357 358 // Getter for target or testing instance if flag in GEN_JNI is set. 359 /* 360 {classname}.Natives get() { 361 if (GEN_JNI.TESTING_ENABLED) { 362 if (testInst != null) { 363 return testInst; 364 } 365 if (GEN_JNI.REQUIRE_MOCK) { 366 throw new UnsupportedOperationException($noMockExceptionString); 367 } 368 } 369 NativeLibraryLoadedStatus.checkLoaded() 370 return new {classname}Jni(); 371 } 372 */ 373 String noMockExceptionString = 374 String.format("No mock found for the native implementation for %s. " 375 + "The current configuration requires all native " 376 + "implementations to have a mock instance.", 377 nativeInterfaceType); 378 379 MethodSpec instanceGetter = 380 MethodSpec.methodBuilder("get") 381 .addModifiers(Modifier.PUBLIC, Modifier.STATIC) 382 .returns(nativeInterfaceType) 383 .beginControlFlow("if ($T.$N)", mGenJniClassName, NATIVE_TEST_FIELD_NAME) 384 .beginControlFlow("if ($N != null)", testTarget) 385 .addStatement("return $N", testTarget) 386 .endControlFlow() 387 .beginControlFlow( 388 "if ($T.$N)", mGenJniClassName, NATIVE_REQUIRE_MOCK_FIELD_NAME) 389 .addStatement("throw new UnsupportedOperationException($S)", 390 noMockExceptionString) 391 .endControlFlow() 392 .endControlFlow() 393 .addStatement("$T.$N()", JNI_STATUS_CLASS_NAME, "checkLoaded") 394 .addStatement("return new $N()", name) 395 .build(); 396 397 builder.addMethod(instanceGetter); 398 399 // Next add TEST_HOOKS to set testInstance... should look like this: 400 // JniStaticTestMocker<ClassNameJni> TEST_HOOKS = new JniStaticTestMocker<>() { 401 // @Override 402 // public static setInstanceForTesting(ClassNameJni instance) { 403 // if (!GEN_JNI.TESTING_ENABLED) { 404 // throw new RuntimeException($mocksNotEnabledExceptionString); 405 // } 406 // testInstance = instance; 407 // } 408 // } 409 String mocksNotEnabledExceptionString = 410 "Tried to set a JNI mock when mocks aren't enabled!"; 411 MethodSpec testHookMockerMethod = 412 MethodSpec.methodBuilder("setInstanceForTesting") 413 .addModifiers(Modifier.PUBLIC) 414 .addAnnotation(Override.class) 415 .addParameter(nativeInterfaceType, "instance") 416 .beginControlFlow("if (!$T.$N)", mGenJniClassName, NATIVE_TEST_FIELD_NAME) 417 .addStatement( 418 "throw new RuntimeException($S)", mocksNotEnabledExceptionString) 419 .endControlFlow() 420 .addStatement("$N = instance", testTarget) 421 .build(); 422 423 // Make the anonymous TEST_HOOK class. 424 ParameterizedTypeName genericMockerInterface = ParameterizedTypeName.get( 425 ClassName.get(JniStaticTestMocker.class), ClassName.get(nativeInterface)); 426 427 TypeSpec testHook = TypeSpec.anonymousClassBuilder("") 428 .addSuperinterface(genericMockerInterface) 429 .addMethod(testHookMockerMethod) 430 .build(); 431 432 FieldSpec testHookSpec = 433 FieldSpec.builder(genericMockerInterface, "TEST_HOOKS") 434 .addModifiers(Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) 435 .initializer("$L", testHook.toString()) 436 .build(); 437 438 builder.addField(testHookSpec); 439 return builder.build(); 440 } 441 442 /** 443 * Creates a wrapper method that overrides interfaceMethod and calls staticNativeMethod. 444 * @param interfaceMethod method that will be overridden in a {@link NativeMethods} annotated 445 * interface. 446 * @param staticNativeMethod method that will be called in NativeClass. 447 */ createNativeWrapperMethod( ExecutableElement interfaceMethod, MethodSpec staticNativeMethod)448 MethodSpec createNativeWrapperMethod( 449 ExecutableElement interfaceMethod, MethodSpec staticNativeMethod) { 450 // Method will have the same name and be public. 451 MethodSpec.Builder builder = 452 MethodSpec.methodBuilder(interfaceMethod.getSimpleName().toString()) 453 .addModifiers(Modifier.PUBLIC) 454 .addAnnotation(Override.class); 455 456 // Method will need the same params and return type as the one we're overriding. 457 copyMethodParamsAndReturnType(builder, interfaceMethod); 458 459 // Add return if method return type is not void. 460 if (!interfaceMethod.getReturnType().toString().equals("void")) { 461 // Also need to cast because non-primitives are Objects in NativeClass. 462 builder.addCode("return ($T)", interfaceMethod.getReturnType()); 463 } 464 465 // Make call to native function. 466 builder.addCode("$T.$N(", mGenJniClassName, staticNativeMethod); 467 468 // Add params to native call. 469 ArrayList<String> paramNames = new ArrayList<>(); 470 for (VariableElement param : interfaceMethod.getParameters()) { 471 paramNames.add(param.getSimpleName().toString()); 472 } 473 474 builder.addCode(String.join(", ", paramNames) + ");\n"); 475 return builder.build(); 476 } 477 shouldDowncastToObjectForJni(TypeName t)478 boolean shouldDowncastToObjectForJni(TypeName t) { 479 if (t.isPrimitive()) { 480 return false; 481 } 482 // There are some non-primitives that should not be downcasted. 483 return !JNI_OBJECT_TYPE_EXCEPTIONS.contains(t.toString()); 484 } 485 toTypeName(TypeMirror t, boolean useJni)486 TypeName toTypeName(TypeMirror t, boolean useJni) { 487 if (t.getKind() == TypeKind.ARRAY) { 488 return ArrayTypeName.of(toTypeName(((ArrayType) t).getComponentType(), useJni)); 489 } 490 TypeName typeName = TypeName.get(t); 491 if (useJni && shouldDowncastToObjectForJni(typeName)) { 492 return TypeName.OBJECT; 493 } 494 return typeName; 495 } 496 497 /** 498 * Since some types may decay to objects in the native method 499 * this method returns a javadoc string that contains the 500 * type information from the old method. 501 **/ createNativeMethodJavadocString(ClassName outerType, ExecutableElement oldMethod)502 String createNativeMethodJavadocString(ClassName outerType, ExecutableElement oldMethod) { 503 ArrayList<String> docLines = new ArrayList<>(); 504 505 // Class descriptor. 506 String descriptor = String.format("%s.%s.%s", outerType.packageName(), 507 outerType.simpleName(), oldMethod.getSimpleName().toString()); 508 docLines.add(descriptor); 509 510 // Parameters. 511 for (VariableElement param : oldMethod.getParameters()) { 512 TypeName paramType = TypeName.get(param.asType()); 513 String paramTypeName = paramType.toString(); 514 String name = param.getSimpleName().toString(); 515 docLines.add(String.format("@param %s (%s)", name, paramTypeName)); 516 } 517 518 // Return type. 519 docLines.add(String.format("@return (%s)", oldMethod.getReturnType().toString())); 520 521 return String.join("\n", docLines) + "\n"; 522 } 523 copyMethodParamsAndReturnType( MethodSpec.Builder builder, ExecutableElement method, boolean useJniTypes)524 void copyMethodParamsAndReturnType( 525 MethodSpec.Builder builder, ExecutableElement method, boolean useJniTypes) { 526 for (VariableElement param : method.getParameters()) { 527 builder.addParameter(createParamSpec(param, useJniTypes)); 528 } 529 TypeMirror givenReturnType = method.getReturnType(); 530 TypeName returnType = toTypeName(givenReturnType, useJniTypes); 531 532 builder.returns(returnType); 533 } 534 copyMethodParamsAndReturnType(MethodSpec.Builder builder, ExecutableElement method)535 void copyMethodParamsAndReturnType(MethodSpec.Builder builder, ExecutableElement method) { 536 copyMethodParamsAndReturnType(builder, method, false); 537 } 538 createParamSpec(VariableElement param, boolean useJniObjects)539 ParameterSpec createParamSpec(VariableElement param, boolean useJniObjects) { 540 TypeName paramType = toTypeName(param.asType(), useJniObjects); 541 return ParameterSpec.builder(paramType, param.getSimpleName().toString()) 542 .addModifiers(param.getModifiers()) 543 .build(); 544 } 545 } 546