aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/glavo/javah/JNIGenerator.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/glavo/javah/JNIGenerator.java')
-rw-r--r--src/main/java/org/glavo/javah/JNIGenerator.java315
1 files changed, 205 insertions, 110 deletions
diff --git a/src/main/java/org/glavo/javah/JNIGenerator.java b/src/main/java/org/glavo/javah/JNIGenerator.java
index c1a9d40..bec2ea9 100644
--- a/src/main/java/org/glavo/javah/JNIGenerator.java
+++ b/src/main/java/org/glavo/javah/JNIGenerator.java
@@ -1,143 +1,238 @@
package org.glavo.javah;
-import org.objectweb.asm.*;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.UncheckedIOException;
+import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-public class JNIGenerator extends ClassVisitor {
- public static final String FILE_HEADER =
- "/* DO NOT EDIT THIS FILE - it is machine generated */\n" +
- "#include <jni.h>\n";
+import static org.glavo.javah.Utils.*;
- public static final String FILE_END = "\n";
+public class JNIGenerator {
- private final PrintWriter output;
- private final Path classFile;
+ private final PrintWriter errorHandle;
+ private final Iterable<SearchPath> searchPaths;
+ private final Path outputDir;
- private String className;
+ public JNIGenerator(Path outputDir) {
+ this(outputDir, null, null);
+ }
+
+ public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths) {
+ this(outputDir, searchPaths, null);
+ }
- private Map<String, Integer> methodNameCount = new HashMap<>();
- private LinkedList<NativeMethod> methods = new LinkedList<>();
- private LinkedList<ConstantField> constants = new LinkedList<>();
+ public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths, PrintWriter errorHandle) {
+ Objects.requireNonNull(outputDir);
- public JNIGenerator(PrintWriter output, Path classFile) {
- super(Opcodes.ASM7);
- Objects.requireNonNull(output);
- Objects.requireNonNull(classFile);
- this.output = output;
- this.classFile = classFile;
+ if (searchPaths == null) {
+ searchPaths = Collections.singleton(RuntimeSearchPath.INSTANCE);
+ }
+ if (errorHandle == null) {
+ errorHandle = NOOP_WRITER;
+ }
+
+ this.errorHandle = errorHandle;
+ this.searchPaths = searchPaths;
+ this.outputDir = outputDir;
}
- public void generate() {
- ClassReader cls = null;
- try {
- cls = new ClassReader(Files.readAllBytes(classFile));
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- cls.accept(this, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
-
- String name = Utils.encode(className).replace('/', '_');
- String guard = "_Included_" + className;
-
- output.println("/* Header for class " + className + " */\n\n");
- output.println("#ifndef " + guard);
- output.println("#define " + guard);
- output.println("#ifdef __cplusplus");
- output.println("extern \"C\" {");
- output.println("#endif");
-
- for (ConstantField constant : constants) {
- String cm = name + "_" + Utils.encode(constant.name);
- String value;
- if (constant.value instanceof Float) {
- value = constant.value.toString() + "f";
- } else if (constant.value instanceof Long) {
- value = constant.value.toString() + "i64";
- } else if (constant.value instanceof Double) {
- value = constant.value.toString();
- } else {
- value = constant.value.toString() + "L";
+ public void generate(ClassName name) {
+ Objects.requireNonNull(name);
+ if (Files.exists(outputDir) && !Files.isDirectory(outputDir)) {
+ throw new IllegalArgumentException(outputDir + "is not a directory");
+ }
+ if (Files.notExists(outputDir)) {
+ try {
+ Files.createDirectories(outputDir);
+ } catch (IOException e) {
+ errorHandle.println("error: cannot create directory " + outputDir);
+ e.printStackTrace(errorHandle);
+ return;
}
- output.println("#undef " + cm);
- output.println("#define " + cm + " " + value);
}
- for (NativeMethod method : methods) {
- String mm = "Java_" + name + "_" + Utils.encode(method.name);
- if (methodNameCount.getOrDefault(method.name, 1) > 1) {
- mm = mm + "__" + Utils.mangledArgSignature(method.type);
+ Path op = outputDir.resolve(name.mangledName() + ".h");
+ try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(op))) {
+ generateTo(name, out);
+ } catch (Exception ex) {
+ errorHandle.println("error: cannot write to " + op);
+ ex.printStackTrace(errorHandle);
+ try {
+ Files.deleteIfExists(op);
+ } catch (IOException ignored) {
}
- String r = Utils.mapToNativeType(method.type.getReturnType());
- String args = Stream.concat(Stream.of("JNIEnv *", method.isStatic ? "jclass" : "jobject"),
- Arrays.stream(method.type.getArgumentTypes()).map(Utils::mapToNativeType))
- .collect(Collectors.joining(","));
-
- output.println("/*");
- output.println(" * Class: " + name);
- output.println(" * Method: " + mm);
- output.println(" * Signature: " + method.type);
- output.println(" */");
- output.println("JNIEXPORT " + r + " JNICALL " + mm);
- output.println(" (" + args + ");");
- output.println();
- }
-
- output.println("#ifdef __cplusplus");
- output.println("}");
- output.println("#endif");
- output.println("#endif");
+ }
}
- @Override
- public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
- className = name;
- }
+ public void generateTo(ClassName name, Writer writer) throws IOException {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(writer);
+
+ ClassMetaInfo meta = new ClassMetaInfo();
+ {
+ Path f = search(name);
+ if (f == null) {
+ errorHandle.println("Not found class " + name);
+ return;
+ }
+
+ try (InputStream in = Files.newInputStream(f)) {
+ ClassReader reader = new ClassReader(in);
+ reader.accept(meta, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ } catch (IOException e) {
+ errorHandle.println("error: cannot open class file of " + name);
+ e.printStackTrace(errorHandle);
+ errorHandle.flush();
+ throw e;
+ }
+ }
- @Override
- public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
- if ((access & Opcodes.ACC_NATIVE) != 0) {
- methodNameCount.put(name, methodNameCount.getOrDefault(name, 0) + 1);
- methods.add(new NativeMethod(name, Type.getType(descriptor), (access & Opcodes.ACC_STATIC) != 0));
+ PrintWriter out = writer instanceof PrintWriter ? (PrintWriter) writer : new PrintWriter(writer);
+ out.println("/* DO NOT EDIT THIS FILE - it is machine generated */");
+ out.println("#include <jni.h>");
+ out.println("/* Header for class " + name.mangledName() + " */");
+ out.println();
+ out.println("#ifndef _Included_" + name.mangledName());
+ out.println("#define _Included_" + name.mangledName());
+ out.println("#ifdef __cplusplus");
+ out.println("extern \"C\" {");
+ out.println("#endif");
+
+ for (Constant constant : meta.constants) {
+ String cn = name.mangledName() + "_" + constant.mangledName();
+ out.println("#undef " + cn);
+ out.println("#define " + cn + " " + constant.valueToString());
}
- return null;
- }
- @Override
- public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
- if (value != null && !(value instanceof String)) {
- constants.add(new ConstantField(name, Type.getType(descriptor), value));
+ for (NativeMethod method : meta.methods) {
+ String ret = mapTypeToNative(method.type().getReturnType());
+ List<String> args = new ArrayList<>();
+ args.add("JNIEnv *");
+ args.add(method.isStatic() ? "jclass" : "jobject");
+ args.addAll(Arrays.asList(mapArgsTypeToNative(method.type())));
+
+ String methodName =
+ "Java_" + name.mangledName() + "_" + (meta.isOverloadMethod(method) ? method.longMangledName() : method.mangledName());
+
+ out.println("/*");
+ out.println(" * Class: " + name.mangledName());
+ out.println(" * Method: " + method.mangledName());
+ out.println(" * Signature: " + escape(method.type().toString()));
+ out.println(" */");
+ out.println("JNIEXPORT " + ret + " JNICALL " + methodName);
+ out.println(" (" + String.join(", ", args) + ");");
+ out.println();
}
- return null;
+
+ out.println("#ifdef __cplusplus");
+ out.println("}");
+ out.println("#endif");
+ out.println("#endif");
+
+ }
+
+ private Path search(ClassName name) {
+ return SearchPath.searchFrom(searchPaths, name);
}
- private static class NativeMethod {
- String name;
- Type type;
- boolean isStatic;
+ private String mapTypeToNative(Type type) {
+ Objects.requireNonNull(type);
+ String tpe = type.toString();
+ if (tpe.startsWith("(")) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (tpe) {
+ case "Z":
+ return "jboolean";
+ case "B":
+ return "jbyte";
+ case "C":
+ return "jchar";
+ case "S":
+ return "jshort";
+ case "I":
+ return "jint";
+ case "J":
+ return "jlong";
+ case "F":
+ return "jfloat";
+ case "D":
+ return "jdouble";
+ case "V":
+ return "void";
+ case "Ljava/lang/Class;":
+ return "jclass";
+ case "Ljava/lang/String;":
+ return "jstring";
+ case "Ljava/lang/Throwable;":
+ return "jthrowable";
+ case "[Z":
+ return "jbooleanArray";
+ case "[B":
+ return "jbyteArray";
+ case "[C":
+ return "jcharArray";
+ case "[S":
+ return "jshortArray";
+ case "[I":
+ return "jintArray";
+ case "[J":
+ return "jlongArray";
+ case "[F":
+ return "jfloatArray";
+ case "[D":
+ return "jdoubleArray";
+ }
+
+ if (tpe.startsWith("[")) {
+ return "jobjectArray";
+ }
+
+ if (tpe.startsWith("L") && tpe.endsWith(";")) {
+ ClassName n = ClassName.of(tpe.substring(1, tpe.length() - 1).replace('/', '.'));
+ if (isThrowable(n)) {
+ return "jthrowable";
+ } else {
+ return "jobject";
+ }
+ }
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
- public NativeMethod(String name, Type type, boolean isStatic) {
- this.name = name;
- this.type = type;
- this.isStatic = isStatic;
+ private String[] mapArgsTypeToNative(Type methodType) {
+ Objects.requireNonNull(methodType);
+ if (!METHOD_TYPE_PATTERN.matcher(methodType.toString()).matches()) {
+ throw new IllegalArgumentException(methodType + " is not a method type");
+ }
+ Type[] args = methodType.getArgumentTypes();
+ String[] ans = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ ans[i] = mapTypeToNative(args[i]);
}
+ return ans;
}
- private static class ConstantField {
- String name;
- Type type;
- Object value;
+ private boolean isThrowable(ClassName name) {
+ if (name == null) {
+ return false;
+ }
+ switch (name.className()) {
+ case "java.lang.Throwable":
+ case "java.lang.Error":
+ case "java.lang.Exception":
+ return true;
+ case "java.lang.Object":
+ return false;
+ }
- public ConstantField(String name, Type type, Object value) {
- this.name = name;
- this.type = type;
- this.value = value;
+ try (InputStream in = Files.newInputStream(search(name))) {
+ return isThrowable(superClassOf(new ClassReader(in)));
+ } catch (Exception ignored) {
+ errorHandle.println("warning: class " + name + " not found");
+ return false;
}
}
}