From 3e0d3a7902104212e8788e5aa13405fce7aa8443 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Sat, 12 Aug 2023 09:36:22 -0600 Subject: [PATCH] Transformer: Support converting default/private interface methods, #53 This converts interface methods with a body, such as default and private interface methods, to C# 8 interface methods with bodies. Also, this cleans up some use of modifier keyword determination that was messy as a reult of the JavaParser upgrade. --- JavaToCSharp.Tests/ConvertInterfaceTests.cs | 5 +- JavaToCSharp.Tests/IntegrationTests.cs | 1 + .../Java9PrivateInterfaceMethods.java | 22 +++ .../ClassOrInterfaceDeclarationVisitor.cs | 22 +-- .../ConstructorDeclarationVisitor.cs | 8 +- .../Declarations/EnumDeclarationVisitor.cs | 8 +- .../Declarations/FieldDeclarationVisitor.cs | 14 +- .../Declarations/MethodDeclarationVisitor.cs | 132 ++++++++++-------- JavaToCSharp/Extensions.cs | 5 + 9 files changed, 133 insertions(+), 84 deletions(-) create mode 100644 JavaToCSharp.Tests/Resources/Java9PrivateInterfaceMethods.java diff --git a/JavaToCSharp.Tests/ConvertInterfaceTests.cs b/JavaToCSharp.Tests/ConvertInterfaceTests.cs index d188e60a..8c5699a1 100644 --- a/JavaToCSharp.Tests/ConvertInterfaceTests.cs +++ b/JavaToCSharp.Tests/ConvertInterfaceTests.cs @@ -41,7 +41,10 @@ namespace MyApp { public interface IResolvedType { - bool IsArray(); + bool IsArray() + { + return false; + } } public class InferenceVariableType : IResolvedType diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index e9b9790b..40558131 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -52,6 +52,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath) [InlineData("Resources/Java7BasicTryWithResources.java")] [InlineData("Resources/Java7TryWithResources.java")] [InlineData("Resources/Java9TryWithResources.java")] + [InlineData("Resources/Java9PrivateInterfaceMethods.java")] public void FullIntegrationTests(string filePath) { var options = new JavaConversionOptions diff --git a/JavaToCSharp.Tests/Resources/Java9PrivateInterfaceMethods.java b/JavaToCSharp.Tests/Resources/Java9PrivateInterfaceMethods.java new file mode 100644 index 00000000..cf3757f2 --- /dev/null +++ b/JavaToCSharp.Tests/Resources/Java9PrivateInterfaceMethods.java @@ -0,0 +1,22 @@ +/// Expect: +/// - output: "Hello world!\n" +package example; + +public class Program { + public static void main(String[] args) { + InterfaceWithPrivateMethod implementation = new Implementation(); + implementation.defaultMethod(); + } +} + +class Implementation implements InterfaceWithPrivateMethod { +} + +interface InterfaceWithPrivateMethod { + default void defaultMethod() { + privateMethod(); + } + private void privateMethod() { + System.out.println("Hello world!"); + } +} \ No newline at end of file diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index 169a0d3c..580eb454 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -50,15 +50,15 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } - var mods = javai.getModifiers().ToList() ?? new List(); + var mods = javai.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL)) + if (mods.Contains(Modifier.Keyword.FINAL)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); var extends = javai.getExtendedTypes().ToList(); @@ -113,17 +113,17 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } - var mods = javac.getModifiers().ToList() ?? new List(); + var mods = javac.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.ABSTRACT)) + if (mods.Contains(Modifier.Keyword.ABSTRACT)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL)) + if (mods.Contains(Modifier.Keyword.FINAL)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); var extends = javac.getExtendedTypes().ToList() ?? new List(); diff --git a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs index e93383d4..8c109c1e 100644 --- a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs @@ -30,13 +30,13 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor() ?? new List(); + var mods = ctorDecl.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) ctorSyntax = ctorSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) ctorSyntax = ctorSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) ctorSyntax = ctorSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); var parameters = ctorDecl.getParameters().ToList(); diff --git a/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs b/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs index bc7fe865..59baaea2 100644 --- a/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs @@ -84,13 +84,13 @@ public class EnumDeclarationVisitor : BodyDeclarationVisitor classSyntax = classSyntax.AddMembers(enumMembers.ToArray()); } - var mods = javai.getModifiers().ToList() ?? new List(); + var mods = javai.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); return classSyntax.WithJavaComments(context, javai); diff --git a/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs b/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs index c2cc365a..28876014 100644 --- a/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs @@ -60,19 +60,19 @@ public override MemberDeclarationSyntax VisitForClass( SyntaxFactory.ParseTypeName(typeName), SyntaxFactory.SeparatedList(variables, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), variables.Count - 1)))); - var mods = fieldDecl.getModifiers().ToList(); + var mods = fieldDecl.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.STATIC)) + if (mods.Contains(Modifier.Keyword.STATIC)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL)) + if (mods.Contains(Modifier.Keyword.FINAL)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.VOLATILE)) + if (mods.Contains(Modifier.Keyword.VOLATILE)) fieldSyntax = fieldSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.VolatileKeyword)); return fieldSyntax; diff --git a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs index 203fd4f5..c2e42a5e 100644 --- a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using com.github.javaparser.ast; using com.github.javaparser.ast.body; @@ -21,6 +22,64 @@ public override MemberDeclarationSyntax VisitForClass( MethodDeclaration methodDecl, IReadOnlyList extends, IReadOnlyList implements) + { + return VisitInternal(context, false, classSyntax.Identifier.Text, classSyntax.Modifiers, methodDecl, extends); + } + + public override MemberDeclarationSyntax VisitForInterface(ConversionContext context, + InterfaceDeclarationSyntax interfaceSyntax, + MethodDeclaration methodDecl) + { + // If there is a body, mostly treat it like a class method + if (methodDecl.getBody().isPresent()) + { + return VisitInternal(context, true, interfaceSyntax.Identifier.Text, interfaceSyntax.Modifiers, methodDecl, + ArraySegment.Empty); + } + + var returnType = methodDecl.getType(); + var returnTypeName = TypeHelper.ConvertType(returnType.toString()); + + var methodName = TypeHelper.Capitalize(methodDecl.getNameAsString()); + methodName = TypeHelper.ReplaceCommonMethodNames(methodName); + + string typeParameters = methodDecl.getTypeParameters().ToString() ?? ""; + if (typeParameters.Length > 2) + { + // Looks like "[T, U]". Convert to "" + methodName += typeParameters.Replace('[', '<').Replace(']', '>'); + } + + var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName); + + var parameters = methodDecl.getParameters().ToList(); + + if (parameters is {Count: > 0}) + { + var paramSyntax = parameters.Select(i => + SyntaxFactory.Parameter( + attributeLists: new SyntaxList(), + modifiers: SyntaxFactory.TokenList(), + type: SyntaxFactory.ParseTypeName(TypeHelper.ConvertTypeOf(i)), + identifier: SyntaxFactory.ParseToken(TypeHelper.EscapeIdentifier(i.getNameAsString())), + @default: null)) + .ToArray(); + + methodSyntax = methodSyntax.AddParameterListParameters(paramSyntax.ToArray()); + } + + methodSyntax = methodSyntax.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); + + return methodSyntax; + } + + private static MemberDeclarationSyntax VisitInternal( + ConversionContext context, + bool isInterface, + string typeIdentifier, + SyntaxTokenList typeModifiers, + MethodDeclaration methodDecl, + IReadOnlyList extends) { var returnType = methodDecl.getType(); var returnTypeName = TypeHelper.ConvertType(returnType.toString()); @@ -37,17 +96,17 @@ public override MemberDeclarationSyntax VisitForClass( var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName); - var mods = methodDecl.getModifiers().ToList() ?? new List(); + var mods = methodDecl.getModifiers().ToModifierKeywordSet(); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PUBLIC)) + if (mods.Contains(Modifier.Keyword.PUBLIC)) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PROTECTED)) + if (mods.Contains(Modifier.Keyword.PROTECTED)) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE)) + if (mods.Contains(Modifier.Keyword.PRIVATE)) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.STATIC)) + if (mods.Contains(Modifier.Keyword.STATIC)) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.ABSTRACT)) + if (mods.Contains(Modifier.Keyword.ABSTRACT)) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.AbstractKeyword)); var annotations = methodDecl.getAnnotations().ToList(); @@ -70,12 +129,13 @@ public override MemberDeclarationSyntax VisitForClass( } } - if (!mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL) - && !mods.Any(i => i.getKeyword() == Modifier.Keyword.ABSTRACT) - && !mods.Any(i => i.getKeyword() == Modifier.Keyword.STATIC) - && !mods.Any(i => i.getKeyword() == Modifier.Keyword.PRIVATE) + if (!mods.Contains(Modifier.Keyword.FINAL) + && !mods.Contains(Modifier.Keyword.ABSTRACT) + && !mods.Contains(Modifier.Keyword.STATIC) + && !mods.Contains(Modifier.Keyword.PRIVATE) && !isOverride - && !classSyntax.Modifiers.Any(i => i.IsKind(SyntaxKind.SealedKeyword))) + && !isInterface + && !typeModifiers.Any(i => i.IsKind(SyntaxKind.SealedKeyword))) methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.VirtualKeyword)); var parameters = methodDecl.getParameters().ToList(); @@ -124,18 +184,14 @@ public override MemberDeclarationSyntax VisitForClass( var statementSyntax = StatementVisitor.VisitStatements(context, statements); - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.SYNCHRONIZED)) + if (mods.Contains(Modifier.Keyword.SYNCHRONIZED)) { var lockBlock = SyntaxFactory.Block(statementSyntax); LockStatementSyntax? lockSyntax = null; - if (mods.Any(i => i.getKeyword() == Modifier.Keyword.STATIC)) + if (mods.Contains(Modifier.Keyword.STATIC)) { - string? identifier = classSyntax.Identifier.Value?.ToString(); - if (identifier is not null) - { - lockSyntax = SyntaxFactory.LockStatement(SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseTypeName(identifier)), lockBlock); - } + lockSyntax = SyntaxFactory.LockStatement(SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseTypeName(typeIdentifier)), lockBlock); } else { @@ -154,42 +210,4 @@ public override MemberDeclarationSyntax VisitForClass( return methodSyntax; } - - public override MemberDeclarationSyntax VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, MethodDeclaration methodDecl) - { - var returnType = methodDecl.getType(); - var returnTypeName = TypeHelper.ConvertType(returnType.toString()); - - var methodName = TypeHelper.Capitalize(methodDecl.getNameAsString()); - methodName = TypeHelper.ReplaceCommonMethodNames(methodName); - - string typeParameters = methodDecl.getTypeParameters().ToString() ?? ""; - if (typeParameters.Length > 2) - { - // Looks like "[T, U]". Convert to "" - methodName += typeParameters.Replace('[', '<').Replace(']', '>'); - } - - var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName); - - var parameters = methodDecl.getParameters().ToList(); - - if (parameters is {Count: > 0}) - { - var paramSyntax = parameters.Select(i => - SyntaxFactory.Parameter( - attributeLists: new SyntaxList(), - modifiers: SyntaxFactory.TokenList(), - type: SyntaxFactory.ParseTypeName(TypeHelper.ConvertTypeOf(i)), - identifier: SyntaxFactory.ParseToken(TypeHelper.EscapeIdentifier(i.getNameAsString())), - @default: null)) - .ToArray(); - - methodSyntax = methodSyntax.AddParameterListParameters(paramSyntax.ToArray()); - } - - methodSyntax = methodSyntax.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); - - return methodSyntax; - } } diff --git a/JavaToCSharp/Extensions.cs b/JavaToCSharp/Extensions.cs index aa549397..342ded21 100644 --- a/JavaToCSharp/Extensions.cs +++ b/JavaToCSharp/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using java.util; using Microsoft.CodeAnalysis; using JavaAst = com.github.javaparser.ast; @@ -60,4 +61,8 @@ public static T FromRequiredOptional(this Optional optional) ? optional.get() as T ?? throw new InvalidOperationException($"Optional did not convert to {typeof(T)}") : throw new InvalidOperationException("Required optional did not have a value"); + + public static ISet ToModifierKeywordSet(this JavaAst.NodeList nodeList) + => nodeList.ToList()?.Select(i => i.getKeyword()).ToHashSet() + ?? new HashSet(); }