aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/glavo/javah
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/glavo/javah')
-rw-r--r--src/main/java/org/glavo/javah/ClassMetaInfo.java46
-rw-r--r--src/main/java/org/glavo/javah/ClassName.java88
-rw-r--r--src/main/java/org/glavo/javah/ClassPath.java45
-rw-r--r--src/main/java/org/glavo/javah/Constant.java83
-rw-r--r--src/main/java/org/glavo/javah/HeaderGenerator.java529
-rw-r--r--src/main/java/org/glavo/javah/JNIGenerator.java238
-rw-r--r--src/main/java/org/glavo/javah/JavahTask.java82
-rw-r--r--src/main/java/org/glavo/javah/Main.java103
-rw-r--r--src/main/java/org/glavo/javah/ModulePath.java50
-rw-r--r--src/main/java/org/glavo/javah/NativeMethod.java92
-rw-r--r--src/main/java/org/glavo/javah/RuntimeSearchPath.java44
-rw-r--r--src/main/java/org/glavo/javah/SearchPath.java92
-rw-r--r--src/main/java/org/glavo/javah/Utils.java134
13 files changed, 1097 insertions, 529 deletions
diff --git a/src/main/java/org/glavo/javah/ClassMetaInfo.java b/src/main/java/org/glavo/javah/ClassMetaInfo.java
new file mode 100644
index 0000000..bc3ffcc
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ClassMetaInfo.java
@@ -0,0 +1,46 @@
+package org.glavo.javah;
+
+import org.objectweb.asm.*;
+
+import java.util.*;
+
+class ClassMetaInfo extends ClassVisitor {
+ final List<Constant> constants = new LinkedList<>();
+ final List<NativeMethod> methods = new LinkedList<>();
+ final Map<String, Integer> counts = new HashMap<>();
+
+ ClassName superClassName;
+ ClassName name;
+
+ public ClassMetaInfo() {
+ super(Opcodes.ASM7);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ this.superClassName = superName == null ? null : ClassName.of(superName.replace('/', '.'));
+ this.name = ClassName.of(name.replace('/', '.'));
+ }
+
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
+ counts.put(name, counts.getOrDefault(name, 0) + 1);
+ if ((access & Opcodes.ACC_NATIVE) != 0) {
+ this.methods.add(NativeMethod.of(access, name, descriptor));
+ }
+ return null;
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
+ if (value != null && !(value instanceof String)) {
+ constants.add(Constant.of(name, value));
+ }
+ return null;
+ }
+
+ boolean isOverloadMethod(NativeMethod method) {
+ Objects.requireNonNull(method);
+ return counts.getOrDefault(method.name(), 1) > 1;
+ }
+}
diff --git a/src/main/java/org/glavo/javah/ClassName.java b/src/main/java/org/glavo/javah/ClassName.java
new file mode 100644
index 0000000..4305036
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ClassName.java
@@ -0,0 +1,88 @@
+package org.glavo.javah;
+
+import java.util.Objects;
+
+import static org.glavo.javah.Utils.*;
+
+public final class ClassName {
+ private final String moduleName;
+ private final String className;
+ private final String simpleName;
+ private final String mangledName;
+
+ public static ClassName of(String moduleName, String className) {
+ Objects.requireNonNull(className, "Class name is null");
+
+ if (moduleName != null && !FULL_NAME_PATTERN.matcher(moduleName).matches()) {
+ throw new IllegalArgumentException("Illegal module name: " + moduleName);
+ }
+ if (!FULL_NAME_PATTERN.matcher(className).matches()) {
+ throw new IllegalArgumentException("Illegal class name: " + moduleName);
+ }
+
+ return new ClassName(moduleName, className);
+ }
+
+ public static ClassName of(String fullName) {
+ Objects.requireNonNull(fullName, "class name is null");
+ int idx = fullName.indexOf('/');
+ if (idx == -1) {
+ return ClassName.of(null, fullName);
+ }
+
+ return ClassName.of(fullName.substring(0, idx), fullName.substring(idx + 1));
+ }
+
+ private ClassName(String moduleName, String className) {
+ this.moduleName = moduleName;
+ this.className = className;
+ this.simpleName = className.substring(className.lastIndexOf('.') + 1);
+ this.mangledName = mangleName(className);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClassName)) return false;
+ ClassName className1 = (ClassName) o;
+ return Objects.equals(moduleName, className1.moduleName) && className.equals(className1.className);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(moduleName, className);
+ }
+
+ @Override
+ public String toString() {
+ if (moduleName == null) {
+ return className;
+ }
+ return moduleName + '/' + className;
+ }
+
+
+ //
+ // Getters and Setters
+ //
+
+ public final String moduleName() {
+ return moduleName;
+ }
+
+ public final String className() {
+ return className;
+ }
+
+ public final String simpleName() {
+ return simpleName;
+ }
+
+ public final String mangledName() {
+ return mangledName;
+ }
+
+ public final String relativePath() {
+ return className.replace('.', '/') + ".class";
+ }
+}
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..3e59c59
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ClassPath.java
@@ -0,0 +1,45 @@
+package org.glavo.javah;
+
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+import static org.glavo.javah.Utils.*;
+
+public class ClassPath implements SearchPath {
+ private final Path path;
+ private final List<Path> roots;
+
+ public ClassPath(Path path) {
+ Objects.requireNonNull(path);
+ this.path = path.toAbsolutePath();
+
+ Path root = classPathRoot(path);
+ roots = root == null ? Collections.emptyList() : SearchPath.multiReleaseRoots(root);
+ }
+
+ @Override
+ public Path search(ClassName name) {
+ Objects.requireNonNull(name);
+ return SearchPath.searchFromRoots(roots, name);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ClassPath classPath = (ClassPath) o;
+ return Objects.equals(path, classPath.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return path.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ClassPath[" + path + "]";
+ }
+}
diff --git a/src/main/java/org/glavo/javah/Constant.java b/src/main/java/org/glavo/javah/Constant.java
new file mode 100644
index 0000000..1e734ba
--- /dev/null
+++ b/src/main/java/org/glavo/javah/Constant.java
@@ -0,0 +1,83 @@
+package org.glavo.javah;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static org.glavo.javah.Utils.*;
+
+public final class Constant {
+ private static final List<Class<?>> TYPES = Arrays.asList(
+ Byte.class, Short.class, Integer.class, Long.class, Character.class, Float.class, Double.class
+ );
+
+ private final String name;
+ private final Object value;
+ private final String mangledName;
+
+ public static Constant of(String name, Object value) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(value);
+
+ if (!TYPES.contains(value.getClass())) {
+ throw new IllegalArgumentException();
+ }
+ if (!SIMPLE_NAME_PATTERN.matcher(name).matches()) {
+ throw new IllegalArgumentException(String.format("\"%s\" is not a qualified constant name", name));
+ }
+
+ return new Constant(name, value);
+ }
+
+ private Constant(String name, Object value) {
+ this.name = name;
+ this.value = value;
+ this.mangledName = mangleName(name);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Constant)) return false;
+ Constant constant = (Constant) o;
+ return name.equals(constant.name) && value.equals(constant.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, value);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Constant[name=%s, value=%s]", name, value);
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public Object value() {
+ return value;
+ }
+
+ public String mangledName() {
+ return mangledName;
+ }
+
+ public String valueToString() {
+ if (value instanceof Double) {
+ return value.toString();
+ }
+ if (value instanceof Float) {
+ return value + "f";
+ }
+ if (value instanceof Long) {
+ return value + "i64";
+ }
+ if (value instanceof Character) {
+ return ((int) (char) value) + "L";
+ }
+ return value + "L";
+ }
+}
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 81fe6c5..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();
- 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/JNIGenerator.java b/src/main/java/org/glavo/javah/JNIGenerator.java
new file mode 100644
index 0000000..bec2ea9
--- /dev/null
+++ b/src/main/java/org/glavo/javah/JNIGenerator.java
@@ -0,0 +1,238 @@
+package org.glavo.javah;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Type;
+
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+
+import static org.glavo.javah.Utils.*;
+
+public class JNIGenerator {
+
+ private final PrintWriter errorHandle;
+ private final Iterable<SearchPath> searchPaths;
+ private final Path outputDir;
+
+ public JNIGenerator(Path outputDir) {
+ this(outputDir, null, null);
+ }
+
+ public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths) {
+ this(outputDir, searchPaths, null);
+ }
+
+ public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths, PrintWriter errorHandle) {
+ Objects.requireNonNull(outputDir);
+
+ if (searchPaths == null) {
+ searchPaths = Collections.singleton(RuntimeSearchPath.INSTANCE);
+ }
+ if (errorHandle == null) {
+ errorHandle = NOOP_WRITER;
+ }
+
+ this.errorHandle = errorHandle;
+ this.searchPaths = searchPaths;
+ this.outputDir = outputDir;
+ }
+
+ public void generate(ClassName name) {
+ Objects.requireNonNull(name);
+ if (Files.exists(outputDir) && !Files.isDirectory(outputDir)) {
+ throw new IllegalArgumentException(outputDir + "is not a directory");
+ }
+ if (Files.notExists(outputDir)) {
+ try {
+ Files.createDirectories(outputDir);
+ } catch (IOException e) {
+ errorHandle.println("error: cannot create directory " + outputDir);
+ e.printStackTrace(errorHandle);
+ return;
+ }
+ }
+ Path op = outputDir.resolve(name.mangledName() + ".h");
+ try (PrintWriter out = new PrintWriter(Files.newBufferedWriter(op))) {
+ generateTo(name, out);
+ } catch (Exception ex) {
+ errorHandle.println("error: cannot write to " + op);
+ ex.printStackTrace(errorHandle);
+ try {
+ Files.deleteIfExists(op);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ public void generateTo(ClassName name, Writer writer) throws IOException {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(writer);
+
+ ClassMetaInfo meta = new ClassMetaInfo();
+ {
+ Path f = search(name);
+ if (f == null) {
+ errorHandle.println("Not found class " + name);
+ return;
+ }
+
+ try (InputStream in = Files.newInputStream(f)) {
+ ClassReader reader = new ClassReader(in);
+ reader.accept(meta, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
+ } catch (IOException e) {
+ errorHandle.println("error: cannot open class file of " + name);
+ e.printStackTrace(errorHandle);
+ errorHandle.flush();
+ throw e;
+ }
+ }
+
+ PrintWriter out = writer instanceof PrintWriter ? (PrintWriter) writer : new PrintWriter(writer);
+ out.println("/* DO NOT EDIT THIS FILE - it is machine generated */");
+ out.println("#include <jni.h>");
+ out.println("/* Header for class " + name.mangledName() + " */");
+ out.println();
+ out.println("#ifndef _Included_" + name.mangledName());
+ out.println("#define _Included_" + name.mangledName());
+ out.println("#ifdef __cplusplus");
+ out.println("extern \"C\" {");
+ out.println("#endif");
+
+ for (Constant constant : meta.constants) {
+ String cn = name.mangledName() + "_" + constant.mangledName();
+ out.println("#undef " + cn);
+ out.println("#define " + cn + " " + constant.valueToString());
+ }
+
+ for (NativeMethod method : meta.methods) {
+ String ret = mapTypeToNative(method.type().getReturnType());
+ List<String> args = new ArrayList<>();
+ args.add("JNIEnv *");
+ args.add(method.isStatic() ? "jclass" : "jobject");
+ args.addAll(Arrays.asList(mapArgsTypeToNative(method.type())));
+
+ String methodName =
+ "Java_" + name.mangledName() + "_" + (meta.isOverloadMethod(method) ? method.longMangledName() : method.mangledName());
+
+ out.println("/*");
+ out.println(" * Class: " + name.mangledName());
+ out.println(" * Method: " + method.mangledName());
+ out.println(" * Signature: " + escape(method.type().toString()));
+ out.println(" */");
+ out.println("JNIEXPORT " + ret + " JNICALL " + methodName);
+ out.println(" (" + String.join(", ", args) + ");");
+ out.println();
+ }
+
+ out.println("#ifdef __cplusplus");
+ out.println("}");
+ out.println("#endif");
+ out.println("#endif");
+
+ }
+
+ private Path search(ClassName name) {
+ return SearchPath.searchFrom(searchPaths, name);
+ }
+
+ private String mapTypeToNative(Type type) {
+ Objects.requireNonNull(type);
+ String tpe = type.toString();
+ if (tpe.startsWith("(")) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (tpe) {
+ 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 (tpe.startsWith("[")) {
+ return "jobjectArray";
+ }
+
+ if (tpe.startsWith("L") && tpe.endsWith(";")) {
+ ClassName n = ClassName.of(tpe.substring(1, tpe.length() - 1).replace('/', '.'));
+ if (isThrowable(n)) {
+ return "jthrowable";
+ } else {
+ return "jobject";
+ }
+ }
+ throw new IllegalArgumentException("Unknown type: " + type);
+ }
+
+ private String[] mapArgsTypeToNative(Type methodType) {
+ Objects.requireNonNull(methodType);
+ if (!METHOD_TYPE_PATTERN.matcher(methodType.toString()).matches()) {
+ throw new IllegalArgumentException(methodType + " is not a method type");
+ }
+ Type[] args = methodType.getArgumentTypes();
+ String[] ans = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ ans[i] = mapTypeToNative(args[i]);
+ }
+ return ans;
+ }
+
+ private boolean isThrowable(ClassName name) {
+ if (name == null) {
+ return false;
+ }
+ switch (name.className()) {
+ case "java.lang.Throwable":
+ case "java.lang.Error":
+ case "java.lang.Exception":
+ return true;
+ case "java.lang.Object":
+ return false;
+ }
+
+ try (InputStream in = Files.newInputStream(search(name))) {
+ return isThrowable(superClassOf(new ClassReader(in)));
+ } catch (Exception ignored) {
+ errorHandle.println("warning: class " + name + " not found");
+ return false;
+ }
+ }
+}
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..8e239ed
--- /dev/null
+++ b/src/main/java/org/glavo/javah/JavahTask.java
@@ -0,0 +1,82 @@
+package org.glavo.javah;
+
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.file.Path;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+public final class JavahTask {
+ private final List<SearchPath> searchPaths = new LinkedList<>();
+ private Path outputDir;
+ private PrintWriter errorHandle = new PrintWriter(System.err, true);
+ private final List<ClassName> classes = new LinkedList<>();
+
+ public void run() {
+ Objects.requireNonNull(outputDir, "outputDir");
+ JNIGenerator generator = new JNIGenerator(outputDir, searchPaths, errorHandle);
+ for (ClassName cls : classes) {
+ try {
+ generator.generate(cls);
+ } catch (Exception ex) {
+ ex.printStackTrace(errorHandle);
+ }
+ }
+ }
+
+ public void addClass(ClassName name) {
+ Objects.requireNonNull(name);
+ classes.add(name);
+ }
+
+ public void addClass(String name) {
+ Objects.requireNonNull(name);
+ classes.add(ClassName.of(name));
+ }
+
+ public void addClasses(Iterable<String> i) {
+ Objects.requireNonNull(i);
+ i.forEach(c -> classes.add(ClassName.of(c)));
+ }
+
+ public void addRuntimeSearchPath() {
+ searchPaths.add(RuntimeSearchPath.INSTANCE);
+ }
+
+ public void addSearchPath(SearchPath searchPath) {
+ Objects.requireNonNull(searchPath);
+ searchPaths.add(searchPath);
+ }
+
+ public void addClassPath(Path classPath) {
+ Objects.requireNonNull(classPath);
+ searchPaths.add(new ClassPath(classPath));
+ }
+
+ public void addModulePath(Path modulePath) {
+ Objects.requireNonNull(modulePath);
+ searchPaths.add(new ModulePath(modulePath));
+ }
+
+ public Path getOutputDir() {
+ return outputDir;
+ }
+
+ public void setOutputDir(Path outputDir) {
+ this.outputDir = outputDir;
+ }
+
+ public PrintWriter getErrorHandle() {
+ return errorHandle;
+ }
+
+ public void setErrorHandle(Writer errorHandle) {
+ if (errorHandle instanceof PrintWriter || errorHandle == null) {
+ this.errorHandle = (PrintWriter) errorHandle;
+ } else {
+ this.errorHandle = new PrintWriter(errorHandle);
+ }
+ }
+}
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..3c2be04
--- /dev/null
+++ b/src/main/java/org/glavo/javah/Main.java
@@ -0,0 +1,103 @@
+package org.glavo.javah;
+
+import picocli.CommandLine;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.jar.Manifest;
+import java.util.stream.Stream;
+
+import static picocli.CommandLine.*;
+
+@Command(name = "gjavah", version = "gjavah ${gjavah.version}", sortOptions = false)
+public class Main {
+ @Option(names = {"-p", "--module-path"}, description = "Path from which to search modules")
+ private String modulePath;
+ @Option(names = {"-cp", "-classpath", "--classpath", "--class-path"}, description = "Path from which to search classes")
+ private String classpath;
+
+ @Option(names = {"-version", "--version"}, description = "Print version information")
+ private boolean showVersion;
+
+ @Option(names = {"-h", "--help", "-?"}, usageHelp = true, description = "Print this message")
+ private boolean showHelp;
+
+ @Option(names = {"-d"}, description = "Output directory")
+ private Path outputDir = Paths.get("").toAbsolutePath().normalize();
+
+ @Parameters(paramLabel = "classes")
+ private List<String> classes;
+
+ public static void main(String[] args) throws Exception {
+ Main m = new Main();
+ CommandLine cm = new CommandLine(m);
+ if (args == null || args.length == 0) {
+ cm.usage(System.err);
+ System.exit(-1);
+ }
+
+ cm.parseArgs(args);
+ if (m.showHelp) {
+ cm.usage(System.out);
+ return;
+ }
+ if (m.showVersion) {
+ try (InputStream in = Main.class.getResourceAsStream("/META-INF/MANIFEST.MF")) {
+ System.setProperty("gjavah.version", new Manifest(in).getMainAttributes().getValue("GJavah-Version"));
+ cm.printVersionHelp(System.out);
+ }
+ return;
+ }
+ if (m.classes == null || m.classes.isEmpty()) {
+ cm.usage(System.err);
+ System.exit(-1);
+ }
+
+ JavahTask task = new JavahTask();
+ if (m.modulePath != null) {
+ Arrays.stream(m.modulePath.split(File.pathSeparator))
+ .map(Paths::get)
+ .filter(Files::isDirectory)
+ .forEachOrdered(task::addModulePath);
+ }
+ if (m.classpath == null) {
+ m.classpath = System.getenv("CLASSPATH");
+ }
+ if (m.classpath == null) {
+ m.classpath = Paths.get("").toAbsolutePath().normalize().toString();
+ }
+ Arrays.stream(m.classpath.split(File.pathSeparator))
+ .flatMap(p -> {
+ if (p.endsWith("/*") || p.equals("*")) {
+ try {
+ return Files.list(Paths.get(p.substring(0, p.length() - 1)))
+ .filter(Files::isRegularFile)
+ .filter(t -> t.toAbsolutePath().getFileName().toString().toLowerCase().endsWith(".jar"));
+ } catch (Exception e) {
+ return Stream.empty();
+ }
+ }
+ return Stream.of(Paths.get(p));
+ })
+ .filter(Files::exists)
+ .map(Path::toAbsolutePath)
+ .forEachOrdered(task::addClassPath);
+ task.setOutputDir(m.outputDir);
+ task.addClasses(m.classes);
+ task.setErrorHandle(new PrintWriter(System.err, true));
+ task.addRuntimeSearchPath();
+ task.run();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Main[modulePath='%s', classpath='%s', showVersion=%s, showHelp=%s, outputDir=%s, classes=%s]", modulePath, classpath, showVersion, showHelp, outputDir, classes);
+ }
+}
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..0a9c2cc
--- /dev/null
+++ b/src/main/java/org/glavo/javah/ModulePath.java
@@ -0,0 +1,50 @@
+package org.glavo.javah;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class ModulePath implements SearchPath {
+ private final Path path;
+ private List<Path> roots;
+
+ public ModulePath(Path path) {
+ Objects.requireNonNull(path);
+ path = path.toAbsolutePath();
+ this.path = path;
+ if (Files.notExists(path) || !Files.isDirectory(path)) {
+ roots = Collections.emptyList();
+ } else {
+ try {
+ roots = Files.list(path)
+ .map(Path::toAbsolutePath)
+ .filter(Files::isRegularFile)
+ .filter(p -> {
+ String n = p.getFileName().toString().toLowerCase();
+ return n.endsWith(".jar") || n.endsWith(".zip") || n.endsWith(".jmod");
+ })
+ .map(Utils::classPathRoot)
+ .filter(Objects::nonNull)
+ .flatMap(p -> SearchPath.multiReleaseRoots(p).stream())
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ roots = Collections.emptyList();
+ }
+ }
+ }
+
+ @Override
+ public Path search(ClassName name) {
+ Objects.requireNonNull(name);
+ return SearchPath.searchFromRoots(roots, name);
+ }
+
+ @Override
+ public String toString() {
+ return "ModulePath[" + path + "]";
+ }
+}
diff --git a/src/main/java/org/glavo/javah/NativeMethod.java b/src/main/java/org/glavo/javah/NativeMethod.java
new file mode 100644
index 0000000..1500697
--- /dev/null
+++ b/src/main/java/org/glavo/javah/NativeMethod.java
@@ -0,0 +1,92 @@
+package org.glavo.javah;
+
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+import java.util.Objects;
+import java.util.regex.Matcher;
+
+import static org.glavo.javah.Utils.*;
+
+public final class NativeMethod {
+ private final int access;
+ private final String name;
+ private final Type type;
+ private final String mangledName;
+ private final String longMangledName;
+
+ public static NativeMethod of(String name, String descriptor) {
+ return NativeMethod.of(0, name, descriptor);
+ }
+
+ public static NativeMethod of(String name, Type type) {
+ return NativeMethod.of(0, name, type);
+ }
+
+ public static NativeMethod of(int access, String name, String descriptor) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(descriptor);
+ return NativeMethod.of(access, name, Type.getType(descriptor));
+ }
+
+ public static NativeMethod of(int access, String name, Type type) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(type);
+ if (!METHOD_NAME_PATTERN.matcher(name).matches()) {
+ throw new IllegalArgumentException(String.format("\"%s\" is not a qualified method name", name));
+ }
+ Matcher m = METHOD_TYPE_PATTERN.matcher(type.toString());
+ if (!m.matches()) {
+ throw new IllegalArgumentException(String.format("\"%s\" is not a method type", type));
+ }
+ return new NativeMethod(access, name, type, m.group("args"));
+ }
+
+ private NativeMethod(int access, String name, Type type, String arguments) {
+ this.access = access;
+ this.name = name;
+ this.type = type;
+ this.mangledName = mangleName(name);
+ this.longMangledName = mangledName + "__" + mangleName(arguments);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof NativeMethod)) {
+ return false;
+ }
+ NativeMethod that = (NativeMethod) o;
+ return name.equals(that.name) && type.equals(that.type);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, type);
+ }
+
+ @Override
+ public String toString() {
+ return String.format("NativeMethod[name=%s, type=%s}", name, type);
+ }
+
+ public String name() {
+ return name;
+ }
+
+ public Type type() {
+ return type;
+ }
+
+ public String mangledName() {
+ return mangledName;
+ }
+
+ public String longMangledName() {
+ return longMangledName;
+ }
+
+ public boolean isStatic() {
+ return (access & Opcodes.ACC_STATIC) != 0;
+ }
+}
diff --git a/src/main/java/org/glavo/javah/RuntimeSearchPath.java b/src/main/java/org/glavo/javah/RuntimeSearchPath.java
new file mode 100644
index 0000000..fdfe717
--- /dev/null
+++ b/src/main/java/org/glavo/javah/RuntimeSearchPath.java
@@ -0,0 +1,44 @@
+package org.glavo.javah;
+
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.*;
+import java.util.Collections;
+import java.util.Objects;
+
+public class RuntimeSearchPath implements SearchPath {
+ public static final RuntimeSearchPath INSTANCE = new RuntimeSearchPath();
+
+ private RuntimeSearchPath() {
+
+ }
+
+ @Override
+ public Path search(ClassName name) {
+ Objects.requireNonNull(name);
+ URI uri = null;
+ try {
+ Class<?> cls = Class.forName(name.className());
+ uri = cls.getResource(name.simpleName() + ".class").toURI();
+ return Paths.get(uri);
+ } catch (FileSystemNotFoundException ex) {
+ if (uri == null) {
+ return null;
+ }
+ try {
+ return FileSystems.newFileSystem(uri, Collections.emptyMap()).getPath("/", name.relativePath());
+ } catch (IOException | NullPointerException ignored) {
+ }
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
+ public static Path searchClass(String name) {
+ return INSTANCE.search(name);
+ }
+
+ public static Path searchClass(ClassName name) {
+ return INSTANCE.search(name);
+ }
+}
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..adbe27d
--- /dev/null
+++ b/src/main/java/org/glavo/javah/SearchPath.java
@@ -0,0 +1,92 @@
+package org.glavo.javah;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.*;
+import java.util.jar.Manifest;
+import java.util.stream.Collectors;
+
+import static org.glavo.javah.Utils.*;
+
+public interface SearchPath {
+ Path search(ClassName name);
+
+ default Path search(String fullName) {
+ Objects.requireNonNull(fullName);
+ return search(ClassName.of(fullName));
+ }
+
+ static Path searchFrom(Iterable<SearchPath> searchPaths, ClassName name) {
+ Objects.requireNonNull(searchPaths);
+ Objects.requireNonNull(name);
+
+ for (SearchPath searchPath : searchPaths) {
+ if (searchPath == null) {
+ continue;
+ }
+ Path p = searchPath.search(name);
+ if (p != null) {
+ return p;
+ }
+ }
+ return null;
+ }
+
+ static Path searchFromRoots(Iterable<Path> roots, ClassName name) {
+ Objects.requireNonNull(roots);
+ Objects.requireNonNull(name);
+ for (Path root : roots) {
+ if (root == null || !Files.isDirectory(root)) {
+ continue;
+ }
+
+ Path p = root.resolve(name.relativePath());
+ if (Files.isRegularFile(p)) {
+ return p;
+ }
+ if (Files.isSymbolicLink(p)) {
+ try {
+ p = Files.readSymbolicLink(p);
+ if (Files.isRegularFile(p)) {
+ return p;
+ }
+ } catch (IOException ignored) {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ static List<Path> multiReleaseRoots(Path root) {
+ Objects.requireNonNull(root);
+ if (!Files.isDirectory(root)) {
+ return Collections.emptyList();
+ }
+ boolean isMultiRelease = false;
+ try (InputStream in = Files.newInputStream(root.resolve("META-INF").resolve("MANIFEST.MF"))) {
+ isMultiRelease = "true".equals(new Manifest(in).getMainAttributes().getValue("Multi-Release"));
+ } catch (IOException | NullPointerException ignored) {
+ }
+
+ if (isMultiRelease) {
+ Path base = root.resolve("META-INF").resolve("versions");
+ if (Files.isDirectory(base)) {
+ try {
+ List<Path> list = Files.list(base)
+ .map(Path::toAbsolutePath)
+ .filter(Files::isDirectory)
+ .filter(p -> MULTI_RELEASE_VERSIONS.contains(p.getFileName().toString()))
+ .sorted(Comparator.comparing((Path p) -> Integer.parseInt(p.getFileName().toString())).reversed())
+ .collect(Collectors.toCollection(LinkedList::new));
+ list.add(root);
+ return Collections.unmodifiableList(list);
+ } catch (IOException ignored) {
+ }
+ }
+ }
+ return Collections.singletonList(root);
+ }
+}
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..cb1ceef
--- /dev/null
+++ b/src/main/java/org/glavo/javah/Utils.java
@@ -0,0 +1,134 @@
+package org.glavo.javah;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+class Utils {
+ public static final int MAX_SUPPORTED_VERSION = 13;
+
+ public static final List<String> MULTI_RELEASE_VERSIONS =
+ IntStream.rangeClosed(9, MAX_SUPPORTED_VERSION).mapToObj(Integer::toString).collect(Collectors.toList());
+
+ public static final Pattern SIMPLE_NAME_PATTERN = Pattern.compile("[^.;\\[/]+");
+ public static final Pattern FULL_NAME_PATTERN =
+ Pattern.compile("[^.;\\[/]+(\\.[^.;\\[/]+)*");
+
+ public static final Pattern METHOD_NAME_PATTERN = Pattern.compile("(<init>)|(<cinit>)|([^.;\\[/<>]+)");
+ public static final Pattern METHOD_TYPE_PATTERN =
+ Pattern.compile("\\((?<args>(\\[*([BCDFIJSZ]|L[^.;\\[/]+(/[^.;\\\\\\[/]+)*;))*)\\)(?<ret>\\[*([BCDFIJSZV]|L[^.;\\[/]+(/[^.;\\[/]+)*;))");
+
+ public static final PrintWriter NOOP_WRITER = new PrintWriter(new Writer() {
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ }
+
+ @Override
+ public void flush() throws IOException {
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+ });
+
+ public static String mangleName(String name) {
+ StringBuilder builder = new StringBuilder(name.length() * 2);
+ int len = name.length();
+ for (int i = 0; i < len; i++) {
+ char ch = name.charAt(i);
+ if (ch == '.') {
+ builder.append('_');
+ } else if (ch == '_') {
+ builder.append("_1");
+ } else if (ch == ';') {
+ builder.append("_2");
+ } else if (ch == '[') {
+ builder.append("_3");
+ } else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && (ch <= 'Z'))) {
+ builder.append(ch);
+ } else {
+ builder.append(String.format("_0%04x", (int) ch));
+ }
+ }
+ return builder.toString();
+ }
+
+ public static String escape(String unicode) {
+ Objects.requireNonNull(unicode);
+ int len = unicode.length();
+ StringBuilder builder = new StringBuilder(len);
+ for (int i = 0; i < len; i++) {
+ char ch = unicode.charAt(i);
+ if (ch >= ' ' && ch <= '~') {
+ builder.append(ch);
+ } else {
+ builder.append(String.format("\\u%04x", (int) ch));
+ }
+ }
+ return builder.toString();
+ }
+
+ public static Path classPathRoot(Path p) {
+ Objects.requireNonNull(p);
+ p = p.toAbsolutePath();
+
+ if (Files.notExists(p)) {
+ return null;
+ }
+ if (Files.isDirectory(p)) {
+ return p;
+ }
+
+ try {
+ FileSystem fs = FileSystems.newFileSystem(p, (ClassLoader) null);
+ String name = p.getFileName().toString().toLowerCase();
+ if (name.endsWith(".jar") || name.endsWith(".zip")) {
+ return fs.getPath("/");
+ }
+ if (name.endsWith(".jmod")) {
+ return fs.getPath("/", "classes");
+ }
+
+ fs.close();
+ } catch (IOException ignored) {
+ return null;
+ }
+ return null;
+ }
+
+ public static ClassName superClassOf(ClassReader reader) {
+ Objects.requireNonNull(reader);
+ class V extends ClassVisitor {
+ V() {
+ super(Opcodes.ASM7);
+ }
+
+ ClassName superName = null;
+
+ @Override
+ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
+ if (superName != null) {
+ this.superName = ClassName.of(superName.replace('/', '.'));
+ }
+ }
+ }
+ V v = new V();
+ reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
+ return v.superName;
+ }
+}