Skip to content

Commit 563a743

Browse files
[jnigen] Generate classes in Java SDK without providing path (#2082)
* [jnigen] Generate classes in Java SDK without providing path
1 parent 47611ae commit 563a743

File tree

10 files changed

+178
-104
lines changed

10 files changed

+178
-104
lines changed

pkgs/jnigen/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
- The name `factory` can now also be used in a method name without renaming.
44
- Throw when output folder contains non JNIgen files. Users with existing
55
package bindings will need to delete them once for it to start working.
6+
- Added the ability to generate classes in Java SDK (`java.core`) module without
7+
providing the class path.
68

79
## 0.14.1
810

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,10 @@
88
import com.github.dart_lang.jnigen.apisummarizer.doclet.SummarizerDoclet;
99
import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
1010
import com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder;
11-
import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider;
11+
import com.github.dart_lang.jnigen.apisummarizer.util.JavaCoreClassFinder;
1212
import com.github.dart_lang.jnigen.apisummarizer.util.JsonWriter;
1313
import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
14-
import java.io.File;
15-
import java.io.FileNotFoundException;
16-
import java.io.FileOutputStream;
17-
import java.io.OutputStream;
14+
import java.io.*;
1815
import java.util.*;
1916
import javax.tools.DocumentationTool;
2017
import javax.tools.JavaFileObject;
@@ -81,7 +78,7 @@ public static void main(String[] args) throws FileNotFoundException {
8178
var javaDoc = ToolProvider.getSystemDocumentationTool();
8279

8380
var sourceClasses = new LinkedHashMap<String, List<JavaFileObject>>();
84-
var binaryClasses = new LinkedHashMap<String, List<InputStreamProvider>>();
81+
var binaryClasses = new LinkedHashMap<String, List<InputStream>>();
8582

8683
for (var qualifiedName : options.args) {
8784
sourceClasses.put(qualifiedName, null);
@@ -102,7 +99,22 @@ public static void main(String[] args) throws FileNotFoundException {
10299
var foundSource = sourceClasses.get(qualifiedName) != null;
103100
var foundBinary = binaryClasses.get(qualifiedName) != null;
104101
if (!foundBinary && !foundSource) {
105-
notFound.add(qualifiedName);
102+
Map<String, InputStream> inputStreams = null;
103+
try {
104+
inputStreams = JavaCoreClassFinder.findAll(qualifiedName);
105+
} catch (IOException e) {
106+
throw new RuntimeException(e);
107+
}
108+
if (inputStreams != null) {
109+
inputStreams.forEach(
110+
(className, inputStream) -> {
111+
var list = new ArrayList<InputStream>();
112+
list.add(inputStream);
113+
binaryClasses.put(className, list);
114+
});
115+
} else {
116+
notFound.add(qualifiedName);
117+
}
106118
}
107119
}
108120

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmSummarizer.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,24 @@
77
import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException;
88

99
import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
10-
import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider;
10+
import java.io.IOException;
11+
import java.io.InputStream;
1112
import java.util.List;
1213
import java.util.Map;
1314
import org.objectweb.asm.ClassReader;
1415

1516
public class AsmSummarizer {
1617

17-
public static Map<String, ClassDecl> run(List<InputStreamProvider> inputProviders) {
18+
public static Map<String, ClassDecl> run(List<InputStream> inputStreams) {
1819
var visitor = new AsmClassVisitor();
19-
for (var provider : inputProviders) {
20-
var inputStream = provider.getInputStream();
20+
for (var inputStream : inputStreams) {
2121
var classReader = wrapCheckedException(ClassReader::new, inputStream);
2222
classReader.accept(visitor, 0);
23-
provider.close();
23+
try {
24+
inputStream.close();
25+
} catch (IOException e) {
26+
throw new RuntimeException(e);
27+
}
2428
}
2529
return visitor.getVisited();
2630
}

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException;
44

5-
import java.io.File;
6-
import java.io.IOException;
5+
import java.io.*;
76
import java.nio.file.Files;
87
import java.nio.file.Path;
98
import java.util.*;
@@ -163,13 +162,29 @@ private static List<JavaFileObject> getJavaFileObjectsFromJar(
163162
return StreamUtil.map(entries, (entry) -> new JarEntryFileObject(jarFile, entry));
164163
}
165164

166-
private static List<InputStreamProvider> getInputStreamProvidersFromFiles(List<Path> files) {
167-
return StreamUtil.map(files, (path) -> new FileInputStreamProvider(path.toFile()));
165+
private static List<InputStream> getInputStreamProvidersFromFiles(List<Path> files) {
166+
return StreamUtil.map(
167+
files,
168+
(path) -> {
169+
try {
170+
return new FileInputStream(path.toFile());
171+
} catch (FileNotFoundException e) {
172+
throw new RuntimeException(e);
173+
}
174+
});
168175
}
169176

170-
private static List<InputStreamProvider> getInputStreamProvidersFromJar(
177+
private static List<InputStream> getInputStreamProvidersFromJar(
171178
JarFile jarFile, List<ZipEntry> entries) {
172-
return StreamUtil.map(entries, entry -> new JarEntryInputStreamProvider(jarFile, entry));
179+
return StreamUtil.map(
180+
entries,
181+
entry -> {
182+
try {
183+
return jarFile.getInputStream(entry);
184+
} catch (IOException e) {
185+
throw new RuntimeException(e);
186+
}
187+
});
173188
}
174189

175190
public static void findJavaSources(
@@ -185,7 +200,7 @@ public static void findJavaSources(
185200
}
186201

187202
public static void findJavaClasses(
188-
Map<String, List<InputStreamProvider>> classes, List<String> searchPaths) {
203+
Map<String, List<InputStream>> classes, List<String> searchPaths) {
189204
find(
190205
classes,
191206
searchPaths,

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/FileInputStreamProvider.java

Lines changed: 0 additions & 38 deletions
This file was deleted.

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/InputStreamProvider.java

Lines changed: 0 additions & 17 deletions
This file was deleted.

pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JarEntryInputStreamProvider.java

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
package com.github.dart_lang.jnigen.apisummarizer.util;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
import java.net.URI;
11+
import java.nio.file.FileSystems;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import java.util.ArrayList;
15+
import java.util.HashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import org.objectweb.asm.ClassReader;
19+
import org.objectweb.asm.ClassVisitor;
20+
import org.objectweb.asm.Opcodes;
21+
22+
public class JavaCoreClassFinder {
23+
private static List<String> findInnerClasses(InputStream inputStream) throws IOException {
24+
List<String> innerClasses = new ArrayList<>();
25+
ClassReader classReader = new ClassReader(inputStream);
26+
27+
classReader.accept(
28+
new ClassVisitor(Opcodes.ASM9) {
29+
@Override
30+
public void visitInnerClass(String name, String outerName, String innerName, int access) {
31+
innerClasses.add(name.replace('/', '.'));
32+
super.visitInnerClass(name, outerName, innerName, access);
33+
}
34+
},
35+
0);
36+
37+
return innerClasses;
38+
}
39+
40+
private static InputStream find(String className) {
41+
String classPath = "/" + className.replace('.', '/') + ".class";
42+
URI uri = URI.create("jrt:/");
43+
Map<String, String> env = new HashMap<>();
44+
try (var fs = FileSystems.newFileSystem(uri, env)) {
45+
Path path = fs.getPath("modules/java.base", classPath);
46+
if (Files.notExists(path)) {
47+
return null;
48+
}
49+
return Files.newInputStream(path);
50+
} catch (IOException e) {
51+
return null;
52+
}
53+
}
54+
55+
/// Finds the class and all its inner classes.
56+
public static Map<String, InputStream> findAll(String className) throws IOException {
57+
var classes = new HashMap<String, InputStream>();
58+
var classInputStream = find(className);
59+
if (classInputStream == null) {
60+
return null;
61+
}
62+
var bytes = classInputStream.readAllBytes();
63+
classInputStream.close();
64+
classes.put(className, new ByteArrayInputStream(bytes));
65+
try {
66+
var innerClasses = findInnerClasses(new ByteArrayInputStream(bytes));
67+
for (var innerClass : innerClasses) {
68+
var innerClassInputStream = find(innerClass);
69+
if (innerClassInputStream != null) {
70+
classes.put(innerClass, innerClassInputStream);
71+
}
72+
}
73+
} catch (IOException e) {
74+
throw new RuntimeException(e);
75+
}
76+
return classes;
77+
}
78+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:jnigen/src/config/config_types.dart';
6+
import 'package:test/test.dart';
7+
8+
import 'test_util/test_util.dart';
9+
10+
void main() {
11+
test('Java core libraries are generated without providing class path',
12+
() async {
13+
await generateAndAnalyzeBindings(
14+
Config(
15+
outputConfig: OutputConfig(
16+
dartConfig: DartCodeOutputConfig(
17+
path: Uri.file('foo.dart'),
18+
structure: OutputStructure.singleFile,
19+
),
20+
),
21+
classes: [
22+
// A random assortment of Java core classes.
23+
'java.lang.StringBuilder',
24+
'java.lang.ModuleLayer',
25+
'java.net.SocketOption',
26+
],
27+
),
28+
confirmExists: ['StringBuilder', 'ModuleLayer', 'SocketOption'],
29+
);
30+
});
31+
}

pkgs/jnigen/test/test_util/test_util.dart

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ Future<void> generateAndCompareBindings(Config config) async {
131131
}
132132
}
133133

134-
Future<void> generateAndAnalyzeBindings(Config config) async {
134+
Future<void> generateAndAnalyzeBindings(Config config,
135+
{Iterable<String> confirmExists = const []}) async {
135136
final tempDir = Directory.current.createTempSync('jnigen_test_temp');
136137
try {
137138
await _generateTempBindings(config, tempDir);
@@ -140,6 +141,21 @@ Future<void> generateAndAnalyzeBindings(Config config) async {
140141
stderr.write(analyzeResult.stdout);
141142
fail('Analyzer exited with non-zero status (${analyzeResult.exitCode})');
142143
}
144+
final singleFile =
145+
config.outputConfig.dartConfig.structure == OutputStructure.singleFile;
146+
if (!singleFile && confirmExists.isNotEmpty) {
147+
throw UnimplementedError('Currently only supports single file mode '
148+
'for confirming that classes exists');
149+
} else if (singleFile && confirmExists.isNotEmpty) {
150+
final generatedCode =
151+
await File.fromUri(tempDir.uri.resolve('generated.dart'))
152+
.readAsString();
153+
for (final className in confirmExists) {
154+
if (!generatedCode.contains(className)) {
155+
fail('$className does not exist in the generated bindings');
156+
}
157+
}
158+
}
143159
} finally {
144160
tempDir.deleteSync(recursive: true);
145161
}

0 commit comments

Comments
 (0)