From 4c0edeb4fc0ab85bae4bd41dfbf6a0089dfbf467 Mon Sep 17 00:00:00 2001 From: Glavo Date: Fri, 29 Nov 2019 03:30:24 +0800 Subject: update --- build.gradle | 37 +- src/main/java/module-info.java | 6 + src/main/java/org/glavo/javah/ClassPath.java | 32 ++ src/main/java/org/glavo/javah/FileManager.java | 9 + src/main/java/org/glavo/javah/HeaderGenerator.java | 529 --------------------- src/main/java/org/glavo/javah/JavahConfig.java | 51 ++ src/main/java/org/glavo/javah/Main.java | 6 + src/main/java/org/glavo/javah/ModulePath.java | 36 ++ .../java/org/glavo/javah/RuntimeClassPath.java | 41 ++ src/main/java/org/glavo/javah/SearchPath.java | 7 + src/main/java/org/glavo/javah/Utils.java | 134 ++++++ .../java/org/glavo/javah/HeaderGeneratorTest.java | 12 - src/test/java/org/glavo/javah/Test.java | 9 - 13 files changed, 337 insertions(+), 572 deletions(-) create mode 100644 src/main/java/module-info.java create mode 100644 src/main/java/org/glavo/javah/ClassPath.java create mode 100644 src/main/java/org/glavo/javah/FileManager.java delete mode 100644 src/main/java/org/glavo/javah/HeaderGenerator.java create mode 100644 src/main/java/org/glavo/javah/JavahConfig.java create mode 100644 src/main/java/org/glavo/javah/Main.java create mode 100644 src/main/java/org/glavo/javah/ModulePath.java create mode 100644 src/main/java/org/glavo/javah/RuntimeClassPath.java create mode 100644 src/main/java/org/glavo/javah/SearchPath.java create mode 100644 src/main/java/org/glavo/javah/Utils.java delete mode 100644 src/test/java/org/glavo/javah/HeaderGeneratorTest.java delete mode 100644 src/test/java/org/glavo/javah/Test.java 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 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 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 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] \n" + - "where [options] include:\n" + - " -o Output file (only one of -d or -o may be used)\n" + - " -d Output directory\n" + - " -h -help --help -? Print this message\n" + - " -version Print version information\n" + - " -classpath Path from which to load classes\n" + - " -cp Path from which to load classes\n" + - " 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> 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 "); - - 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 cps = new ArrayList<>(); - Path outputFile = null; - Path outputDir = null; - ArrayList 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 constants = new LinkedHashMap<>(); - private Map> methods = new LinkedHashMap>(); - - public Generator() { - super(Opcodes.ASM5); - } - - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - className = name; - } - - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - if ((access & Opcodes.ACC_NATIVE) != 0) { - if (methods.containsKey(name)) { - methods.get(name).add(new MethodDesc((access & Opcodes.ACC_STATIC) != 0, descriptor)); - } else { - LinkedHashSet set = new LinkedHashSet<>(); - set.add(new MethodDesc((access & Opcodes.ACC_STATIC) != 0, descriptor)); - methods.put(name, set); - } - } - return null; - } - - public String getClassName() { - return className; - } - - public Map> getMethods() { - return methods; - } -} \ 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 searchPaths = new ArrayList<>(); + + + // + // Getters and Setters + // + + public void addSearchPath(SearchPath searchPath) { + Objects.requireNonNull(searchPath); + searchPaths.add(searchPath); + } + + public List searchPaths() { + return searchPaths; + } + + public PrintWriter getErrorHandle() { + return errorHandle; + } + + public void setErrorHandle(Writer handle) { + if (handle == null || handle instanceof PrintWriter) { + errorHandle = (PrintWriter) handle; + } else { + errorHandle = new PrintWriter(handle); + } + } + + public boolean isOutputToSignalFile() { + return outputToSignalFile; + } + + public JavahConfig setOutputToSignalFile(boolean outputToSignalFile) { + this.outputToSignalFile = outputToSignalFile; + return this; + } +} diff --git a/src/main/java/org/glavo/javah/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 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 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 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 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 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(); -} -- cgit v1.2.3