Skip to content

Commit

Permalink
Transformer: Add try-with-resources Java 7/9 language support, #53
Browse files Browse the repository at this point in the history
It turns out we somehow missed try-with-resources Java 7 support, and no
one has complained yet. This adds support for the Java 7 case, as well
as the Java 9 more concise syntax if the `try` is initialized with a
name expression resource instead of a variable declaration.
  • Loading branch information
paulirwin committed Aug 10, 2023
1 parent 1b50193 commit f9e9b1f
Show file tree
Hide file tree
Showing 14 changed files with 252 additions and 56 deletions.
20 changes: 15 additions & 5 deletions JavaToCSharp.Tests/IntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ public class IntegrationTests
public void GeneralSuccessfulConversionTest(string filePath)
{
var options = new JavaConversionOptions();

options.WarningEncountered += (_, eventArgs)
=> Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message);
var parsed = JavaToCSharpConverter.ConvertText(System.IO.File.ReadAllText(filePath), options);
=> throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}");

var parsed = JavaToCSharpConverter.ConvertText(File.ReadAllText(filePath), options);
Assert.NotNull(parsed);
}

[Theory]
[InlineData("Resources/HelloWorld.java")]
[InlineData("Resources/Java7TryWithResources.java")]
[InlineData("Resources/Java9TryWithResources.java")]
public void FullIntegrationTests(string filePath)
{
var options = new JavaConversionOptions
Expand All @@ -36,15 +40,17 @@ public void FullIntegrationTests(string filePath)
};

options.WarningEncountered += (_, eventArgs)
=> Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message);
=> throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}");

var javaText = File.ReadAllText(filePath);

var parsed = JavaToCSharpConverter.ConvertText(System.IO.File.ReadAllText(filePath), options);
var parsed = JavaToCSharpConverter.ConvertText(javaText, options);
Assert.NotNull(parsed);

var fileName = Path.GetFileNameWithoutExtension(filePath);
var assembly = CompileAssembly(fileName, parsed);

var expectation = ParseExpectation(parsed);
var expectation = ParseExpectation(javaText);

// NOTE: examples must have a class name of Program in the example package
var programType = assembly.GetType("Example.Program");
Expand Down Expand Up @@ -88,6 +94,10 @@ public void FullIntegrationTests(string filePath)
{
throw new InvalidOperationException("Expected an error, but app ran successfully");
}
else
{
throw new InvalidOperationException("Test must have either an output or error expectation");
}
}

private static Assembly CompileAssembly(string assemblyName, string cSharpLanguageText)
Expand Down
27 changes: 27 additions & 0 deletions JavaToCSharp.Tests/Resources/Java7TryWithResources.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/// Expect:
/// - output: "Creating TryableResource 1\nCreating TryableResource 2\nHello world!\nClosing TryableResource 2\nClosing TryableResource 1\nFinally\n"
package example;

public class Program {
public static void main(String[] args) {
try (TryableResource resource1 = new TryableResource();
TryableResource resource2 = new TryableResource()) {
System.out.println("Hello world!");
} catch (Exception ignored) {
} finally {
System.out.println("Finally");
}
}

public static class TryableResource implements AutoCloseable {
private static int counter = 1;
private final int id = counter++;
public TryableResource() {
System.out.println("Creating TryableResource " + id);
}
@Override
public void close() throws Exception {
System.out.println("Closing TryableResource " + id);
}
}
}
30 changes: 30 additions & 0 deletions JavaToCSharp.Tests/Resources/Java9TryWithResources.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// Expect:
/// - output: "Creating TryableResource 1\nCreating TryableResource 2\nHello world!\nClosing TryableResource 2\nClosing TryableResource 1\nFinally\n"
package example;

public class Program {
public static void main(String[] args) {
TryableResource resource1 = new TryableResource();
TryableResource resource2 = new TryableResource();

try (resource1; resource2) {
System.out.println("Hello world!");
} catch (Exception ignored) {
} finally {
System.out.println("Finally");
}
}

public static class TryableResource implements AutoCloseable {
private static int counter = 1;
private final int id = counter++;
public TryableResource() {
System.out.println("Creating TryableResource " + id);
}
@Override
public void close() throws Exception {
System.out.println("Closing TryableResource " + id);
}
}
}

10 changes: 8 additions & 2 deletions JavaToCSharp/Declarations/AnnotationDeclarationVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
using System;
using System.Collections.Generic;
using com.github.javaparser;
using com.github.javaparser.ast.body;
using com.github.javaparser.ast.type;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace JavaToCSharp.Declarations;

