diff options
author | Glavo <zjx001202@126.com> | 2019-12-06 03:42:00 +0800 |
---|---|---|
committer | Glavo <zjx001202@126.com> | 2019-12-06 03:42:00 +0800 |
commit | e48c0668029849af07d1966729c5b703e89b1d00 (patch) | |
tree | fec378e2c6e52d86e601f0df45ba13728003fa2b /src/main/java/org | |
parent | 6fd5804e3f2c337839efcf5c10426faed1d8b1af (diff) | |
download | gjavah-e48c0668029849af07d1966729c5b703e89b1d00.tar.gz gjavah-e48c0668029849af07d1966729c5b703e89b1d00.tar.bz2 gjavah-e48c0668029849af07d1966729c5b703e89b1d00.zip |
update
Diffstat (limited to 'src/main/java/org')
-rw-r--r-- | src/main/java/org/glavo/javah/ClassMetaInfo.java | 46 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/ClassName.java | 88 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/ClassPath.java | 30 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/Constant.java | 83 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/JNIGenerator.java | 315 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/JavahTask.java | 163 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/Main.java | 142 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/ModulePath.java | 40 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/NativeMethod.java | 92 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/RuntimeClassPath.java | 41 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/RuntimeSearchPath.java | 44 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/SearchPath.java | 87 | ||||
-rw-r--r-- | src/main/java/org/glavo/javah/Utils.java | 271 |
13 files changed, 916 insertions, 526 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 index 2aea039..3e59c59 100644 --- a/src/main/java/org/glavo/javah/ClassPath.java +++ b/src/main/java/org/glavo/javah/ClassPath.java @@ -1,23 +1,41 @@ 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> searchPaths; + private final List<Path> roots; public ClassPath(Path path) { Objects.requireNonNull(path); - this.path = path; - searchPaths = Utils.getPathsFrom(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 Path searchClass(String className) { - Objects.requireNonNull(className); - return Utils.searchFrom(searchPaths, className); + public int hashCode() { + return path.hashCode(); } @Override 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/JNIGenerator.java b/src/main/java/org/glavo/javah/JNIGenerator.java index c1a9d40..bec2ea9 100644 --- a/src/main/java/org/glavo/javah/JNIGenerator.java +++ b/src/main/java/org/glavo/javah/JNIGenerator.java @@ -1,143 +1,238 @@ package org.glavo.javah; -import org.objectweb.asm.*; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.UncheckedIOException; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; -public class JNIGenerator extends ClassVisitor { - public static final String FILE_HEADER = - "/* DO NOT EDIT THIS FILE - it is machine generated */\n" + - "#include <jni.h>\n"; +import static org.glavo.javah.Utils.*; - public static final String FILE_END = "\n"; +public class JNIGenerator { - private final PrintWriter output; - private final Path classFile; + private final PrintWriter errorHandle; + private final Iterable<SearchPath> searchPaths; + private final Path outputDir; - private String className; + public JNIGenerator(Path outputDir) { + this(outputDir, null, null); + } + + public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths) { + this(outputDir, searchPaths, null); + } - private Map<String, Integer> methodNameCount = new HashMap<>(); - private LinkedList<NativeMethod> methods = new LinkedList<>(); - private LinkedList<ConstantField> constants = new LinkedList<>(); + public JNIGenerator(Path outputDir, Iterable<SearchPath> searchPaths, PrintWriter errorHandle) { + Objects.requireNonNull(outputDir); - public JNIGenerator(PrintWriter output, Path classFile) { - super(Opcodes.ASM7); - Objects.requireNonNull(output); - Objects.requireNonNull(classFile); - this.output = output; - this.classFile = classFile; + 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() { - ClassReader cls = null; - try { - cls = new ClassReader(Files.readAllBytes(classFile)); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - cls.accept(this, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - - String name = Utils.encode(className).replace('/', '_'); - String guard = "_Included_" + className; - - output.println("/* Header for class " + className + " */\n\n"); - output.println("#ifndef " + guard); - output.println("#define " + guard); - output.println("#ifdef __cplusplus"); - output.println("extern \"C\" {"); - output.println("#endif"); - - for (ConstantField constant : constants) { - String cm = name + "_" + Utils.encode(constant.name); - String value; - if (constant.value instanceof Float) { - value = constant.value.toString() + "f"; - } else if (constant.value instanceof Long) { - value = constant.value.toString() + "i64"; - } else if (constant.value instanceof Double) { - value = constant.value.toString(); - } else { - value = constant.value.toString() + "L"; + 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; } - output.println("#undef " + cm); - output.println("#define " + cm + " " + value); } - for (NativeMethod method : methods) { - String mm = "Java_" + name + "_" + Utils.encode(method.name); - if (methodNameCount.getOrDefault(method.name, 1) > 1) { - mm = mm + "__" + Utils.mangledArgSignature(method.type); + 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) { } - String r = Utils.mapToNativeType(method.type.getReturnType()); - String args = Stream.concat(Stream.of("JNIEnv *", method.isStatic ? "jclass" : "jobject"), - Arrays.stream(method.type.getArgumentTypes()).map(Utils::mapToNativeType)) - .collect(Collectors.joining(",")); - - output.println("/*"); - output.println(" * Class: " + name); - output.println(" * Method: " + mm); - output.println(" * Signature: " + method.type); - output.println(" */"); - output.println("JNIEXPORT " + r + " JNICALL " + mm); - output.println(" (" + args + ");"); - output.println(); - } - - output.println("#ifdef __cplusplus"); - output.println("}"); - output.println("#endif"); - output.println("#endif"); + } } - @Override - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - className = name; - } + 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; + } + } - @Override - public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { - if ((access & Opcodes.ACC_NATIVE) != 0) { - methodNameCount.put(name, methodNameCount.getOrDefault(name, 0) + 1); - methods.add(new NativeMethod(name, Type.getType(descriptor), (access & Opcodes.ACC_STATIC) != 0)); + 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()); } - return null; - } - @Override - public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { - if (value != null && !(value instanceof String)) { - constants.add(new ConstantField(name, Type.getType(descriptor), value)); + 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(); } - return null; + + out.println("#ifdef __cplusplus"); + out.println("}"); + out.println("#endif"); + out.println("#endif"); + + } + + private Path search(ClassName name) { + return SearchPath.searchFrom(searchPaths, name); } - private static class NativeMethod { - String name; - Type type; - boolean isStatic; + 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); + } - public NativeMethod(String name, Type type, boolean isStatic) { - this.name = name; - this.type = type; - this.isStatic = isStatic; + 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 static class ConstantField { - String name; - Type type; - Object value; + 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; + } - public ConstantField(String name, Type type, Object value) { - this.name = name; - this.type = type; - this.value = value; + 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 index 2da9400..8e239ed 100644 --- a/src/main/java/org/glavo/javah/JavahTask.java +++ b/src/main/java/org/glavo/javah/JavahTask.java @@ -1,159 +1,82 @@ package org.glavo.javah; -import java.io.File; -import java.io.IOException; +import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; -import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Arrays; +import java.util.LinkedList; import java.util.List; import java.util.Objects; -import java.util.stream.Stream; -public class JavahTask { - private PrintWriter errorHandle = new PrintWriter(System.err); - private boolean outputToSignalFile = false; - private Path outputPath = null; - - private final List<SearchPath> searchPaths = new ArrayList<>(); - private final List<String> classList = new ArrayList<>(); - - public void run() throws IOException { - if (outputPath == null) { - outputPath = Paths.get(".").toAbsolutePath(); - outputToSignalFile = false; - } - - if (outputToSignalFile) { - try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(outputPath))) { - writer.write(JNIGenerator.FILE_HEADER); - classList.stream() - .map(this::search) - .filter(Objects::nonNull) - .filter(Files::isReadable) - .map(p -> new JNIGenerator(writer, p)) - .forEachOrdered(JNIGenerator::generate); - - writer.write(JNIGenerator.FILE_END); - } - } else { - for (String c : classList) { - Path p = search(c); - if (p == null) { - continue; - } - try (PrintWriter output = new PrintWriter(Files.newBufferedWriter( - outputPath.resolve(Utils.encode(c).replace('.', '_') + ".h")))) { - output.write(JNIGenerator.FILE_HEADER); - new JNIGenerator(output, p).generate(); - output.write(JNIGenerator.FILE_END); - } +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); } } } - private Path search(String className) { - for (SearchPath searchPath : searchPaths) { - Path c = searchPath.searchClass(className); - if (c != null) { - return c; - } - } - errorHandle.println("class" + className + " not found"); - return null; + public void addClass(ClassName name) { + Objects.requireNonNull(name); + classes.add(name); } - // - // Getters and Setters - // + public void addClass(String name) { + Objects.requireNonNull(name); + classes.add(ClassName.of(name)); + } - public void addSearchPath(SearchPath searchPath) { - Objects.requireNonNull(searchPath); - searchPaths.add(searchPath); + public void addClasses(Iterable<String> i) { + Objects.requireNonNull(i); + i.forEach(c -> classes.add(ClassName.of(c))); } - public void addModulePath(Path modulePath) { - Objects.requireNonNull(modulePath); - searchPaths.add(new ModulePath(modulePath)); + public void addRuntimeSearchPath() { + searchPaths.add(RuntimeSearchPath.INSTANCE); } - public void addModulePaths(String modulePaths) { - Objects.requireNonNull(modulePaths); - Arrays.stream(modulePaths.split(File.pathSeparator)) - .filter(s -> !"".equals(s)) - .map(Paths::get) - .filter(Files::isDirectory) - .forEachOrdered(this::addModulePath); + public void addSearchPath(SearchPath searchPath) { + Objects.requireNonNull(searchPath); + searchPaths.add(searchPath); } - public void addClasspath(Path classPath) { + public void addClassPath(Path classPath) { Objects.requireNonNull(classPath); searchPaths.add(new ClassPath(classPath)); } - public void addClasspaths(String cps) { - Arrays.stream(cps.split(File.pathSeparator)) - .filter(s -> !"".equals(s)) - .flatMap(s -> { - if (s.endsWith(File.separatorChar + "*")) { - try { - return Files.list(Paths.get(s.substring(0, s.length() - 2))) - .filter(p -> p.getFileName().toString().toLowerCase().endsWith(".jar")); - } catch (IOException e) { - return Stream.empty(); - } - } - return Stream.of(Paths.get(s)); - }) - .filter(Files::exists) - .map(ClassPath::new) - .forEachOrdered(searchPaths::add); + public void addModulePath(Path modulePath) { + Objects.requireNonNull(modulePath); + searchPaths.add(new ModulePath(modulePath)); } - public void addRuntimeClasspath() { - searchPaths.add(RuntimeClassPath.INSTANCE); + public Path getOutputDir() { + return outputDir; } - public List<SearchPath> searchPaths() { - return searchPaths; + public void setOutputDir(Path outputDir) { + this.outputDir = outputDir; } public PrintWriter getErrorHandle() { return errorHandle; } - public void setErrorHandle(Writer handle) { - if (handle == null || handle instanceof PrintWriter) { - errorHandle = (PrintWriter) handle; + public void setErrorHandle(Writer errorHandle) { + if (errorHandle instanceof PrintWriter || errorHandle == null) { + this.errorHandle = (PrintWriter) errorHandle; } else { - errorHandle = new PrintWriter(handle); + this.errorHandle = new PrintWriter(errorHandle); } } - - public boolean isOutputToSignalFile() { - return outputToSignalFile; - } - - public void setOutputToSignalFile(boolean outputToSignalFile) { - this.outputToSignalFile = outputToSignalFile; - } - - public Path getOutputPath() { - return outputPath; - } - - public void setOutputPath(Path outputPath) { - this.outputPath = outputPath; - } - - public void addClass(String name) { - classList.add(name); - } - - public List<String> getClassList() { - return classList; - } } diff --git a/src/main/java/org/glavo/javah/Main.java b/src/main/java/org/glavo/javah/Main.java index 6df9539..1229520 100644 --- a/src/main/java/org/glavo/javah/Main.java +++ b/src/main/java/org/glavo/javah/Main.java @@ -1,71 +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 %1", sortOptions = false) public class Main { - private static final String HELP_MESSAGE = - "usage:\n" + - " gjavah [options] <classes>\n" + - "where [options] include:\n" + - " -o <file> Output file (only one of -d or -o may be used)\n" + - " -d <dir> Output directory\n" + - " --module-path <path> Path from which to search modules\n" + - " --class-path <path> | " + - "-classpath <path> | -cp <path>\n" + - " " + - " Path from which to search classes\n" + - " --version Print version information\n" + - " -h --help -? Print this message"; + @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 { - if (args.length == 0) { - System.err.println(HELP_MESSAGE); + Main m = new Main(); + CommandLine cm = new CommandLine(m); + if (args == null || args.length == 0) { + cm.usage(System.err); System.exit(-1); } - JavahTask task = new JavahTask(); - task.addRuntimeClasspath(); - loop: - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - switch (arg) { - case "--version": - case "-version": - try (InputStream in = Main.class.getResourceAsStream("/META-INF/MANIFEST.MF")) { - Manifest manifest = new Manifest(in); - System.out.println("gjavah version: " + manifest.getMainAttributes().getValue("GJavah-Version")); - } - return; - case "-h": - case "--help": - case "-help": - case "-?": - System.out.println(HELP_MESSAGE); - return; - case "-o": - task.setOutputToSignalFile(true); - task.setOutputPath(Paths.get(args[++i])); - break; - case "-d": - task.setOutputToSignalFile(false); - task.setOutputPath(Paths.get(args[++i])); - break; - case "--module-path": - task.addModulePaths(args[++i]); - break; - case "--class-path": - case "--classpath": - case "-classpath": - case "-cp": - task.addClasspaths(args[++i]); - break; - default: - while (i < args.length) { - task.addClass(args[i++]); - } + + cm.parseArgs(args); + System.out.println(m); + if (m.showHelp) { + cm.usage(System.out); + return; + } + if (m.showVersion) { + try (InputStream in = Main.class.getResourceAsStream("/META-INF/MANIFEST.MF")) { + cm.printVersionHelp(System.out, Help.Ansi.AUTO, new Manifest(in).getMainAttributes().getValue("GJavah-Version")); } + 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 index b355b24..0a9c2cc 100644 --- a/src/main/java/org/glavo/javah/ModulePath.java +++ b/src/main/java/org/glavo/javah/ModulePath.java @@ -1,34 +1,46 @@ 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.Collections; +import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class ModulePath implements SearchPath { private final Path path; - private final List<Path> paths; + private List<Path> roots; public ModulePath(Path path) { Objects.requireNonNull(path); - + path = path.toAbsolutePath(); 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); + 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 searchClass(String className) { - return Utils.searchFrom(paths, className); + public Path search(ClassName name) { + Objects.requireNonNull(name); + return SearchPath.searchFromRoots(roots, name); } @Override 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/RuntimeClassPath.java b/src/main/java/org/glavo/javah/RuntimeClassPath.java deleted file mode 100644 index c16cfa3..0000000 --- a/src/main/java/org/glavo/javah/RuntimeClassPath.java +++ /dev/null @@ -1,41 +0,0 @@ -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/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 index b95b7c0..adbe27d 100644 --- a/src/main/java/org/glavo/javah/SearchPath.java +++ b/src/main/java/org/glavo/javah/SearchPath.java @@ -1,7 +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 searchClass(String className) ; + 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 index e9043ea..cb1ceef 100644 --- a/src/main/java/org/glavo/javah/Utils.java +++ b/src/main/java/org/glavo/javah/Utils.java @@ -1,221 +1,134 @@ package org.glavo.javah; -import org.objectweb.asm.Type; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; import java.io.IOException; -import java.io.InputStream; +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.*; -import java.util.jar.Manifest; +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 { - static String encode(String str) { - StringBuilder buffer = new StringBuilder(str.length()); + public static final int MAX_SUPPORTED_VERSION = 13; - int len = str.length(); - for (int i = 0; i < len; i++) { - char ch = str.charAt(i); - if (ch == '_') { - buffer.append("_1"); - } else if (ch == ';') { - buffer.append("_2"); - } else if (ch == '[') { - buffer.append("_3"); - } else if ((ch >= '0' && ch <= '9') - || (ch >= 'a' && ch <= 'z') - || (ch >= 'A' && ch <= 'Z') - || ch == '/' - || ch == '.') { - buffer.append(ch); - } else { - buffer.append(String.format("_0%04x", (int) ch)); - } - } - return buffer.toString(); - } + public static final List<String> MULTI_RELEASE_VERSIONS = + IntStream.rangeClosed(9, MAX_SUPPORTED_VERSION).mapToObj(Integer::toString).collect(Collectors.toList()); - static String mapToNativeType(Type jt) { - switch (jt.toString()) { - case "Z": - return "jboolean"; - case "B": - return "jbyte"; - case "C": - return "jchar"; - case "S": - return "jshort"; - case "I": - return "jint"; - case "J": - return "jlong"; - case "F": - return "jfloat"; - case "D": - return "jdouble"; - case "V": - return "void"; - case "Ljava/lang/Class;": - return "jclass"; - case "Ljava/lang/String;": - return "jstring"; - case "Ljava/lang/Throwable ;": - return "jthrowable"; - case "[Z": - return "jbooleanArray"; - case "[B": - return "jbyteArray"; - case "[C": - return "jcharArray"; - case "[S": - return "jshortArray"; - case "[I": - return "jintArray"; - case "[J": - return "jlongArray"; - case "[F": - return "jfloatArray"; - case "[D": - return "jdoubleArray"; - } + public static final Pattern SIMPLE_NAME_PATTERN = Pattern.compile("[^.;\\[/]+"); + public static final Pattern FULL_NAME_PATTERN = + Pattern.compile("[^.;\\[/]+(\\.[^.;\\[/]+)*"); - if (jt.toString().startsWith("[")) { - return "jobjectArray"; - } - - return "jobject"; - } + 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[^.;\\[/]+(/[^.;\\[/]+)*;))"); - static String mangledArgSignature(Type methodType) { - Objects.requireNonNull(methodType); - String str = methodType.toString(); - int idx = str.indexOf(')'); - if (idx == -1) { - throw new IllegalArgumentException(methodType.toString() + "is not a method type"); + public static final PrintWriter NOOP_WRITER = new PrintWriter(new Writer() { + @Override + public void write(char[] cbuf, int off, int len) throws IOException { } - return encode(str.substring(1, idx)); - } - - 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; + @Override + public void flush() throws IOException { } - 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); - } + @Override + public void close() throws IOException { + } + }); - 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; + 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 null; + return builder.toString(); } - 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, (ClassLoader) 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(); + 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)); } } - if (!Files.isDirectory(root)) { - return Collections.emptyList(); - } + return builder.toString(); + } - 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() - ); + public static Path classPathRoot(Path p) { + Objects.requireNonNull(p); + p = p.toAbsolutePath(); - } catch (Exception ignored) { - } + if (Files.notExists(p)) { + return null; } - - if (!isMultiRelease) { - return Collections.singletonList(root); + if (Files.isDirectory(p)) { + return p; } - 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); + 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; } - - list.add(root); - return list; + return null; } - 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; + public static ClassName superClassOf(ClassReader reader) { + Objects.requireNonNull(reader); + class V extends ClassVisitor { + V() { + super(Opcodes.ASM7); } - } - return false; - } - - static Path searchFrom(List<Path> searchPaths, String name) { - Objects.requireNonNull(searchPaths); - Objects.requireNonNull(name); - name = simpleNameOf(name).replace('.', '/') + ".class"; + ClassName superName = null; - for (Path searchPath : searchPaths) { - Path p = searchPath.resolve(name); - if (Files.isRegularFile(p)) { - return p; + @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('/', '.')); + } } } - return null; + V v = new V(); + reader.accept(v, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG); + return v.superName; } } |