Skip to content
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
4 changes: 3 additions & 1 deletion pkgs/jnigen/lib/src/bindings/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,9 @@ ${modifier}final $classRef = $_jni.JClass.forName(r'$internalName');
),
);
final implementsClause = {superName, ...interfaces}.join(', ');
final implClassName = '\$$name';
final implClassName = node.declKind == DeclKind.interfaceKind

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand this conditional. Why isn't this just final implClassName = node.finalInterfaceMixinName;? If it's a late initialization issue, just move it inside the if (node.declKind == DeclKind.interfaceKind) { below. Better yet, you probably don't even need this variable anymore.

? node.finalInterfaceMixinName
: '';
final typeParamsDef = node.allTypeParams
.accept(const _TypeParamDef())
.join(', ')
Expand Down
45 changes: 32 additions & 13 deletions pkgs/jnigen/lib/src/bindings/renamer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,23 @@ class Renamer extends Visitor<Classes, void> with TopLevelVisitor {
class _ClassRenamer implements Visitor<ClassDecl, void> {
final Config config;
final Set<ClassDecl> renamed;
final Map<String, int> topLevelNameCounts = {
..._definedSyms,
..._reservedTopLevelNames,
};
final Map<String, Map<String, int>> topLevelNameCounts = {};
final Map<ClassDecl, Map<String, int>> nameCounts = {};

_ClassRenamer(
this.config,
) : renamed = {...config.importedClasses.values};

Map<String, int> _getTopLevelNameCounts(ClassDecl node) {
return topLevelNameCounts.putIfAbsent(
node.path,
() => {
..._definedSyms,
..._reservedTopLevelNames,
},
);
}

@override
void visit(ClassDecl node) {
if (renamed.contains(node)) return;
Expand All @@ -217,13 +224,25 @@ class _ClassRenamer implements Visitor<ClassDecl, void> {
final className =
'$outerClassName${_preprocess(node.userDefinedName ?? node.name)}';

// When generating all the classes in a single file
// the names need to be unique.
final uniquifyName =
config.output.dart.structure == OutputStructure.singleFile;
node.finalName = uniquifyName
? _renameConflict(topLevelNameCounts, className, _ElementKind.klass)
: className;
final generatedFileNameCounts = _getTopLevelNameCounts(node);

node.finalName = _renameConflict(
generatedFileNameCounts,
className,
_ElementKind.klass,
);

if (node.declKind == DeclKind.interfaceKind) {
final interfaceMixinName = node.userDefinedInterfaceMixinName == null
? '\$${node.finalName}'
: _preprocess(node.userDefinedInterfaceMixinName!);

node.finalInterfaceMixinName = _renameConflict(
generatedFileNameCounts,
interfaceMixinName,
_ElementKind.klass,
);
}

if (node.userDefinedName == null ||
node.userDefinedName == node.finalName) {
Expand All @@ -238,15 +257,15 @@ class _ClassRenamer implements Visitor<ClassDecl, void> {
// method will be renamed.
final fieldRenamer = _FieldRenamer(
config,
uniquifyName && node.isTopLevel ? topLevelNameCounts : nameCounts[node]!,
node.isTopLevel ? generatedFileNameCounts : nameCounts[node]!,
);
for (final field in node.fields) {
field.accept(fieldRenamer);
}

final methodRenamer = _MethodRenamer(
config,
uniquifyName && node.isTopLevel ? topLevelNameCounts : nameCounts[node]!,
node.isTopLevel ? generatedFileNameCounts : nameCounts[node]!,
node.declKind == DeclKind.interfaceKind,
);
for (final method in node.methods) {
Expand Down
6 changes: 6 additions & 0 deletions pkgs/jnigen/lib/src/elements/elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class ClassDecl with ClassMember, Annotated implements Element<ClassDecl> {
@JsonKey(includeFromJson: false)
String? userDefinedName;

@JsonKey(includeFromJson: false)
String? userDefinedInterfaceMixinName;

@override
final Set<String> modifiers;

Expand Down Expand Up @@ -149,6 +152,9 @@ class ClassDecl with ClassMember, Annotated implements Element<ClassDecl> {
@override
late String finalName;

@JsonKey(includeFromJson: false)
late String finalInterfaceMixinName;

/// Name of the type class.
@JsonKey(includeFromJson: false)
String get typeClassName => '\$$finalName\$Type\$';
Expand Down
9 changes: 9 additions & 0 deletions pkgs/jnigen/lib/src/elements/j_elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,15 @@ class ClassDecl implements _Element {
/// The original name of the class in Java.
String get originalName => _classDecl.name;

/// The custom name of the mixin generated for implementing this Java
/// interface
///
/// If null, the default generated name is used.
String? get interfaceMixinName => _classDecl.userDefinedInterfaceMixinName;

set interfaceMixinName(String? newName) =>
_classDecl.userDefinedInterfaceMixinName = newName;

@override
void accept(Visitor visitor) {
visitor.visitClass(this);
Expand Down
72 changes: 69 additions & 3 deletions pkgs/jnigen/test/renamer_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ extension on Iterable<Method> {
}).toList();
}

Future<void> rename(Classes classes) async {
Future<void> rename(
Classes classes, {
OutputStructure structure = OutputStructure.singleFile,
}) async {
final config = Config(
input: Input(classes: []),
output: Output(
dart: DartCodeOutput(
path: Uri.file('test.dart'),
structure: OutputStructure.singleFile,
path: structure == OutputStructure.singleFile
? Uri.file('test.dart')
: Uri.directory('test_output/'),
structure: structure,
),
),
);
Expand Down Expand Up @@ -303,6 +308,67 @@ void main() {
expect(classRenamedMethods, [r'implement', r'implementIn']);
});

test('Interface mixin names', () async {
final classes = Classes({
'Foo': ClassDecl(
binaryName: 'Foo',
declKind: DeclKind.interfaceKind,
superclass: DeclaredType.object,
),
'Bar': ClassDecl(
binaryName: 'Bar',
declKind: DeclKind.interfaceKind,
superclass: DeclaredType.object,
)..userDefinedInterfaceMixinName = 'Foo',
'Baz': ClassDecl(
binaryName: 'Baz',
declKind: DeclKind.interfaceKind,
superclass: DeclaredType.object,
)..userDefinedInterfaceMixinName = 'class',
});

await rename(classes);

expect(classes.decls['Foo']!.finalInterfaceMixinName, r'$Foo');
expect(classes.decls['Bar']!.finalInterfaceMixinName, r'Foo$1');
expect(classes.decls['Baz']!.finalInterfaceMixinName, r'class$');
});

test('Interface mixin name preprocessing', () async {
final classes = Classes({
'Foo': ClassDecl(
binaryName: 'Foo',
declKind: DeclKind.interfaceKind,
superclass: DeclaredType.object,
)..userDefinedInterfaceMixinName = r'_Foo$',
});

await rename(classes);

expect(
classes.decls['Foo']!.finalInterfaceMixinName,
r'$_Foo$$',
);
});

test('Interface mixin name conflicts in package structure', () async {
final classes = Classes({
'Foo': ClassDecl(
binaryName: 'Foo',
declKind: DeclKind.interfaceKind,
superclass: DeclaredType.object,
)..userDefinedInterfaceMixinName = 'Foo',
});

await rename(
classes,
structure: OutputStructure.packageStructure,
);

expect(classes.decls['Foo']!.finalName, 'Foo');
expect(classes.decls['Foo']!.finalInterfaceMixinName, r'Foo$1');
});

test('Inner classes vs classes with dollar signs', () async {
final classes = Classes({
'Outer': ClassDecl(
Expand Down
100 changes: 100 additions & 0 deletions pkgs/jnigen/test/user_visitor_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
// 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 'dart:io';

import 'package:jnigen/jnigen.dart';
import 'package:jnigen/src/bindings/dart_generator.dart';
import 'package:jnigen/src/bindings/linker.dart';
import 'package:jnigen/src/bindings/renamer.dart';
import 'package:jnigen/src/elements/elements.dart' as ast;
Expand Down Expand Up @@ -255,4 +258,101 @@ void main() {
expect(classes.decls['y.Foo']?.methods.first.params.finalNames,
['Bar', 'Bar1']);
});

test('Rename interface mixin using the user visitor', () async {
final classes = ast.Classes({
'Foo': ast.ClassDecl(
binaryName: 'Foo',
declKind: ast.DeclKind.interfaceKind,
superclass: ast.DeclaredType.object,
),
});

Classes(classes).accept(
Visitor(
visitClass: (c) {
if (c.originalName == 'Foo') {
c.interfaceMixinName = 'FooInterface';
}
},
),
);

expect(
classes.decls['Foo']!.userDefinedInterfaceMixinName,
'FooInterface',
);

await rename(classes);

expect(
classes.decls['Foo']!.finalInterfaceMixinName,
'FooInterface',
);
});

test('Use the renamed interface mixin in generated bindings', () async {
final tempDirectory = Directory.systemTemp.createTempSync(
'jnigen_interface_mixin_test_',
);
addTearDown(() => tempDirectory.deleteSync(recursive: true));

final output = tempDirectory.uri.resolve('bindings.dart');
final config = Config(
input: Input(classes: []),
output: Output(
dart: DartCodeOutput(
path: output,
structure: OutputStructure.singleFile,
),
),
);

final classes = ast.Classes({
'Foo': ast.ClassDecl(
binaryName: 'Foo',
declKind: ast.DeclKind.interfaceKind,
superclass: ast.DeclaredType.object,
methods: [
ast.Method(
name: 'run',
returnType: ast.PrimitiveType.fromJson({'name': 'void'}),
),
],
),
});

Classes(classes).accept(
Visitor(
visitClass: (c) {
if (c.originalName == 'Foo') {
c.interfaceMixinName = 'FooInterface';
}
},
),
);

await classes.accept(Linker(config));
classes.accept(Renamer(config));
await classes.accept(DartGenerator(config));

final content = File.fromUri(output).readAsStringSync();

expect(
content,
contains('abstract base mixin class FooInterface'),
);
expect(
content,
contains('final class _FooInterface with FooInterface'),
);
expect(
content,
contains(r'FooInterface $impl'),
);
expect(
content,
isNot(contains(r'abstract base mixin class $Foo')),
);
});
}
Loading