From e48c0668029849af07d1966729c5b703e89b1d00 Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 6 Dec 2019 03:42:00 +0800 Subject: update --- src/main/java/org/glavo/javah/JNIGenerator.java | 315 +++++++++++++++--------- 1 file changed, 205 insertions(+), 110 deletions(-) (limited to 'src/main/java/org/glavo/javah/JNIGenerator.java') 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 \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 searchPaths; + private final Path outputDir; - private String className; + public JNIGenerator(Path outputDir) { + this(outputDir, null, null); + } + + public JNIGenerator(Path outputDir, Iterable searchPaths) { + this(outputDir, searchPaths, null); + } - private Map methodNameCount = new HashMap<>(); - private LinkedList methods = new LinkedList<>(); - private LinkedList constants = new LinkedList<>(); + public JNIGenerator(Path outputDir, Iterable 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 "); + 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 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; } } } -- cgit v1.2.3