Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[jnigen] Generate classes in Java SDK without providing path #2082

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -81,7 +78,7 @@ public static void main(String[] args) throws FileNotFoundException {
var javaDoc = ToolProvider.getSystemDocumentationTool();

var sourceClasses = new LinkedHashMap<String, List<JavaFileObject>>();
var binaryClasses = new LinkedHashMap<String, List<InputStreamProvider>>();
var binaryClasses = new LinkedHashMap<String, List<InputStream>>();

for (var qualifiedName : options.args) {
sourceClasses.put(qualifiedName, null);
Expand All @@ -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<String, InputStream> 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<InputStream>();
list.add(inputStream);
binaryClasses.put(className, list);
});
} else {
notFound.add(qualifiedName);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, ClassDecl> run(List<InputStreamProvider> inputProviders) {
public static Map<String, ClassDecl> run(List<InputStream> 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();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -163,13 +162,29 @@ private static List<JavaFileObject> getJavaFileObjectsFromJar(
return StreamUtil.map(entries, (entry) -> new JarEntryFileObject(jarFile, entry));
}

private static List<InputStreamProvider> getInputStreamProvidersFromFiles(List<Path> files) {
return StreamUtil.map(files, (path) -> new FileInputStreamProvider(path.toFile()));
private static List<InputStream> getInputStreamProvidersFromFiles(List<Path> files) {
return StreamUtil.map(
files,
(path) -> {
try {
return new FileInputStream(path.toFile());
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
});
}

private static List<InputStreamProvider> getInputStreamProvidersFromJar(
private static List<InputStream> getInputStreamProvidersFromJar(
JarFile jarFile, List<ZipEntry> 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(
Expand All @@ -185,7 +200,7 @@ public static void findJavaSources(
}

public static void findJavaClasses(
Map<String, List<InputStreamProvider>> classes, List<String> searchPaths) {
Map<String, List<InputStream>> classes, List<String> searchPaths) {
find(
classes,
searchPaths,
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<String> findInnerClasses(InputStream inputStream) throws IOException {
List<String> 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<String, String> 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<String, InputStream> findAll(String className) throws IOException {
var classes = new HashMap<String, InputStream>();
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;
}
}
30 changes: 30 additions & 0 deletions pkgs/jnigen/test/java_core_generation.dart
Original file line number Diff line number Diff line change
@@ -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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't seem to check that the generated bindings contain the classes.

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',
],
),
);
});
}