Skip to content

Commit

Permalink
Transformer: Support converting default/private interface methods, #53
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
paulirwin committed Aug 12, 2023
1 parent 55b2768 commit 3e0d3a7
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 84 deletions.
5 changes: 4 additions & 1 deletion JavaToCSharp.Tests/ConvertInterfaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ namespace MyApp
{
public interface IResolvedType
{
bool IsArray();
bool IsArray()
{
return false;
}
}

public class InferenceVariableType : IResolvedType
Expand Down
1 change: 1 addition & 0 deletions JavaToCSharp.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions JavaToCSharp.Tests/Resources/Java9PrivateInterfaceMethods.java
Original file line number Diff line number Diff line change
@@ -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!");
}
}
22 changes: 11 additions & 11 deletions JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,15 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOr
classSyntax = classSyntax.AddTypeParameterListParameters(typeParams.Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray());
}

var mods = javai.getModifiers().ToList<Modifier>() ?? new List<Modifier>();
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<ClassOrInterfaceType>();
Expand Down Expand Up @@ -113,17 +113,17 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOr
classSyntax = classSyntax.AddTypeParameterListParameters(typeParams.Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray());
}

var mods = javac.getModifiers().ToList<Modifier>() ?? new List<Modifier>();
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<ClassOrInterfaceType>() ?? new List<ClassOrInterfaceType>();
Expand Down
8 changes: 4 additions & 4 deletions JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor<ConstructorD
var ctorSyntax = SyntaxFactory.ConstructorDeclaration(identifier)
.WithLeadingTrivia(SyntaxFactory.CarriageReturnLineFeed);

var mods = ctorDecl.getModifiers().ToList<Modifier>() ?? new List<Modifier>();
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<Parameter>();
Expand Down
8 changes: 4 additions & 4 deletions JavaToCSharp/Declarations/EnumDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@ public class EnumDeclarationVisitor : BodyDeclarationVisitor<EnumDeclaration>
classSyntax = classSyntax.AddMembers(enumMembers.ToArray());
}

var mods = javai.getModifiers().ToList<Modifier>() ?? new List<Modifier>();
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);
Expand Down
14 changes: 7 additions & 7 deletions JavaToCSharp/Declarations/FieldDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Modifier>();
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;
Expand Down
132 changes: 75 additions & 57 deletions JavaToCSharp/Declarations/MethodDeclarationVisitor.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -21,6 +22,64 @@ public override MemberDeclarationSyntax VisitForClass(
MethodDeclaration methodDecl,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> 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<ClassOrInterfaceType>.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 "<T, U>"
methodName += typeParameters.Replace('[', '<').Replace(']', '>');
}

var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName);

var parameters = methodDecl.getParameters().ToList<Parameter>();

if (parameters is {Count: > 0})
{
var paramSyntax = parameters.Select(i =>
SyntaxFactory.Parameter(
attributeLists: new SyntaxList<AttributeListSyntax>(),
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<ClassOrInterfaceType> extends)
{
var returnType = methodDecl.getType();
var returnTypeName = TypeHelper.ConvertType(returnType.toString());
Expand All @@ -37,17 +96,17 @@ public override MemberDeclarationSyntax VisitForClass(

var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName);

var mods = methodDecl.getModifiers().ToList<Modifier>() ?? new List<Modifier>();
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<AnnotationExpr>();
Expand All @@ -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<Parameter>();
Expand Down Expand Up @@ -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
{
Expand All @@ -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 "<T, U>"
methodName += typeParameters.Replace('[', '<').Replace(']', '>');
}

var methodSyntax = SyntaxFactory.MethodDeclaration(SyntaxFactory.ParseTypeName(returnTypeName), methodName);

var parameters = methodDecl.getParameters().ToList<Parameter>();

if (parameters is {Count: > 0})
{
var paramSyntax = parameters.Select(i =>
SyntaxFactory.Parameter(
attributeLists: new SyntaxList<AttributeListSyntax>(),
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;
}
}
5 changes: 5 additions & 0 deletions JavaToCSharp/Extensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -60,4 +61,8 @@ public static T FromRequiredOptional<T>(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<JavaAst.Modifier.Keyword> ToModifierKeywordSet(this JavaAst.NodeList nodeList)
=> nodeList.ToList<JavaAst.Modifier>()?.Select(i => i.getKeyword()).ToHashSet()
?? new HashSet<JavaAst.Modifier.Keyword>();
}

0 comments on commit 3e0d3a7

Please sign in to comment.