diff --git a/pkgs/jnigen/lib/src/bindings/renamer.dart b/pkgs/jnigen/lib/src/bindings/renamer.dart index 572a2eb964..ef77bd0746 100644 --- a/pkgs/jnigen/lib/src/bindings/renamer.dart +++ b/pkgs/jnigen/lib/src/bindings/renamer.dart @@ -185,7 +185,8 @@ class _ClassRenamer implements Visitor { final outerClassName = node.outerClass == null ? '' : '${node.outerClass!.finalName}\$'; - final className = '$outerClassName${_preprocess(node.name)}'; + final className = + '$outerClassName${_preprocess(node.userDefinedName ?? node.name)}'; // When generating all the classes in a single file // the names need to be unique. @@ -196,7 +197,14 @@ class _ClassRenamer implements Visitor { : className; node.typeClassName = '\$${node.finalName}\$Type'; node.nullableTypeClassName = '\$${node.finalName}\$NullableType'; - log.fine('Class ${node.binaryName} is named ${node.finalName}'); + + if (node.userDefinedName == null || + node.userDefinedName == node.finalName) { + log.fine('Class ${node.binaryName} is named ${node.finalName}'); + } else { + log.warning('Renaming Class ${node.binaryName} to ${node.userDefinedName}' + ' causes a name collision. Renamed to ${node.finalName} instead.'); + } // Rename fields before renaming methods. In case a method and a field have // identical names, the field will keep its original name and the @@ -227,7 +235,8 @@ class _MethodRenamer implements Visitor { @override void visit(Method node) { - final name = _preprocess(node.isConstructor ? 'new' : node.name); + final name = _preprocess( + node.isConstructor ? 'new' : node.userDefinedName ?? node.name); final sig = node.javaSig; // If node is in super class, assign its number, overriding it. final superClass = @@ -245,8 +254,16 @@ class _MethodRenamer implements Visitor { node.finalName = _renameConflict(nameCounts, name, _ElementKind.method); node.classDecl.methodNumsAfterRenaming[sig] = nameCounts[name]! - 1; } - log.fine('Method ${node.classDecl.binaryName}#${node.name}' - ' is named ${node.finalName}'); + + if (node.userDefinedName == null || + node.userDefinedName == node.finalName) { + log.fine('Method ${node.classDecl.binaryName}#${node.name}' + ' is named ${node.finalName}'); + } else { + log.warning('Renaming Method ${node.classDecl.binaryName}#' + '${node.name} to ${node.userDefinedName} cause a name collision. ' + 'Renamed to ${node.finalName} instead.'); + } final paramRenamer = _ParamRenamer(config); for (final param in node.params) { @@ -263,10 +280,18 @@ class _FieldRenamer implements Visitor { @override void visit(Field node) { - final fieldName = _preprocess(node.name); + final fieldName = _preprocess(node.userDefinedName ?? node.name); node.finalName = _renameConflict(nameCounts, fieldName, _ElementKind.field); - log.fine('Field ${node.classDecl.binaryName}#${node.name}' - ' is named ${node.finalName}'); + + if (node.userDefinedName == null || + node.userDefinedName == node.finalName) { + log.fine('Field ${node.classDecl.binaryName}#${node.name}' + ' is named ${node.finalName}'); + } else { + log.warning('Renaming Field ${node.classDecl.binaryName}#${node.name}' + ' to ${node.userDefinedName} cause a name collision. ' + 'Renamed to ${node.finalName} instead.'); + } } } @@ -277,6 +302,7 @@ class _ParamRenamer implements Visitor { @override void visit(Param node) { - node.finalName = _keywordRename(node.name, _ElementKind.field); + node.finalName = + _keywordRename(node.userDefinedName ?? node.name, _ElementKind.field); } } diff --git a/pkgs/jnigen/lib/src/elements/elements.dart b/pkgs/jnigen/lib/src/elements/elements.dart index b8e96f3298..ece6b684e4 100644 --- a/pkgs/jnigen/lib/src/elements/elements.dart +++ b/pkgs/jnigen/lib/src/elements/elements.dart @@ -81,6 +81,9 @@ class ClassDecl with ClassMember, Annotated implements Element { @JsonKey(includeFromJson: false) bool isExcluded; + @JsonKey(includeFromJson: false) + String? userDefinedName; + @override final Set modifiers; @@ -647,6 +650,9 @@ class Method with ClassMember, Annotated implements Element { @JsonKey(includeFromJson: false) bool isExcluded; + @JsonKey(includeFromJson: false) + String? userDefinedName; + @override final String name; @override @@ -707,6 +713,9 @@ class Param with Annotated implements Element { required this.type, }); + @JsonKey(includeFromJson: false) + String? userDefinedName; + @override List? annotations; final JavaDocComment? javadoc; @@ -751,6 +760,9 @@ class Field with ClassMember, Annotated implements Element { @JsonKey(includeFromJson: false) bool isExcluded; + @JsonKey(includeFromJson: false) + String? userDefinedName; + @override final String name; @override diff --git a/pkgs/jnigen/lib/src/elements/j_elements.dart b/pkgs/jnigen/lib/src/elements/j_elements.dart index 883786aea3..6ee83bc877 100644 --- a/pkgs/jnigen/lib/src/elements/j_elements.dart +++ b/pkgs/jnigen/lib/src/elements/j_elements.dart @@ -12,6 +12,7 @@ abstract class Visitor { void visitClass(ClassDecl c) {} void visitMethod(Method method) {} void visitField(Field field) {} + void visitParam(Param parameter) {} } class Classes implements Element { @@ -30,15 +31,19 @@ class Classes implements Element { } class ClassDecl implements Element { - ClassDecl(this._classDecl) : binaryName = _classDecl.binaryName; + ClassDecl(this._classDecl); final ast.ClassDecl _classDecl; - // Ex: com.x.Foo. - final String binaryName; + String get binaryName => _classDecl.binaryName; bool get isExcluded => _classDecl.isExcluded; set isExcluded(bool value) => _classDecl.isExcluded = value; + String get name => _classDecl.userDefinedName ?? _classDecl.name; + set name(String newName) => _classDecl.userDefinedName = newName; + + String get originalName => _classDecl.name; + @override void accept(Visitor visitor) { visitor.visitClass(this); @@ -57,14 +62,37 @@ class Method implements Element { final ast.Method _method; - String get name => _method.name; - bool get isExcluded => _method.isExcluded; set isExcluded(bool value) => _method.isExcluded = value; + String get name => _method.userDefinedName ?? _method.name; + set name(String newName) => _method.userDefinedName = newName; + + String get originalName => _method.name; + @override void accept(Visitor visitor) { visitor.visitMethod(this); + if (_method.isExcluded) return; + for (final param in _method.params) { + Param(param).accept(visitor); + } + } +} + +class Param implements Element { + Param(this._param); + + final ast.Param _param; + + String get name => _param.userDefinedName ?? _param.name; + set name(String newName) => _param.userDefinedName = newName; + + String get originalName => _param.name; + + @override + void accept(Visitor visitor) { + visitor.visitParam(this); } } @@ -73,11 +101,14 @@ class Field implements Element { final ast.Field _field; - String get name => _field.name; - bool get isExcluded => _field.isExcluded; set isExcluded(bool value) => _field.isExcluded = value; + String get name => _field.userDefinedName ?? _field.name; + set name(String newName) => _field.userDefinedName = newName; + + String get originalName => _field.name; + @override void accept(Visitor visitor) { visitor.visitField(this); diff --git a/pkgs/jnigen/test/user_excluder.dart b/pkgs/jnigen/test/user_excluder.dart deleted file mode 100644 index f13adcda41..0000000000 --- a/pkgs/jnigen/test/user_excluder.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2024, 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/elements/elements.dart' as ast; -import 'package:jnigen/src/elements/j_elements.dart'; -import 'package:test/test.dart'; - -extension on Iterable { - List get isExcludedValues => map((c) => c.isExcluded).toList(); -} - -extension on Iterable { - List get isExcludedValues => map((c) => c.isExcluded).toList(); -} - -// This is customizable by the user -class UserExcluder extends Visitor { - @override - void visitClass(ClassDecl c) { - if (c.binaryName.contains('y')) { - c.isExcluded = true; - } - } - - @override - void visitMethod(Method method) { - if (method.name == 'Bar') { - method.isExcluded = true; - } - } - - @override - void visitField(Field field) { - if (field.name == 'Bar') { - field.isExcluded = true; - } - } -} - -void main() { - test('Exclude something using the user excluder, Simple AST', () async { - final classes = ast.Classes({ - 'Foo': ast.ClassDecl( - binaryName: 'Foo', - declKind: ast.DeclKind.classKind, - superclass: ast.TypeUsage.object, - methods: [ - ast.Method(name: 'foo', returnType: ast.TypeUsage.object), - ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), - ast.Method(name: 'foo1', returnType: ast.TypeUsage.object), - ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), - ], - fields: [ - ast.Field(name: 'foo', type: ast.TypeUsage.object), - ast.Field(name: 'Bar', type: ast.TypeUsage.object), - ast.Field(name: 'foo1', type: ast.TypeUsage.object), - ast.Field(name: 'Bar', type: ast.TypeUsage.object), - ], - ), - 'y.Foo': ast.ClassDecl( - binaryName: 'y.Foo', - declKind: ast.DeclKind.classKind, - superclass: ast.TypeUsage.object, - methods: [ - ast.Method(name: 'foo', returnType: ast.TypeUsage.object), - ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), - ], - fields: [ - ast.Field(name: 'foo', type: ast.TypeUsage.object), - ast.Field(name: 'Bar', type: ast.TypeUsage.object), - ]), - }); - - final simpleClasses = Classes(classes); - simpleClasses.accept(UserExcluder()); - - expect(classes.decls['y.Foo']?.isExcluded, true); - expect(classes.decls['Foo']?.isExcluded, false); - - expect(classes.decls['Foo']?.fields.isExcludedValues, - [false, true, false, true]); - expect(classes.decls['Foo']?.methods.isExcludedValues, - [false, true, false, true]); - }); -} diff --git a/pkgs/jnigen/test/user_visitors.dart b/pkgs/jnigen/test/user_visitors.dart new file mode 100644 index 0000000000..190347a52b --- /dev/null +++ b/pkgs/jnigen/test/user_visitors.dart @@ -0,0 +1,196 @@ +// Copyright (c) 2024, 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/jnigen.dart' as jnigen; +import 'package:jnigen/src/bindings/linker.dart'; +import 'package:jnigen/src/bindings/renamer.dart'; +import 'package:jnigen/src/elements/elements.dart' as ast; +import 'package:jnigen/src/elements/j_elements.dart'; +import 'package:test/test.dart'; + +extension on Iterable { + List get isExcludedValues => map((c) => c.isExcluded).toList(); +} + +extension on Iterable { + List get isExcludedValues => map((c) => c.isExcluded).toList(); +} + +extension on Iterable { + List get finalNames => map((m) => m.finalName).toList(); +} + +extension on Iterable { + List get finalNames => map((p) => p.finalName).toList(); +} + +extension on Iterable { + List get finalNames => map((f) => f.finalName).toList(); +} + +// This is customizable by the user +class UserExcluder extends Visitor { + @override + void visitClass(ClassDecl c) { + if (c.binaryName.contains('y')) { + c.isExcluded = true; + } + } + + @override + void visitMethod(Method method) { + if (method.name == 'Bar') { + method.isExcluded = true; + } + } + + @override + void visitField(Field field) { + if (field.name == 'Bar') { + field.isExcluded = true; + } + } +} + +// This is customizable by the user +class UserRenamer extends Visitor { + @override + void visitClass(ClassDecl c) { + if (c.originalName.contains('Foo')) { + c.name = c.originalName.replaceAll('Foo', 'Bar'); + } + } + + @override + void visitMethod(Method method) { + if (method.originalName.contains('Foo')) { + method.name = method.originalName.replaceAll('Foo', 'Bar'); + } + } + + @override + void visitField(Field field) { + if (field.originalName.contains('Foo')) { + field.name = field.originalName.replaceAll('Foo', 'Bar'); + } + } + + @override + void visitParam(Param parameter) { + if (parameter.originalName.contains('Foo')) { + parameter.name = parameter.originalName.replaceAll('Foo', 'Bar'); + } + } +} + +Future rename(ast.Classes classes) async { + final config = jnigen.Config( + outputConfig: jnigen.OutputConfig( + dartConfig: jnigen.DartCodeOutputConfig( + path: Uri.file('test.dart'), + structure: jnigen.OutputStructure.singleFile, + ), + ), + classes: []); + await classes.accept(Linker(config)); + classes.accept(Renamer(config)); +} + +void main() { + test('Exclude something using the user excluder, Simple AST', () async { + final classes = ast.Classes({ + 'Foo': ast.ClassDecl( + binaryName: 'Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.TypeUsage.object, + methods: [ + ast.Method(name: 'foo', returnType: ast.TypeUsage.object), + ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), + ast.Method(name: 'foo1', returnType: ast.TypeUsage.object), + ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), + ], + fields: [ + ast.Field(name: 'foo', type: ast.TypeUsage.object), + ast.Field(name: 'Bar', type: ast.TypeUsage.object), + ast.Field(name: 'foo1', type: ast.TypeUsage.object), + ast.Field(name: 'Bar', type: ast.TypeUsage.object), + ], + ), + 'y.Foo': ast.ClassDecl( + binaryName: 'y.Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.TypeUsage.object, + methods: [ + ast.Method(name: 'foo', returnType: ast.TypeUsage.object), + ast.Method(name: 'Bar', returnType: ast.TypeUsage.object), + ], + fields: [ + ast.Field(name: 'foo', type: ast.TypeUsage.object), + ast.Field(name: 'Bar', type: ast.TypeUsage.object), + ]), + }); + + final simpleClasses = Classes(classes); + simpleClasses.accept(UserExcluder()); + + expect(classes.decls['y.Foo']?.isExcluded, true); + expect(classes.decls['Foo']?.isExcluded, false); + + expect(classes.decls['Foo']?.fields.isExcludedValues, + [false, true, false, true]); + expect(classes.decls['Foo']?.methods.isExcludedValues, + [false, true, false, true]); + }); + + test('Rename classes, fields, methods and params using the user renamer', + () async { + final classes = ast.Classes({ + 'Foo': ast.ClassDecl( + binaryName: 'Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.TypeUsage.object, + methods: [ + ast.Method(name: 'Foo', returnType: ast.TypeUsage.object), + ast.Method(name: 'Foo', returnType: ast.TypeUsage.object), + ast.Method(name: 'Foo1', returnType: ast.TypeUsage.object), + ast.Method(name: 'Foo1', returnType: ast.TypeUsage.object), + ], + fields: [ + ast.Field(name: 'Foo', type: ast.TypeUsage.object), + ast.Field(name: 'Foo', type: ast.TypeUsage.object), + ast.Field(name: 'Foo1', type: ast.TypeUsage.object), + ast.Field(name: 'Foo1', type: ast.TypeUsage.object), + ], + ), + 'y.Foo': ast.ClassDecl( + binaryName: 'y.Foo', + declKind: ast.DeclKind.classKind, + superclass: ast.TypeUsage.object, + methods: [ + ast.Method(name: 'Foo', returnType: ast.TypeUsage.object, params: [ + ast.Param(name: 'Foo', type: ast.TypeUsage.object), + ast.Param(name: 'Foo1', type: ast.TypeUsage.object), + ]), + ], + ), + }); + + final simpleClasses = Classes(classes); + simpleClasses.accept(UserRenamer()); + + await rename(classes); + + expect(classes.decls['Foo']?.finalName, 'Bar'); + expect(classes.decls['y.Foo']?.finalName, r'Bar$1'); + + expect(classes.decls['Foo']?.fields.finalNames, + ['Bar', r'Bar$1', 'Bar1', r'Bar1$1']); + + expect(classes.decls['Foo']?.methods.finalNames, + [r'Bar$2', r'Bar$3', r'Bar1$2', r'Bar1$3']); + + expect(classes.decls['y.Foo']?.methods.first.params.finalNames, + ['Bar', 'Bar1']); + }); +}