aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlavo <zjx001202@126.com>2019-11-29 07:29:34 +0800
committerGlavo <zjx001202@126.com>2019-11-29 07:29:34 +0800
commite8c30ce4df4fa9e9b8f559153b54dfc2616d4ebc (patch)
tree255ca3b4adf4f6a7b25ebcbdec8b8b8781af2155
parent4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467 (diff)
downloadgjavah-e8c30ce4df4fa9e9b8f559153b54dfc2616d4ebc.tar.gz
gjavah-e8c30ce4df4fa9e9b8f559153b54dfc2616d4ebc.tar.bz2
gjavah-e8c30ce4df4fa9e9b8f559153b54dfc2616d4ebc.zip
update
-rw-r--r--build.gradle3
-rw-r--r--src/main/java/org/glavo/javah/ClassPath.java5
-rw-r--r--src/main/java/org/glavo/javah/FileManager.java9
-rw-r--r--src/main/java/org/glavo/javah/JNIGenerator.java141
-rw-r--r--src/main/java/org/glavo/javah/JavahConfig.java51
-rw-r--r--src/main/java/org/glavo/javah/JavahTask.java162
-rw-r--r--src/main/java/org/glavo/javah/Main.java71
-rw-r--r--src/main/java/org/glavo/javah/ModulePath.java8
-rw-r--r--src/main/java/org/glavo/javah/Utils.java87
9 files changed, 465 insertions, 72 deletions
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<Path> 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 <jni.h>\n";
+
+ public static final String FILE_END = "\n";
+
+ private final PrintWriter output;
+ private final Path classFile;
+
+ private String className;
+
+ private Map<String, Integer> methodNameCount = new HashMap<>();
+ private LinkedList<NativeMethod> methods = new LinkedList<>();
+ private LinkedList<ConstantField> 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<SearchPath> searchPaths = new ArrayList<>();
-
-
- //
- // Getters and Setters
- //
-
- public void addSearchPath(SearchPath searchPath) {
- Objects.requireNonNull(searchPath);
- searchPaths.add(searchPath);
- }
-
- public List<SearchPath> 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<SearchPath> searchPaths = new ArrayList<>();
+ private final List<String> 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<SearchPath> 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<String> 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] <classes>\n" +
+ "where [options] include:\n" +
+ " -o <file> Output file (only one of -d or -o may be used)\n" +
+ " -d <dir> Output directory\n" +
+ " --module-path <path> Path from which to search modules\n" +
+ " --class-path <path> | " +
+ "-classpath <path> | -cp <path>\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")) {