• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*******************************************************************************
2  * Copyright 2011 See AUTHORS file.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  ******************************************************************************/
16 
17 package com.badlogic.gdx.jnigen;
18 
19 import java.io.InputStream;
20 import java.nio.Buffer;
21 import java.util.ArrayList;
22 
23 import com.badlogic.gdx.jnigen.parsing.CMethodParser;
24 import com.badlogic.gdx.jnigen.parsing.CMethodParser.CMethod;
25 import com.badlogic.gdx.jnigen.parsing.CMethodParser.CMethodParserResult;
26 import com.badlogic.gdx.jnigen.parsing.JavaMethodParser;
27 import com.badlogic.gdx.jnigen.parsing.JavaMethodParser.Argument;
28 import com.badlogic.gdx.jnigen.parsing.JavaMethodParser.JavaMethod;
29 import com.badlogic.gdx.jnigen.parsing.JavaMethodParser.JavaSegment;
30 import com.badlogic.gdx.jnigen.parsing.JavaMethodParser.JniSection;
31 import com.badlogic.gdx.jnigen.parsing.JniHeaderCMethodParser;
32 import com.badlogic.gdx.jnigen.parsing.RobustJavaMethodParser;
33 
34 /** Goes through a Java source directory, checks each .java file for native methods and emits C/C++ code accordingly, both .h and
35  * .cpp files.
36  *
37  * <h2>Augmenting Java Files with C/C++</h2> C/C++ code can be directly added to native methods in the Java file as block comments
38  * starting at the same line as the method signature. Custom JNI code that is not associated with a native method can be added via
39  * a special block comment as shown below.</p>
40  *
41  * All arguments can be accessed by the name specified in the Java native method signature (unless you use $ in your identifier
42  * which is allowed in Java).
43  *
44  * <pre>
45  * package com.badlogic.jnigen;
46  *
47  * public class MyJniClass {
48  *   /*JNI
49  *   #include &lt;math.h&gt;
50  *   *<i>/</i>
51  *
52  *   public native void addToArray(float[] array, int len, float value); /*
53  *     for(int i = 0; i < len; i++) {
54  *       array[i] = value;
55  *     }
56  *   *<i>/</i>
57  * }
58  * </pre>
59  *
60  * The generated header file is automatically included in the .cpp file. Methods and custom JNI code can be mixed throughout the
61  * Java file, their order is preserved in the generated .cpp file. Method overloading is supported but not recommended as the
62  * overloading detection is very basic.</p>
63  *
64  * If a native method has strings, one dimensional primitive arrays or direct {@link Buffer} instances as arguments, JNI setup and
65  * cleanup code is automatically generated.</p>
66  *
67  * The following list gives the mapping from Java to C/C++ types for arguments:
68  *
69  * <table border="1">
70  * <tr>
71  * <td>Java</td>
72  * <td>C/C++</td>
73  * </tr>
74  * <tr>
75  * <td>String</td>
76  * <td>char* (UTF-8)</td>
77  * </tr>
78  * <tr>
79  * <td>boolean[]</td>
80  * <td>bool*</td>
81  * </tr>
82  * <tr>
83  * <td>byte[]</td>
84  * <td>char*</td>
85  * </tr>
86  * <tr>
87  * <td>char[]</td>
88  * <td>unsigned short*</td>
89  * </tr>
90  * <tr>
91  * <td>short[]</td>
92  * <td>short*</td>
93  * </tr>
94  * <tr>
95  * <td>int[]</td>
96  * <td>int*</td>
97  * </tr>
98  * <tr>
99  * <td>long[]</td>
100  * <td>long long*</td>
101  * </tr>
102  * <tr>
103  * <td>float[]</td>
104  * <td>float*</td>
105  * </tr>
106  * <tr>
107  * <td>double[]</td>
108  * <td>double*</td>
109  * </tr>
110  * <tr>
111  * <td>Buffer</td>
112  * <td>unsigned char*</td>
113  * </tr>
114  * <tr>
115  * <td>ByteBuffer</td>
116  * <td>char*</td>
117  * </tr>
118  * <tr>
119  * <td>CharBuffer</td>
120  * <td>unsigned short*</td>
121  * </tr>
122  * <tr>
123  * <td>ShortBuffer</td>
124  * <td>short*</td>
125  * </tr>
126  * <tr>
127  * <td>IntBuffer</td>
128  * <td>int*</td>
129  * </tr>
130  * <tr>
131  * <td>LongBuffer</td>
132  * <td>long long*</td>
133  * </tr>
134  * <tr>
135  * <td>FloatBuffer</td>
136  * <td>float*</td>
137  * </tr>
138  * <tr>
139  * <td>DoubleBuffer</td>
140  * <td>double*</td>
141  * </tr>
142  * <tr>
143  * <td>Anything else</td>
144  * <td>jobject/jobjectArray</td>
145  * </tr>
146  * </table>
147  *
148  * If you need control over setting up and cleaning up arrays/strings and direct buffers you can tell the NativeCodeGenerator to
149  * omit setup and cleanup code by starting the native code block comment with "/*MANUAL" instead of just "/*" to the method name.
150  * See libgdx's Gdx2DPixmap load() method for an example.
151  *
152  * <h2>.h/.cpp File Generation</h2> The .h files are created via javah, which has to be on your path. The Java classes have to be
153  * compiled and accessible to the javah tool. The name of the generated .h/.cpp files is the fully qualified name of the class,
154  * e.g. com.badlogic.jnigen.MyJniClass.h/.cpp. The generator takes the following parameters as input:
155  *
156  * <ul>
157  * <li>Java source directory, containing the .java files, e.g. src/ in an Eclipse project</li>
158  * <li>Java class directory, containing the compiled .class files, e.g. bin/ in an Eclipse project</li>
159  * <li>JNI output directory, where the resulting .h and .cpp files will be stored, e.g. jni/</li>
160  * </ul>
161  *
162  * A default invocation of the generator looks like this:
163  *
164  * <pre>
165  * new NativeCodeGenerator().generate(&quot;src&quot;, &quot;bin&quot;, &quot;jni&quot;);
166  * </pre>
167  *
168  * To automatically compile and load the native code, see the classes {@link AntScriptGenerator}, {@link BuildExecutor} and
169  * {@link JniGenSharedLibraryLoader} classes. </p>
170  *
171  * @author mzechner */
172 public class NativeCodeGenerator {
173 	private static final String JNI_METHOD_MARKER = "native";
174 	private static final String JNI_ARG_PREFIX = "obj_";
175 	private static final String JNI_RETURN_VALUE = "JNI_returnValue";
176 	private static final String JNI_WRAPPER_PREFIX = "wrapped_";
177 	FileDescriptor sourceDir;
178 	String classpath;
179 	FileDescriptor jniDir;
180 	String[] includes;
181 	String[] excludes;
182 	AntPathMatcher matcher = new AntPathMatcher();
183 	JavaMethodParser javaMethodParser = new RobustJavaMethodParser();
184 	CMethodParser cMethodParser = new JniHeaderCMethodParser();
185 	CMethodParserResult cResult;
186 
187 	/** Generates .h/.cpp files from the Java files found in "src/", with their .class files being in "bin/". The generated files
188 	 * will be stored in "jni/". All paths are relative to the applications working directory.
189 	 * @throws Exception */
generate()190 	public void generate () throws Exception {
191 		generate("src", "bin", "jni", null, null);
192 	}
193 
194 	/** Generates .h/.cpp fiels from the Java files found in <code>sourceDir</code>, with their .class files being in
195 	 * <code>classpath</code>. The generated files will be stored in <code>jniDir</code>. All paths are relative to the
196 	 * applications working directory.
197 	 * @param sourceDir the directory containing the Java files
198 	 * @param classpath the directory containing the .class files
199 	 * @param jniDir the output directory
200 	 * @throws Exception */
generate(String sourceDir, String classpath, String jniDir)201 	public void generate (String sourceDir, String classpath, String jniDir) throws Exception {
202 		generate(sourceDir, classpath, jniDir, null, null);
203 	}
204 
205 	/** Generates .h/.cpp fiels from the Java files found in <code>sourceDir</code>, with their .class files being in
206 	 * <code>classpath</code>. The generated files will be stored in <code>jniDir</code>. The <code>includes</code> and
207 	 * <code>excludes</code> parameters allow to specify directories and files that should be included/excluded from the
208 	 * generation. These can be given in the Ant path format. All paths are relative to the applications working directory.
209 	 * @param sourceDir the directory containing the Java files
210 	 * @param classpath the directory containing the .class files
211 	 * @param jniDir the output directory
212 	 * @param includes files/directories to include, can be null (all files are used)
213 	 * @param excludes files/directories to exclude, can be null (no files are excluded)
214 	 * @throws Exception */
generate(String sourceDir, String classpath, String jniDir, String[] includes, String[] excludes)215 	public void generate (String sourceDir, String classpath, String jniDir, String[] includes, String[] excludes)
216 		throws Exception {
217 		this.sourceDir = new FileDescriptor(sourceDir);
218 		this.jniDir = new FileDescriptor(jniDir);
219 		this.classpath = classpath;
220 		this.includes = includes;
221 		this.excludes = excludes;
222 
223 		// check if source directory exists
224 		if (!this.sourceDir.exists()) {
225 			throw new Exception("Java source directory '" + sourceDir + "' does not exist");
226 		}
227 
228 		// generate jni directory if necessary
229 		if (!this.jniDir.exists()) {
230 			if (!this.jniDir.mkdirs()) {
231 				throw new Exception("Couldn't create JNI directory '" + jniDir + "'");
232 			}
233 		}
234 
235 		// process the source directory, emitting c/c++ files to jniDir
236 		processDirectory(this.sourceDir);
237 	}
238 
processDirectory(FileDescriptor dir)239 	private void processDirectory (FileDescriptor dir) throws Exception {
240 		FileDescriptor[] files = dir.list();
241 		for (FileDescriptor file : files) {
242 			if (file.isDirectory()) {
243 				if (file.path().contains(".svn")) continue;
244 				if (excludes != null && matcher.match(file.path(), excludes)) continue;
245 				processDirectory(file);
246 			} else {
247 				if (file.extension().equals("java")) {
248 					if (file.name().contains("NativeCodeGenerator")) continue;
249 					if (includes != null && !matcher.match(file.path(), includes)) continue;
250 					if (excludes != null && matcher.match(file.path(), excludes)) continue;
251 					String className = getFullyQualifiedClassName(file);
252 					FileDescriptor hFile = new FileDescriptor(jniDir.path() + "/" + className + ".h");
253 					FileDescriptor cppFile = new FileDescriptor(jniDir + "/" + className + ".cpp");
254 					if (file.lastModified() < cppFile.lastModified()) {
255 						System.out.println("C/C++ for '" + file.path() + "' up to date");
256 						continue;
257 					}
258 					String javaContent = file.readString();
259 					if (javaContent.contains(JNI_METHOD_MARKER)) {
260 						ArrayList<JavaSegment> javaSegments = javaMethodParser.parse(javaContent);
261 						if (javaSegments.size() == 0) {
262 							System.out.println("Skipping '" + file + "', no JNI code found.");
263 							continue;
264 						}
265 						System.out.print("Generating C/C++ for '" + file + "'...");
266 						generateHFile(file);
267 						generateCppFile(javaSegments, hFile, cppFile);
268 						System.out.println("done");
269 					}
270 				}
271 			}
272 		}
273 	}
274 
getFullyQualifiedClassName(FileDescriptor file)275 	private String getFullyQualifiedClassName (FileDescriptor file) {
276 		String className = file.path().replace(sourceDir.path(), "").replace('\\', '.').replace('/', '.').replace(".java", "");
277 		if (className.startsWith(".")) className = className.substring(1);
278 		return className;
279 	}
280 
generateHFile(FileDescriptor file)281 	private void generateHFile (FileDescriptor file) throws Exception {
282 		String className = getFullyQualifiedClassName(file);
283 		String command = "javah -classpath " + classpath + " -o " + jniDir.path() + "/" + className + ".h " + className;
284 		Process process = Runtime.getRuntime().exec(command);
285 		process.waitFor();
286 		if (process.exitValue() != 0) {
287 			System.out.println();
288 			System.out.println("Command: " + command);
289 			InputStream errorStream = process.getErrorStream();
290 			int c = 0;
291 			while ((c = errorStream.read()) != -1) {
292 				System.out.print((char)c);
293 			}
294 		}
295 	}
296 
emitHeaderInclude(StringBuffer buffer, String fileName)297 	protected void emitHeaderInclude (StringBuffer buffer, String fileName) {
298 		buffer.append("#include <" + fileName + ">\n");
299 	}
300 
generateCppFile(ArrayList<JavaSegment> javaSegments, FileDescriptor hFile, FileDescriptor cppFile)301 	private void generateCppFile (ArrayList<JavaSegment> javaSegments, FileDescriptor hFile, FileDescriptor cppFile)
302 		throws Exception {
303 		String headerFileContent = hFile.readString();
304 		ArrayList<CMethod> cMethods = cMethodParser.parse(headerFileContent).getMethods();
305 
306 		StringBuffer buffer = new StringBuffer();
307 		emitHeaderInclude(buffer, hFile.name());
308 
309 		for (JavaSegment segment : javaSegments) {
310 			if (segment instanceof JniSection) {
311 				emitJniSection(buffer, (JniSection)segment);
312 			}
313 
314 			if (segment instanceof JavaMethod) {
315 				JavaMethod javaMethod = (JavaMethod)segment;
316 				if (javaMethod.getNativeCode() == null) {
317 					throw new RuntimeException("Method '" + javaMethod.getName() + "' has no body");
318 				}
319 				CMethod cMethod = findCMethod(javaMethod, cMethods);
320 				if (cMethod == null)
321 					throw new RuntimeException("Couldn't find C method for Java method '" + javaMethod.getClassName() + "#"
322 						+ javaMethod.getName() + "'");
323 				emitJavaMethod(buffer, javaMethod, cMethod);
324 			}
325 		}
326 		cppFile.writeString(buffer.toString(), false, "UTF-8");
327 	}
328 
findCMethod(JavaMethod javaMethod, ArrayList<CMethod> cMethods)329 	private CMethod findCMethod (JavaMethod javaMethod, ArrayList<CMethod> cMethods) {
330 		for (CMethod cMethod : cMethods) {
331 			String javaMethodName = javaMethod.getName().replace("_", "_1");
332 			String javaClassName = javaMethod.getClassName().toString().replace("_", "_1");
333 			if (cMethod.getHead().endsWith(javaClassName + "_" + javaMethodName)
334 				|| cMethod.getHead().contains(javaClassName + "_" + javaMethodName + "__")) {
335 				// FIXME poor man's overloaded method check...
336 				// FIXME float test[] won't work, needs to be float[] test.
337 				if (cMethod.getArgumentTypes().length - 2 == javaMethod.getArguments().size()) {
338 					boolean match = true;
339 					for (int i = 2; i < cMethod.getArgumentTypes().length; i++) {
340 						String cType = cMethod.getArgumentTypes()[i];
341 						String javaType = javaMethod.getArguments().get(i - 2).getType().getJniType();
342 						if (!cType.equals(javaType)) {
343 							match = false;
344 							break;
345 						}
346 					}
347 
348 					if (match) {
349 						return cMethod;
350 					}
351 				}
352 			}
353 		}
354 		return null;
355 	}
356 
emitLineMarker(StringBuffer buffer, int line)357 	private void emitLineMarker (StringBuffer buffer, int line) {
358 		buffer.append("\n//@line:");
359 		buffer.append(line);
360 		buffer.append("\n");
361 	}
362 
emitJniSection(StringBuffer buffer, JniSection section)363 	private void emitJniSection (StringBuffer buffer, JniSection section) {
364 		emitLineMarker(buffer, section.getStartIndex());
365 		buffer.append(section.getNativeCode().replace("\r", ""));
366 	}
367 
emitJavaMethod(StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod)368 	private void emitJavaMethod (StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod) {
369 		// get the setup and cleanup code for arrays, buffers and strings
370 		StringBuffer jniSetupCode = new StringBuffer();
371 		StringBuffer jniCleanupCode = new StringBuffer();
372 		StringBuffer additionalArgs = new StringBuffer();
373 		StringBuffer wrapperArgs = new StringBuffer();
374 		emitJniSetupCode(jniSetupCode, javaMethod, additionalArgs, wrapperArgs);
375 		emitJniCleanupCode(jniCleanupCode, javaMethod, cMethod);
376 
377 		// check if the user wants to do manual setup of JNI args
378 		boolean isManual = javaMethod.isManual();
379 
380 		// if we have disposable arguments (string, buffer, array) and if there is a return
381 		// in the native code (conservative, not syntactically checked), emit a wrapper method.
382 		if (javaMethod.hasDisposableArgument() && javaMethod.getNativeCode().contains("return")) {
383 			// if the method is marked as manual, we just emit the signature and let the
384 			// user do whatever she wants.
385 			if (isManual) {
386 				emitMethodSignature(buffer, javaMethod, cMethod, null, false);
387 				emitMethodBody(buffer, javaMethod);
388 				buffer.append("}\n\n");
389 			} else {
390 				// emit the method containing the actual code, called by the wrapper
391 				// method with setup pointers to arrays, buffers and strings
392 				String wrappedMethodName = emitMethodSignature(buffer, javaMethod, cMethod, additionalArgs.toString());
393 				emitMethodBody(buffer, javaMethod);
394 				buffer.append("}\n\n");
395 
396 				// emit the wrapper method, the one with the declaration in the header file
397 				emitMethodSignature(buffer, javaMethod, cMethod, null);
398 				if (!isManual) {
399 					buffer.append(jniSetupCode);
400 				}
401 
402 				if (cMethod.getReturnType().equals("void")) {
403 					buffer.append("\t" + wrappedMethodName + "(" + wrapperArgs.toString() + ");\n\n");
404 					if (!isManual) {
405 						buffer.append(jniCleanupCode);
406 					}
407 					buffer.append("\treturn;\n");
408 				} else {
409 					buffer.append("\t" + cMethod.getReturnType() + " " + JNI_RETURN_VALUE + " = " + wrappedMethodName + "("
410 						+ wrapperArgs.toString() + ");\n\n");
411 					if (!isManual) {
412 						buffer.append(jniCleanupCode);
413 					}
414 					buffer.append("\treturn " + JNI_RETURN_VALUE + ";\n");
415 				}
416 				buffer.append("}\n\n");
417 			}
418 		} else {
419 			emitMethodSignature(buffer, javaMethod, cMethod, null);
420 			if (!isManual) {
421 				buffer.append(jniSetupCode);
422 			}
423 			emitMethodBody(buffer, javaMethod);
424 			if (!isManual) {
425 				buffer.append(jniCleanupCode);
426 			}
427 			buffer.append("}\n\n");
428 		}
429 
430 	}
431 
emitMethodBody(StringBuffer buffer, JavaMethod javaMethod)432 	protected void emitMethodBody (StringBuffer buffer, JavaMethod javaMethod) {
433 		// emit a line marker
434 		emitLineMarker(buffer, javaMethod.getEndIndex());
435 
436 		// FIXME add tabs cleanup
437 		buffer.append(javaMethod.getNativeCode());
438 		buffer.append("\n");
439 	}
440 
emitMethodSignature(StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod, String additionalArguments)441 	private String emitMethodSignature (StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod, String additionalArguments) {
442 		return emitMethodSignature(buffer, javaMethod, cMethod, additionalArguments, true);
443 	}
444 
emitMethodSignature(StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod, String additionalArguments, boolean appendPrefix)445 	private String emitMethodSignature (StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod, String additionalArguments,
446 		boolean appendPrefix) {
447 		// emit head, consisting of JNIEXPORT,return type and method name
448 		// if this is a wrapped method, prefix the method name
449 		String wrappedMethodName = null;
450 		if (additionalArguments != null) {
451 			String[] tokens = cMethod.getHead().replace("\r\n", "").replace("\n", "").split(" ");
452 			wrappedMethodName = JNI_WRAPPER_PREFIX + tokens[3];
453 			buffer.append("static inline ");
454 			buffer.append(tokens[1]);
455 			buffer.append(" ");
456 			buffer.append(wrappedMethodName);
457 			buffer.append("\n");
458 		} else {
459 			buffer.append(cMethod.getHead());
460 		}
461 
462 		// construct argument list
463 		// Differentiate between static and instance method, then output each argument
464 		if (javaMethod.isStatic()) {
465 			buffer.append("(JNIEnv* env, jclass clazz");
466 		} else {
467 			buffer.append("(JNIEnv* env, jobject object");
468 		}
469 		if (javaMethod.getArguments().size() > 0) buffer.append(", ");
470 		for (int i = 0; i < javaMethod.getArguments().size(); i++) {
471 			// output the argument type as defined in the header
472 			buffer.append(cMethod.getArgumentTypes()[i + 2]);
473 			buffer.append(" ");
474 			// if this is not a POD or an object, we need to add a prefix
475 			// as we will output JNI code to get pointers to strings, arrays
476 			// and direct buffers.
477 			Argument javaArg = javaMethod.getArguments().get(i);
478 			if (!javaArg.getType().isPlainOldDataType() && !javaArg.getType().isObject() && appendPrefix) {
479 				buffer.append(JNI_ARG_PREFIX);
480 			}
481 			// output the name of the argument
482 			buffer.append(javaArg.getName());
483 
484 			// comma, if this is not the last argument
485 			if (i < javaMethod.getArguments().size() - 1) buffer.append(", ");
486 		}
487 
488 		// if this is a wrapper method signature, add the additional arguments
489 		if (additionalArguments != null) {
490 			buffer.append(additionalArguments);
491 		}
492 
493 		// close signature, open method body
494 		buffer.append(") {\n");
495 
496 		// return the wrapped method name if any
497 		return wrappedMethodName;
498 	}
499 
emitJniSetupCode(StringBuffer buffer, JavaMethod javaMethod, StringBuffer additionalArgs, StringBuffer wrapperArgs)500 	private void emitJniSetupCode (StringBuffer buffer, JavaMethod javaMethod, StringBuffer additionalArgs,
501 		StringBuffer wrapperArgs) {
502 		// add environment and class/object as the two first arguments for
503 		// wrapped method.
504 		if (javaMethod.isStatic()) {
505 			wrapperArgs.append("env, clazz, ");
506 		} else {
507 			wrapperArgs.append("env, object, ");
508 		}
509 
510 		// arguments for wrapper method
511 		for (int i = 0; i < javaMethod.getArguments().size(); i++) {
512 			Argument arg = javaMethod.getArguments().get(i);
513 			if (!arg.getType().isPlainOldDataType() && !arg.getType().isObject()) {
514 				wrapperArgs.append(JNI_ARG_PREFIX);
515 			}
516 			// output the name of the argument
517 			wrapperArgs.append(arg.getName());
518 			if (i < javaMethod.getArguments().size() - 1) wrapperArgs.append(", ");
519 		}
520 
521 		// direct buffer pointers
522 		for (Argument arg : javaMethod.getArguments()) {
523 			if (arg.getType().isBuffer()) {
524 				String type = arg.getType().getBufferCType();
525 				buffer.append("\t" + type + " " + arg.getName() + " = (" + type + ")(" + JNI_ARG_PREFIX + arg.getName()
526 					+ "?env->GetDirectBufferAddress(" + JNI_ARG_PREFIX + arg.getName() + "):0);\n");
527 				additionalArgs.append(", ");
528 				additionalArgs.append(type);
529 				additionalArgs.append(" ");
530 				additionalArgs.append(arg.getName());
531 				wrapperArgs.append(", ");
532 				wrapperArgs.append(arg.getName());
533 			}
534 		}
535 
536 		// string pointers
537 		for (Argument arg : javaMethod.getArguments()) {
538 			if (arg.getType().isString()) {
539 				String type = "char*";
540 				buffer.append("\t" + type + " " + arg.getName() + " = (" + type + ")env->GetStringUTFChars(" + JNI_ARG_PREFIX
541 					+ arg.getName() + ", 0);\n");
542 				additionalArgs.append(", ");
543 				additionalArgs.append(type);
544 				additionalArgs.append(" ");
545 				additionalArgs.append(arg.getName());
546 				wrapperArgs.append(", ");
547 				wrapperArgs.append(arg.getName());
548 			}
549 		}
550 
551 		// Array pointers, we have to collect those last as GetPrimitiveArrayCritical
552 		// will explode into our face if we call another JNI method after that.
553 		for (Argument arg : javaMethod.getArguments()) {
554 			if (arg.getType().isPrimitiveArray()) {
555 				String type = arg.getType().getArrayCType();
556 				buffer.append("\t" + type + " " + arg.getName() + " = (" + type + ")env->GetPrimitiveArrayCritical(" + JNI_ARG_PREFIX
557 					+ arg.getName() + ", 0);\n");
558 				additionalArgs.append(", ");
559 				additionalArgs.append(type);
560 				additionalArgs.append(" ");
561 				additionalArgs.append(arg.getName());
562 				wrapperArgs.append(", ");
563 				wrapperArgs.append(arg.getName());
564 			}
565 		}
566 
567 		// new line for separation
568 		buffer.append("\n");
569 	}
570 
emitJniCleanupCode(StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod)571 	private void emitJniCleanupCode (StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod) {
572 		// emit cleanup code for arrays, must come first
573 		for (Argument arg : javaMethod.getArguments()) {
574 			if (arg.getType().isPrimitiveArray()) {
575 				buffer.append("\tenv->ReleasePrimitiveArrayCritical(" + JNI_ARG_PREFIX + arg.getName() + ", " + arg.getName()
576 					+ ", 0);\n");
577 			}
578 		}
579 
580 		// emit cleanup code for strings
581 		for (Argument arg : javaMethod.getArguments()) {
582 			if (arg.getType().isString()) {
583 				buffer.append("\tenv->ReleaseStringUTFChars(" + JNI_ARG_PREFIX + arg.getName() + ", " + arg.getName() + ");\n");
584 			}
585 		}
586 
587 		// new line for separation
588 		buffer.append("\n");
589 	}
590 }
591