aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGlavo <zjx001202@126.com>2019-11-29 03:30:24 +0800
committerGlavo <zjx001202@126.com>2019-11-29 03:30:24 +0800
commit4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467 (patch)
treef6441f077f76e3788728eed65f8aeb08540d8324
parentb664cbe6a72f551d12b5868ed4b48927bacf941e (diff)
downloadgjavah-4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467.tar.gz
gjavah-4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467.tar.bz2
gjavah-4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467.zip
update
-rw-r--r--build.gradle37
-rw-r--r--src/main/java/module-info.java6
-rw-r--r--src/main/java/org/glavo/javah/ClassPath.java32
-rw-r--r--src/main/java/org/glavo/javah/FileManager.java9
-rw-r--r--src/main/java/org/glavo/javah/HeaderGenerator.java529
-rw-r--r--src/main/java/org/glavo/javah/JavahConfig.java51
-rw-r--r--src/main/java/org/glavo/javah/Main.java6
-rw-r--r--src/main/java/org/glavo/javah/ModulePath.java36
-rw-r--r--src/main/java/org/glavo/javah/RuntimeClassPath.java41
-rw-r--r--src/main/java/org/glavo/javah/SearchPath.java7
-rw-r--r--src/main/java/org/glavo/javah/Utils.java134
-rw-r--r--src/test/java/org/glavo/javah/HeaderGeneratorTest.java12
-rw-r--r--src/test/java/org/glavo/javah/Test.java9
13 files changed, 337 insertions, 572 deletions
diff --git a/build.gradle b/build.gradle
index 1a66499..9bdd733 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,35 +1,28 @@
+plugins {
+ id 'java'
+ id "org.javamodularity.moduleplugin" version "1.6.0"
+}
+
group 'org.glavo'
-version '0.1.1'
+version '0.2'
-apply plugin: 'java'
+modularity.mixedJavaRelease 8
-sourceCompatibility = 1.8
-targetCompatibility = 1.8
+jar {
+ manifest.attributes(
+ 'Implementation-Version': '1.2',
+ 'Main-Class': 'org.glavo.javah.Main'
+ )
+}
repositories {
mavenCentral()
}
dependencies {
- compile group: 'org.ow2.asm', name: 'asm', version: '6.1.1'
+ // https://mvnrepository.com/artifact/org.ow2.asm/asm
+ compile group: 'org.ow2.asm', name: 'asm', version: '7.2'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
-jar {
- manifest {
- attributes 'Implementation-Version': '1.2',
- 'Main-Class': 'org.glavo.javah.HeaderGenerator'
- }
-}
-
-task fatJar(type: Jar) {
- baseName = project.name + '-all'
- description = 'Assembles a jar archive containing the main classes and all the dependencies.'
- from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
- manifest {
- attributes 'Implementation-Version': '1.2',
- 'Main-Class': 'org.glavo.javah.HeaderGenerator'
- }
- with jar
-} \ No newline at end of file
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
new file mode 100644
index 0000000..27cf6ae
--- /dev/null
+++ b/src/main/java/module-info.java
@@ -0,0 +1,6 @@
+module org.glavo.javah {
+ requires org.objectweb.asm;
+ requires jdk.zipfs;
+
+ exports org.glavo.javah;
+} \ No newline at end of file
diff --git a/src/main/java/org/glavo/javah/ClassPath.java b/src/main/java/org/glavo/javah/ClassPath.java
new file mode 100644
index 0000000..552cb9e
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ClassPath.java
@@ -0,0 +1,32 @@
+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;
+
+public class ClassPath implements SearchPath {
+ private final Path path;
+ private final List<Path> searchPaths;
+
+ public ClassPath(Path path) {
+ Objects.requireNonNull(path);
+ this.path = path;
+ searchPaths = Utils.getPathsFrom(path);
+ }
+
+ @Override
+ public Path searchClass(String className) {
+ Objects.requireNonNull(className);
+ return Utils.searchFrom(searchPaths, className);
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPath[" + path + "]";
+ }
+}
diff --git a/src/main/java/org/glavo/javah/FileManager.java b/src/main/java/org/glavo/javah/FileManager.java
new file mode 100644
index 0000000..2914f93
--- /dev/null
+++ b/src/main/java/org/glavo/javah/FileManager.java
@@ -0,0 +1,9 @@
+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/HeaderGenerator.java b/src/main/java/org/glavo/javah/HeaderGenerator.java
deleted file mode 100644
index 6c585ec..0000000
--- a/src/main/java/org/glavo/javah/HeaderGenerator.java
+++ /dev/null
@@ -1,529 +0,0 @@
-package org.glavo.javah;
-
-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<String> THROWABLE_NAME_LIST = Arrays.asList("Ljava/lang/Throwable;", "Ljava/lang/Error;", "Ljava/lang/Exception");
- private static final HeaderGenerator generator = new HeaderGenerator();
- public static final String VERSION = "0.1.1";
- private static final String message = "Usage: \n" +
- " javah [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" +
- " -h -help --help -? Print this message\n" +
- " -version Print version information\n" +
- " -classpath <path> Path from which to load classes\n" +
- " -cp <path> Path from which to load classes\n" +
- "<classes> are specified with their fully qualified names\n" +
- "(for example, java.lang.Object).";
-
- 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();
- }
-
- 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());
-
- for (Map.Entry<String, Set<MethodDesc>> 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");
- }
-
- }
- }
-
- 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) {
- 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");
-
- 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 <jni.h>");
-
- 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 main(String[] args) throws IOException {
- boolean isWindows = System.getProperty("os.name", "").toLowerCase().contains("windows");
- ArrayList<Path> cps = new ArrayList<>();
- Path outputFile = null;
- Path outputDir = null;
- ArrayList<String> classNames = new ArrayList<>();
-
- if (args.length == 0) {
- System.out.println(message);
- return;
- }
- for (int i = 0; i < args.length; i++) {
- String arg = args[i];
- if (arg.startsWith("-")) {
- switch (arg) {
- case "-h":
- case "-help":
- case "--help":
- case "-?":
- System.out.println(message);
- return;
- case "-version":
- System.out.println(VERSION);
- return;
- case "-cp":
- case "-classpath":
- i++;
- if (i == args.length) {
- System.err.println("javah: " + arg + " requires an argument.");
- return;
- }
- String[] s = args[i].split(isWindows ? ";" : ":");
- for (String ss : s) {
- Path p = Paths.get(ss);
- if (Files.exists(p)) {
- if (Files.isDirectory(p)) {
- cps.add(p);
- } else if (ss.toLowerCase().endsWith(".jar") || ss.toLowerCase().endsWith(".zip")) {
- try {
- cps.add(FileSystems.newFileSystem(p, null).getPath("/"));
- } catch (Exception ignored) {
- }
- }
- }
- }
- break;
- case "-o":
- i++;
- if (i == args.length) {
- System.err.println("javah: " + arg + " requires an argument.");
- return;
- }
- if (outputDir != null) {
- System.err.println("Error: Can't mix options -d and -o. Try -help.");
- return;
- }
- outputFile = Paths.get(args[i]);
- break;
-
- case "-d":
- i++;
- if (i == args.length) {
- System.err.println("javah: " + arg + " requires an argument.");
- return;
- }
- if (outputFile != null) {
- System.err.println("Error: Can't mix options -d and -o. Try -help.");
- return;
- }
- outputDir = Paths.get(args[i]);
-
- break;
- default:
- System.err.println("Error: unknown option: " + arg);
- return;
- }
-
- } else {
- if (arg.contains("/")) {
- int idx = arg.indexOf('/');
- if (idx != arg.lastIndexOf('/')) {
- System.err.println("Not a valid class name: " + arg);
- }
- arg = arg.substring(idx + 1);
- }
- classNames.add(arg);
- }
-
- if (outputDir == null && outputFile == null) {
- outputDir = Paths.get(System.getProperty("user.dir", "."));
- }
- if (cps.isEmpty()) {
- cps.add(Paths.get(System.getProperty("user.dir", ".")));
- }
-
- if (outputDir != null && Files.notExists(outputDir)) {
- Files.createDirectories(outputDir);
- }
-
- if (outputFile != null && Files.notExists(outputFile)) {
- if (Files.notExists(outputFile.getParent())) {
- Files.createDirectories(outputFile.getParent());
- }
- }
-
- HeaderGenerator generator = new HeaderGenerator(cps.toArray(new Path[0]));
- if (outputFile != null) {
- if (Files.notExists(outputFile.getParent())) {
- Files.createDirectories(outputFile.getParent());
- }
- try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
- boolean first = true;
- 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);
- if (first) {
- generator.classGenerateHeader(g, writer);
- first = false;
- } else {
- generator.classGenerateHeaderWithoutInclude(g, writer);
- }
- }
- }
- }
- if (outputDir != null) {
- 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('.', '_') + ".h"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING))) {
- generator.classGenerateHeader(g, writer);
- }
- }
- }
-
- }
- }
-}
-
-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<String, String> constants = new LinkedHashMap<>();
- private Map<String, Set<MethodDesc>> methods = new LinkedHashMap<String, Set<MethodDesc>>();
-
- 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<MethodDesc> 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<String, Set<MethodDesc>> getMethods() {
- return methods;
- }
-} \ No newline at end of file
diff --git a/src/main/java/org/glavo/javah/JavahConfig.java b/src/main/java/org/glavo/javah/JavahConfig.java
new file mode 100644
index 0000000..e358c50
--- /dev/null
+++ b/src/main/java/org/glavo/javah/JavahConfig.java
@@ -0,0 +1,51 @@
+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/Main.java b/src/main/java/org/glavo/javah/Main.java
new file mode 100644
index 0000000..26facc6
--- /dev/null
+++ b/src/main/java/org/glavo/javah/Main.java
@@ -0,0 +1,6 @@
+package org.glavo.javah;
+
+public class Main {
+ public static void main(String[] args) {
+ }
+}
diff --git a/src/main/java/org/glavo/javah/ModulePath.java b/src/main/java/org/glavo/javah/ModulePath.java
new file mode 100644
index 0000000..fdb7499
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ModulePath.java
@@ -0,0 +1,36 @@
+package org.glavo.javah;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class ModulePath implements SearchPath {
+ private final Path path;
+ private final List<Path> paths;
+
+ public ModulePath(Path path) {
+ Objects.requireNonNull(path);
+ if (!Files.isDirectory(path)) {
+ throw new IllegalArgumentException(path + "is not a dir");
+ }
+
+ this.path = path;
+ try {
+ paths = Files.list(path)
+ .filter(Files::isRegularFile)
+ .flatMap(p -> Utils.getPathsFrom(p).stream())
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ @Override
+ public Path searchClass(String className) {
+ return Utils.searchFrom(paths, className);
+ }
+}
diff --git a/src/main/java/org/glavo/javah/RuntimeClassPath.java b/src/main/java/org/glavo/javah/RuntimeClassPath.java
new file mode 100644
index 0000000..c16cfa3
--- /dev/null
+++ b/src/main/java/org/glavo/javah/RuntimeClassPath.java
@@ -0,0 +1,41 @@
+package org.glavo.javah;
+
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.*;
+
+public class RuntimeClassPath implements SearchPath {
+ public static final RuntimeClassPath INSTANCE = new RuntimeClassPath();
+
+ private RuntimeClassPath() {
+ }
+
+ @Override
+ @SuppressWarnings("ConstantConditions")
+ public Path searchClass(String className) {
+ className = Utils.fullClassNameOf(className);
+ URI uri = null;
+ try {
+ Class<?> cls = Class.forName(className);
+ URL url = cls.getResource(Utils.simpleNameOf(className) + ".class");
+ if (url == null) {
+ return null;
+ }
+ uri = url.toURI();
+ return Paths.get(uri);
+ } catch (FileSystemNotFoundException e) {
+ try {
+ FileSystem fs = FileSystems.newFileSystem(uri, null);
+ Path p = fs.getPath(className.replace('.', '/') + ".class");
+ if (Files.isRegularFile(p)) {
+ return p;
+ }
+ return null;
+ } catch (Exception ex) {
+ return null;
+ }
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/org/glavo/javah/SearchPath.java b/src/main/java/org/glavo/javah/SearchPath.java
new file mode 100644
index 0000000..b95b7c0
--- /dev/null
+++ b/src/main/java/org/glavo/javah/SearchPath.java
@@ -0,0 +1,7 @@
+package org.glavo.javah;
+
+import java.nio.file.Path;
+
+public interface SearchPath {
+ Path searchClass(String className) ;
+}
diff --git a/src/main/java/org/glavo/javah/Utils.java b/src/main/java/org/glavo/javah/Utils.java
new file mode 100644
index 0000000..989d348
--- /dev/null
+++ b/src/main/java/org/glavo/javah/Utils.java
@@ -0,0 +1,134 @@
+package org.glavo.javah;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+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 boolean isRegularClassName(String name) {
+ Objects.requireNonNull(name);
+ return !name.contains("//")
+ && !name.contains("..")
+ && name.indexOf('\\') == -1
+ && name.indexOf(';') == -1
+ && name.indexOf('[') == -1;
+ }
+
+ static String fullClassNameOf(String name) {
+ Objects.requireNonNull(name);
+ int idx = name.indexOf('/');
+ if (idx == -1) {
+ return name;
+ }
+ String cn = name.substring(idx + 1);
+ if (cn.isEmpty() || cn.indexOf('/') == -1) {
+ throw new IllegalArgumentException("Illegal class name: " + name);
+ }
+ return cn;
+ }
+
+ static String simpleNameOf(String name) {
+ int idx = name.lastIndexOf('.');
+ return name.substring(idx + 1);
+ }
+
+ static Path searchFromPathList(List<Path> paths, String className) {
+ Objects.requireNonNull(paths);
+ Objects.requireNonNull(className);
+ String fp = fullClassNameOf(className).replace('.', '/') + ".class";
+ for (Path path : paths) {
+ Objects.requireNonNull(path);
+ Path p = path.resolve(fp);
+ if (Files.isRegularFile(p)) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ static List<Path> getPathsFrom(Path root) {
+ Objects.requireNonNull(root);
+ root = root.toAbsolutePath();
+ if (Files.isRegularFile(root)) {
+ String name = root.toString().toLowerCase();
+ try {
+ FileSystem fs = FileSystems.newFileSystem(root, null);
+ if (name.endsWith(".jar")) {
+ root = fs.getPath("/");
+ } else if (name.endsWith(".jmod")) {
+ root = fs.getPath("/classes");
+ } else {
+ return Collections.emptyList();
+ }
+ } catch (IOException e) {
+ return Collections.emptyList();
+ }
+ }
+ if (!Files.isDirectory(root)) {
+ return Collections.emptyList();
+ }
+
+ boolean isMultiRelease = false;
+ Path manifest = root.resolve("META-INF").resolve("MANIFEST.MF");
+ if (Files.exists(manifest) && Files.isRegularFile(manifest)) {
+ try (InputStream input = Files.newInputStream(manifest)) {
+ isMultiRelease = Boolean.parseBoolean(
+ new Manifest(input).getMainAttributes().getOrDefault("Multi-Release", "false").toString()
+ );
+
+ } catch (Exception ignored) {
+ }
+ }
+
+ if (!isMultiRelease) {
+ return Collections.singletonList(root);
+ }
+ LinkedList<Path> list = new LinkedList<>();
+ Path mr = root.resolve("META-INF").resolve("versions");
+ try {
+ Files.list(mr)
+ .filter(Utils::checkMultiReleaseVersion)
+ .sorted(Comparator.comparingInt(a -> Integer.parseInt(a.getFileName().toString())))
+ .forEachOrdered(list::addFirst);
+
+ } catch (IOException ignored) {
+ }
+
+ list.add(root);
+ return list;
+ }
+
+ private static final String[] MULTI_VERSIONS = {"9", "10", "11", "12", "13"};
+
+ private static boolean checkMultiReleaseVersion(Path p) {
+ Objects.requireNonNull(p);
+ String n = p.getFileName().toString();
+ for (String version : MULTI_VERSIONS) {
+ if (version.equals(n)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static Path searchFrom(List<Path> searchPaths, String name) {
+ Objects.requireNonNull(searchPaths);
+ Objects.requireNonNull(name);
+
+ name = simpleNameOf(name).replace('.', '/') + ".class";
+
+ for (Path searchPath : searchPaths) {
+ Path p = searchPath.resolve(name);
+ if (Files.isRegularFile(p)) {
+ return p;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/test/java/org/glavo/javah/HeaderGeneratorTest.java b/src/test/java/org/glavo/javah/HeaderGeneratorTest.java
deleted file mode 100644
index b3f0380..0000000
--- a/src/test/java/org/glavo/javah/HeaderGeneratorTest.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package org.glavo.javah;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-
-public class HeaderGeneratorTest {
- public static void main(String[] args) throws IOException {
- PrintWriter writer = new PrintWriter(System.out);
- HeaderGenerator.generateHeader("java.lang.System", writer);
- writer.flush();
- }
-}
diff --git a/src/test/java/org/glavo/javah/Test.java b/src/test/java/org/glavo/javah/Test.java
deleted file mode 100644
index 989b37e..0000000
--- a/src/test/java/org/glavo/javah/Test.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.glavo.javah;
-
-public class Test {
- public native String[][] f1();
-
- public native Throwable f2();
-
- public native Class<?>[] f3();
-}