• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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