diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md index 30994c625..ddaf49602 100644 --- a/pkgs/jnigen/CHANGELOG.md +++ b/pkgs/jnigen/CHANGELOG.md @@ -3,6 +3,8 @@ - The name `factory` can now also be used in a method name without renaming. - Throw when output folder contains non JNIgen files. Users with existing package bindings will need to delete them once for it to start working. +- Added the ability to generate classes in Java SDK (`java.core`) module without + providing the class path. ## 0.14.1 diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java index 0c5a0773a..718770db2 100644 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java +++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java @@ -8,13 +8,10 @@ import com.github.dart_lang.jnigen.apisummarizer.doclet.SummarizerDoclet; import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl; import com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder; -import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider; +import com.github.dart_lang.jnigen.apisummarizer.util.JavaCoreClassFinder; import com.github.dart_lang.jnigen.apisummarizer.util.JsonWriter; import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.OutputStream; +import java.io.*; import java.util.*; import javax.tools.DocumentationTool; import javax.tools.JavaFileObject; @@ -81,7 +78,7 @@ public static void main(String[] args) throws FileNotFoundException { var javaDoc = ToolProvider.getSystemDocumentationTool(); var sourceClasses = new LinkedHashMap>(); - var binaryClasses = new LinkedHashMap>(); + var binaryClasses = new LinkedHashMap>(); for (var qualifiedName : options.args) { sourceClasses.put(qualifiedName, null); @@ -102,7 +99,22 @@ public static void main(String[] args) throws FileNotFoundException { var foundSource = sourceClasses.get(qualifiedName) != null; var foundBinary = binaryClasses.get(qualifiedName) != null; if (!foundBinary && !foundSource) { - notFound.add(qualifiedName); + Map inputStreams = null; + try { + inputStreams = JavaCoreClassFinder.findAll(qualifiedName); + } catch (IOException e) { + throw new RuntimeException(e); + } + if (inputStreams != null) { + inputStreams.forEach( + (className, inputStream) -> { + var list = new ArrayList(); + list.add(inputStream); + binaryClasses.put(className, list); + }); + } else { + notFound.add(qualifiedName); + } } } diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java index 4d7417e93..d399ffb7f 100644 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java +++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java @@ -7,20 +7,24 @@ import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException; import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl; -import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider; +import java.io.IOException; +import java.io.InputStream; import java.util.List; import java.util.Map; import org.objectweb.asm.ClassReader; public class AsmSummarizer { - public static Map run(List inputProviders) { + public static Map run(List inputStreams) { var visitor = new AsmClassVisitor(); - for (var provider : inputProviders) { - var inputStream = provider.getInputStream(); + for (var inputStream : inputStreams) { var classReader = wrapCheckedException(ClassReader::new, inputStream); classReader.accept(visitor, 0); - provider.close(); + try { + inputStream.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } } return visitor.getVisited(); } diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java index e959ce9e0..01cc4ef61 100644 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java +++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java @@ -2,8 +2,7 @@ import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException; -import java.io.File; -import java.io.IOException; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -163,13 +162,29 @@ private static List getJavaFileObjectsFromJar( return StreamUtil.map(entries, (entry) -> new JarEntryFileObject(jarFile, entry)); } - private static List getInputStreamProvidersFromFiles(List files) { - return StreamUtil.map(files, (path) -> new FileInputStreamProvider(path.toFile())); + private static List getInputStreamProvidersFromFiles(List files) { + return StreamUtil.map( + files, + (path) -> { + try { + return new FileInputStream(path.toFile()); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + }); } - private static List getInputStreamProvidersFromJar( + private static List getInputStreamProvidersFromJar( JarFile jarFile, List entries) { - return StreamUtil.map(entries, entry -> new JarEntryInputStreamProvider(jarFile, entry)); + return StreamUtil.map( + entries, + entry -> { + try { + return jarFile.getInputStream(entry); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } public static void findJavaSources( @@ -185,7 +200,7 @@ public static void findJavaSources( } public static void findJavaClasses( - Map> classes, List searchPaths) { + Map> classes, List searchPaths) { find( classes, searchPaths, diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java deleted file mode 100644 index da5cadf9e..000000000 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.github.dart_lang.jnigen.apisummarizer.util; - -import java.io.*; - -/** Implementation of InputStreamProvider backed by a File. */ -public class FileInputStreamProvider implements InputStreamProvider { - File file; - InputStream stream; - - public FileInputStreamProvider(File file) { - this.file = file; - } - - @Override - public InputStream getInputStream() { - if (stream == null) { - try { - stream = new FileInputStream(file); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - } - return stream; - } - - @Override - public void close() { - if (stream == null) { - return; - } - try { - stream.close(); - stream = null; - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java deleted file mode 100644 index 85ba15b2e..000000000 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.github.dart_lang.jnigen.apisummarizer.util; - -import java.io.InputStream; - -/** - * Implementers of this interface provide an InputStream on-demand for writing, and provide a way to - * close the same.
- * The implementation doesn't need to be thread-safe, since this is only used in AsmSummarizer, - * which reads the classes serially. - */ -public interface InputStreamProvider { - /** Return the input stream, initializing it if needed. */ - InputStream getInputStream(); - - /** close the underlying InputStream. */ - void close(); -} diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java deleted file mode 100644 index cba3f76bd..000000000 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.github.dart_lang.jnigen.apisummarizer.util; - -import java.io.IOException; -import java.io.InputStream; -import java.util.jar.JarFile; -import java.util.zip.ZipEntry; - -public class JarEntryInputStreamProvider implements InputStreamProvider { - - private final JarFile jarFile; - private final ZipEntry zipEntry; - - public JarEntryInputStreamProvider(JarFile jarFile, ZipEntry zipEntry) { - this.jarFile = jarFile; - this.zipEntry = zipEntry; - } - - @Override - public InputStream getInputStream() { - try { - return jarFile.getInputStream(zipEntry); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - @Override - public void close() {} -} diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JavaCoreClassFinder.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JavaCoreClassFinder.java new file mode 100644 index 000000000..1d16f40dd --- /dev/null +++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JavaCoreClassFinder.java @@ -0,0 +1,78 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.github.dart_lang.jnigen.apisummarizer.util; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +public class JavaCoreClassFinder { + private static List findInnerClasses(InputStream inputStream) throws IOException { + List innerClasses = new ArrayList<>(); + ClassReader classReader = new ClassReader(inputStream); + + classReader.accept( + new ClassVisitor(Opcodes.ASM9) { + @Override + public void visitInnerClass(String name, String outerName, String innerName, int access) { + innerClasses.add(name.replace('/', '.')); + super.visitInnerClass(name, outerName, innerName, access); + } + }, + 0); + + return innerClasses; + } + + private static InputStream find(String className) { + String classPath = "/" + className.replace('.', '/') + ".class"; + URI uri = URI.create("jrt:/"); + Map env = new HashMap<>(); + try (var fs = FileSystems.newFileSystem(uri, env)) { + Path path = fs.getPath("modules/java.base", classPath); + if (Files.notExists(path)) { + return null; + } + return Files.newInputStream(path); + } catch (IOException e) { + return null; + } + } + + /// Finds the class and all its inner classes. + public static Map findAll(String className) throws IOException { + var classes = new HashMap(); + var classInputStream = find(className); + if (classInputStream == null) { + return null; + } + var bytes = classInputStream.readAllBytes(); + classInputStream.close(); + classes.put(className, new ByteArrayInputStream(bytes)); + try { + var innerClasses = findInnerClasses(new ByteArrayInputStream(bytes)); + for (var innerClass : innerClasses) { + var innerClassInputStream = find(innerClass); + if (innerClassInputStream != null) { + classes.put(innerClass, innerClassInputStream); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return classes; + } +} diff --git a/pkgs/jnigen/test/java_core_generation.dart b/pkgs/jnigen/test/java_core_generation.dart new file mode 100644 index 000000000..f4f8a9fdd --- /dev/null +++ b/pkgs/jnigen/test/java_core_generation.dart @@ -0,0 +1,30 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:jnigen/src/config/config_types.dart'; +import 'package:test/test.dart'; + +import 'test_util/test_util.dart'; + +void main() { + test('Java core libraries are generated without providing class path', + () async { + await generateAndAnalyzeBindings( + Config( + outputConfig: OutputConfig( + dartConfig: DartCodeOutputConfig( + path: Uri.file('foo.dart'), + structure: OutputStructure.singleFile, + ), + ), + classes: [ + // A random assortment of Java core classes. + 'java.lang.StringBuilder', + 'java.lang.ModuleLayer', + 'java.net.SocketOption', + ], + ), + ); + }); +}