Skip to content
Merged
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 pkgs/jnigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- Flip `isExcluded` to `isIncluded`.
- Make a bunch of nullable fields non-null, if null was functionally
identical to a default value.
- Allow interface mixin names to be customized using the visitor API.

## 0.17.0

Expand Down
21 changes: 11 additions & 10 deletions pkgs/jnigen/lib/src/bindings/dart_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,6 @@ ${modifier}final $classRef = $_jni.JClass.forName(r'$internalName');
),
);
final implementsClause = {superName, ...interfaces}.join(', ');
final implClassName = '\$$name';
final typeParamsDef = node.allTypeParams
.accept(const _TypeParamDef())
.join(', ')
Expand Down Expand Up @@ -493,9 +492,10 @@ extension type $name$typeParamsDef._($_jObject _\$this) implements $implementsCl
node.compareTo?.accept(_ComparatorGenerator(resolver, instanceSink));

if (node.declKind == DeclKind.interfaceKind) {
final interfaceMixinName = node.finalInterfaceMixinName;
s.write('''
/// Maps a specific port to the implemented interface.
static final $_core.Map<$_core.int, $implClassName> _\$impls = {};
static final $_core.Map<$_core.int, $interfaceMixinName> _\$impls = {};
''');
s.write('''
static $_jni.JObjectPtr _\$invoke(
Expand Down Expand Up @@ -540,7 +540,7 @@ extension type $name$typeParamsDef._($_jObject _\$this) implements $implementsCl

static void implementIn$typeParamsDef(
$_jni.JImplementer implementer,
$implClassName$typeParamsCall \$impl,
$interfaceMixinName$typeParamsCall \$impl,
) {
late final $_jni.RawReceivePort \$p;
\$p = $_jni.RawReceivePort((\$m) {
Expand All @@ -564,7 +564,7 @@ extension type $name$typeParamsDef._($_jObject _\$this) implements $implementsCl
final interfaceAsyncMethod = _InterfaceIfAsyncMethod(
resolver,
s,
implClassName: implClassName,
implClassName: interfaceMixinName,
);
for (final method in node.methods) {
method.accept(interfaceAsyncMethod);
Expand All @@ -577,7 +577,7 @@ extension type $name$typeParamsDef._($_jObject _\$this) implements $implementsCl
}

factory $name.implement(
$implClassName$typeParamsCall \$impl,
$interfaceMixinName$typeParamsCall \$impl,
) {
final \$i = $_jni.JImplementer();
implementIn(\$i, \$impl);
Expand Down Expand Up @@ -608,16 +608,17 @@ extension type $name$typeParamsDef._($_jObject _\$this) implements $implementsCl
// Abstract and concrete Impl class definition.
// Used for interface implementation.
if (node.declKind == DeclKind.interfaceKind) {
final interfaceMixinName = node.finalInterfaceMixinName;
// Abstract Impl class.
final abstractFactoryArgs = node.methods
.accept(_AbstractImplFactoryArg(resolver))
.join(_newLine(depth: 2))
.encloseIfNotEmpty('{', '}');
s.write('''
abstract base mixin class $implClassName$typeParamsDef {
factory $implClassName(
abstract base mixin class $interfaceMixinName$typeParamsDef {
factory $interfaceMixinName(
$abstractFactoryArgs
) = _$implClassName$typeParamsCall;
) = _$interfaceMixinName$typeParamsCall;

''');
final abstractImplMethod = _AbstractImplMethod(resolver, s);
Expand All @@ -638,8 +639,8 @@ abstract base mixin class $implClassName$typeParamsDef {
.encloseIfNotEmpty(' : ', '');
s.write('''

final class _$implClassName$typeParamsDef with $implClassName$typeParamsCall {
_$implClassName(
final class _$interfaceMixinName$typeParamsDef with $interfaceMixinName$typeParamsCall {
_$interfaceMixinName(
$concreteCtorArgs
)$setClosures;

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
Loading
Loading