Skip to content

Commit

Permalink
[jnigen] Use .jar metadata to generate generic types (#158)
Browse files Browse the repository at this point in the history
Closes #132.

ASM backend, parses classes, fields and method signatures from the metadata to add generic type information to other-wise type-erased generics.
  • Loading branch information
HosseinYousefi authored Dec 13, 2022
1 parent bc631e4 commit cb5b9d7
Show file tree
Hide file tree
Showing 22 changed files with 1,195 additions and 42 deletions.
1 change: 1 addition & 0 deletions pkgs/jnigen/example/in_app_java/jnigen.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ source_path:
classes:
- 'com.example.in_app_java.AndroidUtils' # from source_path
- 'android.os.Build' # from gradle's compile classpath
- 'java.util.HashMap' # from gradle's compile classpath
397 changes: 397 additions & 0 deletions pkgs/jnigen/example/in_app_java/lib/android_utils.dart

Large diffs are not rendered by default.

19 changes: 17 additions & 2 deletions pkgs/jnigen/example/in_app_java/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,24 @@ import 'android_utils.dart';

JObject activity = JObject.fromRef(Jni.getCurrentActivity());

/// Display device model number as Toast
final hashmap = HashMap.ctor2(JString.type, JString.type);

extension IntX on int {
JString toJString() {
return toString().toJString();
}
}

/// Display device model number and the number of times this was called
/// as Toast.
void showToast() {
AndroidUtils.showToast(activity, Build.MODEL, 0);
final toastCount =
hashmap.getOrDefault("toastCount".toJString(), 0.toJString());
final newToastCount = (int.parse(toastCount.toDartString()) + 1).toJString();
hashmap.put("toastCount".toJString(), newToastCount);
final message =
'${newToastCount.toDartString()} - ${Build.MODEL.toDartString()}';
AndroidUtils.showToast(activity, message.toJString(), 0);
}

void main() {
Expand Down
469 changes: 469 additions & 0 deletions pkgs/jnigen/example/in_app_java/src/android_utils/android_utils.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ class PDDocument extends jni.JObject {
///@return a <code>List</code> of <code>PDSignatureField</code>s
///@throws IOException if no document catalog can be found.
jni.JObject getSignatureFields() =>
jni.JObjectType().fromRef(_getSignatureFields(reference).object);
const jni.JObjectType().fromRef(_getSignatureFields(reference).object);

static final _getSignatureDictionaries = jniLookup<
ffi.NativeFunction<
Expand All @@ -498,8 +498,8 @@ class PDDocument extends jni.JObject {
/// Retrieve all signature dictionaries from the document.
///@return a <code>List</code> of <code>PDSignatureField</code>s
///@throws IOException if no document catalog can be found.
jni.JObject getSignatureDictionaries() =>
jni.JObjectType().fromRef(_getSignatureDictionaries(reference).object);
jni.JObject getSignatureDictionaries() => const jni.JObjectType()
.fromRef(_getSignatureDictionaries(reference).object);

static final _registerTrueTypeFontForClosing = jniLookup<
ffi.NativeFunction<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ class PDDocumentInformation extends jni.JObject {
///@return all metadata key strings.
///@since Apache PDFBox 1.3.0
jni.JObject getMetadataKeys() =>
jni.JObjectType().fromRef(_getMetadataKeys(reference).object);
const jni.JObjectType().fromRef(_getMetadataKeys(reference).object);

static final _getCustomMetadataValue = jniLookup<
ffi.NativeFunction<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ class PDFTextStripper extends jni.JObject {
/// text after second article
///
/// Most PDFs won't have any beads, so charactersByArticle will contain a single entry.
jni.JObject get charactersByArticle =>
jni.JObjectType().fromRef(_get_charactersByArticle(reference).object);
jni.JObject get charactersByArticle => const jni.JObjectType()
.fromRef(_get_charactersByArticle(reference).object);
static final _set_charactersByArticle = jniLookup<
ffi.NativeFunction<
jni.JThrowablePtr Function(
Expand Down Expand Up @@ -642,8 +642,8 @@ class PDFTextStripper extends jni.JObject {
/// Character strings are grouped by articles. It is quite common that there will only be a single article. This
/// returns a List that contains List objects, the inner lists will contain TextPosition objects.
///@return A double List of TextPositions for all text strings on the page.
jni.JObject getCharactersByArticle() =>
jni.JObjectType().fromRef(_getCharactersByArticle(reference).object);
jni.JObject getCharactersByArticle() => const jni.JObjectType()
.fromRef(_getCharactersByArticle(reference).object);

static final _setSuppressDuplicateOverlappingText = jniLookup<
ffi.NativeFunction<
Expand Down Expand Up @@ -1191,7 +1191,7 @@ class PDFTextStripper extends jni.JObject {
/// This method returns a list of such regular expression Patterns.
///@return a list of Pattern objects.
jni.JObject getListItemPatterns() =>
jni.JObjectType().fromRef(_getListItemPatterns(reference).object);
const jni.JObjectType().fromRef(_getListItemPatterns(reference).object);

static final _matchPattern = jniLookup<
ffi.NativeFunction<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright (c) 2022, 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.disasm;

import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeParam;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
import org.objectweb.asm.signature.SignatureVisitor;

public class AsmClassSignatureVisitor extends SignatureVisitor {
private final ClassDecl decl;
private int interfaceIndex = -1;

public AsmClassSignatureVisitor(ClassDecl decl) {
super(AsmConstants.API);
this.decl = decl;
}

@Override
public void visitFormalTypeParameter(String name) {
var typeParam = new TypeParam();
typeParam.name = name;
decl.typeParams.add(typeParam);
}

@Override
public SignatureVisitor visitClassBound() {
var typeUsage = new TypeUsage();
// ClassDecl initially has no type parameters. In visitFormalTypeParameter we add them
// and sequentially visitClassBound and visitInterfaceBound.
decl.typeParams.get(decl.typeParams.size() - 1).bounds.add(typeUsage);
return new AsmTypeUsageSignatureVisitor(typeUsage);
}

@Override
public SignatureVisitor visitInterfaceBound() {
var typeUsage = new TypeUsage();
decl.typeParams.get(decl.typeParams.size() - 1).bounds.add(typeUsage);
return new AsmTypeUsageSignatureVisitor(typeUsage);
}

@Override
public SignatureVisitor visitSuperclass() {
return new AsmTypeUsageSignatureVisitor(decl.superclass);
}

@Override
public SignatureVisitor visitInterface() {
interfaceIndex++;
return new AsmTypeUsageSignatureVisitor(decl.interfaces.get(interfaceIndex));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil;
import java.util.*;
import org.objectweb.asm.*;
import org.objectweb.asm.signature.SignatureReader;

public class AsmClassVisitor extends ClassVisitor implements AsmAnnotatedElementVisitor {
private static Param param(
Expand Down Expand Up @@ -53,6 +54,10 @@ public void visit(
current.superclass = TypeUtils.typeUsage(Type.getObjectType(superName), null);
current.interfaces =
StreamUtil.map(interfaces, i -> TypeUtils.typeUsage(Type.getObjectType(i), null));
if (signature != null) {
var reader = new SignatureReader(signature);
reader.accept(new AsmClassSignatureVisitor(current));
}
super.visit(version, access, name, signature, superName, interfaces);
}

Expand All @@ -66,11 +71,16 @@ public FieldVisitor visitField(
if (name.contains("$")) {
return null;
}

var field = new Field();
field.name = name;
field.type = TypeUtils.typeUsage(Type.getType(descriptor), signature);
field.defaultValue = value;
field.modifiers = TypeUtils.access(access);
if (signature != null) {
var reader = new SignatureReader(signature);
reader.accept(new AsmTypeUsageSignatureVisitor(field.type));
}
peekVisiting().fields.add(field);
return new AsmFieldVisitor(field);
}
Expand Down Expand Up @@ -101,6 +111,10 @@ public MethodVisitor visitMethod(
method.returnType = TypeUtils.typeUsage(type.getReturnType(), signature);
method.modifiers = TypeUtils.access(access);
method.params = params;
if (signature != null) {
var reader = new SignatureReader(signature);
reader.accept(new AsmMethodSignatureVisitor(method));
}
peekVisiting().methods.add(method);
return new AsmMethodVisitor(method);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) 2022, 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.disasm;

import com.github.dart_lang.jnigen.apisummarizer.elements.Method;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeParam;
import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
import org.objectweb.asm.signature.SignatureVisitor;

public class AsmMethodSignatureVisitor extends SignatureVisitor {
private final Method method;
private int paramIndex = -1;

public AsmMethodSignatureVisitor(Method method) {
super(AsmConstants.API);
this.method = method;
}

@Override
public void visitFormalTypeParameter(String name) {
var typeParam = new TypeParam();
typeParam.name = name;
method.typeParams.add(typeParam);
}

@Override
public SignatureVisitor visitClassBound() {
var typeUsage = new TypeUsage();
// Method initially has no type parameters. In visitFormalTypeParameter we add them
// and sequentially visitClassBound and visitInterfaceBound.
method.typeParams.get(method.typeParams.size() - 1).bounds.add(typeUsage);
return new AsmTypeUsageSignatureVisitor(typeUsage);
}

@Override
public SignatureVisitor visitInterfaceBound() {
var typeUsage = new TypeUsage();
method.typeParams.get(method.typeParams.size() - 1).bounds.add(typeUsage);
return new AsmTypeUsageSignatureVisitor(typeUsage);
}

@Override
public SignatureVisitor visitReturnType() {
return new AsmTypeUsageSignatureVisitor(method.returnType);
}

@Override
public SignatureVisitor visitParameterType() {
paramIndex++;
return new AsmTypeUsageSignatureVisitor(method.params.get(paramIndex).type);
}

@Override
public SignatureVisitor visitExceptionType() {
// Do nothing.
return new AsmTypeUsageSignatureVisitor(new TypeUsage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright (c) 2022, 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.disasm;

import com.github.dart_lang.jnigen.apisummarizer.elements.TypeUsage;
import java.util.ArrayList;
import org.objectweb.asm.signature.SignatureVisitor;

public class AsmTypeUsageSignatureVisitor extends SignatureVisitor {
private final TypeUsage typeUsage;

public AsmTypeUsageSignatureVisitor(TypeUsage typeUsage) {
super(AsmConstants.API);
this.typeUsage = typeUsage;
}

@Override
public void visitBaseType(char descriptor) {
typeUsage.kind = TypeUsage.Kind.PRIMITIVE;
var name = "";
switch (descriptor) {
case 'Z':
name = "boolean";
break;
case 'B':
name = "byte";
break;
case 'C':
name = "char";
break;
case 'D':
name = "double";
break;
case 'F':
name = "float";
break;
case 'I':
name = "int";
break;
case 'J':
name = "long";
break;
case 'L':
name = "object";
break;
case 'S':
name = "short";
break;
case 'V':
name = "void";
break;
}
typeUsage.shorthand = name;
typeUsage.type = new TypeUsage.PrimitiveType(name);
}

@Override
public SignatureVisitor visitArrayType() {
typeUsage.kind = TypeUsage.Kind.ARRAY;
typeUsage.shorthand = "java.lang.Object[]";
var elementType = new TypeUsage();
typeUsage.type = new TypeUsage.Array(elementType);
return new AsmTypeUsageSignatureVisitor(elementType);
}

@Override
public void visitTypeVariable(String name) {
typeUsage.kind = TypeUsage.Kind.TYPE_VARIABLE;
typeUsage.shorthand = name;
typeUsage.type = new TypeUsage.TypeVar(name);
}

@Override
public void visitClassType(String name) {
typeUsage.kind = TypeUsage.Kind.DECLARED;
typeUsage.shorthand = name.substring(0, name.length()).replace('/', '.');
var components = name.split("[/$]");
var simpleName = components[components.length - 1];
typeUsage.type = new TypeUsage.DeclaredType(name, simpleName, new ArrayList<>());
}

@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
// TODO(#141) support wildcards
// TODO(#144) support extend/super clauses
assert (typeUsage.type instanceof TypeUsage.DeclaredType);
var typeArg = new TypeUsage();
((TypeUsage.DeclaredType) typeUsage.type).params.add(typeArg);
return new AsmTypeUsageSignatureVisitor(typeArg);
}

@Override
public void visitInnerClassType(String name) {
super.visitInnerClassType(name);
// TODO(#139) support nested generic classes
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@ public class ClassDecl {

public String parentName;
public String packageName;
public List<TypeParam> typeParams;
public List<TypeParam> typeParams = new ArrayList<>();
public List<Method> methods = new ArrayList<>();
public List<Field> fields = new ArrayList<>();
public TypeUsage superclass;
public List<TypeUsage> interfaces;
public List<TypeUsage> interfaces = new ArrayList<>();
public boolean hasStaticInit;
public boolean hasInstanceInit;
public JavaDocComment javadoc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public class Method {
public Set<String> modifiers = new HashSet<>();
public String name;
public List<TypeParam> typeParams;
public List<TypeParam> typeParams = new ArrayList<>();
public List<Param> params = new ArrayList<>();
public TypeUsage returnType;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

package com.github.dart_lang.jnigen.apisummarizer.elements;

import java.util.ArrayList;
import java.util.List;

public class TypeParam {
public String name;
public List<TypeUsage> bounds;
public List<TypeUsage> bounds = new ArrayList<>();
}
Loading

0 comments on commit cb5b9d7

Please sign in to comment.