public class AnnotationDeclarationVisitor : BodyDeclarationVisitor<AnnotationDeclaration>
{
public override MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax,
AnnotationDeclaration declaration)
public override MemberDeclarationSyntax? VisitForClass(
ConversionContext context,
ClassDeclarationSyntax classSyntax,
AnnotationDeclaration declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
context.Options.Warning("Declaring an annotation inside a class NotImplemented.",
declaration.getBegin().FromRequiredOptional<Position>().line);
Expand Down
30 changes: 24 additions & 6 deletions JavaToCSharp/Declarations/BodyDeclarationVisitor.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
using System;
using System.Collections.Generic;
using com.github.javaparser.ast.body;
using com.github.javaparser.ast.type;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Range = com.github.javaparser.Range;
using Type = System.Type;

namespace JavaToCSharp.Declarations;

public abstract class BodyDeclarationVisitor<T> : BodyDeclarationVisitor
where T : BodyDeclaration
{
public abstract MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax, T declaration);
public abstract MemberDeclarationSyntax? VisitForClass(ConversionContext context,
ClassDeclarationSyntax classSyntax,
T declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements);

public abstract MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, T declaration);

protected sealed override MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax, BodyDeclaration declaration)
protected sealed override MemberDeclarationSyntax? VisitForClass(ConversionContext context,
ClassDeclarationSyntax classSyntax,
BodyDeclaration declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
return VisitForClass(context, classSyntax, (T)declaration);
return VisitForClass(context, classSyntax, (T)declaration, extends, implements);
}

protected sealed override MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, BodyDeclaration declaration)
Expand All @@ -42,19 +52,27 @@ static BodyDeclarationVisitor()
};
}

protected abstract MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax, BodyDeclaration declaration);
protected abstract MemberDeclarationSyntax? VisitForClass(ConversionContext context,
ClassDeclarationSyntax classSyntax,
BodyDeclaration declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements);

protected abstract MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, BodyDeclaration declaration);

public static MemberDeclarationSyntax? VisitBodyDeclarationForClass(ConversionContext context, ClassDeclarationSyntax classSyntax, BodyDeclaration declaration)
public static MemberDeclarationSyntax? VisitBodyDeclarationForClass(ConversionContext context,
ClassDeclarationSyntax classSyntax,
BodyDeclaration declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
if (!_visitors.TryGetValue(declaration.GetType(), out var visitor))
{
var message = $"No visitor has been implemented for body declaration `{declaration}`, {declaration.getRange().FromRequiredOptional<Range>().begin} type `{declaration.GetType()}`.";
throw new InvalidOperationException(message);
}

return visitor.VisitForClass(context, classSyntax, declaration)
return visitor.VisitForClass(context, classSyntax, declaration, extends, implements)
.WithJavaComments(declaration);
}

Expand Down
53 changes: 25 additions & 28 deletions JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ namespace JavaToCSharp.Declarations;

public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOrInterfaceDeclaration>
{
public override MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax,
ClassOrInterfaceDeclaration declaration)
public override MemberDeclarationSyntax? VisitForClass(
ConversionContext context,
ClassDeclarationSyntax classSyntax,
ClassOrInterfaceDeclaration declaration,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
return VisitClassDeclaration(context, declaration);
}


public override MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax,
ClassOrInterfaceDeclaration declaration)
{
Expand Down Expand Up @@ -58,23 +61,23 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOr
if (mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL))
classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));

var extends = javai.getExtendedTypes().ToList<ClassOrInterfaceType>();
if (extends != null)
var extends = javai.getExtendedTypes().ToList<ClassOrInterfaceType>();
if (extends != null)
{
foreach (var extend in extends)
{
foreach (var extend in extends)
{
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(extend)));
}
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(extend)));
}
}

var implements = javai.getImplementedTypes().ToList<ClassOrInterfaceType>();
if (implements != null)
var implements = javai.getImplementedTypes().ToList<ClassOrInterfaceType>();
if (implements != null)
{
foreach (var implement in implements)
{
foreach (var implement in implements)
{
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(implement)));
}
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(implement)));
}
}

var members = javai.getMembers()?.ToList<BodyDeclaration>();

Expand Down Expand Up @@ -123,24 +126,18 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOr
if (mods.Any(i => i.getKeyword() == Modifier.Keyword.FINAL))
classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword));

var extends = javac.getExtendedTypes().ToList<ClassOrInterfaceType>();
var extends = javac.getExtendedTypes().ToList<ClassOrInterfaceType>() ?? new List<ClassOrInterfaceType>();

