From e8c30ce4df4fa9e9b8f559153b54dfc2616d4ebc Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 29 Nov 2019 07:29:34 +0800 Subject: update --- build.gradle | 3 +- src/main/java/org/glavo/javah/ClassPath.java | 5 - src/main/java/org/glavo/javah/FileManager.java | 9 -- src/main/java/org/glavo/javah/JNIGenerator.java | 141 +++++++++++++++++++++ src/main/java/org/glavo/javah/JavahConfig.java | 51 -------- src/main/java/org/glavo/javah/JavahTask.java | 162 ++++++++++++++++++++++++ src/main/java/org/glavo/javah/Main.java | 71 ++++++++++- src/main/java/org/glavo/javah/ModulePath.java | 8 +- src/main/java/org/glavo/javah/Utils.java | 87 ++++++++++++- 9 files changed, 465 insertions(+), 72 deletions(-) delete mode 100644 src/main/java/org/glavo/javah/FileManager.java create mode 100644 src/main/java/org/glavo/javah/JNIGenerator.java delete mode 100644 src/main/java/org/glavo/javah/JavahConfig.java create mode 100644 src/main/java/org/glavo/javah/JavahTask.java diff --git a/build.gradle b/build.gradle index 9bdd733..e12cda7 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,8 @@ modularity.mixedJavaRelease 8 jar { manifest.attributes( 'Implementation-Version': '1.2', - 'Main-Class': 'org.glavo.javah.Main' + 'Main-Class': 'org.glavo.javah.Main', + "GJavah-Version": project.version ) } diff --git a/src/main/java/org/glavo/javah/ClassPath.java b/src/main/java/org/glavo/javah/ClassPath.java index 552cb9e..2aea039 100644 --- a/src/main/java/org/glavo/javah/ClassPath.java +++ b/src/main/java/org/glavo/javah/ClassPath.java @@ -1,10 +1,5 @@ package org.glavo.javah; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; import java.nio.file.Path; import java.util.List; import java.util.Objects; diff --git a/src/main/java/org/glavo/javah/FileManager.java b/src/main/java/org/glavo/javah/FileManager.java deleted file mode 100644 index 2914f93..0000000 --- a/src/main/java/org/glavo/javah/FileManager.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.glavo.javah; - -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -public class FileManager { - private final List modules = new ArrayList<>(); -} diff --git a/src/main/java/org/glavo/javah/JNIGenerator.java b/src/main/java/org/glavo/javah/JNIGenerator.java new file mode 100644 index 0000000..a00bc6c --- /dev/null +++ b/src/main/java/org/glavo/javah/JNIGenerator.java @@ -0,0 +1,141 @@ +package org.glavo.javah; + +import org.objectweb.asm.*; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +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"; + + public static final String FILE_END = "\n"; + + private final PrintWriter output; + private final Path classFile; + + private String className; + + private Map methodNameCount = new HashMap<>(); + private LinkedList methods = new LinkedList<>(); + private LinkedList constants = new LinkedList<>(); + + public JNIGenerator(PrintWriter output, Path classFile) { + super(Opcodes.ASM7); + Objects.requireNonNull(output); + Objects.requireNonNull(classFile); + this.output = output; + this.classFile = classFile; + } + + 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 { + value = constant.value.toString(); + } + 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); + } + 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; + } + + @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)); + } + 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)); + } + return null; + } + + private static class NativeMethod { + String name; + Type type; + boolean isStatic; + + public NativeMethod(String name, Type type, boolean isStatic) { + this.name = name; + this.type = type; + this.isStatic = isStatic; + } + } + + private static class ConstantField { + String name; + Type type; + Object value; + + public ConstantField(String name, Type type, Object value) { + this.name = name; + this.type = type; + this.value = value; + } + } +} diff --git a/src/main/java/org/glavo/javah/JavahConfig.java b/src/main/java/org/glavo/javah/JavahConfig.java deleted file mode 100644 index e358c50..0000000 --- a/src/main/java/org/glavo/javah/JavahConfig.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.glavo.javah; - -import java.io.PrintWriter; -import java.io.Writer; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class JavahConfig { - private PrintWriter errorHandle = null; - private boolean outputToSignalFile = false; - private Path outputPath = null; - - private final List searchPaths = new ArrayList<>(); - - - // - // Getters and Setters - // - - public void addSearchPath(SearchPath searchPath) { - Objects.requireNonNull(searchPath); - searchPaths.add(searchPath); - } - - public List searchPaths() { - return searchPaths; - } - - public PrintWriter getErrorHandle() { - return errorHandle; - } - - public void setErrorHandle(Writer handle) { - if (handle == null || handle instanceof PrintWriter) { - errorHandle = (PrintWriter) handle; - } else { - errorHandle = new PrintWriter(handle); - } - } - - public boolean isOutputToSignalFile() { - return outputToSignalFile; - } - - public JavahConfig setOutputToSignalFile(boolean outputToSignalFile) { - this.outputToSignalFile = outputToSignalFile; - return this; - } -} diff --git a/src/main/java/org/glavo/javah/JavahTask.java b/src/main/java/org/glavo/javah/JavahTask.java new file mode 100644 index 0000000..5614854 --- /dev/null +++ b/src/main/java/org/glavo/javah/JavahTask.java @@ -0,0 +1,162 @@ +package org.glavo.javah; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Stream; + +public class JavahTask { + private PrintWriter errorHandle = new PrintWriter(System.err); + private boolean outputToSignalFile = false; + private Path outputPath = null; + + private final List searchPaths = new ArrayList<>(); + private final List classList = new ArrayList<>(); + + public void run() throws IOException { + if (outputPath == null) { + errorHandle.println("output path is not set"); + return; + } + + if (outputToSignalFile) { + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputPath))) { + writer.write(JNIGenerator.FILE_HEADER); + classList.stream() + .map(this::search) + .filter(Objects::nonNull) + .filter(Files::isReadable) + .map(p -> new JNIGenerator(writer, p)) + .forEachOrdered(JNIGenerator::generate); + + writer.write(JNIGenerator.FILE_END); + } + } else { + for (String c : classList) { + Path p = search(c); + if (p == null) { + continue; + } + try (PrintWriter output = new PrintWriter(Files.newBufferedWriter( + outputPath.resolve(Utils.encode(c).replace('.', '_') + ".h")))) { + output.write(JNIGenerator.FILE_HEADER); + new JNIGenerator(output, p).generate(); + output.write(JNIGenerator.FILE_END); + } + } + } + } + + private Path search(String className) { + for (SearchPath searchPath : searchPaths) { + Path c = searchPath.searchClass(className); + if (c != null) { + return c; + } + } + errorHandle.println("class" + className + " not found"); + return null; + } + + // + // Getters and Setters + // + + public void addSearchPath(SearchPath searchPath) { + Objects.requireNonNull(searchPath); + searchPaths.add(searchPath); + } + + public void addModulePath(Path modulePath) { + Objects.requireNonNull(modulePath); + searchPaths.add(new ModulePath(modulePath)); + } + + public void addModulePaths(String modulePaths) { + Objects.requireNonNull(modulePaths); + Arrays.stream(modulePaths.split(File.pathSeparator)) + .filter(s -> !"".equals(s)) + .map(Paths::get) + .filter(Files::isDirectory) + .forEachOrdered(this::addModulePath); + } + + public void addClasspath(Path classPath) { + Objects.requireNonNull(classPath); + searchPaths.add(new ClassPath(classPath)); + } + + public void addClasspaths(String cps) { + Arrays.stream(cps.split(File.pathSeparator)) + .filter(s -> !"".equals(s)) + .flatMap(s -> { + if (s.endsWith(File.separatorChar + "*")) { + try { + return Files.list(Paths.get(s.substring(0, s.length() - 2))) + .filter(p -> p.getFileName().toString().toLowerCase().endsWith(".jar")); + } catch (IOException e) { + return Stream.empty(); + } + } + return Stream.of(Paths.get(s)); + }) + .filter(Files::exists) + .map(ClassPath::new) + .forEachOrdered(searchPaths::add); + } + + public void addRuntimeClasspath() { + searchPaths.add(RuntimeClassPath.INSTANCE); + } + + public List searchPaths() { + return searchPaths; + } + + public PrintWriter getErrorHandle() { + return errorHandle; + } + + public void setErrorHandle(Writer handle) { + if (handle == null || handle instanceof PrintWriter) { + errorHandle = (PrintWriter) handle; + } else { + errorHandle = new PrintWriter(handle); + } + } + + public boolean isOutputToSignalFile() { + return outputToSignalFile; + } + + public JavahTask setOutputToSignalFile(boolean outputToSignalFile) { + this.outputToSignalFile = outputToSignalFile; + return this; + } + + public Path getOutputPath() { + return outputPath; + } + + public JavahTask setOutputPath(Path outputPath) { + this.outputPath = outputPath; + return this; + } + + public void addClass(String name) { + classList.add(name); + } + + public List getClassList() { + return classList; + } +} diff --git a/src/main/java/org/glavo/javah/Main.java b/src/main/java/org/glavo/javah/Main.java index 26facc6..60bc0b1 100644 --- a/src/main/java/org/glavo/javah/Main.java +++ b/src/main/java/org/glavo/javah/Main.java @@ -1,6 +1,75 @@ package org.glavo.javah; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.jar.Manifest; + public class Main { - public static void main(String[] args) { + private static final String HELP_MESSAGE = + "usage:\n" + + " gjavah [options] \n" + + "where [options] include:\n" + + " -o Output file (only one of -d or -o may be used)\n" + + " -d Output directory\n" + + " --module-path Path from which to search modules\n" + + " --class-path | " + + "-classpath | -cp \n" + + " " + + " Path from which to search classes\n" + + " --version Print version information\n" + + " -h --help -? Print this message"; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println(HELP_MESSAGE); + System.exit(-1); + } + JavahTask task = new JavahTask(); + task.addRuntimeClasspath(); + loop: + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + switch (arg) { + case "--version": + case "-version": + try (InputStream in = Main.class.getResourceAsStream("/META-INF/MANIFEST.MF")) { + Manifest manifest = new Manifest(in); + System.out.println("gjavah version: " + manifest.getMainAttributes().getValue("GJavah-Version")); + } + return; + case "-h": + case "--help": + case "-help": + case "-?": + System.out.println(HELP_MESSAGE); + return; + case "-o": + task.setOutputToSignalFile(true); + task.setOutputPath(Paths.get(args[++i])); + break; + case "-d": + task.setOutputToSignalFile(false); + task.setOutputPath(Paths.get(args[++i])); + break; + case "--module-path": + task.addModulePaths(args[++i]); + break; + case "--class-path": + case "--classpath": + case "-classpath": + case "-cp": + task.addClasspaths(args[++i]); + break; + default: + for (int j = i; j < args.length; j++) { + task.addClass(args[i]); + } + break loop; + } + if (task.getClassList().isEmpty()) { + System.err.println("error: no classes specified"); + } + task.run(); + } } } diff --git a/src/main/java/org/glavo/javah/ModulePath.java b/src/main/java/org/glavo/javah/ModulePath.java index fdb7499..b355b24 100644 --- a/src/main/java/org/glavo/javah/ModulePath.java +++ b/src/main/java/org/glavo/javah/ModulePath.java @@ -14,9 +14,6 @@ public class ModulePath implements SearchPath { public ModulePath(Path path) { Objects.requireNonNull(path); - if (!Files.isDirectory(path)) { - throw new IllegalArgumentException(path + "is not a dir"); - } this.path = path; try { @@ -33,4 +30,9 @@ public class ModulePath implements SearchPath { public Path searchClass(String className) { return Utils.searchFrom(paths, className); } + + @Override + public String toString() { + return "ModulePath[" + path + "]"; + } } diff --git a/src/main/java/org/glavo/javah/Utils.java b/src/main/java/org/glavo/javah/Utils.java index 989d348..b3554ff 100644 --- a/src/main/java/org/glavo/javah/Utils.java +++ b/src/main/java/org/glavo/javah/Utils.java @@ -1,5 +1,7 @@ package org.glavo.javah; +import org.objectweb.asm.Type; + import java.io.IOException; import java.io.InputStream; import java.nio.file.FileSystem; @@ -8,9 +10,90 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.jar.Manifest; -import java.util.regex.Pattern; class Utils { + static String encode(String str) { + StringBuilder buffer = new StringBuilder(str.length()); + + int len = str.length(); + for (int i = 0; i < len; i++) { + char ch = str.charAt(i); + if (ch == '_') { + buffer.append("_1"); + } else if (ch == ';') { + buffer.append("_2"); + } else if (ch == '[') { + buffer.append("_3"); + } else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + buffer.append(ch); + } else { + buffer.append(String.format("_0%04x", (int) ch)); + } + } + return buffer.toString(); + } + + static String mapToNativeType(Type jt) { + switch (jt.toString()) { + 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 (jt.toString().startsWith("[")) { + return "jobjectArray"; + } + + return "jobject"; + } + + static String mangledArgSignature(Type methodType) { + Objects.requireNonNull(methodType); + String str = methodType.toString(); + int idx = str.indexOf(')'); + if (idx == -1) { + throw new IllegalArgumentException(methodType.toString() + "is not a method type"); + } + return encode(str.substring(1, idx)); + } + static boolean isRegularClassName(String name) { Objects.requireNonNull(name); return !name.contains("//") @@ -58,7 +141,7 @@ class Utils { if (Files.isRegularFile(root)) { String name = root.toString().toLowerCase(); try { - FileSystem fs = FileSystems.newFileSystem(root, null); + FileSystem fs = FileSystems.newFileSystem(root, (ClassLoader) null); if (name.endsWith(".jar")) { root = fs.getPath("/"); } else if (name.endsWith(".jmod")) { -- cgit v1.2.3