package ch.jodersky.sbt.jni.javah; /* * Code is copied from (https://github.com/Glavo/gjavah) * MIT License */ import org.objectweb.asm.*; import java.io.*; import java.nio.file.*; import java.util.*; public final class HeaderGenerator { private static final Path[] EMPTY_PATH_ARRAY = new Path[0]; private static final List THROWABLE_NAME_LIST = Arrays.asList("Ljava/lang/Throwable;", "Ljava/lang/Error;", "Ljava/lang/Exception"); private static final HeaderGenerator generator = new HeaderGenerator(); private static String escape(String source, Boolean escapeDots) { StringBuilder builder = new StringBuilder(); char ch; for (int i = 0; i < source.length(); i++) { switch (ch = source.charAt(i)) { case '_': builder.append("_1"); break; case ';': builder.append("_2"); break; case '[': builder.append("_3"); break; case '/': if(escapeDots) builder.append('_'); else builder.append('.'); break; default: if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { builder.append(ch); } else { builder.append("_0").append(String.format("%04x", (int) ch)); } } } return builder.toString(); } public static void generateFunctionDeclarations(ClassReader reader, PrintWriter output) { getGenerator().classGenerateFunctionDeclarations(reader, output); } public static void generateHeader(ClassReader reader, PrintWriter output) { getGenerator().classGenerateHeader(reader, output); } public static void generateHeader(byte[] classFile, PrintWriter output) { getGenerator().classGenerateHeader(classFile, output); } public static void generateHeader(byte[] classFileBuffer, int classFileOffset, int classFileLength, PrintWriter output) { getGenerator().classGenerateHeader(classFileBuffer, classFileOffset, classFileLength, output); } public static void generateHeader(String className, PrintWriter output) throws IOException { getGenerator().classGenerateHeader(className, output); } public static void generateHeader(InputStream input, PrintWriter output) throws IOException { getGenerator().classGenerateHeader(input, output); } public static HeaderGenerator getGenerator() { return generator; } private Path[] classPaths; private boolean useRuntimeClassPath; public HeaderGenerator() { this(EMPTY_PATH_ARRAY, true); } public HeaderGenerator(boolean useRuntimeClassPath) { this(EMPTY_PATH_ARRAY, useRuntimeClassPath); } public HeaderGenerator(Path[] classPaths) { this(classPaths, true); } public HeaderGenerator(Path[] classPaths, boolean useRuntimeClassPath) { Objects.requireNonNull(classPaths); this.classPaths = classPaths; this.useRuntimeClassPath = useRuntimeClassPath; } private boolean isThrowable(Type type) { String desc = type.getDescriptor(); if (!desc.startsWith("L")) { return false; } if (classPaths.length == 0 && !useRuntimeClassPath) { return THROWABLE_NAME_LIST.contains(type.getDescriptor()); } String className = type.getInternalName(); while (true) { if (className == null) { return false; } if (className.equals("java/lang/Throwable")) { return true; } try { ClassReader reader = findClass(className); if (reader == null) { return false; } className = reader.getSuperName(); } catch (Exception ignored) { return false; } } } private String typeToNative(Type tpe) { if (tpe == Type.BOOLEAN_TYPE) { return "jboolean"; } else if (tpe == Type.BYTE_TYPE) { return "jbyte"; } else if (tpe == Type.CHAR_TYPE) { return "jchar"; } else if (tpe == Type.SHORT_TYPE) { return "jshort"; } else if (tpe == Type.INT_TYPE) { return "jint"; } else if (tpe == Type.LONG_TYPE) { return "jlong"; } else if (tpe == Type.FLOAT_TYPE) { return "jfloat"; } else if (tpe == Type.DOUBLE_TYPE) { return "jdouble"; } else if (tpe == Type.VOID_TYPE) { return "void"; } else { String desc = tpe.getDescriptor(); if (desc.startsWith("[")) { Type elemTpe = tpe.getElementType(); String descriptor = elemTpe.getDescriptor(); if (descriptor.startsWith("[") || descriptor.startsWith("L")) { return "jobjectArray"; } return typeToNative(elemTpe) + "Array"; } if (desc.equals("Ljava/lang/String;")) { return "jstring"; } if (desc.equals("Ljava/lang/Class;")) { return "jclass"; } if (isThrowable(tpe)) { return "jthrowable"; } return "jobject"; } } private ClassReader findClass(String name) { loop: for (Path path : classPaths) { path = path.toAbsolutePath(); if (!Files.exists(path)) { continue; } String[] ps = name.split("/|\\."); if (ps.length == 0) { continue; } ps[ps.length - 1] += ".class"; for (String p : ps) { path = path.resolve(p); if (!Files.exists(path)) { continue loop; } } try { return new ClassReader(Files.newInputStream(path)); } catch (IOException ignored) { } } try { return new ClassReader(name.replace('/', '.')); } catch (IOException e) { return null; } } private void classGenerateFunctionDeclarations(Generator generator, PrintWriter output) { String className = escape(generator.getClassName(), false); String classNameNoDots = escape(generator.getClassName(), true); for (Map.Entry> entry : generator.getMethods().entrySet()) { boolean overload = entry.getValue().size() > 1; for (MethodDesc desc : entry.getValue()) { String methodName = escape(entry.getKey(), false); output.println("/*" + "\n" + " * Class: " + className + "\n" + " * Method: " + entry.getKey() + "\n" + " * Signature: " + desc.descriptor + "\n" + " */" ); Type[] argTypes = Type.getArgumentTypes(desc.descriptor); Type retType = Type.getReturnType(desc.descriptor); output.print( "JNIEXPORT " + typeToNative(retType) + " JNICALL Java_" + classNameNoDots + "_" + methodName ); if (overload) { output.print("__"); for (Type tpe : argTypes) { output.print(escape(tpe.toString(), true)); } } output.println(); output.print(" (JNIEnv *, "); if (desc.isStatic) { output.print("jclass"); } else { output.print("jobject"); } for (Type tpe : argTypes) { output.print(", " + typeToNative(tpe)); } output.println(");\n"); } } } public void classGenerateFunctionDeclarations(ClassReader reader, PrintWriter output) { Generator generator = new Generator(); reader.accept(generator, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); classGenerateFunctionDeclarations(generator, output); } private void classGenerateHeaderWithoutInclude(Generator generator, PrintWriter output) { String className = escape(generator.getClassName(), true); output.println("/* Header for class " + className + " */"); String includeHeader = "_Include_" + className; output.println("#ifndef " + includeHeader); output.println("#define " + includeHeader); output.println("#ifdef __cplusplus\n" + "extern \"C\" {\n" + "#endif"); classGenerateFunctionDeclarations(generator, output); output.println("#ifdef __cplusplus\n" + "}\n" + "#endif\n" + "#endif\n"); } private void classGenerateHeaderWithoutInclude(ClassReader reader, PrintWriter output) { Generator generator = new Generator(); reader.accept(generator, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); classGenerateHeaderWithoutInclude(generator, output); } private void classGenerateHeader(Generator generator, PrintWriter output) { output.println("/* DO NOT EDIT THIS FILE - it is machine generated */"); output.println("#include "); classGenerateHeaderWithoutInclude(generator, output); } public void classGenerateHeader(ClassReader reader, PrintWriter output) { Generator generator = new Generator(); reader.accept(generator, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); classGenerateHeader(generator, output); } public void classGenerateHeader(byte[] classFile, PrintWriter output) { classGenerateHeader(new ClassReader(classFile), output); } public void classGenerateHeader(byte[] classFileBuffer, int classFileOffset, int classFileLength, PrintWriter output) { classGenerateHeader(new ClassReader(classFileBuffer, classFileOffset, classFileLength), output); } public void classGenerateHeader(String className, PrintWriter output) throws IOException { ClassReader reader = findClass(className); if (reader == null) { throw new IOException(); } classGenerateHeader(reader, output); } public void classGenerateHeader(InputStream input, PrintWriter output) throws IOException { classGenerateHeader(new ClassReader(input), output); } public Path[] getClassPaths() { return classPaths; } public void setClassPaths(Path[] classPaths) { Objects.requireNonNull(classPaths); this.classPaths = classPaths; } public boolean isUseRuntimeClassPath() { return useRuntimeClassPath; } public void setUseRuntimeClassPath(boolean useRuntimeClassPath) { this.useRuntimeClassPath = useRuntimeClassPath; } public static void run(ArrayList classNames, Path outputDir , ArrayList cps) throws IOException { HeaderGenerator generator = new HeaderGenerator(cps.toArray(new Path[0])); if (Files.notExists(outputDir)) { Files.createDirectories(outputDir); } for (String name : classNames) { ClassReader reader = generator.findClass(name); if (reader == null) { System.err.println("Error: Could not find class file for '" + name + "'."); return; } Generator g = new Generator(); reader.accept(g, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter( outputDir.resolve(name.replace('.', '_').replace("$", "__") + ".h"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) { generator.classGenerateHeader(g, writer); } } } } final class MethodDesc { public final boolean isStatic; public final String descriptor; public MethodDesc(boolean isStatic, String descriptor) { this.isStatic = isStatic; this.descriptor = descriptor; } } final class Generator extends ClassVisitor { private String className; private Map constants = new LinkedHashMap<>(); private Map> methods = new LinkedHashMap>(); public Generator() { super(Opcodes.ASM5); } @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { className = name; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if ((access & Opcodes.ACC_NATIVE) != 0) { if (methods.containsKey(name)) { methods.get(name).add(new MethodDesc((access & Opcodes.ACC_STATIC) != 0, descriptor)); } else { LinkedHashSet set = new LinkedHashSet<>(); set.add(new MethodDesc((access & Opcodes.ACC_STATIC) != 0, descriptor)); methods.put(name, set); } } return null; } public String getClassName() { return className; } public Map> getMethods() { return methods; } }