package org.glavo.javah; import org.objectweb.asm.*; import java.io.*; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; public final class HeaderGenerator { private static String escape(String source) { 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 '/': 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(); } private static 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 str = tpe.toString(); if (str.startsWith("[")) { str = typeToNative(tpe.getElementType()); if ("jclass".equals(str)) { return "jobjectArray"; } else { return str + "Array"; } } return "jobject"; //TODO: jthrowable } } public static void generateHeader(ClassReader reader, PrintWriter output) { output.println("/* DO NOT EDIT THIS FILE - it is machine generated */"); output.println("#include "); Generator generator = new Generator(); reader.accept(generator, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); StringBuilder builder = new StringBuilder(); String className = escape(generator.getClassName()); 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"); for (Map.Entry> entry : generator.getMethods().entrySet()) { boolean overload = entry.getValue().size() > 1; for (MethodDesc desc : entry.getValue()) { String methodName = escape(entry.getKey()); 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_" + className + "_" + methodName ); if (overload) { output.print("__"); for (Type tpe : argTypes) { output.print(escape(tpe.toString())); } } 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"); } } output.println("#ifdef __cplusplus\n" + "}\n" + "#endif\n" + "#endif\n"); } public static void generateHeader(byte[] classFile, PrintWriter output) { generateHeader(new ClassReader(classFile), output); } public static void generateHeader(byte[] classFileBuffer, int classFileOffset, int classFileLength, PrintWriter output) { generateHeader(new ClassReader(classFileBuffer, classFileOffset, classFileLength), output); } public static void generateHeader(String className, PrintWriter output) throws IOException { generateHeader(new ClassReader(className), output); } public static void generateHeader(InputStream input, PrintWriter output) throws IOException { generateHeader(new ClassReader(input), output); } public static void main(String[] args) throws IOException { File f = new File(args[0]); PrintWriter writer = new PrintWriter(System.out); generateHeader(new FileInputStream(f), writer); writer.flush(); } } class MethodDesc { public final boolean isStatic; public final String descriptor; public MethodDesc(boolean isStatic, String descriptor) { this.isStatic = isStatic; this.descriptor = descriptor; } } class Generator extends ClassVisitor { private String className; 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; } }