Home | History | Annotate | Download | only in jnigen
      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 */
    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 */
    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 */
    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 
    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 
    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 
    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 
    297 	protected void emitHeaderInclude (StringBuffer buffer, String fileName) {
    298 		buffer.append("#include <" + fileName + ">\n");
    299 	}
    300 
    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 
    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 
    357 	private void emitLineMarker (StringBuffer buffer, int line) {
    358 		buffer.append("\n//@line:");
    359 		buffer.append(line);
    360 		buffer.append("\n");
    361 	}
    362 
    363 	private void emitJniSection (StringBuffer buffer, JniSection section) {
    364 		emitLineMarker(buffer, section.getStartIndex());
    365 		buffer.append(section.getNativeCode().replace("\r", ""));
    366 	}
    367 
    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 
    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 
    441 	private String emitMethodSignature (StringBuffer buffer, JavaMethod javaMethod, CMethod cMethod, String additionalArguments) {
    442 		return emitMethodSignature(buffer, javaMethod, cMethod, additionalArguments, true);
    443 	}
    444 
    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 
    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 
    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