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

Add Java helper generator tasks #157

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
api project(':ToolchainPlugin')
implementation 'org.ajoberstar.grgit:grgit-core:5.0.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'org.ow2.asm:asm:9.5'
api 'edu.wpi.first:gradle-cpp-vscode:1.3.0'

testImplementation('org.spockframework:spock-core:2.0-M4-groovy-3.0') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package edu.wpi.first.nativeutils.java;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.util.List;

import javax.inject.Inject;

import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.tasks.InputFile;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

public class NativeMethodsGenerationTask extends DefaultTask {
private final RegularFileProperty inputFile;
private final RegularFileProperty outputFile;

@InputFile
public RegularFileProperty getInputFile() {
return inputFile;
}

@OutputFile
public RegularFileProperty getOutputFile() {
return outputFile;
}

@Inject
public NativeMethodsGenerationTask() {
ObjectFactory objects = getProject().getObjects();
inputFile = objects.fileProperty();
outputFile = objects.fileProperty();
}

@TaskAction
public void execute() throws IOException {
NativeMethodsGenerator generator = new NativeMethodsGenerator();
List<String> results = generator.generate(inputFile.get().getAsFile().toPath());
Files.write(outputFile.get().getAsFile().toPath(), results, Charset.defaultCharset(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package edu.wpi.first.nativeutils.java;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class NativeMethodsGenerator {
private static class Method {
Method(String name, String descriptor) {
this.name = name;
descriptors = new ArrayList<>(1);
descriptors.add(descriptor);
}

final String name;
final List<String> descriptors;
}

private String m_className;
private final Map<String, Method> m_methods = new TreeMap<>();

private void parse(InputStream classStream) throws IOException {
ClassReader reader = new ClassReader(classStream);
reader.accept(new ClassVisitor(Opcodes.ASM9) {
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
m_className = name;
}

@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
// only look at native methods
if ((access & Opcodes.ACC_NATIVE) == 0) {
return null;
}
Method method = m_methods.get(name);
if (method == null) {
m_methods.put(name, new Method(name, descriptor));
} else {
method.descriptors.add(descriptor);
}
return null;
}
}, ClassReader.SKIP_DEBUG|ClassReader.SKIP_FRAMES);
}

private static String mangle(String in) {
StringBuilder out = new StringBuilder();
int length = in.length();

for (int i = 0; i < length; i++) {
char ch = in.charAt(i);
if (isalnum(ch)) {
out.append(ch);
continue;
}
switch (ch) {
case '/':
case '.':
out.append("_");
break;
case '_':
out.append("_1");
break;
case ';':
out.append("_2");
break;
case '[':
out.append("_3");
break;
default:
out.append(encodeChar(ch));
}
}
return out.toString();
}

private static String encodeChar(char ch) {
String s = Integer.toHexString(ch);
int nzeros = 5 - s.length();
char[] result = new char[6];
result[0] = '_';
for (int i = 1; i <= nzeros; i++) {
result[i] = '0';
}
for (int i = nzeros + 1, j = 0; i < 6; i++, j++) {
result[i] = s.charAt(j);
}
return new String(result);
}

private static boolean isalnum(char ch) {
return ch <= 0x7f
&& ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'));
}

private String makeNativeMethod(String mangledClassName, String methodName, String descriptor, boolean overloaded) {
StringBuilder out = new StringBuilder();
out.append(" { const_cast<char*>(\"");
out.append(methodName);
out.append("\"), const_cast<char*>(\"");
out.append(descriptor);
out.append("\"), reinterpret_cast<void*>(Java_");
out.append(mangledClassName);
out.append("_");
out.append(mangle(methodName));
if (overloaded) {
out.append("__");
// only the parameters portion of the descriptor is appended
int begin = descriptor.indexOf('(') + 1;
int end = descriptor.indexOf(')', begin);
out.append(mangle(descriptor.substring(begin, end)));
}
out.append(") },");
return out.toString();
}

public List<String> generate(Path filename) {
try (InputStream classStream = Files.newInputStream(filename)) {
parse(classStream);
} catch(IOException e) {
System.err.println("Could not read " + filename + ": " + e);
return null;
}

String mangledClassName = mangle(m_className);
List<String> out = new ArrayList<>(m_methods.size());

out.add("static const JNINativeMethod nativeMethods_" + mangledClassName + "[] = {");

for (Method method : m_methods.values()) {
if (method.descriptors.size() == 1) {
out.add(makeNativeMethod(mangledClassName, method.name, method.descriptors.get(0), false));
} else {
for (String descriptor : method.descriptors) {
out.add(makeNativeMethod(mangledClassName, method.name, descriptor, true));
}
}
}

out.add("};");

return out;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package edu.wpi.first.nativeutils.java;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;

import javax.inject.Inject;

import org.gradle.api.DefaultTask;
import org.gradle.api.file.RegularFileProperty;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.tasks.Input;
import org.gradle.api.tasks.InputFiles;
import org.gradle.api.tasks.OutputFile;
import org.gradle.api.tasks.TaskAction;

public class PreloadListGenerationTask extends DefaultTask {
private final ListProperty<File> classPath;
private final RegularFileProperty preloadListFile;
private final ListProperty<String> excludePrefixes;
private final ListProperty<String> includePrefixes;
private final ListProperty<String> excludeClasses;
private final ListProperty<String> includeClasses;

@InputFiles
public ListProperty<File> getClassPath() {
return classPath;
}

@OutputFile
public RegularFileProperty getPreloadListFile() {
return preloadListFile;
}

@Input
public ListProperty<String> getExcludePrefixes() {
return excludePrefixes;
}

@Input
public ListProperty<String> getIncludePrefixes() {
return includePrefixes;
}

@Input
public ListProperty<String> getExcludeClasses() {
return excludeClasses;
}

@Input
public ListProperty<String> getIncludeClasses() {
return includeClasses;
}

@Inject
public PreloadListGenerationTask() {
ObjectFactory objects = getProject().getObjects();
classPath = objects.listProperty(File.class);
preloadListFile = objects.fileProperty();
excludePrefixes = objects.listProperty(String.class);
includePrefixes = objects.listProperty(String.class);
excludeClasses = objects.listProperty(String.class);
includeClasses = objects.listProperty(String.class);
}

@TaskAction
public void execute() throws IOException {
try (PreloadScanner scanner = new PreloadScanner()) {
scanner.setClassPath(classPath.get());
for (String prefix : excludePrefixes.get()) {
scanner.addIgnoredPrefix(prefix);
}
for (String prefix : includePrefixes.get()) {
scanner.removeIgnoredPrefix(prefix);
}
for (String className : excludeClasses.get()) {
scanner.ignoreClass(className);
}
for (String className : includeClasses.get()) {
scanner.scan(className);
}
Files.write(preloadListFile.get().getAsFile().toPath(), scanner.getResults(), Charset.defaultCharset(), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
}
}
}
Loading