if (extends != null)
foreach (var extend in extends)
{
foreach (var extend in extends)
{
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(extend)));
}
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(extend)));
}

var implements = javac.getImplementedTypes().ToList<ClassOrInterfaceType>();
var implements = javac.getImplementedTypes().ToList<ClassOrInterfaceType>() ?? new List<ClassOrInterfaceType>();

if (implements != null)
foreach (var implement in implements)
{
foreach (var implement in implements)
{
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(implement)));
}
classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(implement)));
}

var members = javac.getMembers()?.ToList<BodyDeclaration>();
Expand Down Expand Up @@ -169,7 +166,7 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor<ClassOr
}
else
{
var syntax = VisitBodyDeclarationForClass(context, classSyntax, member);
var syntax = VisitBodyDeclarationForClass(context, classSyntax, member, extends, implements);
var withJavaComments = syntax?.WithJavaComments(member);
if (withJavaComments != null)
{
Expand Down
8 changes: 7 additions & 1 deletion JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using com.github.javaparser.ast;
using com.github.javaparser.ast.body;
using com.github.javaparser.ast.stmt;
using com.github.javaparser.ast.type;
using JavaToCSharp.Statements;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -13,7 +14,12 @@ namespace JavaToCSharp.Declarations;

public class ConstructorDeclarationVisitor : BodyDeclarationVisitor<ConstructorDeclaration>
{
public override MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax classSyntax, ConstructorDeclaration ctorDecl)
public override MemberDeclarationSyntax? VisitForClass(
ConversionContext context,
ClassDeclarationSyntax classSyntax,
ConstructorDeclaration ctorDecl,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
string? identifier = classSyntax.Identifier.Value?.ToString();
if (identifier is null)
Expand Down
11 changes: 8 additions & 3 deletions JavaToCSharp/Declarations/EnumDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using com.github.javaparser;
using com.github.javaparser.ast;
using com.github.javaparser.ast.body;

using com.github.javaparser.ast.type;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand All @@ -12,14 +12,19 @@ namespace JavaToCSharp.Declarations;

public class EnumDeclarationVisitor : BodyDeclarationVisitor<EnumDeclaration>
{
public override MemberDeclarationSyntax? VisitForClass(ConversionContext context, ClassDeclarationSyntax? classSyntax, EnumDeclaration enumDecl)
public override MemberDeclarationSyntax? VisitForClass(
ConversionContext context,
ClassDeclarationSyntax? classSyntax,
EnumDeclaration enumDecl,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
return VisitEnumDeclaration(context, enumDecl);
}

public override MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, EnumDeclaration declaration)
{
return VisitForClass(context, null, declaration);
return VisitEnumDeclaration(context, declaration);
}

public static EnumDeclarationSyntax? VisitEnumDeclaration(ConversionContext context, EnumDeclaration javai)
Expand Down
9 changes: 7 additions & 2 deletions JavaToCSharp/Declarations/FieldDeclarationVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using com.github.javaparser.ast;
using com.github.javaparser.ast.body;
using com.github.javaparser.ast.expr;
using com.github.javaparser.ast.type;
using com.sun.org.apache.bcel.@internal.classfile;
using JavaToCSharp.Expressions;
using Microsoft.CodeAnalysis.CSharp;
Expand All @@ -13,7 +14,11 @@ namespace JavaToCSharp.Declarations;
public class FieldDeclarationVisitor : BodyDeclarationVisitor<FieldDeclaration>
{
public override MemberDeclarationSyntax VisitForClass(
ConversionContext context, ClassDeclarationSyntax? classSyntax, FieldDeclaration fieldDecl)
ConversionContext context,
ClassDeclarationSyntax? classSyntax,
FieldDeclaration fieldDecl,
IReadOnlyList<ClassOrInterfaceType> extends,
IReadOnlyList<ClassOrInterfaceType> implements)
{
var variables = new List<VariableDeclaratorSyntax>();

Expand Down Expand Up @@ -77,6 +82,6 @@ public override MemberDeclarationSyntax VisitForInterface(ConversionContext cont
InterfaceDeclarationSyntax interfaceSyntax, FieldDeclaration declaration)
{
// TODO: throw new NotImplementedException("Need to implement diversion of static fields from interface declaration to static class");
return VisitForClass(context, null, declaration);
return VisitForClass(context, null, declaration, new List<ClassOrInterfaceType>(), new List<ClassOrInterfaceType>());
}
}
Loading

0 comments on commit f9e9b1f

Please sign in to comment.