From f5fea463a2760db87e0ba0c04ec637cf0fdc8e60 Mon Sep 17 00:00:00 2001 From: Chad Currie Date: Sun, 17 Dec 2023 15:45:40 +1300 Subject: [PATCH 01/25] Obsolete / Deprecated class --- .../ClassOrInterfaceDeclarationVisitor.cs | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index 580eb454..39c29c1e 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -2,6 +2,7 @@ using System.Linq; using com.github.javaparser.ast; using com.github.javaparser.ast.body; +using com.github.javaparser.ast.expr; using com.github.javaparser.ast.type; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -181,6 +182,53 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); + + if (annotations is { Count: > 0 }) + { + foreach (var annotation in annotations) + { + string annotationName = annotation.getNameAsString(); + string annotationText = "Obsolete"; // TODO parse from java comment + + if (annotationName == "Deprecated") + { + + classSyntax = classSyntax.AddAttributeLists( + new AttributeListSyntax[]{ + SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute( + SyntaxFactory.IdentifierName("Obsolete") + ) + .WithArgumentList( + SyntaxFactory.AttributeArgumentList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.AttributeArgument( + SyntaxFactory.LiteralExpression( + SyntaxKind.StringLiteralExpression, + SyntaxFactory.Literal(annotationText) + ) + ) + ) + ) + ) + ) + ) + } + ); + isObsolete = true; + } + + if (isObsolete) + { + break; + } + } + } + return classSyntax.WithJavaComments(context, javac); } } \ No newline at end of file From b608f95e97840699b64e671f9176dcdd2793e379 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Tue, 19 Dec 2023 14:31:12 -0700 Subject: [PATCH 02/25] Code formatting cleanup, add Deprecated annotation unit test, #90 --- JavaToCSharp.Tests/IntegrationTests.cs | 49 ++++++------ .../Resources/DeprecatedAnnotation.java | 10 +++ .../ClassOrInterfaceDeclarationVisitor.cs | 77 +++++++++---------- 3 files changed, 72 insertions(+), 64 deletions(-) create mode 100644 JavaToCSharp.Tests/Resources/DeprecatedAnnotation.java diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index 28f67d4e..61d9121c 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -25,10 +25,10 @@ public void GeneralSuccessfulConversionTest(string filePath) { IncludeComments = false, }; - + options.WarningEncountered += (_, eventArgs) => throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}"); - + var parsed = JavaToCSharpConverter.ConvertText(File.ReadAllText(filePath), options); Assert.NotNull(parsed); } @@ -41,10 +41,11 @@ public void GeneralUnsuccessfulConversionTest(string filePath) { IncludeComments = false, }; - + options.WarningEncountered += (_, eventArgs) - => throw new InvalidOperationException($"Encountered a warning in conversion when we expected a failure: {eventArgs.Message}"); - + => throw new InvalidOperationException( + $"Encountered a warning in conversion when we expected a failure: {eventArgs.Message}"); + Assert.ThrowsAny(() => JavaToCSharpConverter.ConvertText(File.ReadAllText(filePath), options)); } @@ -57,6 +58,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath) [InlineData("Resources/Java10TypeInference.java")] [InlineData("Resources/NewArrayLiteralBug.java")] [InlineData("Resources/OctalLiteralBug.java")] + [InlineData("Resources/DeprecatedAnnotation.java")] public void FullIntegrationTests(string filePath) { var options = new JavaConversionOptions @@ -64,20 +66,20 @@ public void FullIntegrationTests(string filePath) ConvertSystemOutToConsole = true, IncludeComments = false, }; - + options.WarningEncountered += (_, eventArgs) => throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}"); var javaText = File.ReadAllText(filePath); - + var parsed = JavaToCSharpConverter.ConvertText(javaText, options); Assert.NotNull(parsed); var fileName = Path.GetFileNameWithoutExtension(filePath); var assembly = CompileAssembly(fileName, parsed); - + var expectation = ParseExpectation(javaText); - + // NOTE: examples must have a class name of Program in the example package var programType = assembly.GetType("Example.Program"); @@ -85,17 +87,17 @@ public void FullIntegrationTests(string filePath) { throw new InvalidOperationException("Cannot find expected Program type in assembly"); } - + var mainMethod = programType.GetMethod("Main", BindingFlags.Static | BindingFlags.Public); if (mainMethod is null) { throw new InvalidOperationException("Cannot find expected Main method in assembly"); } - + using var sw = new StringWriter(); Console.SetOut(sw); - + try { mainMethod.Invoke(null, new object[] { Array.Empty() }); @@ -109,7 +111,7 @@ public void FullIntegrationTests(string filePath) return; } - + var output = sw.ToString().ReplaceLineEndings("\n"); if (expectation.Output != null) @@ -129,14 +131,14 @@ public void FullIntegrationTests(string filePath) private static Assembly CompileAssembly(string assemblyName, string cSharpLanguageText) { var syntaxTree = CSharpSyntaxTree.ParseText(cSharpLanguageText); - + var options = new CSharpCompilationOptions(OutputKind.ConsoleApplication) .WithOverflowChecks(true) .WithOptimizationLevel(OptimizationLevel.Debug); - - var compilation = CSharpCompilation.Create(assemblyName, - new List { syntaxTree }, - GetMetadataReferencesForBcl(), + + var compilation = CSharpCompilation.Create(assemblyName, + new List { syntaxTree }, + GetMetadataReferencesForBcl(), options); var outputDir = Path.Join(Environment.CurrentDirectory, "bin"); @@ -150,7 +152,8 @@ private static Assembly CompileAssembly(string assemblyName, string cSharpLangua if (!emitResult.Success) { - throw new InvalidOperationException($"Failed to emit Roslyn assembly: {string.Join(", ", emitResult.Diagnostics)}"); + throw new InvalidOperationException( + $"Failed to emit Roslyn assembly: {string.Join(", ", emitResult.Diagnostics)}"); } ms.Position = 0; @@ -188,7 +191,7 @@ private static IEnumerable GetMetadataReferencesForBcl() yield return MetadataReference.CreateFromFile(Path.Combine(assemblyPath, "System.Runtime.dll")); } } - + private static Expectation ParseExpectation(string contents) { using var sr = new StringReader(contents); @@ -216,7 +219,7 @@ private static Expectation ParseExpectation(string contents) var literal = FindLiteralExpressionSyntax(root); - if (literal != null) + if (literal != null) { expectation.Output = literal.Token.ValueText; } @@ -235,7 +238,7 @@ private static Expectation ParseExpectation(string contents) return expectation; } - + private static LiteralExpressionSyntax? FindLiteralExpressionSyntax(SyntaxNode node) { if (node is LiteralExpressionSyntax literal) @@ -254,4 +257,4 @@ private class Expectation public string? Error { get; set; } } -} \ No newline at end of file +} diff --git a/JavaToCSharp.Tests/Resources/DeprecatedAnnotation.java b/JavaToCSharp.Tests/Resources/DeprecatedAnnotation.java new file mode 100644 index 00000000..45f1874c --- /dev/null +++ b/JavaToCSharp.Tests/Resources/DeprecatedAnnotation.java @@ -0,0 +1,10 @@ +/// Expect: +/// - output: "Hello world!\n" +package example; + +@Deprecated +public class Program { + public static void main(String[] args) { + System.out.println("Hello world!"); + } +} diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index 39c29c1e..d68518fc 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -12,7 +12,7 @@ namespace JavaToCSharp.Declarations; public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor { public override MemberDeclarationSyntax? VisitForClass( - ConversionContext context, + ConversionContext context, ClassDeclarationSyntax classSyntax, ClassOrInterfaceDeclaration declaration, IReadOnlyList extends, @@ -21,16 +21,20 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); - if (typeParams is {Count: > 0}) + if (typeParams is { Count: > 0 }) { - classSyntax = classSyntax.AddTypeParameterListParameters(typeParams.Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); + classSyntax = + classSyntax.AddTypeParameterListParameters(typeParams + .Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } var mods = javai.getModifiers().ToModifierKeywordSet(); @@ -67,7 +73,8 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); - if (typeParams is {Count: > 0}) + if (typeParams is { Count: > 0 }) { - classSyntax = classSyntax.AddTypeParameterListParameters(typeParams.Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); + classSyntax = + classSyntax.AddTypeParameterListParameters(typeParams + .Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } var mods = javac.getModifiers().ToModifierKeywordSet(); @@ -131,19 +142,22 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor() ?? new List(); 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(); if (members is not null) + { foreach (var member in members) { if (member is ClassOrInterfaceDeclaration childType) @@ -181,9 +195,8 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); if (annotations is { Count: > 0 }) @@ -191,39 +204,21 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor( - SyntaxFactory.Attribute( - SyntaxFactory.IdentifierName("Obsolete") - ) + classSyntax = classSyntax.AddAttributeLists(SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.IdentifierName("Obsolete")) .WithArgumentList( SyntaxFactory.AttributeArgumentList( - SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.SingletonSeparatedList( SyntaxFactory.AttributeArgument( SyntaxFactory.LiteralExpression( SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(annotationText) - ) - ) - ) - ) - ) - ) - ) - } - ); - isObsolete = true; - } + SyntaxFactory.Literal(annotationText))))))))); - if (isObsolete) - { break; } } @@ -231,4 +226,4 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor Date: Fri, 9 Feb 2024 16:48:28 -0700 Subject: [PATCH 03/25] Fix issue with comments appearing out of order at start of file, #96 --- JavaToCSharp.Tests/CommentTests.cs | 46 +++++++ JavaToCSharp.Tests/ConvertInterfaceTests.cs | 138 ++++++++++---------- JavaToCSharp/CommentsHelper.cs | 59 +++++++-- JavaToCSharp/Extensions.cs | 28 ++-- JavaToCSharp/JavaToCSharpConverter.cs | 21 ++- 5 files changed, 187 insertions(+), 105 deletions(-) create mode 100644 JavaToCSharp.Tests/CommentTests.cs diff --git a/JavaToCSharp.Tests/CommentTests.cs b/JavaToCSharp.Tests/CommentTests.cs new file mode 100644 index 00000000..cde172f0 --- /dev/null +++ b/JavaToCSharp.Tests/CommentTests.cs @@ -0,0 +1,46 @@ +namespace JavaToCSharp.Tests; + +public class CommentTests +{ + [Fact] + public void CommentsBeforePackage_ShouldRemainAtTopOfFile() + { + const string javaCode = """ + // 1 comment + // 2 comment + // Red comment + // Blue comment + package com.example.code; + + import java.something.Bar; + + public class Foo { + } + """; + var options = new JavaConversionOptions + { + StartInterfaceNamesWithI = true, + }; + + options.Usings.Clear(); + + var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + + const string expected = """ + // 1 comment + // 2 comment + // Red comment + // Blue comment + using Java.Something; + + namespace Com.Example.Code + { + public class Foo + { + } + } + """; + + Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + } +} diff --git a/JavaToCSharp.Tests/ConvertInterfaceTests.cs b/JavaToCSharp.Tests/ConvertInterfaceTests.cs index 8c5699a1..3d9b732b 100644 --- a/JavaToCSharp.Tests/ConvertInterfaceTests.cs +++ b/JavaToCSharp.Tests/ConvertInterfaceTests.cs @@ -5,61 +5,61 @@ public class ConvertInterfaceTests [Fact] public void Verify_Method_Param_Types_Use_New_Interface_Name_Types() { - var javaCode = """ - import com.github.javaparser.resolution.Context; - import com.github.javaparser.resolution.Test; - public interface ResolvedType { - default boolean isArray() { - return false; - } - } + const string javaCode = """ + import com.github.javaparser.resolution.Context; + import com.github.javaparser.resolution.Test; + public interface ResolvedType { + default boolean isArray() { + return false; + } + } - public class InferenceVariableType implements ResolvedType { - public void registerEquivalentType(ResolvedType type) { - } - } + public class InferenceVariableType implements ResolvedType { + public void registerEquivalentType(ResolvedType type) { + } + } - public interface ResolvedValueDeclaration extends ResolvedDeclaration { - ResolvedType getType(); - } + public interface ResolvedValueDeclaration extends ResolvedDeclaration { + ResolvedType getType(); + } - """; + """; var options = new JavaConversionOptions { StartInterfaceNamesWithI = true }; options.WarningEncountered += (_, eventArgs) - => Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message); + => Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message); var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; - var expectedCSharpCode = """ - using Com.Github.Javaparser.Resolution; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Text; + const string expectedCSharpCode = """ + using Com.Github.Javaparser.Resolution; + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Text; - namespace MyApp - { - public interface IResolvedType - { - bool IsArray() - { - return false; - } - } - - public class InferenceVariableType : IResolvedType - { - public virtual void RegisterEquivalentType(IResolvedType type) - { - } - } - - public interface IResolvedValueDeclaration : ResolvedDeclaration - { - IResolvedType GetType(); - } - } - """; + namespace MyApp + { + public interface IResolvedType + { + bool IsArray() + { + return false; + } + } + + public class InferenceVariableType : IResolvedType + { + public virtual void RegisterEquivalentType(IResolvedType type) + { + } + } + + public interface IResolvedValueDeclaration : ResolvedDeclaration + { + IResolvedType GetType(); + } + } + """; Assert.Equal(expectedCSharpCode.ReplaceLineEndings(), parsed.ReplaceLineEndings()); } @@ -67,32 +67,30 @@ public interface IResolvedValueDeclaration : ResolvedDeclaration [Fact] public void Verify_Interface_Extends_Are_Converted() { - var javaCode = """ - public interface CharTermAttribute extends Attribute, CharSequence, Appendable { - } - """; + const string javaCode = """ + public interface CharTermAttribute extends Attribute, CharSequence, Appendable { + } + """; var options = new JavaConversionOptions { StartInterfaceNamesWithI = true }; options.WarningEncountered += (_, eventArgs) - => Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message); + => Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message); var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; - var expectedCSharpCode = """ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Text; + const string expected = """ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Text; - namespace MyApp - { - public interface ICharTermAttribute : Attribute, CharSequence, Appendable - { - } - } - """; - - - - Assert.Equal(expectedCSharpCode.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + namespace MyApp + { + public interface ICharTermAttribute : Attribute, CharSequence, Appendable + { + } + } + """; + + Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index 1ff289ac..b1acb014 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -33,21 +29,57 @@ private enum CommentPosition ["@throws"] = "exception" }; - public static TSyntax? AddCommentsTrivias(TSyntax? syntax, JavaAst.Node? node, string? commentEnding) where TSyntax : SyntaxNode + public static CompilationUnitSyntax AddPackageComments(CompilationUnitSyntax syntax, + JavaAst.CompilationUnit compilationUnit, + JavaAst.PackageDeclaration? packageDeclaration) + { + var leadingTriviaList = new List(); + + if (compilationUnit.getComment().FromOptional() is { } compilationUnitComment) + { + var (kind, pre, post) = GetCommentInfo(compilationUnitComment); + var commentTrivia = SyntaxFactory.SyntaxTrivia(kind, pre + compilationUnitComment.getContent() + post + Environment.NewLine); + leadingTriviaList.Add(commentTrivia); + } + + if (packageDeclaration is not null) + { + var packageComments = GatherComments(packageDeclaration); + + if (packageComments.Count > 0) + { + foreach (var (comment, _) in packageComments) + { + var (kind, pre, post) = GetCommentInfo(comment); + var commentTrivia = SyntaxFactory.SyntaxTrivia(kind, pre + comment.getContent() + post + Environment.NewLine); + leadingTriviaList.Add(commentTrivia); + } + + syntax = syntax.WithLeadingTrivia(leadingTriviaList); + } + } + + return leadingTriviaList.Count > 0 ? syntax.WithLeadingTrivia(leadingTriviaList) : syntax; + } + + public static TSyntax? AddCommentsTrivias(TSyntax? syntax, JavaAst.Node? node) where TSyntax : SyntaxNode { if (syntax is null) { return null; } - + var comments = GatherComments(node); + if (comments.Count > 0) { var leadingTriviaList = new List(); var trailingTriviaList = new List(); + foreach (var (comment, pos) in comments) { - var (kind, pre, post) = GetCommentInfo(comment, commentEnding); + var (kind, pre, post) = GetCommentInfo(comment); + if (kind == SyntaxKind.XmlComment) { leadingTriviaList.AddRange(ConvertDocComment(comment, post)); @@ -75,14 +107,13 @@ private enum CommentPosition } private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( - JavaComments.Comment comment, - string? commentEnding) + JavaComments.Comment comment) { return comment switch { - JavaComments.BlockComment => (SyntaxKind.MultiLineCommentTrivia, "/*", "*/" + commentEnding), - JavaComments.JavadocComment => (SyntaxKind.XmlComment, null, commentEnding), - _ => (SyntaxKind.SingleLineCommentTrivia, "//", commentEnding), + JavaComments.BlockComment => (SyntaxKind.MultiLineCommentTrivia, "/*", "*/"), + JavaComments.JavadocComment => (SyntaxKind.XmlComment, null, null), + _ => (SyntaxKind.SingleLineCommentTrivia, "//", null), }; } @@ -451,4 +482,4 @@ private static int GetIndentation(SyntaxTriviaList leading, int commentIndex) return s.All(c => c == ' ') ? s.Length : 0; } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Extensions.cs b/JavaToCSharp/Extensions.cs index 342ded21..17227e30 100644 --- a/JavaToCSharp/Extensions.cs +++ b/JavaToCSharp/Extensions.cs @@ -1,8 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using java.util; +using java.util; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using JavaAst = com.github.javaparser.ast; namespace JavaToCSharp; @@ -41,12 +39,24 @@ public static IEnumerable OfType(this java.lang.Iterable iterable) return newList; } - + public static bool HasFlag(this java.util.EnumSet values, T flag) => values.contains(flag); - public static TSyntax? WithJavaComments(this TSyntax? syntax, ConversionContext context, JavaAst.Node? node, string? singleLineCommentEnd = null) - where TSyntax : SyntaxNode => - context.Options.IncludeComments ? CommentsHelper.AddCommentsTrivias(syntax, node, singleLineCommentEnd) : syntax; + public static TSyntax? WithJavaComments(this TSyntax? syntax, + ConversionContext context, + JavaAst.Node? node) + where TSyntax : SyntaxNode + => context.Options.IncludeComments + ? CommentsHelper.AddCommentsTrivias(syntax, node) + : syntax; + + public static CompilationUnitSyntax WithPackageFileComments(this CompilationUnitSyntax syntax, + ConversionContext context, + JavaAst.CompilationUnit compilationUnit, + JavaAst.PackageDeclaration? packageDeclaration) + => context.Options.IncludeComments + ? CommentsHelper.AddPackageComments(syntax, compilationUnit, packageDeclaration) + : syntax; public static T? FromOptional(this Optional optional) where T : class @@ -63,6 +73,6 @@ public static T FromRequiredOptional(this Optional optional) : 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() + => nodeList.ToList()?.Select(i => i.getKeyword()).ToHashSet() ?? new HashSet(); } diff --git a/JavaToCSharp/JavaToCSharpConverter.cs b/JavaToCSharp/JavaToCSharpConverter.cs index f52834d0..803a946f 100644 --- a/JavaToCSharp/JavaToCSharpConverter.cs +++ b/JavaToCSharp/JavaToCSharpConverter.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +using System.Text; using com.github.javaparser; using com.github.javaparser.ast; using com.github.javaparser.ast.body; @@ -24,7 +20,7 @@ public static class JavaToCSharpConverter var context = new ConversionContext(options); - var textBytes = Encoding.UTF8.GetBytes(javaText ?? System.String.Empty); + var textBytes = Encoding.UTF8.GetBytes(javaText ?? string.Empty); using var memoryStream = new MemoryStream(textBytes); using var wrapper = new InputStreamWrapper(memoryStream); @@ -32,7 +28,7 @@ public static class JavaToCSharpConverter options.ConversionStateChanged(ConversionState.ParsingJavaAst); var parser = new JavaParser(); - + var parsed = parser.parse(wrapper); if (!parsed.isSuccessful()) @@ -65,18 +61,17 @@ public static class JavaToCSharpConverter foreach (var packageReplacement in options.PackageReplacements) { - if (System.String.IsNullOrWhiteSpace(packageName)) + if (string.IsNullOrWhiteSpace(packageName)) { continue; } - + packageName = packageReplacement.Replace(packageName)!; } packageName = TypeHelper.Capitalize(packageName); - namespaceSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(packageName)) - .WithJavaComments(context, package); + namespaceSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(packageName)); } foreach (var type in types) @@ -124,7 +119,8 @@ public static class JavaToCSharpConverter ) .NormalizeWhitespace(); - root = root.WithJavaComments(context, result, Environment.NewLine); + root = root.WithPackageFileComments(context, result, package); + if (root is null) { return null; @@ -132,6 +128,7 @@ public static class JavaToCSharpConverter var postConversionSanitizer = new SanitizingSyntaxRewriter(); var sanitizedRoot = postConversionSanitizer.VisitCompilationUnit(root); + if (sanitizedRoot is null) { return null; From a06d4a301813253d0b4d3f24ddb7c88f8f83a996 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Fri, 9 Feb 2024 17:21:02 -0700 Subject: [PATCH 04/25] Fix issue where using directive comments weren't translated, #96 --- JavaToCSharp.Tests/CommentTests.cs | 61 ++++++++++++++++++++++++++- JavaToCSharp/CommentsHelper.cs | 26 +++++++++++- JavaToCSharp/Extensions.cs | 4 +- JavaToCSharp/JavaToCSharpConverter.cs | 11 ++--- JavaToCSharp/UsingsHelper.cs | 32 +++++++------- 5 files changed, 106 insertions(+), 28 deletions(-) diff --git a/JavaToCSharp.Tests/CommentTests.cs b/JavaToCSharp.Tests/CommentTests.cs index cde172f0..115822b1 100644 --- a/JavaToCSharp.Tests/CommentTests.cs +++ b/JavaToCSharp.Tests/CommentTests.cs @@ -1,6 +1,9 @@ +using Microsoft.CodeAnalysis.CSharp; +using Xunit.Abstractions; + namespace JavaToCSharp.Tests; -public class CommentTests +public class CommentTests(ITestOutputHelper testOutputHelper) { [Fact] public void CommentsBeforePackage_ShouldRemainAtTopOfFile() @@ -26,15 +29,71 @@ public class Foo { var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + testOutputHelper.WriteLine(parsed); + + const string expected = """ + // 1 comment + // 2 comment + // Red comment + // Blue comment + using Java.Something; + + namespace Com.Example.Code + { + public class Foo + { + } + } + """; + + Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + } + + [Fact] + public void CommentsBeforePackage_ShouldRemainAtTopOfFile_WithUsings() + { + const string javaCode = """ + // 1 comment + // 2 comment + // Red comment + // Blue comment + package com.example.code; + + // Import some package + import java.something.Bar; + + // Import some other package + import java.something.other.Baz; + + // Define some class + public class Foo { + } + """; + var options = new JavaConversionOptions + { + StartInterfaceNamesWithI = true, + }; + + options.Usings.Clear(); + + var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + + testOutputHelper.WriteLine(parsed); + + // TODO.PI: improve whitespace const string expected = """ // 1 comment // 2 comment // Red comment // Blue comment + // Import some package using Java.Something; + // Import some other package + using Java.Something.Other; namespace Com.Example.Code { + // Define some class public class Foo { } diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index b1acb014..5cfec487 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -29,10 +29,32 @@ private enum CommentPosition ["@throws"] = "exception" }; + public static UsingDirectiveSyntax AddUsingComments(UsingDirectiveSyntax syntax, JavaAst.ImportDeclaration import) + { + var comments = GatherComments(import); + + if (comments.Count == 0) + { + return syntax; + } + + var leadingTriviaList = new List(); + + foreach (var (comment, _) in comments) + { + var (kind, pre, post) = GetCommentInfo(comment); + var commentTrivia = SyntaxFactory.SyntaxTrivia(kind, pre + comment.getContent() + post); + leadingTriviaList.Add(commentTrivia); + } + + return syntax.WithUsingKeyword(SyntaxFactory.Token(SyntaxKind.UsingKeyword).WithLeadingTrivia(leadingTriviaList)); + } + public static CompilationUnitSyntax AddPackageComments(CompilationUnitSyntax syntax, JavaAst.CompilationUnit compilationUnit, JavaAst.PackageDeclaration? packageDeclaration) { + var originalLeadingTrivia = syntax.GetLeadingTrivia(); var leadingTriviaList = new List(); if (compilationUnit.getComment().FromOptional() is { } compilationUnitComment) @@ -54,11 +76,11 @@ public static CompilationUnitSyntax AddPackageComments(CompilationUnitSyntax syn var commentTrivia = SyntaxFactory.SyntaxTrivia(kind, pre + comment.getContent() + post + Environment.NewLine); leadingTriviaList.Add(commentTrivia); } - - syntax = syntax.WithLeadingTrivia(leadingTriviaList); } } + leadingTriviaList.AddRange(originalLeadingTrivia); + return leadingTriviaList.Count > 0 ? syntax.WithLeadingTrivia(leadingTriviaList) : syntax; } diff --git a/JavaToCSharp/Extensions.cs b/JavaToCSharp/Extensions.cs index 17227e30..c8bcddd4 100644 --- a/JavaToCSharp/Extensions.cs +++ b/JavaToCSharp/Extensions.cs @@ -1,4 +1,5 @@ -using java.util; +using System.Diagnostics.CodeAnalysis; +using java.util; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using JavaAst = com.github.javaparser.ast; @@ -42,6 +43,7 @@ public static IEnumerable OfType(this java.lang.Iterable iterable) public static bool HasFlag(this java.util.EnumSet values, T flag) => values.contains(flag); + [return: NotNullIfNotNull(nameof(node))] public static TSyntax? WithJavaComments(this TSyntax? syntax, ConversionContext context, JavaAst.Node? node) diff --git a/JavaToCSharp/JavaToCSharpConverter.cs b/JavaToCSharp/JavaToCSharpConverter.cs index 803a946f..7b02bd9b 100644 --- a/JavaToCSharp/JavaToCSharpConverter.cs +++ b/JavaToCSharp/JavaToCSharpConverter.cs @@ -112,20 +112,15 @@ public static class JavaToCSharpConverter rootMembers.Add(namespaceSyntax); var root = SyntaxFactory.CompilationUnit( - externs: new SyntaxList(), - usings: SyntaxFactory.List(UsingsHelper.GetUsings(imports, options)), - attributeLists: new SyntaxList(), + externs: [], + usings: SyntaxFactory.List(UsingsHelper.GetUsings(context, imports, options)), + attributeLists: [], members: SyntaxFactory.List(rootMembers) ) .NormalizeWhitespace(); root = root.WithPackageFileComments(context, result, package); - if (root is null) - { - return null; - } - var postConversionSanitizer = new SanitizingSyntaxRewriter(); var sanitizedRoot = postConversionSanitizer.VisitCompilationUnit(root); diff --git a/JavaToCSharp/UsingsHelper.cs b/JavaToCSharp/UsingsHelper.cs index 30e1db47..cbf24a0f 100644 --- a/JavaToCSharp/UsingsHelper.cs +++ b/JavaToCSharp/UsingsHelper.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; using com.github.javaparser.ast; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9,37 +6,41 @@ namespace JavaToCSharp; public static class UsingsHelper { - public static IList GetUsings(List imports, JavaConversionOptions? options) + public static IList GetUsings(ConversionContext context, List imports, JavaConversionOptions? options) { var usings = new List(); foreach (var import in imports) { - // The import directive in Java will import a specific class. + // The import directive in Java will import a specific class. string importName = import.getNameAsString(); var lastPartStartIndex = importName.LastIndexOf(".", StringComparison.Ordinal); - var importNameWithoutClassName = lastPartStartIndex == -1 ? - importName : + var importNameWithoutClassName = lastPartStartIndex == -1 ? + importName : importName[..lastPartStartIndex]; var nameSpace = TypeHelper.Capitalize(importNameWithoutClassName); var usingSyntax = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(nameSpace)); + + if (context.Options.IncludeComments) + { + usingSyntax = CommentsHelper.AddUsingComments(usingSyntax, import); + } + usings.Add(usingSyntax); } if (options?.IncludeUsings == true) { - foreach (string ns in options.Usings.Where(x => !String.IsNullOrWhiteSpace(x))) - { - var usingSyntax = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)); - usings.Add(usingSyntax); - } + usings.AddRange(options.Usings + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(ns => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)))); } return usings.Distinct(new UsingDirectiveSyntaxComparer()).ToList(); } } -public class UsingDirectiveSyntaxComparer : IEqualityComparer +public class UsingDirectiveSyntaxComparer : IEqualityComparer { public bool Equals(UsingDirectiveSyntax? x, UsingDirectiveSyntax? y) { @@ -47,8 +48,8 @@ public bool Equals(UsingDirectiveSyntax? x, UsingDirectiveSyntax? y) if (x is null) return false; if (y is null) return false; if (x.GetType() != y.GetType()) return false; - - return Equals(x.Alias?.ToString(), y.Alias?.ToString()) && + + return Equals(x.Alias?.ToString(), y.Alias?.ToString()) && Equals(x.Name?.ToString(), y.Name?.ToString()); } @@ -57,4 +58,3 @@ public int GetHashCode(UsingDirectiveSyntax obj) return HashCode.Combine(obj.Alias?.ToString() ?? "", obj.Name?.ToString() ?? ""); } } - From fd6de6815e701262279c9fb9c6fd5e59a9deb797 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Fri, 9 Feb 2024 17:23:18 -0700 Subject: [PATCH 05/25] Use .NET 8 SDK in build pipeline --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6e0d0a8..5fec5114 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,10 +11,10 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v1 - - name: Setup .NET 7 + - name: Setup .NET 8 uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.x + dotnet-version: 8.x - name: Build JavaToCSharp run: dotnet build ./JavaToCSharp/JavaToCSharp.csproj --configuration Release @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Setup .NET 7 + - name: Setup .NET 8 uses: actions/setup-dotnet@v1 with: - dotnet-version: 7.x + dotnet-version: 8.x - name: Build JavaToCSharp run: dotnet build ./JavaToCSharp/JavaToCSharp.csproj --configuration Release From 167623fe3e20fd91958984b20220ec6f6b7e26da Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Fri, 9 Feb 2024 17:25:03 -0700 Subject: [PATCH 06/25] Upgrade Test project to .NET 8 --- JavaToCSharp.Tests/JavaToCSharp.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj index 7ede5601..2fc325fd 100644 --- a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj +++ b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj @@ -1,7 +1,7 @@  - net7.0 + net8.0 false enable latest From 5183369a2d577e86df20c12a44b23d240f5fa456 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Mon, 12 Feb 2024 17:09:29 -0700 Subject: [PATCH 07/25] Fix issues with array type conversion, #98 (#101) --- JavaToCSharp.Tests/ConvertTypeTests.cs | 70 ++++++++++++++++++ JavaToCSharp.Tests/IntegrationTests.cs | 13 +++- .../Resources/BooleanArrays.java | 17 +++++ .../Resources/MultidimensionalArrays.java | 47 ++++++++++++ JavaToCSharp.sln | 3 - .../ConstructorDeclarationVisitor.cs | 15 ++-- .../Declarations/FieldDeclarationVisitor.cs | 34 +++++---- .../Declarations/MethodDeclarationVisitor.cs | 32 ++++----- .../ArrayCreationExpressionVisitor.cs | 17 +++-- .../Expressions/LambdaExpressionVisitor.cs | 21 +++--- .../Statements/ExpressionStatementVisitor.cs | 39 +++++----- JavaToCSharp/TypeHelper.cs | 72 ++++++++++++++----- 12 files changed, 288 insertions(+), 92 deletions(-) create mode 100644 JavaToCSharp.Tests/Resources/BooleanArrays.java create mode 100644 JavaToCSharp.Tests/Resources/MultidimensionalArrays.java diff --git a/JavaToCSharp.Tests/ConvertTypeTests.cs b/JavaToCSharp.Tests/ConvertTypeTests.cs index ae499545..b9cf4b79 100644 --- a/JavaToCSharp.Tests/ConvertTypeTests.cs +++ b/JavaToCSharp.Tests/ConvertTypeTests.cs @@ -1,3 +1,5 @@ +using com.github.javaparser.ast.type; + namespace JavaToCSharp.Tests; public class ConvertTypeTests @@ -43,4 +45,72 @@ public void ConvertType_WithGenericWildcard_ShouldReplaceToken() { Assert.Equal("MyType", TypeHelper.ConvertType("MyType")); } + + [Fact] + public void ConvertTypeSyntax_GivenNonArrayType_ShouldReturnCorrectSyntax() + { + var type = new PrimitiveType(PrimitiveType.Primitive.INT); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 0); + + Assert.Equal("int", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenNonArrayTypeWithRank_ShouldReturnCorrectSyntax() + { + var type = new PrimitiveType(PrimitiveType.Primitive.INT); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 1); + + Assert.Equal("int[]", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenNonArrayTypeWithMultidimensionalRank_ShouldReturnCorrectSyntax() + { + var type = new PrimitiveType(PrimitiveType.Primitive.INT); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 2); + + Assert.Equal("int[,]", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenArrayTypeWithoutRank_ShouldReturnCorrectSyntax() + { + var type = new ArrayType(new PrimitiveType(PrimitiveType.Primitive.INT)); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 0); + + Assert.Equal("int[]", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenArrayTypeWithRank_ShouldReturnCorrectSyntax() + { + var type = new ArrayType(new PrimitiveType(PrimitiveType.Primitive.INT)); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 1); + + Assert.Equal("int[]", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenArrayTypeWithMultidimensionalRank_ShouldReturnCorrectSyntax() + { + var type = new ArrayType(new ArrayType(new PrimitiveType(PrimitiveType.Primitive.INT))); + + var syntax = TypeHelper.ConvertTypeSyntax(type, 2); + + Assert.Equal("int[,]", syntax.ToString()); + } + + [Fact] + public void ConvertTypeSyntax_GivenMismatchedRank_ShouldThrowException() + { + var type = new ArrayType(new ArrayType(new PrimitiveType(PrimitiveType.Primitive.INT))); + + Assert.Throws(() => TypeHelper.ConvertTypeSyntax(type, 1)); + } } diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index 61d9121c..f7ebf2e2 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -19,15 +19,21 @@ public class IntegrationTests [InlineData("Resources/TestNumericDocValuesUpdates.java")] [InlineData("Resources/Java9DiamondOperatorInnerClass.java")] [InlineData("Resources/Java11LambdaInference.java")] - public void GeneralSuccessfulConversionTest(string filePath) + [InlineData("Resources/MultidimensionalArrays.java", true)] + public void GeneralSuccessfulConversionTest(string filePath, bool allowWarnings = false) { var options = new JavaConversionOptions { IncludeComments = false, }; - options.WarningEncountered += (_, eventArgs) - => throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}"); + options.WarningEncountered += (_, eventArgs) => + { + if (!allowWarnings) + { + throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}"); + } + }; var parsed = JavaToCSharpConverter.ConvertText(File.ReadAllText(filePath), options); Assert.NotNull(parsed); @@ -59,6 +65,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath) [InlineData("Resources/NewArrayLiteralBug.java")] [InlineData("Resources/OctalLiteralBug.java")] [InlineData("Resources/DeprecatedAnnotation.java")] + [InlineData("Resources/BooleanArrays.java")] public void FullIntegrationTests(string filePath) { var options = new JavaConversionOptions diff --git a/JavaToCSharp.Tests/Resources/BooleanArrays.java b/JavaToCSharp.Tests/Resources/BooleanArrays.java new file mode 100644 index 00000000..36ed7da3 --- /dev/null +++ b/JavaToCSharp.Tests/Resources/BooleanArrays.java @@ -0,0 +1,17 @@ +/// Expect: +/// - output: "2\n" +package example; + +// see issue #98 +public class Program { + static boolean[] myBooleans = new boolean[] { true, false, true, false }; + public static void main(String[] args) { + int trueCount = 0; + for (boolean b : myBooleans) { + if (b) { + trueCount++; + } + } + System.out.println(trueCount); + } +} diff --git a/JavaToCSharp.Tests/Resources/MultidimensionalArrays.java b/JavaToCSharp.Tests/Resources/MultidimensionalArrays.java new file mode 100644 index 00000000..79ef5acb --- /dev/null +++ b/JavaToCSharp.Tests/Resources/MultidimensionalArrays.java @@ -0,0 +1,47 @@ +// NOTE: this test case only parses and converts successfully, it does not yet work. +// The multidimensional array indexers do not translate to the correct syntax. +package example; + +public class Program { + private static int[][] multiField = new int[2][2]; + private static int[] singleField = new int[2]; + + static { + multiField[0][0] = 10; + multiField[0][1] = 20; + multiField[1][0] = 30; + multiField[1][1] = 40; + + singleField[0] = 11; + singleField[1] = 21; + } + + public static void main(String[] args) { + int multi[][] = new int[2][2]; + int single[] = new int[2]; + + multi[0][0] = 1; + multi[0][1] = 2; + multi[1][0] = 3; + multi[1][1] = 4; + + System.out.println(multi[0][0]); + System.out.println(multi[0][1]); + System.out.println(multi[1][0]); + System.out.println(multi[1][1]); + + single[0] = 1; + single[1] = 2; + + System.out.println(single[0]); + System.out.println(single[1]); + + System.out.println(multiField[0][0]); + System.out.println(multiField[0][1]); + System.out.println(multiField[1][0]); + System.out.println(multiField[1][1]); + + System.out.println(singleField[0]); + System.out.println(singleField[1]); + } +} diff --git a/JavaToCSharp.sln b/JavaToCSharp.sln index 13adf14d..ca90a449 100644 --- a/JavaToCSharp.sln +++ b/JavaToCSharp.sln @@ -21,8 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaToCSharp.Tests", "JavaToCSharp.Tests\JavaToCSharp.Tests.csproj", "{0CBBEF05-FD79-474A-A5F0-25B341B8B77D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{DE56E074-10C7-47BA-8E8A-DAB0F3F92181}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,7 +48,6 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {0CBBEF05-FD79-474A-A5F0-25B341B8B77D} = {DE56E074-10C7-47BA-8E8A-DAB0F3F92181} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {EB538484-5025-4753-B0A5-2C66A6C06193} diff --git a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs index 8c109c1e..73589f87 100644 --- a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast; +using com.github.javaparser.ast; using com.github.javaparser.ast.body; using com.github.javaparser.ast.stmt; using com.github.javaparser.ast.type; @@ -26,7 +23,7 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor(); @@ -82,8 +79,8 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor { public override MemberDeclarationSyntax VisitForClass( - ConversionContext context, - ClassDeclarationSyntax? classSyntax, + ConversionContext context, + ClassDeclarationSyntax? classSyntax, FieldDeclaration fieldDecl, IReadOnlyList extends, IReadOnlyList implements) { var variables = new List(); - string typeName = fieldDecl.getCommonType().toString(); + var commonType = fieldDecl.getCommonType(); + int? arrayRank = null; + + var variableDeclarators = fieldDecl.getVariables()?.ToList() ?? []; - var variableDeclarators = fieldDecl.getVariables()?.ToList() ?? new List(); foreach (var item in variableDeclarators) { var type = item.getType(); + + if (arrayRank is not null && type.getArrayLevel() != arrayRank) + { + throw new InvalidOperationException("Different array levels in the same field declaration are not yet supported"); + } + + arrayRank ??= type.getArrayLevel(); + string name = item.getNameAsString(); if (type.getArrayLevel() > 0) { - if (!typeName.EndsWith("[]")) - typeName += "[]"; - if (name.EndsWith("[]")) + while (name.EndsWith("[]")) + { name = name[..^2]; + } } var initExpr = item.getInitializer().FromOptional(); @@ -43,6 +50,7 @@ public override MemberDeclarationSyntax VisitForClass( if (initExpr != null) { var initSyntax = ExpressionVisitor.VisitExpression(context, initExpr); + if (initSyntax is not null) { var varDeclarationSyntax = SyntaxFactory.VariableDeclarator(name).WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax)); @@ -53,11 +61,11 @@ public override MemberDeclarationSyntax VisitForClass( variables.Add(SyntaxFactory.VariableDeclarator(name)); } - typeName = TypeHelper.ConvertType(typeName); + var typeSyntax = TypeHelper.ConvertTypeSyntax(commonType, arrayRank ?? 0); var fieldSyntax = SyntaxFactory.FieldDeclaration( SyntaxFactory.VariableDeclaration( - SyntaxFactory.ParseTypeName(typeName), + typeSyntax, SyntaxFactory.SeparatedList(variables, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), variables.Count - 1)))); var mods = fieldDecl.getModifiers().ToModifierKeywordSet(); diff --git a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs index c2e42a5e..b22b0cd5 100644 --- a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast; +using com.github.javaparser.ast; using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using com.github.javaparser.ast.stmt; @@ -17,8 +14,8 @@ namespace JavaToCSharp.Declarations; public class MethodDeclarationVisitor : BodyDeclarationVisitor { public override MemberDeclarationSyntax VisitForClass( - ConversionContext context, - ClassDeclarationSyntax classSyntax, + ConversionContext context, + ClassDeclarationSyntax classSyntax, MethodDeclaration methodDecl, IReadOnlyList extends, IReadOnlyList implements) @@ -26,8 +23,8 @@ public override MemberDeclarationSyntax VisitForClass( return VisitInternal(context, false, classSyntax.Identifier.Text, classSyntax.Modifiers, methodDecl, extends); } - public override MemberDeclarationSyntax VisitForInterface(ConversionContext context, - InterfaceDeclarationSyntax interfaceSyntax, + public override MemberDeclarationSyntax VisitForInterface(ConversionContext context, + InterfaceDeclarationSyntax interfaceSyntax, MethodDeclaration methodDecl) { // If there is a body, mostly treat it like a class method @@ -36,7 +33,7 @@ public override MemberDeclarationSyntax VisitForInterface(ConversionContext cont return VisitInternal(context, true, interfaceSyntax.Identifier.Text, interfaceSyntax.Modifiers, methodDecl, ArraySegment.Empty); } - + var returnType = methodDecl.getType(); var returnTypeName = TypeHelper.ConvertType(returnType.toString()); @@ -118,9 +115,9 @@ private static MemberDeclarationSyntax VisitInternal( foreach (var annotation in annotations) { string name = annotation.getNameAsString(); - + // ignore @Override annotation on interface-only classes. Unfortunately this is as good as we can get for now. - if (name == "Override" + if (name == "Override" && extends.Count > 0) { methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); @@ -146,11 +143,14 @@ private static MemberDeclarationSyntax VisitInternal( foreach (var param in parameters) { - string typeName = TypeHelper.ConvertTypeOf(param); + var type = param.getType(); + int arrayLevel = type.getArrayLevel(); string identifier = TypeHelper.EscapeIdentifier(param.getNameAsString()); - if ((param.getType().getArrayLevel() > 0 && !typeName.EndsWith("[]")) || param.isVarArgs()) - typeName += "[]"; + if (param.isVarArgs() && arrayLevel == 0) + { + arrayLevel = 1; + } var modifiers = SyntaxFactory.TokenList(); @@ -158,9 +158,9 @@ private static MemberDeclarationSyntax VisitInternal( modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)); var paramSyntax = SyntaxFactory.Parameter( - attributeLists: new SyntaxList(), + attributeLists: [], modifiers: modifiers, - type: SyntaxFactory.ParseTypeName(typeName), + type: TypeHelper.ConvertTypeSyntax(type, arrayLevel), identifier: SyntaxFactory.ParseToken(identifier), @default: null); diff --git a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs index c781ac90..22aece93 100644 --- a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs @@ -1,4 +1,5 @@ -using com.github.javaparser.ast; +using com.github.javaparser; +using com.github.javaparser.ast; using com.github.javaparser.ast.expr; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -10,9 +11,17 @@ public class ArrayCreationExpressionVisitor : ExpressionVisitor(); + if (rankDimensions is { Count: > 1 }) + { + context.Options.Warning("Multi-dimensional arrays will likely have conversion issues. Review the generated code carefully.", + expr.getBegin().FromRequiredOptional().line); + } + var initializer = expr.getInitializer().FromOptional(); var rankSyntaxes = new List(); @@ -30,7 +39,7 @@ public override ExpressionSyntax Visit(ConversionContext context, ArrayCreationE : SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(rankSyntaxes, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), rankSyntaxes.Count - 1))); if (initializer == null) - return SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(SyntaxFactory.ParseTypeName(type))) + return SyntaxFactory.ArrayCreationExpression(arrayTypeSyntax) .AddTypeRankSpecifiers(rankSpecifier); // todo: support multi-dimensional and jagged arrays @@ -46,7 +55,7 @@ public override ExpressionSyntax Visit(ConversionContext context, ArrayCreationE Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), syntaxes.Count - 1))) : SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression); - return SyntaxFactory.ArrayCreationExpression(SyntaxFactory.ArrayType(SyntaxFactory.ParseTypeName(type)), initSyntax) + return SyntaxFactory.ArrayCreationExpression(arrayTypeSyntax, initSyntax) .AddTypeRankSpecifiers(rankSpecifier); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs b/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs index 91a4b3cf..be29e54b 100644 --- a/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using JavaToCSharp.Statements; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -14,7 +12,7 @@ public class LambdaExpressionVisitor : ExpressionVisitor { var bodyStatement = expr.getBody(); var bodyStatementSyntax = StatementVisitor.VisitStatement(context, bodyStatement); - + ParenthesizedLambdaExpressionSyntax? lambdaExpressionSyntax = null; if (bodyStatementSyntax is ExpressionStatementSyntax ess) @@ -35,11 +33,14 @@ public class LambdaExpressionVisitor : ExpressionVisitor foreach (var param in parameters) { - string typeName = TypeHelper.ConvertTypeOf(param); + var type = param.getType(); + int arrayLevel = type.getArrayLevel(); string identifier = TypeHelper.EscapeIdentifier(param.getNameAsString()); - if ((param.getType().getArrayLevel() > 0 && !typeName.EndsWith("[]")) || param.isVarArgs()) - typeName += "[]"; + if (param.isVarArgs() && arrayLevel == 0) + { + arrayLevel = 1; + } var modifiers = SyntaxFactory.TokenList(); @@ -47,9 +48,9 @@ public class LambdaExpressionVisitor : ExpressionVisitor modifiers = SyntaxFactory.TokenList(SyntaxFactory.Token(SyntaxKind.ParamsKeyword)); var paramSyntax = SyntaxFactory.Parameter( - attributeLists: new SyntaxList(), + attributeLists: [], modifiers: modifiers, - type: SyntaxFactory.ParseTypeName(typeName), + type: TypeHelper.ConvertTypeSyntax(type, arrayLevel), identifier: SyntaxFactory.ParseToken(identifier), @default: null); @@ -59,4 +60,4 @@ public class LambdaExpressionVisitor : ExpressionVisitor lambdaExpressionSyntax = lambdaExpressionSyntax?.AddParameterListParameters(paramSyntaxes.ToArray()); return lambdaExpressionSyntax; } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Statements/ExpressionStatementVisitor.cs b/JavaToCSharp/Statements/ExpressionStatementVisitor.cs index 6aea4649..2300f6b1 100644 --- a/JavaToCSharp/Statements/ExpressionStatementVisitor.cs +++ b/JavaToCSharp/Statements/ExpressionStatementVisitor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using com.github.javaparser.ast.stmt; using JavaToCSharp.Expressions; @@ -20,32 +18,39 @@ public class ExpressionStatementVisitor : StatementVisitor return VisitVariableDeclarationStatement(context, expr); var expressionSyntax = ExpressionVisitor.VisitExpression(context, expression); - if (expressionSyntax is null) - { - return null; - } - return SyntaxFactory.ExpressionStatement(expressionSyntax); + return expressionSyntax is null ? null : SyntaxFactory.ExpressionStatement(expressionSyntax); } private static StatementSyntax VisitVariableDeclarationStatement(ConversionContext context, VariableDeclarationExpr varExpr) { - var type = TypeHelper.ConvertType(varExpr.getCommonType()); + var commonType = varExpr.getCommonType(); + int? arrayRank = null; var variables = new List(); - var variableDeclarators = varExpr.getVariables()?.ToList() ?? new List(); + var variableDeclarators = varExpr.getVariables()?.ToList() ?? []; + foreach (var item in variableDeclarators) { + var type = item.getType(); + + if (arrayRank is not null && type.getArrayLevel() != arrayRank) + { + throw new InvalidOperationException("Different array levels in the same field declaration are not yet supported"); + } + + arrayRank ??= type.getArrayLevel(); + var id = item.getType(); string name = item.getNameAsString(); - if (id.getArrayLevel() > 0) + if (type.getArrayLevel() > 0) { - if (!type.EndsWith("[]")) - type += "[]"; - if (name.EndsWith("[]")) - name = name.Substring(0, name.Length - 2); + while (name.EndsWith("[]")) + { + name = name[..^2]; + } } var initExpr = item.getInitializer().FromOptional(); @@ -63,7 +68,9 @@ private static StatementSyntax VisitVariableDeclarationStatement(ConversionConte variables.Add(SyntaxFactory.VariableDeclarator(name)); } + var typeSyntax = TypeHelper.ConvertTypeSyntax(commonType, arrayRank ?? 0); + return SyntaxFactory.LocalDeclarationStatement( - SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(type), SyntaxFactory.SeparatedList(variables, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), variables.Count - 1)))); + SyntaxFactory.VariableDeclaration(typeSyntax, SyntaxFactory.SeparatedList(variables, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), variables.Count - 1)))); } } diff --git a/JavaToCSharp/TypeHelper.cs b/JavaToCSharp/TypeHelper.cs index d532418a..61c71f40 100644 --- a/JavaToCSharp/TypeHelper.cs +++ b/JavaToCSharp/TypeHelper.cs @@ -1,11 +1,11 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.type; using JavaToCSharp.Expressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using ast = com.github.javaparser.ast; +using Type = com.github.javaparser.ast.type.Type; namespace JavaToCSharp; @@ -55,8 +55,8 @@ public static string ConvertTypeOf(ast.nodeTypes.NodeWithType typedNode) { return ConvertType(typedNode.getType().toString()); } - - public static string ConvertType(ast.type.Type type) + + public static string ConvertType(Type type) { return ConvertType(type.toString()); } @@ -73,11 +73,47 @@ public static string ConvertType(string typeName) }); } + public static TypeSyntax ConvertTypeSyntax(Type type, int arrayRank) + { + if (type is ArrayType arrayType) + { + if (arrayRank > 0 && arrayType.getArrayLevel() != arrayRank) + { + throw new ArgumentException("Given array rank does not match the array level of the type", nameof(arrayRank)); + } + + arrayRank = arrayType.getArrayLevel(); + Type elementType; + + while ((elementType = arrayType.getElementType()) is ArrayType nestedArrayType) + { + arrayType = nestedArrayType; + } + + return ConvertTypeSyntax(elementType, arrayRank); + } + + return arrayRank == 0 + ? SyntaxFactory.ParseTypeName(ConvertType(type)) + : ConvertArrayTypeSyntax(type, arrayRank); + } + + public static ArrayTypeSyntax ConvertArrayTypeSyntax(Type type, int arrayRank) + { + var rankSpecifiers = SyntaxFactory.ArrayRankSpecifier( + SyntaxFactory.SeparatedList( + Enumerable.Repeat(SyntaxFactory.OmittedArraySizeExpression(), arrayRank) + )); + + return SyntaxFactory.ArrayType(ConvertTypeSyntax(type, 0)) + .WithRankSpecifiers(SyntaxFactory.SingletonList(rankSpecifiers)); + } + public static string Capitalize(string name) { var parts = name.Split('.'); - var joined = System.String.Join(".", parts.Select(i => + var joined = string.Join(".", parts.Select(i => { if (i.Length == 1) return i.ToUpper(); @@ -92,8 +128,8 @@ public static string EscapeIdentifier(string name) { // @ (C# Reference): https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/verbatim return name switch { - "string" or "ref" or "object" or "int" or "short" or "float" or "long" or "double" or "decimal" or "in" or - "out" or "byte" or "class" or "delegate" or "params" or "is" or "as" or "base" or "namespace" or "event" or + "string" or "ref" or "object" or "int" or "short" or "float" or "long" or "double" or "decimal" or "in" or + "out" or "byte" or "class" or "delegate" or "params" or "is" or "as" or "base" or "namespace" or "event" or "lock" or "operator" or "override" => "@" + name, _ => name, }; @@ -109,10 +145,10 @@ public static string ReplaceCommonMethodNames(string name) }; } - public static TypeSyntax GetSyntaxFromType(ast.type.ClassOrInterfaceType type) + public static TypeSyntax GetSyntaxFromType(ClassOrInterfaceType type) { string typeName = ConvertType(type.getNameAsString()); - var typeArgs = type.getTypeArguments().FromOptional()?.ToList(); + var typeArgs = type.getTypeArguments().FromOptional()?.ToList(); TypeSyntax typeSyntax; @@ -150,7 +186,7 @@ private static SeparatedSyntaxList GetSeparatedListFromArguments { continue; } - + argSyntaxes.Add(SyntaxFactory.Argument(argSyntax)); } @@ -182,23 +218,23 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE case "get" when args.size() == 1: var scopeSyntaxGet = ExpressionVisitor.VisitExpression(context, scope); - if (scopeSyntaxGet is null) + if (scopeSyntaxGet is null) { transformedSyntax = null; return false; } - + transformedSyntax = ReplaceGetByIndexAccess(context, scopeSyntaxGet, args); return true; case "set" when args.size() == 2: var scopeSyntaxSet = ExpressionVisitor.VisitExpression(context, scope); - if (scopeSyntaxSet is null) + if (scopeSyntaxSet is null) { transformedSyntax = null; return false; } - + transformedSyntax = ReplaceSetByIndexAccess(context, scopeSyntaxSet, args); return true; } @@ -214,7 +250,7 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE { return null; } - + // Replace expr.Size() by expr.Count return SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, @@ -233,7 +269,7 @@ static ExpressionSyntax ReplaceGetByIndexAccess(ConversionContext context, Expre } static ExpressionSyntax? ReplaceSetByIndexAccess( - ConversionContext context, + ConversionContext context, ExpressionSyntax scopeSyntax, java.util.List args) { @@ -248,7 +284,7 @@ static ExpressionSyntax ReplaceGetByIndexAccess(ConversionContext context, Expre { return null; } - + return SyntaxFactory.AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, left, right); } } From 221a420a4d0fd360b0305e6a6933d1b408c2b255 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Mon, 12 Feb 2024 17:27:34 -0700 Subject: [PATCH 08/25] Upgrade to .NET 8, update NuGet packages, #102 (#104) --- JavaToCSharp.Tests/JavaToCSharp.Tests.csproj | 6 +++--- JavaToCSharp/JavaToCSharp.csproj | 2 +- JavaToCSharp/TypeHelper.cs | 6 +++--- JavaToCSharpCli/JavaToCSharpCli.csproj | 4 ++-- JavaToCSharpGui/JavaToCSharpGui.csproj | 16 ++++++++-------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj index 2fc325fd..a21d46f3 100644 --- a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj +++ b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj @@ -10,9 +10,9 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/JavaToCSharp/JavaToCSharp.csproj b/JavaToCSharp/JavaToCSharp.csproj index 346eae3b..29179755 100644 --- a/JavaToCSharp/JavaToCSharp.csproj +++ b/JavaToCSharp/JavaToCSharp.csproj @@ -1,7 +1,7 @@  3.0.0 - net6.0 + net8.0 enable enable nullable diff --git a/JavaToCSharp/TypeHelper.cs b/JavaToCSharp/TypeHelper.cs index 61c71f40..35945e10 100644 --- a/JavaToCSharp/TypeHelper.cs +++ b/JavaToCSharp/TypeHelper.cs @@ -4,7 +4,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using ast = com.github.javaparser.ast; +using Ast = com.github.javaparser.ast; using Type = com.github.javaparser.ast.type.Type; namespace JavaToCSharp; @@ -51,7 +51,7 @@ public static void AddOrUpdateTypeNameConversions(string key, string value) _typeNameConversions[key] = value; } - public static string ConvertTypeOf(ast.nodeTypes.NodeWithType typedNode) + public static string ConvertTypeOf(Ast.nodeTypes.NodeWithType typedNode) { return ConvertType(typedNode.getType().toString()); } @@ -148,7 +148,7 @@ public static string ReplaceCommonMethodNames(string name) public static TypeSyntax GetSyntaxFromType(ClassOrInterfaceType type) { string typeName = ConvertType(type.getNameAsString()); - var typeArgs = type.getTypeArguments().FromOptional()?.ToList(); + var typeArgs = type.getTypeArguments().FromOptional()?.ToList(); TypeSyntax typeSyntax; diff --git a/JavaToCSharpCli/JavaToCSharpCli.csproj b/JavaToCSharpCli/JavaToCSharpCli.csproj index 9bd6ecd6..a5bf66cc 100644 --- a/JavaToCSharpCli/JavaToCSharpCli.csproj +++ b/JavaToCSharpCli/JavaToCSharpCli.csproj @@ -2,7 +2,7 @@ 3.0.0 Exe - net6.0 + net8.0 latest enable enable @@ -15,4 +15,4 @@ - \ No newline at end of file + diff --git a/JavaToCSharpGui/JavaToCSharpGui.csproj b/JavaToCSharpGui/JavaToCSharpGui.csproj index 3148ed76..08f41ee4 100644 --- a/JavaToCSharpGui/JavaToCSharpGui.csproj +++ b/JavaToCSharpGui/JavaToCSharpGui.csproj @@ -4,7 +4,7 @@ true app.manifest WinExe - net6.0 + net8.0 latest enable enable @@ -20,15 +20,15 @@ - - - - + + + + - - + + - \ No newline at end of file + From 9957153503da30cb42669f41455b3d89de12cc2b Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Mon, 12 Feb 2024 17:39:34 -0700 Subject: [PATCH 09/25] Support binary literals, #83 (#105) --- JavaToCSharp.Tests/IntegrationTests.cs | 1 + JavaToCSharp.Tests/Resources/BinaryLiterals.java | 13 +++++++++++++ .../Expressions/IntegerLiteralExpressionVisitor.cs | 10 +++++++--- .../Expressions/LongLiteralExpressionVisitor.cs | 8 ++++++-- 4 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 JavaToCSharp.Tests/Resources/BinaryLiterals.java diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index f7ebf2e2..124c7312 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -66,6 +66,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath) [InlineData("Resources/OctalLiteralBug.java")] [InlineData("Resources/DeprecatedAnnotation.java")] [InlineData("Resources/BooleanArrays.java")] + [InlineData("Resources/BinaryLiterals.java")] public void FullIntegrationTests(string filePath) { var options = new JavaConversionOptions diff --git a/JavaToCSharp.Tests/Resources/BinaryLiterals.java b/JavaToCSharp.Tests/Resources/BinaryLiterals.java new file mode 100644 index 00000000..cdb11f1b --- /dev/null +++ b/JavaToCSharp.Tests/Resources/BinaryLiterals.java @@ -0,0 +1,13 @@ +/// Expect: +/// - output: "5\n5\n" +package example; + +// see issue #83 +public class Program { + public static void main(String[] args) { + int binary = 0b101; + long longBinary = 0b101L; + System.out.println(binary); + System.out.println(longBinary); + } +} diff --git a/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs index fa99d9a1..35950eb4 100644 --- a/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs @@ -15,7 +15,11 @@ public override ExpressionSyntax Visit(ConversionContext context, IntegerLiteral { int32Value = Convert.ToInt32(value, 16); } - else if (value.StartsWith("0") && value.Length > 1) + else if (value.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) + { + int32Value = Convert.ToInt32(value[2..], 2); + } + else if (value.StartsWith('0') && value.Length > 1) { int32Value = Convert.ToInt32(value, 8); value = int32Value.ToString(); @@ -24,7 +28,7 @@ public override ExpressionSyntax Visit(ConversionContext context, IntegerLiteral { int32Value = Convert.ToInt32(value); } - + return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(value, int32Value)); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs index bc793936..e922a421 100644 --- a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs @@ -20,7 +20,11 @@ public override ExpressionSyntax Visit(ConversionContext context, LiteralStringV { int64Value = Convert.ToInt64(value, 16); } - else if (value.StartsWith("0") && value.Length > 1) + else if (value.StartsWith("0b", StringComparison.OrdinalIgnoreCase)) + { + int64Value = Convert.ToInt64(value[2..], 2); + } + else if (value.StartsWith('0') && value.Length > 1) { int64Value = Convert.ToInt64(value, 8); value = int64Value.ToString(); @@ -32,4 +36,4 @@ public override ExpressionSyntax Visit(ConversionContext context, LiteralStringV return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(value, int64Value)); } -} \ No newline at end of file +} From 3379c7d8a7cd407edf82aecf27a479e88b4a17fb Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Tue, 13 Feb 2024 19:02:45 -0700 Subject: [PATCH 10/25] Support basic Java 14 switch expressions, #63 (#106) --- JavaToCSharp.Tests/IntegrationTests.cs | 10 ++- .../Resources/Java14SwitchExpressions.java | 23 +++++ JavaToCSharp/CommentsHelper.cs | 2 +- .../ClassOrInterfaceDeclarationVisitor.cs | 78 ++++++++-------- .../Declarations/EnumDeclarationVisitor.cs | 67 +++++++------- .../ArrayAccessExpressionVisitor.cs | 2 +- .../ArrayCreationExpressionVisitor.cs | 4 +- .../ArrayInitializerExpressionVisitor.cs | 7 +- .../AssignmentExpressionVisitor.cs | 4 +- .../Expressions/BinaryExpressionVisitor.cs | 6 +- .../BooleanLiteralExpressionVisitor.cs | 6 +- .../Expressions/CastExpressionVisitor.cs | 4 +- .../CharLiteralExpressionVisitor.cs | 2 +- .../Expressions/ClassExpressionVisitor.cs | 2 +- .../ConditionalExpressionVisitor.cs | 2 +- .../DoubleLiteralExpressionVisitor.cs | 15 ++-- .../Expressions/EnclosedExpressionVisitor.cs | 8 +- JavaToCSharp/Expressions/ExpressionVisitor.cs | 25 +++--- .../FieldAccessExpressionVisitor.cs | 7 +- .../InstanceOfExpressionVisitor.cs | 3 +- .../IntegerLiteralExpressionVisitor.cs | 2 +- .../Expressions/LambdaExpressionVisitor.cs | 3 +- .../LongLiteralExpressionVisitor.cs | 4 +- .../MethodCallExpressionVisitor.cs | 7 +- .../MethodReferenceExpressionVisitor.cs | 5 +- .../Expressions/NameExpressionVisitor.cs | 4 +- .../NullLiteralExpressionVisitor.cs | 2 +- .../ObjectCreationExpressionVisitor.cs | 23 +++-- .../StringLiteralExpressionVisitor.cs | 2 +- .../Expressions/SuperExpressionVisitor.cs | 2 +- .../Expressions/SwitchExpressionVisitor.cs | 90 +++++++++++++++++++ .../Expressions/ThisExpressionVisitor.cs | 2 +- .../Expressions/TypeExpressionVisitor.cs | 4 +- .../Expressions/UnaryExpressionVisitor.cs | 3 +- JavaToCSharp/JavaConversionOptions.cs | 12 +-- JavaToCSharp/JavaToCSharpConverter.cs | 29 ++++-- JavaToCSharp/Replacement.cs | 2 +- JavaToCSharp/UsingsHelper.cs | 18 +++- 38 files changed, 331 insertions(+), 160 deletions(-) create mode 100644 JavaToCSharp.Tests/Resources/Java14SwitchExpressions.java create mode 100644 JavaToCSharp/Expressions/SwitchExpressionVisitor.cs diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index 124c7312..bcf129af 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -2,6 +2,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit.Abstractions; namespace JavaToCSharp.Tests; @@ -11,7 +12,7 @@ namespace JavaToCSharp.Tests; /// /// Uses some BSD-2-Clause licensed code from Jaktnat. License: https://github.com/paulirwin/jaktnat/blob/main/LICENSE /// -public class IntegrationTests +public class IntegrationTests(ITestOutputHelper testOutputHelper) { [Theory] [InlineData("Resources/ArrayField.java")] @@ -36,7 +37,10 @@ public void GeneralSuccessfulConversionTest(string filePath, bool allowWarnings }; var parsed = JavaToCSharpConverter.ConvertText(File.ReadAllText(filePath), options); + Assert.NotNull(parsed); + + testOutputHelper.WriteLine(parsed); } [Theory] @@ -62,6 +66,7 @@ public void GeneralUnsuccessfulConversionTest(string filePath) [InlineData("Resources/Java9TryWithResources.java")] [InlineData("Resources/Java9PrivateInterfaceMethods.java")] [InlineData("Resources/Java10TypeInference.java")] + [InlineData("Resources/Java14SwitchExpressions.java")] [InlineData("Resources/NewArrayLiteralBug.java")] [InlineData("Resources/OctalLiteralBug.java")] [InlineData("Resources/DeprecatedAnnotation.java")] @@ -81,8 +86,11 @@ public void FullIntegrationTests(string filePath) var javaText = File.ReadAllText(filePath); var parsed = JavaToCSharpConverter.ConvertText(javaText, options); + Assert.NotNull(parsed); + testOutputHelper.WriteLine(parsed); + var fileName = Path.GetFileNameWithoutExtension(filePath); var assembly = CompileAssembly(fileName, parsed); diff --git a/JavaToCSharp.Tests/Resources/Java14SwitchExpressions.java b/JavaToCSharp.Tests/Resources/Java14SwitchExpressions.java new file mode 100644 index 00000000..9354ce3e --- /dev/null +++ b/JavaToCSharp.Tests/Resources/Java14SwitchExpressions.java @@ -0,0 +1,23 @@ +/// Expect: +/// - output: "9\n" +package example; + +// https://docs.oracle.com/en/java/javase/14/language/switch-expressions.html#GUID-BA4F63E3-4823-43C6-A5F3-BAA4A2EF3ADC + +enum Day { SUNDAY, MONDAY, TUESDAY, + WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; } + +public class Program { + public static void main(String[] args) { + Day day = Day.WEDNESDAY; + System.out.println( + switch (day) { + case MONDAY, FRIDAY, SUNDAY -> 6; + case TUESDAY -> 7; + case THURSDAY, SATURDAY -> 8; + case WEDNESDAY -> 9; + default -> throw new IllegalStateException("Invalid day: " + day); + } + ); + } +} diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index 5cfec487..8ab6d151 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -475,7 +475,7 @@ private static SyntaxNode AdjustBlockCommentIndentation(SyntaxNode node) lines[l] = indentString + lines[l].TrimStart(); } - node = node.ReplaceTrivia(t, SyntaxFactory.Comment(String.Join(Environment.NewLine, lines).TrimEnd(' '))); + node = node.ReplaceTrivia(t, SyntaxFactory.Comment(string.Join(Environment.NewLine, lines).TrimEnd(' '))); } } diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index d68518fc..0c3532e9 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -28,10 +28,10 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); + var typeParams = interfaceDecl.getTypeParameters().ToList(); if (typeParams is { Count: > 0 }) { - classSyntax = - classSyntax.AddTypeParameterListParameters(typeParams + classSyntax = classSyntax.AddTypeParameterListParameters(typeParams .Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } - var mods = javai.getModifiers().ToModifierKeywordSet(); + var mods = interfaceDecl.getModifiers().ToModifierKeywordSet(); if (mods.Contains(Modifier.Keyword.PRIVATE)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); @@ -68,64 +69,68 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); + var extends = interfaceDecl.getExtendedTypes().ToList(); + if (extends != null) { 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(); + var implements = interfaceDecl.getImplementedTypes().ToList(); + if (implements != null) { 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(); + var members = interfaceDecl.getMembers()?.ToList(); if (members is not null) + { foreach (var member in members) { var syntax = VisitBodyDeclarationForInterface(context, classSyntax, member); var memberWithComments = syntax?.WithJavaComments(context, member); + if (memberWithComments != null) { classSyntax = classSyntax.AddMembers(memberWithComments); } } + } - return classSyntax.WithJavaComments(context, javai); + return classSyntax.WithJavaComments(context, interfaceDecl); } - public static ClassDeclarationSyntax? VisitClassDeclaration(ConversionContext context, - ClassOrInterfaceDeclaration javac, bool isNested = false) + public static ClassDeclarationSyntax VisitClassDeclaration(ConversionContext context, + ClassOrInterfaceDeclaration classDecl, bool isNested = false) { - string name = javac.getNameAsString(); + string name = classDecl.getNameAsString(); if (!isNested) + { context.RootTypeName = name; + } context.LastTypeName = name; var classSyntax = SyntaxFactory.ClassDeclaration(name); - var typeParams = javac.getTypeParameters().ToList(); + var typeParams = classDecl.getTypeParameters().ToList(); if (typeParams is { Count: > 0 }) { - classSyntax = - classSyntax.AddTypeParameterListParameters(typeParams + classSyntax = classSyntax.AddTypeParameterListParameters(typeParams .Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); } - var mods = javac.getModifiers().ToModifierKeywordSet(); + var mods = classDecl.getModifiers().ToModifierKeywordSet(); if (mods.Contains(Modifier.Keyword.PRIVATE)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); @@ -138,23 +143,21 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor() ?? new List(); + var extends = classDecl.getExtendedTypes().ToList() ?? new List(); 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() ?? new List(); + var implements = classDecl.getImplementedTypes().ToList() ?? new List(); 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(); + var members = classDecl.getMembers()?.ToList(); if (members is not null) { @@ -165,24 +168,21 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor(); + var annotations = classDecl.getAnnotations().ToList(); if (annotations is { Count: > 0 }) { @@ -224,6 +224,6 @@ public class ClassOrInterfaceDeclarationVisitor : BodyDeclarationVisitor { - public override MemberDeclarationSyntax? VisitForClass( + public override MemberDeclarationSyntax VisitForClass( ConversionContext context, ClassDeclarationSyntax? classSyntax, EnumDeclaration enumDecl, @@ -22,19 +20,21 @@ public class EnumDeclarationVisitor : BodyDeclarationVisitor return VisitEnumDeclaration(context, enumDecl); } - public override MemberDeclarationSyntax? VisitForInterface(ConversionContext context, InterfaceDeclarationSyntax interfaceSyntax, EnumDeclaration declaration) + public override MemberDeclarationSyntax VisitForInterface(ConversionContext context, + InterfaceDeclarationSyntax interfaceSyntax, EnumDeclaration declaration) { return VisitEnumDeclaration(context, declaration); } - public static EnumDeclarationSyntax? VisitEnumDeclaration(ConversionContext context, EnumDeclaration javai) + public static EnumDeclarationSyntax VisitEnumDeclaration(ConversionContext context, EnumDeclaration enumDecl) { - var name = javai.getNameAsString(); + var name = enumDecl.getNameAsString(); context.LastTypeName = name; - var classSyntax = SyntaxFactory.EnumDeclaration(name); + var enumSyntax = SyntaxFactory.EnumDeclaration(name); + + var typeConstants = enumDecl.getEntries().ToList(); - var typeConstants = javai.getEntries().ToList(); if (typeConstants is { Count: > 0 }) { var useCodeToComment = context.Options.UseUnrecognizedCodeToComment; @@ -42,11 +42,12 @@ public class EnumDeclarationVisitor : BodyDeclarationVisitor var enumMembers = new List(membersCount); var lastMembersIndex = membersCount - 1; var showNoPortedWarning = false; + for (int i = 0; i < membersCount; i++) { var itemConst = typeConstants[i]; var memberDecl = SyntaxFactory.EnumMemberDeclaration(itemConst.getNameAsString()) - .WithJavaComments(context, itemConst); + .WithJavaComments(context, itemConst); if (useCodeToComment) { @@ -57,58 +58,64 @@ public class EnumDeclarationVisitor : BodyDeclarationVisitor { var bodyCodes = CommentsHelper.ConvertToComment(new[] { itemConst }, "enum member body", false); - if (memberDecl is not null && memberDecl.HasLeadingTrivia) + if (memberDecl.HasLeadingTrivia) { var firstLeadingTrivia = memberDecl.GetLeadingTrivia().Last(); memberDecl = memberDecl.InsertTriviaAfter(firstLeadingTrivia, bodyCodes); } else { - memberDecl = memberDecl?.WithLeadingTrivia(bodyCodes); + memberDecl = memberDecl.WithLeadingTrivia(bodyCodes); } showNoPortedWarning = true; } //java-enum `method-body` to `code Comment` - if (i == lastMembersIndex && memberDecl != null) + if (i == lastMembersIndex) + { memberDecl = MembersToCommentTrivia(memberDecl, ref showNoPortedWarning); + } } - if (memberDecl is not null) enumMembers.Add(memberDecl); + enumMembers.Add(memberDecl); } if (showNoPortedWarning) - context.Options.Warning($"Members found in enum {name} will not be ported. Check for correctness.", javai.getBegin().FromRequiredOptional().line); + context.Options.Warning($"Members found in enum {name} will not be ported. Check for correctness.", + enumDecl.getBegin().FromRequiredOptional().line); - classSyntax = classSyntax.AddMembers(enumMembers.ToArray()); + enumSyntax = enumSyntax.AddMembers(enumMembers.ToArray()); } - var mods = javai.getModifiers().ToModifierKeywordSet(); - + var mods = enumDecl.getModifiers().ToModifierKeywordSet(); + if (mods.Contains(Modifier.Keyword.PRIVATE)) - classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); + enumSyntax = enumSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PrivateKeyword)); if (mods.Contains(Modifier.Keyword.PROTECTED)) - classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); + enumSyntax = enumSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.ProtectedKeyword)); if (mods.Contains(Modifier.Keyword.PUBLIC)) - classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + enumSyntax = enumSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.PublicKeyword)); + + context.Options.StaticUsingEnumNames.Add(name); - return classSyntax.WithJavaComments(context, javai); + return enumSyntax.WithJavaComments(context, enumDecl); - EnumMemberDeclarationSyntax MembersToCommentTrivia(EnumMemberDeclarationSyntax lastMemberDecl, ref bool showNoPortedWarning) + EnumMemberDeclarationSyntax MembersToCommentTrivia(EnumMemberDeclarationSyntax lastMemberDecl, + ref bool showNoPortedWarning) { - var members = javai.getMembers().ToList(); + var members = enumDecl.getMembers().ToList(); if (members is { Count: > 0 }) { var todoCodes = CommentsHelper.ConvertToComment(members, "enum body members"); - var lastMemberTrailingTrivia = lastMemberDecl.GetTrailingTrivia(); - lastMemberDecl = lastMemberTrailingTrivia.Count > 0 ? - lastMemberDecl.InsertTriviaAfter(lastMemberTrailingTrivia.Last(), todoCodes) : - lastMemberDecl.WithTrailingTrivia(todoCodes); - showNoPortedWarning = true; + var lastMemberTrailingTrivia = lastMemberDecl.GetTrailingTrivia(); + lastMemberDecl = lastMemberTrailingTrivia.Count > 0 + ? lastMemberDecl.InsertTriviaAfter(lastMemberTrailingTrivia.Last(), todoCodes) + : lastMemberDecl.WithTrailingTrivia(todoCodes); + showNoPortedWarning = true; } return lastMemberDecl; } } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs index 85f20e2b..d2bf676d 100644 --- a/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class ArrayAccessExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, ArrayAccessExpr expr) + protected override ExpressionSyntax? Visit(ConversionContext context, ArrayAccessExpr expr) { var nameExpr = expr.getName(); var nameSyntax = VisitExpression(context, nameExpr); diff --git a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs index 22aece93..4f8d62c6 100644 --- a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs @@ -8,7 +8,7 @@ namespace JavaToCSharp.Expressions; public class ArrayCreationExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, ArrayCreationExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, ArrayCreationExpr expr) { var type = TypeHelper.ConvertType(expr.getElementType()); var typeSyntax = SyntaxFactory.ParseTypeName(type); @@ -34,7 +34,7 @@ public override ExpressionSyntax Visit(ConversionContext context, ArrayCreationE rankSyntaxes.AddRange(expressionSyntaxes!); } - var rankSpecifier = rankDimensions?.Count > 0 && rankSyntaxes.Count == 0 + var rankSpecifier = rankDimensions?.Count > 0 && rankSyntaxes.Count == 0 ? SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression())) : SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(rankSyntaxes, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), rankSyntaxes.Count - 1))); diff --git a/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs index 55d47108..667d8a01 100644 --- a/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.expr; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class ArrayInitializerExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, ArrayInitializerExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, ArrayInitializerExpr expr) { var expressions = expr.getValues()?.ToList() ?? new List(); var syntaxes = expressions.Select(valueExpression => VisitExpression(context, valueExpression)) diff --git a/JavaToCSharp/Expressions/AssignmentExpressionVisitor.cs b/JavaToCSharp/Expressions/AssignmentExpressionVisitor.cs index 0506b4fe..c21db49a 100644 --- a/JavaToCSharp/Expressions/AssignmentExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/AssignmentExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class AssignmentExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, AssignExpr assignExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, AssignExpr assignExpr) { var left = assignExpr.getTarget(); var leftSyntax = VisitExpression(context, left); @@ -49,7 +49,7 @@ public class AssignmentExpressionVisitor : ExpressionVisitor kind = SyntaxKind.MultiplyAssignmentExpression; else if (op == AssignExpr.Operator.XOR) kind = SyntaxKind.ExclusiveOrAssignmentExpression; - + return SyntaxFactory.AssignmentExpression(kind, leftSyntax, rightSyntax); } } diff --git a/JavaToCSharp/Expressions/BinaryExpressionVisitor.cs b/JavaToCSharp/Expressions/BinaryExpressionVisitor.cs index ccfe7053..86aa7656 100644 --- a/JavaToCSharp/Expressions/BinaryExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/BinaryExpressionVisitor.cs @@ -10,14 +10,14 @@ namespace JavaToCSharp.Expressions; /// public class BinaryExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, BinaryExpr binaryExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, BinaryExpr binaryExpr) { var leftExpr = binaryExpr.getLeft(); if (leftExpr is null) { return null; } - + var leftSyntax = VisitExpression(context, leftExpr); if (leftSyntax is null) { @@ -29,7 +29,7 @@ public class BinaryExpressionVisitor : ExpressionVisitor { return null; } - + var rightSyntax = VisitExpression(context, rightExpr); if (rightSyntax is null) { diff --git a/JavaToCSharp/Expressions/BooleanLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/BooleanLiteralExpressionVisitor.cs index cd2b286a..2d69af81 100644 --- a/JavaToCSharp/Expressions/BooleanLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/BooleanLiteralExpressionVisitor.cs @@ -6,8 +6,8 @@ namespace JavaToCSharp.Expressions; public class BooleanLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, BooleanLiteralExpr expr) => - SyntaxFactory.LiteralExpression(expr.getValue() - ? SyntaxKind.TrueLiteralExpression + protected override ExpressionSyntax Visit(ConversionContext context, BooleanLiteralExpr expr) => + SyntaxFactory.LiteralExpression(expr.getValue() + ? SyntaxKind.TrueLiteralExpression : SyntaxKind.FalseLiteralExpression); } diff --git a/JavaToCSharp/Expressions/CastExpressionVisitor.cs b/JavaToCSharp/Expressions/CastExpressionVisitor.cs index 86632cc6..d052d83d 100644 --- a/JavaToCSharp/Expressions/CastExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/CastExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class CastExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, CastExpr castExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, CastExpr castExpr) { var expr = castExpr.getExpression(); var exprSyntax = VisitExpression(context, expr); @@ -18,4 +18,4 @@ public class CastExpressionVisitor : ExpressionVisitor var type = TypeHelper.ConvertTypeOf(castExpr); return SyntaxFactory.CastExpression(SyntaxFactory.ParseTypeName(type), exprSyntax); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Expressions/CharLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/CharLiteralExpressionVisitor.cs index d2ea9803..cc76c3b4 100644 --- a/JavaToCSharp/Expressions/CharLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/CharLiteralExpressionVisitor.cs @@ -7,7 +7,7 @@ namespace JavaToCSharp.Expressions; public class CharLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, CharLiteralExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, CharLiteralExpr expr) { var value = Regex.Unescape(expr.getValue()); return SyntaxFactory.LiteralExpression(SyntaxKind.CharacterLiteralExpression, SyntaxFactory.Literal(value[0])); diff --git a/JavaToCSharp/Expressions/ClassExpressionVisitor.cs b/JavaToCSharp/Expressions/ClassExpressionVisitor.cs index 4d3d40f3..f7e41b0e 100644 --- a/JavaToCSharp/Expressions/ClassExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ClassExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class ClassExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, ClassExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, ClassExpr expr) { var type = TypeHelper.ConvertTypeOf(expr); diff --git a/JavaToCSharp/Expressions/ConditionalExpressionVisitor.cs b/JavaToCSharp/Expressions/ConditionalExpressionVisitor.cs index afcadd73..89c3e572 100644 --- a/JavaToCSharp/Expressions/ConditionalExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ConditionalExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class ConditionalExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, ConditionalExpr conditionalExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, ConditionalExpr conditionalExpr) { var condition = conditionalExpr.getCondition(); var conditionSyntax = VisitExpression(context, condition); diff --git a/JavaToCSharp/Expressions/DoubleLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/DoubleLiteralExpressionVisitor.cs index e3fc5808..cad2a027 100644 --- a/JavaToCSharp/Expressions/DoubleLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/DoubleLiteralExpressionVisitor.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using com.github.javaparser.ast.expr; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -16,13 +15,13 @@ public DoubleLiteralExpressionVisitor() _cultureInfo.NumberFormat.CurrencyDecimalSeparator = "."; } - public override ExpressionSyntax Visit(ConversionContext context, DoubleLiteralExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, DoubleLiteralExpr expr) { - var value = expr.getValue().Replace("_", String.Empty); - - var literalSyntax = value.EndsWith("f", StringComparison.OrdinalIgnoreCase) - ? SyntaxFactory.Literal(Single.Parse(value.TrimEnd('f', 'F'), NumberStyles.Any, _cultureInfo)) - : SyntaxFactory.Literal(Double.Parse(value.TrimEnd('d', 'D'), NumberStyles.Any, _cultureInfo)); + var value = expr.getValue().Replace("_", string.Empty); + + var literalSyntax = value.EndsWith("f", StringComparison.OrdinalIgnoreCase) + ? SyntaxFactory.Literal(float.Parse(value.TrimEnd('f', 'F'), NumberStyles.Any, _cultureInfo)) + : SyntaxFactory.Literal(double.Parse(value.TrimEnd('d', 'D'), NumberStyles.Any, _cultureInfo)); return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, literalSyntax); } diff --git a/JavaToCSharp/Expressions/EnclosedExpressionVisitor.cs b/JavaToCSharp/Expressions/EnclosedExpressionVisitor.cs index d000e0ce..c30523bf 100644 --- a/JavaToCSharp/Expressions/EnclosedExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/EnclosedExpressionVisitor.cs @@ -6,15 +6,11 @@ namespace JavaToCSharp.Expressions; public class EnclosedExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, EnclosedExpr enclosedExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, EnclosedExpr enclosedExpr) { var expr = enclosedExpr.getInner(); var exprSyntax = VisitExpression(context, expr); - if (exprSyntax is null) - { - return null; - } - return SyntaxFactory.ParenthesizedExpression(exprSyntax); + return exprSyntax is null ? null : SyntaxFactory.ParenthesizedExpression(exprSyntax); } } diff --git a/JavaToCSharp/Expressions/ExpressionVisitor.cs b/JavaToCSharp/Expressions/ExpressionVisitor.cs index e0d3325b..51d67a09 100644 --- a/JavaToCSharp/Expressions/ExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ExpressionVisitor.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.expr; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace JavaToCSharp.Expressions; @@ -8,14 +6,15 @@ namespace JavaToCSharp.Expressions; public abstract class ExpressionVisitor : ExpressionVisitor where T : Expression { - public abstract ExpressionSyntax? Visit(ConversionContext context, T expr); + protected abstract ExpressionSyntax? Visit(ConversionContext context, T expr); - protected sealed override ExpressionSyntax? Visit(ConversionContext context, Expression expr) => Visit(context, (T)expr); + protected sealed override ExpressionSyntax? Visit(ConversionContext context, Expression expr) => + Visit(context, (T)expr); } public abstract class ExpressionVisitor { - private static readonly IDictionary _visitors; + private static readonly Dictionary _visitors; static ExpressionVisitor() { @@ -45,9 +44,10 @@ static ExpressionVisitor() { typeof(ThisExpr), new ThisExpressionVisitor() }, { typeof(UnaryExpr), new UnaryExpressionVisitor() }, { typeof(LongLiteralExpr), new LongLiteralExpressionVisitor() }, - { typeof(LambdaExpr), new LambdaExpressionVisitor() }, - { typeof(MethodReferenceExpr), new MethodReferenceExpressionVisitor() }, - { typeof(TypeExpr), new TypeExpressionVisitor() } + { typeof(LambdaExpr), new LambdaExpressionVisitor() }, + { typeof(MethodReferenceExpr), new MethodReferenceExpressionVisitor() }, + { typeof(TypeExpr), new TypeExpressionVisitor() }, + { typeof(SwitchExpr), new SwitchExpressionVisitor() }, }; } @@ -56,7 +56,9 @@ static ExpressionVisitor() public static ExpressionSyntax? VisitExpression(ConversionContext context, Expression? expr) { if (expr == null) + { return null; + } ExpressionVisitor? visitor = null; var t = expr.GetType(); @@ -66,10 +68,13 @@ static ExpressionVisitor() t = t.BaseType; } - if (visitor != null) + if (visitor != null) + { return visitor.Visit(context, expr); + } var message = $"Expression visitor not implemented for expression type `{expr.GetType()}`{Environment.NewLine}{expr}"; + throw new NotImplementedException(message); } } diff --git a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs index b151165a..66e65bdf 100644 --- a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class FieldAccessExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, FieldAccessExpr fieldAccessExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, FieldAccessExpr fieldAccessExpr) { var scope = fieldAccessExpr.getScope(); ExpressionSyntax? scopeSyntax = null; @@ -14,11 +14,11 @@ public class FieldAccessExpressionVisitor : ExpressionVisitor if (scope != null) { scopeSyntax = VisitExpression(context, scope); - + // TODO.PI: This should probably live in TypeHelper somehow if (context.Options.ConvertSystemOutToConsole) { - if (scopeSyntax is IdentifierNameSyntax { Identifier: { Text: "System" } } + if (scopeSyntax is IdentifierNameSyntax { Identifier.Text: "System" } && fieldAccessExpr.getNameAsString() == "out") { return SyntaxFactory.IdentifierName("Console"); @@ -32,6 +32,7 @@ public class FieldAccessExpressionVisitor : ExpressionVisitor } var field = TypeHelper.EscapeIdentifier(fieldAccessExpr.getNameAsString()); + return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, scopeSyntax, SyntaxFactory.IdentifierName(field)); } } diff --git a/JavaToCSharp/Expressions/InstanceOfExpressionVisitor.cs b/JavaToCSharp/Expressions/InstanceOfExpressionVisitor.cs index 85c6c59c..044f8d00 100644 --- a/JavaToCSharp/Expressions/InstanceOfExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/InstanceOfExpressionVisitor.cs @@ -6,10 +6,11 @@ namespace JavaToCSharp.Expressions; public class InstanceOfExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, InstanceOfExpr expr) + protected override ExpressionSyntax? Visit(ConversionContext context, InstanceOfExpr expr) { var innerExpr = expr.getExpression(); var exprSyntax = VisitExpression(context, innerExpr); + if (exprSyntax is null) { return null; diff --git a/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs index 35950eb4..ecf86f61 100644 --- a/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/IntegerLiteralExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class IntegerLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, IntegerLiteralExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, IntegerLiteralExpr expr) { string value = expr.getValue().Replace("_", string.Empty); int int32Value; diff --git a/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs b/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs index be29e54b..e1fa9056 100644 --- a/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/LambdaExpressionVisitor.cs @@ -8,7 +8,7 @@ namespace JavaToCSharp.Expressions; public class LambdaExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, LambdaExpr expr) + protected override ExpressionSyntax? Visit(ConversionContext context, LambdaExpr expr) { var bodyStatement = expr.getBody(); var bodyStatementSyntax = StatementVisitor.VisitStatement(context, bodyStatement); @@ -58,6 +58,7 @@ public class LambdaExpressionVisitor : ExpressionVisitor } lambdaExpressionSyntax = lambdaExpressionSyntax?.AddParameterListParameters(paramSyntaxes.ToArray()); + return lambdaExpressionSyntax; } } diff --git a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs index e922a421..438de6ba 100644 --- a/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/LongLiteralExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class LongLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, LiteralStringValueExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, LiteralStringValueExpr expr) { string value = expr is LongLiteralExpr longLiteralExpr ? longLiteralExpr.getValue() : expr.toString(); value = value.Trim('\"') @@ -33,7 +33,7 @@ public override ExpressionSyntax Visit(ConversionContext context, LiteralStringV { int64Value = Convert.ToInt64(value); } - + return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(value, int64Value)); } } diff --git a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs index 19d46623..69b6ab6a 100644 --- a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs @@ -6,7 +6,7 @@ namespace JavaToCSharp.Expressions; public class MethodCallExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, MethodCallExpr methodCallExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, MethodCallExpr methodCallExpr) { if (TypeHelper.TryTransformMethodCall(context, methodCallExpr, out var transformedSyntax)) { @@ -15,7 +15,7 @@ public class MethodCallExpressionVisitor : ExpressionVisitor var scope = methodCallExpr.getScope().FromOptional(); ExpressionSyntax? scopeSyntax = null; - + var methodName = TypeHelper.Capitalize(methodCallExpr.getNameAsString()); methodName = TypeHelper.ReplaceCommonMethodNames(methodName); @@ -53,8 +53,11 @@ public class MethodCallExpressionVisitor : ExpressionVisitor } var args = methodCallExpr.getArguments(); + if (args == null || args.size() == 0) + { return SyntaxFactory.InvocationExpression(methodExpression); + } return SyntaxFactory.InvocationExpression(methodExpression, TypeHelper.GetSyntaxFromArguments(context, args)); } diff --git a/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs b/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs index d0f66242..bf980604 100644 --- a/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs @@ -7,7 +7,7 @@ namespace JavaToCSharp.Expressions; public class MethodReferenceExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, MethodReferenceExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, MethodReferenceExpr expr) { var scope = expr.getScope(); ExpressionSyntax? scopeSyntax = null; @@ -32,8 +32,11 @@ public override ExpressionSyntax Visit(ConversionContext context, MethodReferenc } var args = expr.getTypeArguments().FromOptional(); + if (args == null || args.size() == 0) + { return SyntaxFactory.InvocationExpression(methodExpression); + } return SyntaxFactory.InvocationExpression(methodExpression, TypeHelper.GetSyntaxFromArguments(context, args)); } diff --git a/JavaToCSharp/Expressions/NameExpressionVisitor.cs b/JavaToCSharp/Expressions/NameExpressionVisitor.cs index af8ff83e..24dbbeb0 100644 --- a/JavaToCSharp/Expressions/NameExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/NameExpressionVisitor.cs @@ -6,6 +6,6 @@ namespace JavaToCSharp.Expressions; public class NameExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, NameExpr nameExpr) => + protected override ExpressionSyntax Visit(ConversionContext context, NameExpr nameExpr) => SyntaxFactory.IdentifierName(TypeHelper.EscapeIdentifier(nameExpr.getNameAsString())); -} \ No newline at end of file +} diff --git a/JavaToCSharp/Expressions/NullLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/NullLiteralExpressionVisitor.cs index 6da7f356..5883f849 100644 --- a/JavaToCSharp/Expressions/NullLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/NullLiteralExpressionVisitor.cs @@ -6,6 +6,6 @@ namespace JavaToCSharp.Expressions; public class NullLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, NullLiteralExpr expr) => + protected override ExpressionSyntax Visit(ConversionContext context, NullLiteralExpr expr) => SyntaxFactory.LiteralExpression(SyntaxKind.NullLiteralExpression); } diff --git a/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs b/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs index 5e51693c..a77708dd 100644 --- a/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using JavaToCSharp.Declarations; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -using System.Linq; using com.github.javaparser.ast; using com.github.javaparser.ast.type; @@ -13,7 +10,7 @@ namespace JavaToCSharp.Expressions; public class ObjectCreationExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, ObjectCreationExpr newExpr) + protected override ExpressionSyntax Visit(ConversionContext context, ObjectCreationExpr newExpr) { var anonBody = newExpr.getAnonymousClassBody().FromOptional().ToList(); @@ -35,13 +32,16 @@ public override ExpressionSyntax Visit(ConversionContext context, ObjectCreation var typeSyntax = TypeHelper.GetSyntaxFromType(type); var args = newExpr.getArguments(); + if (args == null || args.size() == 0) + { return SyntaxFactory.ObjectCreationExpression(typeSyntax).WithArgumentList(SyntaxFactory.ArgumentList()); + } return SyntaxFactory.ObjectCreationExpression(typeSyntax, TypeHelper.GetSyntaxFromArguments(context, args), null); } - private static ExpressionSyntax VisitAnonymousClassCreationExpression(ConversionContext context, ObjectCreationExpr newExpr, List anonBody) + private static ObjectCreationExpressionSyntax VisitAnonymousClassCreationExpression(ConversionContext context, ObjectCreationExpr newExpr, List anonBody) { string baseTypeName = TypeHelper.ConvertType(newExpr.getType().getNameAsString()); string anonTypeName = string.Empty; @@ -49,13 +49,14 @@ private static ExpressionSyntax VisitAnonymousClassCreationExpression(Conversion for (int i = 0; i <= 100; i++) { if (i == 100) + { throw new InvalidOperationException("Too many anonymous types"); + } - anonTypeName = $"Anonymous{baseTypeName}{(i == 0 ? String.Empty : i.ToString())}"; + anonTypeName = $"Anonymous{baseTypeName}{(i == 0 ? string.Empty : i.ToString())}"; - if (!context.UsedAnonymousTypeNames.Contains(anonTypeName)) + if (context.UsedAnonymousTypeNames.Add(anonTypeName)) { - context.UsedAnonymousTypeNames.Add(anonTypeName); break; // go with this one } } @@ -70,6 +71,7 @@ private static ExpressionSyntax VisitAnonymousClassCreationExpression(Conversion }))); string? contextLastTypeName = context.LastTypeName; + if (contextLastTypeName is not null) { var parentField = SyntaxFactory.FieldDeclaration(SyntaxFactory.VariableDeclaration(SyntaxFactory.ParseTypeName(contextLastTypeName)) @@ -95,9 +97,12 @@ private static ExpressionSyntax VisitAnonymousClassCreationExpression(Conversion context.PendingAnonymousTypes.Enqueue(classSyntax); var args = newExpr.getArguments(); + if (args == null || args.size() == 0) + { return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(anonTypeName)) .AddArgumentListArguments(SyntaxFactory.Argument(SyntaxFactory.ThisExpression())); + } return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(anonTypeName), TypeHelper.GetSyntaxFromArguments(context, args), diff --git a/JavaToCSharp/Expressions/StringLiteralExpressionVisitor.cs b/JavaToCSharp/Expressions/StringLiteralExpressionVisitor.cs index a086814b..7eef8c8f 100644 --- a/JavaToCSharp/Expressions/StringLiteralExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/StringLiteralExpressionVisitor.cs @@ -7,7 +7,7 @@ namespace JavaToCSharp.Expressions; public class StringLiteralExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, StringLiteralExpr expr) + protected override ExpressionSyntax Visit(ConversionContext context, StringLiteralExpr expr) { var value = Regex.Unescape(expr.getValue()); return SyntaxFactory.LiteralExpression(SyntaxKind.StringLiteralExpression, SyntaxFactory.Literal(value)); diff --git a/JavaToCSharp/Expressions/SuperExpressionVisitor.cs b/JavaToCSharp/Expressions/SuperExpressionVisitor.cs index 1105ef2f..be8acc3e 100644 --- a/JavaToCSharp/Expressions/SuperExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/SuperExpressionVisitor.cs @@ -6,5 +6,5 @@ namespace JavaToCSharp.Expressions; public class SuperExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, SuperExpr expr) => SyntaxFactory.BaseExpression(); + protected override ExpressionSyntax Visit(ConversionContext context, SuperExpr expr) => SyntaxFactory.BaseExpression(); } diff --git a/JavaToCSharp/Expressions/SwitchExpressionVisitor.cs b/JavaToCSharp/Expressions/SwitchExpressionVisitor.cs new file mode 100644 index 00000000..5087195f --- /dev/null +++ b/JavaToCSharp/Expressions/SwitchExpressionVisitor.cs @@ -0,0 +1,90 @@ +using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.stmt; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; + +namespace JavaToCSharp.Expressions; + +public class SwitchExpressionVisitor : ExpressionVisitor +{ + protected override ExpressionSyntax Visit(ConversionContext context, SwitchExpr expr) + { + var entries = expr.getEntries().ToList() ?? []; + + var governingExpr = VisitExpression(context, expr.getSelector()) + ?? throw new InvalidOperationException("Switch expression selector cannot be null"); + + return SwitchExpression( + governingExpr, + SeparatedList(entries.Select(e => Visit(context, e))) + ); + } + + private SwitchExpressionArmSyntax Visit(ConversionContext context, SwitchEntry entry) + { + var pattern = GetArmPatternSyntax(context, entry); + var expr = GetArmExpressionSyntax(context, entry); + + return SwitchExpressionArm( + pattern, + expr + ); + } + + private static PatternSyntax GetArmPatternSyntax(ConversionContext context, SwitchEntry entry) + { + var labels = entry.getLabels().ToList() ?? []; + + if (labels.Count == 0) + { + return DiscardPattern(); + } + + var patterns = new List(); + + foreach (var label in labels) + { + if (VisitExpression(context, label) is not ExpressionSyntax labelExpr) + { + throw new InvalidOperationException("Switch expression label must contain an expression"); + } + + patterns.Add(ConstantPattern(labelExpr)); + } + + if (patterns.Count == 1) + { + return patterns[0]; + } + + var orPattern = BinaryPattern(SyntaxKind.OrPattern, patterns[0], patterns[1]); + + for (var i = 2; i < patterns.Count; i++) + { + orPattern = BinaryPattern(SyntaxKind.OrPattern, orPattern, patterns[i]); + } + + return orPattern ?? throw new InvalidOperationException("Switch expression label must contain an expression"); + } + + private static ExpressionSyntax GetArmExpressionSyntax(ConversionContext context, SwitchEntry entry) + { + var statements = entry.getStatements().ToList() ?? []; + + if (statements.Count > 1) + { + throw new InvalidOperationException("Switch expressions with multiple statements are not supported"); + } + + var armExpr = statements[0] switch + { + ThrowStmt throwStmt => throwStmt.getExpression(), + ExpressionStmt exprStmt => exprStmt.getExpression(), + _ => throw new InvalidOperationException("Only throw and expression statements are supported in switch expressions") + }; + + return VisitExpression(context, armExpr) + ?? throw new InvalidOperationException("Switch expression entry must contain a single expression statement"); + } +} diff --git a/JavaToCSharp/Expressions/ThisExpressionVisitor.cs b/JavaToCSharp/Expressions/ThisExpressionVisitor.cs index 51c6bb79..d1548cff 100644 --- a/JavaToCSharp/Expressions/ThisExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ThisExpressionVisitor.cs @@ -6,5 +6,5 @@ namespace JavaToCSharp.Expressions; public class ThisExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, ThisExpr expr) => SyntaxFactory.ThisExpression(); + protected override ExpressionSyntax Visit(ConversionContext context, ThisExpr expr) => SyntaxFactory.ThisExpression(); } diff --git a/JavaToCSharp/Expressions/TypeExpressionVisitor.cs b/JavaToCSharp/Expressions/TypeExpressionVisitor.cs index 6887f328..f559b484 100644 --- a/JavaToCSharp/Expressions/TypeExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/TypeExpressionVisitor.cs @@ -6,5 +6,5 @@ namespace JavaToCSharp.Expressions; public class TypeExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax Visit(ConversionContext context, TypeExpr expr) => SyntaxFactory.ParseTypeName(TypeHelper.ConvertTypeOf(expr)); -} \ No newline at end of file + protected override ExpressionSyntax Visit(ConversionContext context, TypeExpr expr) => SyntaxFactory.ParseTypeName(TypeHelper.ConvertTypeOf(expr)); +} diff --git a/JavaToCSharp/Expressions/UnaryExpressionVisitor.cs b/JavaToCSharp/Expressions/UnaryExpressionVisitor.cs index 834c0634..62cfb43e 100644 --- a/JavaToCSharp/Expressions/UnaryExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/UnaryExpressionVisitor.cs @@ -6,10 +6,11 @@ namespace JavaToCSharp.Expressions; public class UnaryExpressionVisitor : ExpressionVisitor { - public override ExpressionSyntax? Visit(ConversionContext context, UnaryExpr unaryExpr) + protected override ExpressionSyntax? Visit(ConversionContext context, UnaryExpr unaryExpr) { var expr = unaryExpr.getExpression(); var exprSyntax = VisitExpression(context, expr); + if (exprSyntax is null) { return null; diff --git a/JavaToCSharp/JavaConversionOptions.cs b/JavaToCSharp/JavaConversionOptions.cs index cf626e41..5ae09a4f 100644 --- a/JavaToCSharp/JavaConversionOptions.cs +++ b/JavaToCSharp/JavaConversionOptions.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; namespace JavaToCSharp; @@ -21,19 +19,21 @@ public class JavaConversionOptions "System.Text" }; + public IList StaticUsingEnumNames { get; } = new List(); + public bool IncludeUsings { get; set; } = true; public bool IncludeNamespace { get; set; } = true; public bool UseDebugAssertForAsserts { get; set; } - + public bool StartInterfaceNamesWithI { get; set; } /// /// Unrecognized code is translated into comments /// public bool UseUnrecognizedCodeToComment { get; set; } = true; - + public bool ConvertSystemOutToConsole { get; set; } public bool IncludeComments { get; set; } = true; @@ -69,4 +69,4 @@ internal void ConversionStateChanged(ConversionState newState) StateChanged?.Invoke(this, new ConversionStateChangedEventArgs(newState)); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/JavaToCSharpConverter.cs b/JavaToCSharp/JavaToCSharpConverter.cs index 7b02bd9b..edd2d88a 100644 --- a/JavaToCSharp/JavaToCSharpConverter.cs +++ b/JavaToCSharp/JavaToCSharpConverter.cs @@ -28,6 +28,7 @@ public static class JavaToCSharpConverter options.ConversionStateChanged(ConversionState.ParsingJavaAst); var parser = new JavaParser(); + parser.getParserConfiguration().setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_17); var parsed = parser.parse(wrapper); @@ -82,38 +83,52 @@ public static class JavaToCSharpConverter { var interfaceSyntax = ClassOrInterfaceDeclarationVisitor.VisitInterfaceDeclaration(context, classOrIntType); - if (namespaceSyntax != null && interfaceSyntax != null) + if (namespaceSyntax != null) + { namespaceSyntax = namespaceSyntax.AddMembers(interfaceSyntax); - else if(interfaceSyntax != null) + } + else + { rootMembers.Add(interfaceSyntax); + } } else { var classSyntax = ClassOrInterfaceDeclarationVisitor.VisitClassDeclaration(context, classOrIntType); - if (namespaceSyntax != null && classSyntax != null) + if (namespaceSyntax != null) + { namespaceSyntax = namespaceSyntax.AddMembers(classSyntax); - else if(classSyntax != null) + } + else + { rootMembers.Add(classSyntax); + } } } else if (type is EnumDeclaration enumType) { var classSyntax = EnumDeclarationVisitor.VisitEnumDeclaration(context, enumType); - if (namespaceSyntax != null && classSyntax != null) + if (namespaceSyntax != null) + { namespaceSyntax = namespaceSyntax.AddMembers(classSyntax); - else if(classSyntax != null) + } + else + { rootMembers.Add(classSyntax); + } } } if (namespaceSyntax != null) + { rootMembers.Add(namespaceSyntax); + } var root = SyntaxFactory.CompilationUnit( externs: [], - usings: SyntaxFactory.List(UsingsHelper.GetUsings(context, imports, options)), + usings: SyntaxFactory.List(UsingsHelper.GetUsings(context, imports, options, rootMembers, namespaceSyntax)), attributeLists: [], members: SyntaxFactory.List(rootMembers) ) diff --git a/JavaToCSharp/Replacement.cs b/JavaToCSharp/Replacement.cs index 18937ed8..18874428 100644 --- a/JavaToCSharp/Replacement.cs +++ b/JavaToCSharp/Replacement.cs @@ -14,7 +14,7 @@ public Replacement(string pattern, string replacement, RegexOptions options = Re public string? ReplacementValue { get; } - public string? Replace(string input) => System.String.IsNullOrWhiteSpace(ReplacementValue) ? null : Regex?.Replace(input, ReplacementValue); + public string? Replace(string input) => string.IsNullOrWhiteSpace(ReplacementValue) ? null : Regex?.Replace(input, ReplacementValue); protected bool Equals(Replacement other) { diff --git a/JavaToCSharp/UsingsHelper.cs b/JavaToCSharp/UsingsHelper.cs index cbf24a0f..9b6df7c5 100644 --- a/JavaToCSharp/UsingsHelper.cs +++ b/JavaToCSharp/UsingsHelper.cs @@ -6,7 +6,11 @@ namespace JavaToCSharp; public static class UsingsHelper { - public static IList GetUsings(ConversionContext context, List imports, JavaConversionOptions? options) + public static IEnumerable GetUsings(ConversionContext context, + IEnumerable imports, + JavaConversionOptions? options, + IEnumerable rootMembers, + NamespaceDeclarationSyntax? namespaceSyntax) { var usings = new List(); @@ -36,6 +40,18 @@ public static IList GetUsings(ConversionContext context, L .Select(ns => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)))); } + if (namespaceSyntax != null) + { + foreach (var staticUsing in options?.StaticUsingEnumNames ?? []) + { + var usingSyntax = SyntaxFactory + .UsingDirective(SyntaxFactory.ParseName($"{namespaceSyntax.Name}.{staticUsing}")) + .WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + + usings.Add(usingSyntax); + } + } + return usings.Distinct(new UsingDirectiveSyntaxComparer()).ToList(); } } From 224c4f99938db53c008496673d8c0e425709f86e Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 15 Feb 2024 10:47:48 -0700 Subject: [PATCH 11/25] Update SECURITY.md --- SECURITY.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 33dc4248..928f7104 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,8 +7,8 @@ The following versions are supported for security updates: | Version | Supported | | ------- | ------------------ | | trunk | :white_check_mark: | -| 2.1.x | :white_check_mark: | -| 2.0.x | :x: | +| 3.0.x | :white_check_mark: | +| 2.x.x | :x: | | 1.x.x | :x: | ## Reporting a Vulnerability From d962dfc1d837aad5808328de8ccb66d31c2cb152 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 15 Feb 2024 11:00:27 -0700 Subject: [PATCH 12/25] Add .NET Support section to README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 724beaad..83175d87 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,23 @@ from the command line. The core library is installable via NuGet at https://www.nuget.org/packages/JavaToCSharp/ +## .NET Support + +Trunk will generally always target the latest LTS version of .NET for the core library and the CLI/GUI apps. +If you require running the app on a prior version of .NET, please see the historical releases. +Where STS BCL or preview (as of latest LTS SDK) C# language features are used in the converter, they will be options that are disabled by default. +These options may become enabled by default when the next LTS ships, as a major version bump. + +.NET Framework is no longer supported and will not be supported going forward. +This includes not just for the core library (i.e. .NET Standard will not be supported) and running the CLI/GUI apps, +but also for the C# code generation where applicable. +You should be running .NET LTS nowadays anyways (.NET 6+ at the time of writing). + +Bug fixes and features may be backported to the latest major version that targets an actively-supported, non-latest .NET LTS, +but only based on community interest. +Please submit an issue if you want a bugfix or feature backported; better yet, please submit a PR. +Only clean cherry-picks or minor merges will be supported for backports; significant/messy merges or PRs will likely not be supported or merged. + ## License for JavaParser Licensed under the Apache License available at https://github.com/javaparser/javaparser/blob/master/LICENSE.APACHE From 500334ae07a607b35e5693b8aca9b442d46d16a8 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Sat, 17 Feb 2024 16:30:16 -0700 Subject: [PATCH 13/25] UI refresh for better small screen support (#108) * GUI: Improvements for small screens and general UX This moves the usings/options selection to a new Settings window behind a gear icon, leaving more room for the Java/C# input/output panes. Additionally, Font Awesome icons were added for the GitHub button, settings gear button, open file button, and copy to clipboard button. The toggle button for light/dark mode has also been changed to use FA icons. Usings are now persisted to settings, which is a nice UX improvement. * GUI: Better support for folder conversion This functionality is now a top-level tab on the main screen instead of a checkbox with potentially confusing UX. * Add back default usings to CLI app --- JavaToCSharp.Tests/ConvertInterfaceTests.cs | 13 +- JavaToCSharp.Tests/IntegrationTests.cs | 2 + JavaToCSharp/JavaConversionOptions.cs | 21 +- JavaToCSharpCli/Program.cs | 28 +- JavaToCSharpGui/App.config | 3 + JavaToCSharpGui/CurrentOptions.cs | 34 ++ JavaToCSharpGui/JavaToCSharpGui.csproj | 1 + JavaToCSharpGui/Program.cs | 9 +- .../Properties/Settings.Designer.cs | 13 + JavaToCSharpGui/Properties/Settings.settings | 5 +- .../ViewModels/MainWindowViewModel.cs | 364 +++++++----------- .../ViewModels/SettingsWindowViewModel.cs | 67 ++++ JavaToCSharpGui/Views/MainWindow.axaml | 224 ++++++----- JavaToCSharpGui/Views/MainWindow.axaml.cs | 5 +- JavaToCSharpGui/Views/SettingsWindow.axaml | 65 ++++ JavaToCSharpGui/Views/SettingsWindow.axaml.cs | 37 ++ 16 files changed, 528 insertions(+), 363 deletions(-) create mode 100644 JavaToCSharpGui/CurrentOptions.cs create mode 100644 JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs create mode 100644 JavaToCSharpGui/Views/SettingsWindow.axaml create mode 100644 JavaToCSharpGui/Views/SettingsWindow.axaml.cs diff --git a/JavaToCSharp.Tests/ConvertInterfaceTests.cs b/JavaToCSharp.Tests/ConvertInterfaceTests.cs index 3d9b732b..96f5a425 100644 --- a/JavaToCSharp.Tests/ConvertInterfaceTests.cs +++ b/JavaToCSharp.Tests/ConvertInterfaceTests.cs @@ -31,12 +31,7 @@ public interface ResolvedValueDeclaration extends ResolvedDeclaration { const string expectedCSharpCode = """ using Com.Github.Javaparser.Resolution; - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Text; - + namespace MyApp { public interface IResolvedType @@ -77,12 +72,6 @@ public interface CharTermAttribute extends Attribute, CharSequence, Appendable { var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; const string expected = """ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Text; - namespace MyApp { public interface ICharTermAttribute : Attribute, CharSequence, Appendable diff --git a/JavaToCSharp.Tests/IntegrationTests.cs b/JavaToCSharp.Tests/IntegrationTests.cs index bcf129af..f12723d5 100644 --- a/JavaToCSharp.Tests/IntegrationTests.cs +++ b/JavaToCSharp.Tests/IntegrationTests.cs @@ -80,6 +80,8 @@ public void FullIntegrationTests(string filePath) IncludeComments = false, }; + options.AddUsing("System"); + options.WarningEncountered += (_, eventArgs) => throw new InvalidOperationException($"Encountered a warning in conversion: {eventArgs.Message}"); diff --git a/JavaToCSharp/JavaConversionOptions.cs b/JavaToCSharp/JavaConversionOptions.cs index 5ae09a4f..32ef8d70 100644 --- a/JavaToCSharp/JavaConversionOptions.cs +++ b/JavaToCSharp/JavaConversionOptions.cs @@ -10,14 +10,7 @@ public class JavaConversionOptions public IList PackageReplacements { get; } = new List(); - public IList Usings { get; } = new List - { - "System", - "System.Collections.Generic", - "System.Collections.ObjectModel", - "System.Linq", - "System.Text" - }; + public IList Usings { get; } = new List(); public IList StaticUsingEnumNames { get; } = new List(); @@ -61,6 +54,18 @@ public JavaConversionOptions AddUsing(string ns) return this; } + public JavaConversionOptions SetUsings(IEnumerable usings) + { + Usings.Clear(); + + foreach (string u in usings) + { + Usings.Add(u); + } + + return this; + } + internal void Warning(string message, int javaLineNumber) => WarningEncountered?.Invoke(this, new ConversionWarningEventArgs(message, javaLineNumber)); internal void ConversionStateChanged(ConversionState newState) diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index f8752b6c..a76f0770 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -17,37 +17,37 @@ public class Program name: "--include-usings", description: "Include using directives in output", getDefaultValue: () => true); - + private static readonly Option _includeNamespaceOption = new( name: "--include-namespace", description: "Include namespace in output", getDefaultValue: () => true); - + private static readonly Option _includeCommentsOption = new( name: "--include-comments", description: "Include comments in output", getDefaultValue: () => true); - + private static readonly Option _useDebugAssertOption = new( name: "--use-debug-assert", description: "Use Debug.Assert for asserts", getDefaultValue: () => false); - + private static readonly Option _startInterfaceNamesWithIOption = new( name: "--start-interface-names-with-i", description: "Prefix interface names with the letter I", getDefaultValue: () => true); - + private static readonly Option _commentUnrecognizedCodeOption = new( name: "--comment-unrecognized-code", description: "Include unrecognized code in output as commented-out code", getDefaultValue: () => true); - + private static readonly Option _systemOutToConsoleOption = new( name: "--system-out-to-console", description: "Convert System.out calls to Console", getDefaultValue: () => false); - + private static readonly Option _clearDefaultUsingsOption = new( name: "--clear-usings", description: "Remove all default usings provided by this app", @@ -76,7 +76,7 @@ public static async Task Main(string[] args) rootCommand.AddCommand(CreateFileCommand()); rootCommand.AddCommand(CreateDirectoryCommand()); - + rootCommand.AddGlobalOption(_includeUsingsOption); rootCommand.AddGlobalOption(_includeNamespaceOption); rootCommand.AddGlobalOption(_includeCommentsOption); @@ -138,12 +138,20 @@ private static JavaConversionOptions GetJavaConversionOptions(InvocationContext { options.ClearUsings(); } + else + { + options.AddUsing("System"); + options.AddUsing("System.Collections.Generic"); + options.AddUsing("System.Collections.ObjectModel"); + options.AddUsing("System.Linq"); + options.AddUsing("System.Text"); + } foreach (string ns in context.ParseResult.GetValueForOption(_addUsingsOption) ?? new List()) { options.AddUsing(ns); } - + return options; } @@ -256,4 +264,4 @@ private static void OutputFileOrPrint(string? fileName, string contents) File.WriteAllText(fileName, contents); } } -} \ No newline at end of file +} diff --git a/JavaToCSharpGui/App.config b/JavaToCSharpGui/App.config index 97587b2d..7e16a1b6 100644 --- a/JavaToCSharpGui/App.config +++ b/JavaToCSharpGui/App.config @@ -25,6 +25,9 @@ True + + System;System.Collections.Generic;System.Collections.ObjectModel;System.Linq;System.Text + \ No newline at end of file diff --git a/JavaToCSharpGui/CurrentOptions.cs b/JavaToCSharpGui/CurrentOptions.cs new file mode 100644 index 00000000..0c876194 --- /dev/null +++ b/JavaToCSharpGui/CurrentOptions.cs @@ -0,0 +1,34 @@ +using JavaToCSharp; +using JavaToCSharpGui.Properties; + +namespace JavaToCSharpGui; + +public static class CurrentOptions +{ + static CurrentOptions() + { + Options.IncludeUsings = Settings.Default.UseUsingsPreference; + Options.IncludeNamespace = Settings.Default.UseNamespacePreference; + Options.IncludeComments = Settings.Default.IncludeComments; + Options.UseDebugAssertForAsserts = Settings.Default.UseDebugAssertPreference; + Options.UseUnrecognizedCodeToComment = Settings.Default.UseUnrecognizedCodeToComment; + Options.ConvertSystemOutToConsole = Settings.Default.ConvertSystemOutToConsole; + + Options.SetUsings(Settings.Default.Usings.Split(';')); + } + + public static JavaConversionOptions Options { get; } = new JavaConversionOptions(); + + public static void Persist() + { + Settings.Default.UseUsingsPreference = Options.IncludeUsings; + Settings.Default.UseNamespacePreference = Options.IncludeNamespace; + Settings.Default.IncludeComments = Options.IncludeComments; + Settings.Default.UseDebugAssertPreference = Options.UseDebugAssertForAsserts; + Settings.Default.UseUnrecognizedCodeToComment = Options.UseUnrecognizedCodeToComment; + Settings.Default.ConvertSystemOutToConsole = Options.ConvertSystemOutToConsole; + Settings.Default.Usings = string.Join(";", Options.Usings); + + Settings.Default.Save(); + } +} diff --git a/JavaToCSharpGui/JavaToCSharpGui.csproj b/JavaToCSharpGui/JavaToCSharpGui.csproj index 08f41ee4..01bdde4e 100644 --- a/JavaToCSharpGui/JavaToCSharpGui.csproj +++ b/JavaToCSharpGui/JavaToCSharpGui.csproj @@ -26,6 +26,7 @@ + diff --git a/JavaToCSharpGui/Program.cs b/JavaToCSharpGui/Program.cs index b4e7b55b..c3856476 100644 --- a/JavaToCSharpGui/Program.cs +++ b/JavaToCSharpGui/Program.cs @@ -1,4 +1,6 @@ using Avalonia; +using Projektanker.Icons.Avalonia; +using Projektanker.Icons.Avalonia.FontAwesome; namespace JavaToCSharpGui; @@ -13,8 +15,13 @@ public static void Main(string[] args) => BuildAvaloniaApp() // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() - => AppBuilder.Configure() + { + IconProvider.Current + .Register(); + + return AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() .LogToTrace(); + } } diff --git a/JavaToCSharpGui/Properties/Settings.Designer.cs b/JavaToCSharpGui/Properties/Settings.Designer.cs index 6d9d0458..1cc99752 100644 --- a/JavaToCSharpGui/Properties/Settings.Designer.cs +++ b/JavaToCSharpGui/Properties/Settings.Designer.cs @@ -93,5 +93,18 @@ public bool IncludeComments { this["IncludeComments"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("System;System.Collections.Generic;System.Collections.ObjectModel;System.Linq;Syst" + + "em.Text")] + public string Usings { + get { + return ((string)(this["Usings"])); + } + set { + this["Usings"] = value; + } + } } } diff --git a/JavaToCSharpGui/Properties/Settings.settings b/JavaToCSharpGui/Properties/Settings.settings index 5535f883..694de9ad 100644 --- a/JavaToCSharpGui/Properties/Settings.settings +++ b/JavaToCSharpGui/Properties/Settings.settings @@ -20,5 +20,8 @@ True + + System;System.Collections.Generic;System.Collections.ObjectModel;System.Linq;System.Text + - \ No newline at end of file + diff --git a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs index 896e9c54..8b44f406 100644 --- a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs @@ -1,40 +1,37 @@ using System.Diagnostics; using Avalonia; -using CommunityToolkit.Mvvm.Input; using Avalonia.Collections; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Media; -using CommunityToolkit.Mvvm.ComponentModel; using Avalonia.Platform.Storage; using Avalonia.Threading; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; using JavaToCSharp; using JavaToCSharpGui.Infrastructure; +using JavaToCSharpGui.Views; namespace JavaToCSharpGui.ViewModels; public partial class MainWindowViewModel : ViewModelBase { - private const string CopyToClipboardDefaultText = "Copy to Clipboard"; - - private bool _includeUsings = true; - private bool _includeNamespace = true; - private bool _includeComments = true; - private bool _useDebugAssertForAsserts; - private bool _useUnrecognizedCodeToComment; - private bool _convertSystemOutToConsole; - private readonly IHostStorageProvider? _storageProvider; private readonly IUIDispatcher _dispatcher; private readonly ITextClipboard? _clipboard; + private bool _usingFolderConvert; + /// /// Constructor for the Avalonia Designer view inside the IDE. /// public MainWindowViewModel() { _dispatcher = new UIDispatcher(Dispatcher.UIThread); - - if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: not null } desktop) + + if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime + { + MainWindow: not null + } desktop) { _storageProvider = new HostStorageProvider(desktop.MainWindow.StorageProvider); _clipboard = new TextClipboard(desktop.MainWindow.Clipboard); @@ -53,24 +50,13 @@ public MainWindowViewModel(IHostStorageProvider storageProvider, IUIDispatcher d _dispatcher = dispatcher; _clipboard = clipboard; DisplayName = "Java to C# Converter"; - - _isConvertEnabled = true; - _useFolderConvert = false; - - _includeUsings = Properties.Settings.Default.UseUsingsPreference; - _includeNamespace = Properties.Settings.Default.UseNamespacePreference; - _includeComments = Properties.Settings.Default.IncludeComments; - _useDebugAssertForAsserts = Properties.Settings.Default.UseDebugAssertPreference; - _useUnrecognizedCodeToComment = Properties.Settings.Default.UseUnrecognizedCodeToComment; - _convertSystemOutToConsole = Properties.Settings.Default.ConvertSystemOutToConsole; } - [ObservableProperty] private string _addUsingInput = ""; + [ObservableProperty] private AvaloniaList _folderInputFiles = []; - private IList _javaFiles = new List(); - private string _currentJavaFile = ""; + [ObservableProperty] private AvaloniaList _folderOutputFiles = []; - [ObservableProperty] private AvaloniaList _usings = new(); + private string _currentJavaFile = ""; [ObservableProperty] private string _javaText = ""; @@ -78,113 +64,15 @@ public MainWindowViewModel(IHostStorageProvider storageProvider, IUIDispatcher d [ObservableProperty] private string _openPath = ""; - [ObservableProperty] private string _copyToClipboardText = CopyToClipboardDefaultText; + [ObservableProperty] private string _openFolderPath = ""; [ObservableProperty] private string _conversionStateLabel = ""; - [ObservableProperty] private string? _selectedUsing; - - [RelayCommand] - private void RemoveSelectedUsing() - { - if (SelectedUsing is not null && Usings.Contains(SelectedUsing)) - { - Usings.Remove(SelectedUsing); - } - } - - public bool IncludeUsings - { - get => _includeUsings; - set - { - SetProperty(ref _includeUsings, value); - Properties.Settings.Default.UseUsingsPreference = value; - Properties.Settings.Default.Save(); - } - } - - public bool IncludeNamespace - { - get => _includeNamespace; - set - { - SetProperty(ref _includeNamespace, value); - Properties.Settings.Default.UseNamespacePreference = value; - Properties.Settings.Default.Save(); - } - } - - public bool IncludeComments - { - get => _includeComments; - set - { - SetProperty(ref _includeComments, value); - Properties.Settings.Default.IncludeComments = value; - Properties.Settings.Default.Save(); - } - } - - public bool UseDebugAssertForAsserts - { - get => _useDebugAssertForAsserts; - set - { - SetProperty(ref _useDebugAssertForAsserts, value); - Properties.Settings.Default.UseDebugAssertPreference = value; - Properties.Settings.Default.Save(); - - if (value && !Usings.Contains("System.Diagnostics")) - { - AddUsingInput = "System.Diagnostics"; - AddUsing(); - } - } - } - - public bool UseUnrecognizedCodeToComment - { - get => _useUnrecognizedCodeToComment; - set - { - _useUnrecognizedCodeToComment = value; - SetProperty(ref _useUnrecognizedCodeToComment, value); - Properties.Settings.Default.UseUnrecognizedCodeToComment = value; - Properties.Settings.Default.Save(); - } - } - - public bool ConvertSystemOutToConsole - { - get => _convertSystemOutToConsole; - set - { - _convertSystemOutToConsole = value; - SetProperty(ref _convertSystemOutToConsole, value); - Properties.Settings.Default.ConvertSystemOutToConsole = value; - Properties.Settings.Default.Save(); - } - } - - public FontFamily MonospaceFontFamily { get; } = FontFamily.Parse("Cascadia Code,SF Mono,DejaVu Sans Mono,Menlo,Consolas"); + public FontFamily MonospaceFontFamily { get; } = + FontFamily.Parse("Cascadia Code,SF Mono,DejaVu Sans Mono,Menlo,Consolas"); [ObservableProperty] private bool _isConvertEnabled = true; - [ObservableProperty] private bool _useFolderConvert; - - [RelayCommand] - private void AddUsing() - { - Usings.Add(AddUsingInput); - AddUsingInput = string.Empty; - } - - public void RemoveUsing(string value) - { - Usings.Remove(value); - } - [ObservableProperty] private string _message = ""; [ObservableProperty] private string _messageTitle = ""; @@ -192,56 +80,37 @@ public void RemoveUsing(string value) [RelayCommand] private async Task Convert() { - var options = new JavaConversionOptions(); - options.ClearUsings(); - - foreach (string ns in Usings) - { - options.AddUsing(ns); - } - - options.IncludeUsings = IncludeUsings; - options.IncludeNamespace = IncludeNamespace; - options.IncludeComments = IncludeComments; - options.UseDebugAssertForAsserts = UseDebugAssertForAsserts; - options.UseUnrecognizedCodeToComment = UseUnrecognizedCodeToComment; - options.ConvertSystemOutToConsole = ConvertSystemOutToConsole; - - options.WarningEncountered += Options_WarningEncountered; - options.StateChanged += Options_StateChanged; + CurrentOptions.Options.WarningEncountered += Options_WarningEncountered; + CurrentOptions.Options.StateChanged += Options_StateChanged; IsConvertEnabled = false; + _usingFolderConvert = false; + await Task.Run(async () => { try { - if (UseFolderConvert) - { - await FolderConvert(options); - } - else - { - string? csharp = JavaToCSharpConverter.ConvertText(JavaText, options); - await DispatcherInvoke(() => CSharpText = csharp ?? ""); - } + string? csharp = JavaToCSharpConverter.ConvertText(JavaText, CurrentOptions.Options); + await DispatcherInvoke(() => CSharpText = csharp ?? ""); } catch (Exception ex) { await DispatcherInvoke(() => - ShowMessage($"There was an error converting the text to C#: {ex.GetBaseException().Message}", "Conversion Error")); + ShowMessage($"There was an error converting the text to C#: {ex.GetBaseException().Message}", + "Conversion Error")); ConversionStateLabel = ""; } finally { await DispatcherInvoke(() => IsConvertEnabled = true); + CurrentOptions.Options.WarningEncountered -= Options_WarningEncountered; + CurrentOptions.Options.StateChanged -= Options_StateChanged; } }); } - /// - /// Folder Browser OpenFolderDialog - /// + [RelayCommand] private async Task OpenFolderDialog() { if (_storageProvider?.CanPickFolder is true) @@ -252,9 +121,9 @@ private async Task OpenFolderDialog() AllowMultiple = false, SuggestedStartLocation = await _storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents) }; - + var result = await _storageProvider.OpenFolderPickerAsync(options); - + if (!result.Any()) { return; @@ -262,92 +131,110 @@ private async Task OpenFolderDialog() string path = result[0].Path.LocalPath; var dir = new DirectoryInfo(path); - + if (!dir.Exists) { - OpenPath = string.Empty; - JavaText = string.Empty; - _javaFiles = Array.Empty(); + OpenFolderPath = string.Empty; + FolderInputFiles.Clear(); return; } - OpenPath = path; + OpenFolderPath = path; - await Task.Run(async () => + await Task.Run(() => { var files = dir.GetFiles("*.java", SearchOption.AllDirectories); - _javaFiles = files; - - int subStartIndex = path.Length; - string javaText = string.Join(Environment.NewLine, files.Select(x => x.FullName[subStartIndex..])); - - await DispatcherInvoke(() => JavaText = javaText); + FolderInputFiles.Clear(); + FolderInputFiles.AddRange(files); + return Task.CompletedTask; }); } } - /// - /// Folder Code Convert - /// - /// The user options used for the conversion. - private async Task FolderConvert(JavaConversionOptions? options) + [RelayCommand] + private async Task FolderConvert() { - if (_javaFiles.Count == 0 || options == null) + if (FolderInputFiles.Count == 0) { ShowMessage("No Java files found in the specified folder!"); return; } - var dir = new DirectoryInfo(OpenPath); - var pDir = dir.Parent ?? throw new FileNotFoundException($"dir {OpenPath} parent"); - string dirName = dir.Name; - string outDirName = $"{dirName}_net_{DateTime.Now.Millisecond}"; - var outDir = pDir.CreateSubdirectory(outDirName); - - if (outDir is not { Exists: true }) - throw new FileNotFoundException($"outDir {outDirName}"); - - string outDirFullName = outDir.FullName; - int subStartIndex = dir.FullName.Length; - - foreach (var jFile in _javaFiles.Where(static x => x.Directory is not null)) - { - string jPath = jFile.Directory!.FullName; - string jOutPath = $"{outDirFullName}{jPath[subStartIndex..]}"; - string jOutFileName = Path.GetFileNameWithoutExtension(jFile.Name) + ".cs"; - string jOutFileFullName = Path.Combine(jOutPath, jOutFileName); - - _currentJavaFile = jFile.FullName; - - if (!Directory.Exists(jOutPath)) - Directory.CreateDirectory(jOutPath); + CurrentOptions.Options.WarningEncountered += Options_WarningEncountered; + CurrentOptions.Options.StateChanged += Options_StateChanged; - string jText = await File.ReadAllTextAsync(_currentJavaFile); - - if (string.IsNullOrEmpty(jText)) - continue; + IsConvertEnabled = false; + _usingFolderConvert = true; + FolderOutputFiles.Clear(); + await Task.Run(async () => + { try { - string? csText = JavaToCSharpConverter.ConvertText(jText, options); - await File.WriteAllTextAsync(jOutFileFullName, csText); + var dir = new DirectoryInfo(OpenFolderPath); + string dirName = dir.Name; + string outDirName = $"{dirName}_csharp_output"; + var outDir = new DirectoryInfo(Path.Combine(OpenFolderPath, outDirName)); - await DispatcherInvoke(() => + if (!outDir.Exists) + outDir.Create(); + + string outDirFullName = outDir.FullName; + int subStartIndex = dir.FullName.Length; + + foreach (var jFile in FolderInputFiles.Where(static x => x.Directory is not null)) { - CSharpText = - $"{CSharpText} {Environment.NewLine}=================={Environment.NewLine}out.path: {jOutPath},{Environment.NewLine}\t\tfile: {jOutFileName}"; - }); + string jPath = jFile.Directory!.FullName; + string jOutPath = $"{outDirFullName}{jPath[subStartIndex..]}"; + string jOutFileName = Path.GetFileNameWithoutExtension(jFile.Name) + ".cs"; + string jOutFileFullName = Path.Combine(jOutPath, jOutFileName); + + _currentJavaFile = jFile.FullName; + + if (!Directory.Exists(jOutPath)) + Directory.CreateDirectory(jOutPath); + + string jText = await File.ReadAllTextAsync(_currentJavaFile); + + if (string.IsNullOrEmpty(jText)) + continue; + + try + { + string? csText = JavaToCSharpConverter.ConvertText(jText, CurrentOptions.Options); + await File.WriteAllTextAsync(jOutFileFullName, csText); + + await DispatcherInvoke(() => + { + FolderOutputFiles.Add(jOutFileFullName); + }); + } + catch (Exception ex) + { + await DispatcherInvoke(() => + { + ShowMessage($"There was an error converting {jFile.FullName} to C#: {ex.GetBaseException().Message}", + "Conversion Error"); + }); + } + } } catch (Exception ex) { await DispatcherInvoke(() => - { - CSharpText = - $"{CSharpText} {Environment.NewLine}=================={Environment.NewLine}[ERROR]out.path: {jOutPath},{Environment.NewLine}ex: {ex} {Environment.NewLine}"; - }); + ShowMessage($"There was an error converting the text to C#: {ex.GetBaseException().Message}", + "Conversion Error")); + + ConversionStateLabel = ""; } - } + finally + { + await DispatcherInvoke(() => IsConvertEnabled = true); + CurrentOptions.Options.WarningEncountered -= Options_WarningEncountered; + CurrentOptions.Options.StateChanged -= Options_StateChanged; + } + }); } [RelayCommand] @@ -381,7 +268,7 @@ private void Options_StateChanged(object? sender, ConversionStateChangedEventArg private async void Options_WarningEncountered(object? sender, ConversionWarningEventArgs e) { - if (UseFolderConvert) + if (_usingFolderConvert) { await DispatcherInvoke(() => { @@ -398,11 +285,7 @@ await DispatcherInvoke(() => [RelayCommand] private async Task OpenFileDialog() { - if (UseFolderConvert) - { - await OpenFolderDialog(); - } - else if (_storageProvider?.CanOpen is true) + if (_storageProvider?.CanOpen is true) { var filePickerOpenOptions = new FilePickerOpenOptions { @@ -416,7 +299,7 @@ private async Task OpenFileDialog() }; var result = await _storageProvider.OpenFilePickerAsync(filePickerOpenOptions); - + if (result.Any()) { OpenPath = result[0].Path.LocalPath; @@ -434,23 +317,38 @@ private async Task CopyOutput() } await _clipboard.SetTextAsync(CSharpText); - CopyToClipboardText = "Copied!"; - - await Task.Delay(1000); - - await _dispatcher.InvokeAsync(() => - { - CopyToClipboardText = CopyToClipboardDefaultText; - }, DispatcherPriority.Background); + ConversionStateLabel = "Copied C# code to clipboard!"; + + await Task.Delay(2000); + + await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); } [RelayCommand] private static void ForkMeOnGitHub() => Process.Start(new ProcessStartInfo { - FileName = "https://www.github.com/paulirwin/javatocsharp", + FileName = "https://github.com/paulirwin/javatocsharp", UseShellExecute = true }); + [RelayCommand] + private static void OpenSettings() + { + var parent = Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop + ? desktop.MainWindow + : null; + var settings = new SettingsWindow(); + + if (parent is not null) + { + settings.ShowDialog(parent); + } + else + { + settings.Show(); + } + } + private async Task DispatcherInvoke(Action callback) => await _dispatcher.InvokeAsync(callback, DispatcherPriority.Normal); -} \ No newline at end of file +} diff --git a/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs b/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs new file mode 100644 index 00000000..2395ff20 --- /dev/null +++ b/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs @@ -0,0 +1,67 @@ +using Avalonia.Collections; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; + +namespace JavaToCSharpGui.ViewModels; + +public partial class SettingsWindowViewModel : ViewModelBase +{ + [ObservableProperty] private string _addUsingInput = ""; + + [ObservableProperty] private AvaloniaList _usings = new(CurrentOptions.Options.Usings); + + [ObservableProperty] private string? _selectedUsing; + + [ObservableProperty] private bool _includeUsings = CurrentOptions.Options.IncludeUsings; + + [ObservableProperty] private bool _includeNamespace = CurrentOptions.Options.IncludeNamespace; + + [ObservableProperty] private bool _includeComments = CurrentOptions.Options.IncludeComments; + + [ObservableProperty] private bool _useDebugAssertForAsserts = CurrentOptions.Options.UseDebugAssertForAsserts; + + [ObservableProperty] private bool _unrecognizedCodeToComment = CurrentOptions.Options.UseUnrecognizedCodeToComment; + + [ObservableProperty] private bool _convertSystemOutToConsole = CurrentOptions.Options.ConvertSystemOutToConsole; + + public event EventHandler? CloseRequested; + + [RelayCommand] + private void RemoveSelectedUsing() + { + if (SelectedUsing is not null && Usings.Contains(SelectedUsing)) + { + Usings.Remove(SelectedUsing); + } + } + + [RelayCommand] + private void AddUsing() + { + Usings.Add(AddUsingInput); + AddUsingInput = string.Empty; + } + + [RelayCommand] + private void Save() + { + CurrentOptions.Options.IncludeUsings = IncludeUsings; + CurrentOptions.Options.IncludeNamespace = IncludeNamespace; + CurrentOptions.Options.IncludeComments = IncludeComments; + CurrentOptions.Options.UseDebugAssertForAsserts = UseDebugAssertForAsserts; + CurrentOptions.Options.UseUnrecognizedCodeToComment = UnrecognizedCodeToComment; + CurrentOptions.Options.ConvertSystemOutToConsole = ConvertSystemOutToConsole; + + CurrentOptions.Options.SetUsings(Usings); + + CurrentOptions.Persist(); + + CloseRequested?.Invoke(this, EventArgs.Empty); + } + + [RelayCommand] + private void Cancel() + { + CloseRequested?.Invoke(this, EventArgs.Empty); + } +} diff --git a/JavaToCSharpGui/Views/MainWindow.axaml b/JavaToCSharpGui/Views/MainWindow.axaml index fa40cdb1..78e318e8 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml +++ b/JavaToCSharpGui/Views/MainWindow.axaml @@ -3,6 +3,7 @@ xmlns:vm="using:JavaToCSharpGui.ViewModels" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:i="https://github.com/projektanker/icons.avalonia" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="JavaToCSharpGui.Views.MainWindow" x:DataType="vm:MainWindowViewModel" @@ -15,7 +16,7 @@ - + @@ -24,115 +25,148 @@ to C#. It does not resolve symbols or namespaces, so the resulting C# code likely will not compile without modification. You must verify the results of the conversion manually. - - + + + + + Java Source Code Input: + + File: + + + + - - - - - Include Usings in Output - Include Namespace in Output - Include Comments in Output - - Use Debug.Assert() for asserts - - - Use Unrecognized Code To Comment - - Use Folder Convert - - Convert System.out to Console - - - - - - - Java Source Code Input: - - File: - - + + C# Output: + + + + + - - - - - - C# Output: - - - + + + + + + C# Output Paths: + + + + - - - + + @@ -155,4 +189,4 @@ - \ No newline at end of file + diff --git a/JavaToCSharpGui/Views/MainWindow.axaml.cs b/JavaToCSharpGui/Views/MainWindow.axaml.cs index 4b047213..83c1e1c9 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml.cs +++ b/JavaToCSharpGui/Views/MainWindow.axaml.cs @@ -23,9 +23,8 @@ protected override void OnOpened(EventArgs e) var vm = new MainWindowViewModel(storageProvider, dispatcher, clipboard); DataContext = vm; - this.Usings.DoubleTapped += (_, _) => vm.RemoveSelectedUsingCommand.Execute(null); } - + private void ToggleButton_OnIsCheckedChanged(object sender, RoutedEventArgs e) { var app = Application.Current; @@ -35,4 +34,4 @@ private void ToggleButton_OnIsCheckedChanged(object sender, RoutedEventArgs e) app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; } } -} \ No newline at end of file +} diff --git a/JavaToCSharpGui/Views/SettingsWindow.axaml b/JavaToCSharpGui/Views/SettingsWindow.axaml new file mode 100644 index 00000000..69c8f08f --- /dev/null +++ b/JavaToCSharpGui/Views/SettingsWindow.axaml @@ -0,0 +1,65 @@ + + + + + + Add Usings: + + + + + + + + + + + + Include usings in output + + + Include namespace in output + + + Include comments in output + + + Use Debug.Assert() for asserts + + + Comment out unrecognized code + + + Convert System.out to Console + + + + + + + + + + diff --git a/JavaToCSharpGui/Views/SettingsWindow.axaml.cs b/JavaToCSharpGui/Views/SettingsWindow.axaml.cs new file mode 100644 index 00000000..c8b29851 --- /dev/null +++ b/JavaToCSharpGui/Views/SettingsWindow.axaml.cs @@ -0,0 +1,37 @@ +using Avalonia.Controls; +using Avalonia.Interactivity; +using JavaToCSharpGui.ViewModels; + +namespace JavaToCSharpGui.Views; + +public partial class SettingsWindow : Window +{ + public SettingsWindow() + { + InitializeComponent(); + } + + protected override void OnOpened(EventArgs e) + { + base.OnOpened(e); + + var vm = new SettingsWindowViewModel(); + DataContext = vm; + + vm.CloseRequested += OnCloseRequested; + + Usings.DoubleTapped += (_, _) => vm.RemoveSelectedUsingCommand.Execute(null); + } + + private void OnCloseRequested(object? o, EventArgs eventArgs) => Close(); + + protected override void OnUnloaded(RoutedEventArgs e) + { + if (DataContext is SettingsWindowViewModel vm) + { + vm.CloseRequested -= OnCloseRequested; + } + + base.OnUnloaded(e); + } +} From 24f31ebd7f9f56c6a09b4256631152fee1a56e71 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Sat, 17 Feb 2024 16:48:37 -0700 Subject: [PATCH 14/25] GUI: Add save button, #109 (#110) --- .../Infrastructure/HostStorageProvider.cs | 31 ++++++++++------ .../Infrastructure/IHostStorageProvider.cs | 19 ++++++++++ .../ViewModels/MainWindowViewModel.cs | 36 +++++++++++++++++++ JavaToCSharpGui/Views/MainWindow.axaml | 7 ++++ 4 files changed, 83 insertions(+), 10 deletions(-) diff --git a/JavaToCSharpGui/Infrastructure/HostStorageProvider.cs b/JavaToCSharpGui/Infrastructure/HostStorageProvider.cs index f70a1fba..3c8f31c4 100644 --- a/JavaToCSharpGui/Infrastructure/HostStorageProvider.cs +++ b/JavaToCSharpGui/Infrastructure/HostStorageProvider.cs @@ -3,33 +3,44 @@ namespace JavaToCSharpGui.Infrastructure; /// -public class HostStorageProvider : IHostStorageProvider +public class HostStorageProvider(IStorageProvider storageProvider) : IHostStorageProvider { - private readonly IStorageProvider _storageProvider; - - public HostStorageProvider(IStorageProvider storageProvider) => _storageProvider = storageProvider; + /// + public bool CanPickFolder => storageProvider.CanPickFolder; /// - public bool CanPickFolder => _storageProvider.CanPickFolder; + public bool CanOpen => storageProvider.CanOpen; /// - public bool CanOpen => _storageProvider.CanOpen; + public bool CanSave => storageProvider.CanSave; /// public async Task> OpenFilePickerAsync(FilePickerOpenOptions options) { - return await _storageProvider.OpenFilePickerAsync(options); + return await storageProvider.OpenFilePickerAsync(options); } /// public async Task> OpenFolderPickerAsync(FolderPickerOpenOptions options) { - return await _storageProvider.OpenFolderPickerAsync(options); + return await storageProvider.OpenFolderPickerAsync(options); } - + /// public async Task TryGetWellKnownFolderAsync(WellKnownFolder wellKnownFolder) { - return await _storageProvider.TryGetWellKnownFolderAsync(wellKnownFolder); + return await storageProvider.TryGetWellKnownFolderAsync(wellKnownFolder); + } + + /// + public async Task OpenSaveFileDialogAsync(FilePickerSaveOptions options) + { + return await storageProvider.SaveFilePickerAsync(options); + } + + /// + public async Task TryGetFolderFromPathAsync(string path) + { + return await storageProvider.TryGetFolderFromPathAsync(path); } } diff --git a/JavaToCSharpGui/Infrastructure/IHostStorageProvider.cs b/JavaToCSharpGui/Infrastructure/IHostStorageProvider.cs index 6bb0d994..2773e2d8 100644 --- a/JavaToCSharpGui/Infrastructure/IHostStorageProvider.cs +++ b/JavaToCSharpGui/Infrastructure/IHostStorageProvider.cs @@ -17,6 +17,11 @@ public interface IHostStorageProvider /// bool CanOpen { get; } + /// + /// Can the save file picker be opened on the current platform. + /// + bool CanSave { get; } + /// /// Gets the path to a well known folder. /// @@ -37,4 +42,18 @@ public interface IHostStorageProvider /// The file picker configuration. /// A list of selected files. Task> OpenFilePickerAsync(FilePickerOpenOptions options); + + /// + /// Opens the save file picker dialog. + /// + /// The file picker configuration. + /// The selected file. + Task OpenSaveFileDialogAsync(FilePickerSaveOptions options); + + /// + /// Tries to get a folder from a path. + /// + /// The path to the folder. + /// The folder, or null if not found. + Task TryGetFolderFromPathAsync(string path); } diff --git a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs index 8b44f406..d167c731 100644 --- a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs @@ -324,6 +324,42 @@ private async Task CopyOutput() await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); } + [RelayCommand] + private async Task SaveOutput() + { + if (_storageProvider?.CanSave is true) + { + IStorageFolder? startLocation = null; + + if (Path.GetDirectoryName(OpenPath) is string dir) + { + startLocation = await _storageProvider.TryGetFolderFromPathAsync(dir); + } + + startLocation ??= await _storageProvider.TryGetWellKnownFolderAsync(WellKnownFolder.Documents); + + var filePickerSaveOptions = new FilePickerSaveOptions + { + SuggestedFileName = Path.GetFileNameWithoutExtension(OpenPath) + ".cs", + SuggestedStartLocation = startLocation, + Title = "Save C# File" + }; + + var result = await _storageProvider.OpenSaveFileDialogAsync(filePickerSaveOptions); + + if (result is not null) + { + await File.WriteAllTextAsync(result.Path.LocalPath, CSharpText); + + ConversionStateLabel = "Saved C# code to file!"; + + await Task.Delay(2000); + + await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); + } + } + } + [RelayCommand] private static void ForkMeOnGitHub() => Process.Start(new ProcessStartInfo { diff --git a/JavaToCSharpGui/Views/MainWindow.axaml b/JavaToCSharpGui/Views/MainWindow.axaml index 78e318e8..95c3039c 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml +++ b/JavaToCSharpGui/Views/MainWindow.axaml @@ -93,6 +93,7 @@ + Date: Tue, 20 Feb 2024 11:13:42 -0700 Subject: [PATCH 15/25] GUI: Improve editor UI and add About dialog, #112 (#114) This cleans up the UI generally and adds AvaloniaEdit as a rich editor with syntax highlighting and many other features. The warning note has been moved to a new About dialog to clean up the header a bit. --- JavaToCSharp/JavaToCSharp.csproj | 2 +- JavaToCSharpCli/JavaToCSharpCli.csproj | 2 +- JavaToCSharpGui/App.axaml | 10 ++-- JavaToCSharpGui/JavaToCSharpGui.csproj | 5 +- .../ViewModels/MainWindowViewModel.cs | 41 +++++++++++---- JavaToCSharpGui/Views/AboutWindow.axaml | 30 +++++++++++ JavaToCSharpGui/Views/AboutWindow.axaml.cs | 32 ++++++++++++ JavaToCSharpGui/Views/MainWindow.axaml | 51 ++++++++++--------- JavaToCSharpGui/Views/MainWindow.axaml.cs | 32 ++++++++++++ 9 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 JavaToCSharpGui/Views/AboutWindow.axaml create mode 100644 JavaToCSharpGui/Views/AboutWindow.axaml.cs diff --git a/JavaToCSharp/JavaToCSharp.csproj b/JavaToCSharp/JavaToCSharp.csproj index 29179755..bc7e8553 100644 --- a/JavaToCSharp/JavaToCSharp.csproj +++ b/JavaToCSharp/JavaToCSharp.csproj @@ -1,6 +1,6 @@  - 3.0.0 + 4.0.0 net8.0 enable enable diff --git a/JavaToCSharpCli/JavaToCSharpCli.csproj b/JavaToCSharpCli/JavaToCSharpCli.csproj index a5bf66cc..a2a5b7cd 100644 --- a/JavaToCSharpCli/JavaToCSharpCli.csproj +++ b/JavaToCSharpCli/JavaToCSharpCli.csproj @@ -1,6 +1,6 @@  - 3.0.0 + 4.0.0 Exe net8.0 latest diff --git a/JavaToCSharpGui/App.axaml b/JavaToCSharpGui/App.axaml index 00e2485b..3253d7b7 100644 --- a/JavaToCSharpGui/App.axaml +++ b/JavaToCSharpGui/App.axaml @@ -1,11 +1,15 @@  - + + 14 + Cascadia Code,SF Mono,DejaVu Sans Mono,Menlo,Consolas + + + - \ No newline at end of file + diff --git a/JavaToCSharpGui/JavaToCSharpGui.csproj b/JavaToCSharpGui/JavaToCSharpGui.csproj index 01bdde4e..7c2ed3c4 100644 --- a/JavaToCSharpGui/JavaToCSharpGui.csproj +++ b/JavaToCSharpGui/JavaToCSharpGui.csproj @@ -1,6 +1,6 @@  - 3.0.0 + 4.0.0 true app.manifest WinExe @@ -16,6 +16,8 @@ + + @@ -28,6 +30,7 @@ + diff --git a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs index d167c731..33242393 100644 --- a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Platform.Storage; using Avalonia.Threading; +using AvaloniaEdit.Document; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using JavaToCSharp; @@ -58,9 +59,9 @@ public MainWindowViewModel(IHostStorageProvider storageProvider, IUIDispatcher d private string _currentJavaFile = ""; - [ObservableProperty] private string _javaText = ""; + [ObservableProperty] private TextDocument _javaText = new(); - [ObservableProperty] private string _cSharpText = ""; + [ObservableProperty] private TextDocument _cSharpText = new(); [ObservableProperty] private string _openPath = ""; @@ -86,12 +87,14 @@ private async Task Convert() IsConvertEnabled = false; _usingFolderConvert = false; + var text = JavaText.Text; + await Task.Run(async () => { try { - string? csharp = JavaToCSharpConverter.ConvertText(JavaText, CurrentOptions.Options); - await DispatcherInvoke(() => CSharpText = csharp ?? ""); + string? csharp = JavaToCSharpConverter.ConvertText(text, CurrentOptions.Options); + await DispatcherInvoke(() => CSharpText.Text = csharp ?? ""); } catch (Exception ex) { @@ -272,8 +275,10 @@ private async void Options_WarningEncountered(object? sender, ConversionWarningE { await DispatcherInvoke(() => { - CSharpText = - $"{CSharpText} {Environment.NewLine}=================={Environment.NewLine}[WARN]out.path: {_currentJavaFile},{Environment.NewLine}\t\tConversionWarning-JavaLine:[{e.JavaLineNumber}]-Message:[{e.Message}]{Environment.NewLine}"; + CSharpText.Text = $"{CSharpText.Text}{Environment.NewLine}" + + $"=================={Environment.NewLine}" + + $"[WARN] {_currentJavaFile}{e.JavaLineNumber}{Environment.NewLine}" + + $"\t\tMessage: {e.Message}{Environment.NewLine}"; }); } else @@ -303,7 +308,7 @@ private async Task OpenFileDialog() if (result.Any()) { OpenPath = result[0].Path.LocalPath; - JavaText = await File.ReadAllTextAsync(result[0].Path.LocalPath); + JavaText.Text = await File.ReadAllTextAsync(result[0].Path.LocalPath); } } } @@ -316,7 +321,7 @@ private async Task CopyOutput() return; } - await _clipboard.SetTextAsync(CSharpText); + await _clipboard.SetTextAsync(CSharpText.Text); ConversionStateLabel = "Copied C# code to clipboard!"; await Task.Delay(2000); @@ -349,7 +354,7 @@ private async Task SaveOutput() if (result is not null) { - await File.WriteAllTextAsync(result.Path.LocalPath, CSharpText); + await File.WriteAllTextAsync(result.Path.LocalPath, CSharpText.Text); ConversionStateLabel = "Saved C# code to file!"; @@ -385,6 +390,24 @@ private static void OpenSettings() } } + [RelayCommand] + private static void OpenAbout() + { + var parent = Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop + ? desktop.MainWindow + : null; + var about = new AboutWindow(); + + if (parent is not null) + { + about.ShowDialog(parent); + } + else + { + about.Show(); + } + } + private async Task DispatcherInvoke(Action callback) => await _dispatcher.InvokeAsync(callback, DispatcherPriority.Normal); } diff --git a/JavaToCSharpGui/Views/AboutWindow.axaml b/JavaToCSharpGui/Views/AboutWindow.axaml new file mode 100644 index 00000000..ce0a4172 --- /dev/null +++ b/JavaToCSharpGui/Views/AboutWindow.axaml @@ -0,0 +1,30 @@ + + + Java to C# + + + NOTE: This tool does a + syntactic conversion only from Java + to C#. It does not resolve symbols or namespaces, so the resulting C# code likely will not compile without + modification. You must verify the results of the conversion manually. + + + https://github.com/paulirwin/javatocsharp + + + diff --git a/JavaToCSharpGui/Views/AboutWindow.axaml.cs b/JavaToCSharpGui/Views/AboutWindow.axaml.cs new file mode 100644 index 00000000..626d9114 --- /dev/null +++ b/JavaToCSharpGui/Views/AboutWindow.axaml.cs @@ -0,0 +1,32 @@ +using System.Diagnostics; +using System.Reflection; +using Avalonia.Controls; +using Avalonia.Input; + +namespace JavaToCSharpGui.Views; + +public partial class AboutWindow : Window +{ + private readonly string _version; + + public AboutWindow() + { + var assembly = Assembly.GetExecutingAssembly(); + _version = assembly.GetCustomAttribute()?.InformationalVersion + ?? assembly.GetName().Version?.ToString() + ?? "Unknown"; + + InitializeComponent(); + DataContext = this; + + + } + + public string VersionString => $"Version {_version}"; + + private void GitHubLinkTapped(object? sender, TappedEventArgs e) => Process.Start(new ProcessStartInfo + { + FileName = "https://github.com/paulirwin/javatocsharp", + UseShellExecute = true + }); +} diff --git a/JavaToCSharpGui/Views/MainWindow.axaml b/JavaToCSharpGui/Views/MainWindow.axaml index 95c3039c..2a9ec3f2 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml +++ b/JavaToCSharpGui/Views/MainWindow.axaml @@ -4,6 +4,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:i="https://github.com/projektanker/icons.avalonia" + xmlns:AvaloniaEdit="clr-namespace:AvaloniaEdit;assembly=AvaloniaEdit" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="JavaToCSharpGui.Views.MainWindow" x:DataType="vm:MainWindowViewModel" @@ -19,12 +20,7 @@ - - NOTE: This tool does a - syntactic conversion only from Java - to C#. It does not resolve symbols or namespaces, so the resulting C# code likely will not compile without - modification. You must verify the results of the conversion manually. - + Java to C# + - + - + diff --git a/JavaToCSharpGui/Views/MainWindow.axaml.cs b/JavaToCSharpGui/Views/MainWindow.axaml.cs index 83c1e1c9..c19dd313 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml.cs +++ b/JavaToCSharpGui/Views/MainWindow.axaml.cs @@ -2,8 +2,11 @@ using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Styling; +using AvaloniaEdit; +using AvaloniaEdit.TextMate; using JavaToCSharpGui.Infrastructure; using JavaToCSharpGui.ViewModels; +using TextMateSharp.Grammars; namespace JavaToCSharpGui.Views; @@ -23,6 +26,34 @@ protected override void OnOpened(EventArgs e) var vm = new MainWindowViewModel(storageProvider, dispatcher, clipboard); DataContext = vm; + + ConfigureEditors(); + InstallTextMate(); + } + + private void ConfigureEditors() + { + ConfigureEditor(JavaTextEditor); + ConfigureEditor(CSharpTextEditor); + } + + private static void ConfigureEditor(ITextEditorComponent editor) + { + // TODO.PI: make these options in settings for people with odd preferences + editor.Options.ConvertTabsToSpaces = true; + editor.Options.IndentationSize = 4; + } + + private void InstallTextMate() + { + var appTheme = Application.Current?.ActualThemeVariant ?? ThemeVariant.Dark; + var registryOptions = new RegistryOptions(appTheme == ThemeVariant.Dark ? ThemeName.DarkPlus : ThemeName.LightPlus); + + var javaTextMate = JavaTextEditor.InstallTextMate(registryOptions); + javaTextMate.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".java").Id)); + + var csharpTextMate = CSharpTextEditor.InstallTextMate(registryOptions); + csharpTextMate.SetGrammar(registryOptions.GetScopeByLanguageId(registryOptions.GetLanguageByExtension(".cs").Id)); } private void ToggleButton_OnIsCheckedChanged(object sender, RoutedEventArgs e) @@ -32,6 +63,7 @@ private void ToggleButton_OnIsCheckedChanged(object sender, RoutedEventArgs e) { var theme = app.ActualThemeVariant; app.RequestedThemeVariant = theme == ThemeVariant.Dark ? ThemeVariant.Light : ThemeVariant.Dark; + InstallTextMate(); } } } From ac597a55acf33b224b3f39866c5e21cc3b82aa4b Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 22 Feb 2024 19:39:43 -0700 Subject: [PATCH 16/25] Add support for file-scoped namespaces, #113 (#115) This change surfaced some annoying whitespace behavior with Roslyn's NormalizeWhitespace method, so this adds some leading/trailing newlines in places after NormalizeWhitespace is called to help prettify the output. --- JavaToCSharp/CommentsHelper.cs | 2 +- .../ConstructorDeclarationVisitor.cs | 4 +- JavaToCSharp/Extensions.cs | 9 +++ JavaToCSharp/JavaConversionOptions.cs | 2 + JavaToCSharp/JavaToCSharpConverter.cs | 66 +++++++++---------- JavaToCSharp/UsingsHelper.cs | 27 +++++--- JavaToCSharp/Whitespace.cs | 11 ++++ JavaToCSharpCli/Program.cs | 9 ++- JavaToCSharpGui/App.config | 3 + JavaToCSharpGui/CurrentOptions.cs | 2 + .../Properties/Settings.Designer.cs | 12 ++++ JavaToCSharpGui/Properties/Settings.settings | 3 + .../ViewModels/SettingsWindowViewModel.cs | 3 + JavaToCSharpGui/Views/SettingsWindow.axaml | 4 ++ 14 files changed, 109 insertions(+), 48 deletions(-) create mode 100644 JavaToCSharp/Whitespace.cs diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index 8ab6d151..8aacd4a9 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -448,7 +448,7 @@ static SyntaxNode InsertEmptyLineBeforeComment(SyntaxNode node) } node = statement.InsertTriviaBefore(leading[index], - Enumerable.Repeat(SyntaxFactory.CarriageReturnLineFeed, 1)); + Enumerable.Repeat(Whitespace.NewLine, 1)); } } diff --git a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs index 73589f87..39b28fb3 100644 --- a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs @@ -3,7 +3,6 @@ using com.github.javaparser.ast.stmt; using com.github.javaparser.ast.type; using JavaToCSharp.Statements; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -24,8 +23,7 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor(this Optional optional) public static ISet ToModifierKeywordSet(this JavaAst.NodeList nodeList) => nodeList.ToList()?.Select(i => i.getKeyword()).ToHashSet() ?? new HashSet(); + + public static TSyntax WithLeadingNewLines(this TSyntax syntax, int count = 1) + where TSyntax : SyntaxNode + => syntax.WithLeadingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); + + public static TSyntax WithTrailingNewLines(this TSyntax syntax, int count = 1) + where TSyntax : SyntaxNode + => syntax.WithTrailingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); } diff --git a/JavaToCSharp/JavaConversionOptions.cs b/JavaToCSharp/JavaConversionOptions.cs index 32ef8d70..2d0c0e57 100644 --- a/JavaToCSharp/JavaConversionOptions.cs +++ b/JavaToCSharp/JavaConversionOptions.cs @@ -31,6 +31,8 @@ public class JavaConversionOptions public bool IncludeComments { get; set; } = true; + public bool UseFileScopedNamespaces { get; set; } + public ConversionState ConversionState { get; set; } public JavaConversionOptions AddPackageReplacement(string pattern, string replacement, RegexOptions options = RegexOptions.None) diff --git a/JavaToCSharp/JavaToCSharpConverter.cs b/JavaToCSharp/JavaToCSharpConverter.cs index edd2d88a..3d11362e 100644 --- a/JavaToCSharp/JavaToCSharpConverter.cs +++ b/JavaToCSharp/JavaToCSharpConverter.cs @@ -54,7 +54,7 @@ public static class JavaToCSharpConverter var package = result.getPackageDeclaration().FromOptional(); var rootMembers = new List(); - NamespaceDeclarationSyntax? namespaceSyntax = null; + NameSyntax? namespaceNameSyntax = null; if (options.IncludeNamespace) { @@ -72,7 +72,7 @@ public static class JavaToCSharpConverter packageName = TypeHelper.Capitalize(packageName); - namespaceSyntax = SyntaxFactory.NamespaceDeclaration(SyntaxFactory.ParseName(packageName)); + namespaceNameSyntax = SyntaxFactory.ParseName(packageName); } foreach (var type in types) @@ -82,57 +82,55 @@ public static class JavaToCSharpConverter if (classOrIntType.isInterface()) { var interfaceSyntax = ClassOrInterfaceDeclarationVisitor.VisitInterfaceDeclaration(context, classOrIntType); - - if (namespaceSyntax != null) - { - namespaceSyntax = namespaceSyntax.AddMembers(interfaceSyntax); - } - else - { - rootMembers.Add(interfaceSyntax); - } + rootMembers.Add(interfaceSyntax.NormalizeWhitespace().WithTrailingNewLines()); } else { var classSyntax = ClassOrInterfaceDeclarationVisitor.VisitClassDeclaration(context, classOrIntType); - - if (namespaceSyntax != null) - { - namespaceSyntax = namespaceSyntax.AddMembers(classSyntax); - } - else - { - rootMembers.Add(classSyntax); - } + rootMembers.Add(classSyntax.NormalizeWhitespace().WithTrailingNewLines()); } } else if (type is EnumDeclaration enumType) { - var classSyntax = EnumDeclarationVisitor.VisitEnumDeclaration(context, enumType); + var enumSyntax = EnumDeclarationVisitor.VisitEnumDeclaration(context, enumType); + rootMembers.Add(enumSyntax.NormalizeWhitespace().WithTrailingNewLines()); + } + } - if (namespaceSyntax != null) - { - namespaceSyntax = namespaceSyntax.AddMembers(classSyntax); - } - else - { - rootMembers.Add(classSyntax); - } + if (rootMembers.Count > 1) + { + for (int i = 1; i < rootMembers.Count; i++) + { + rootMembers[i] = rootMembers[i].WithLeadingNewLines(); } } - if (namespaceSyntax != null) + if (namespaceNameSyntax != null) { - rootMembers.Add(namespaceSyntax); + if (options.UseFileScopedNamespaces && rootMembers.Count > 0) + { + rootMembers[0] = rootMembers[0].WithLeadingNewLines(); + } + + MemberDeclarationSyntax namespaceSyntax = + options.UseFileScopedNamespaces + ? SyntaxFactory.FileScopedNamespaceDeclaration(namespaceNameSyntax) + .NormalizeWhitespace() + .WithTrailingNewLines() + .WithMembers(SyntaxFactory.List(rootMembers)) + : SyntaxFactory.NamespaceDeclaration(namespaceNameSyntax) + .WithMembers(SyntaxFactory.List(rootMembers)) + .NormalizeWhitespace(); + + rootMembers = [namespaceSyntax]; } var root = SyntaxFactory.CompilationUnit( externs: [], - usings: SyntaxFactory.List(UsingsHelper.GetUsings(context, imports, options, rootMembers, namespaceSyntax)), + usings: SyntaxFactory.List(UsingsHelper.GetUsings(context, imports, options, namespaceNameSyntax)), attributeLists: [], members: SyntaxFactory.List(rootMembers) - ) - .NormalizeWhitespace(); + ); root = root.WithPackageFileComments(context, result, package); diff --git a/JavaToCSharp/UsingsHelper.cs b/JavaToCSharp/UsingsHelper.cs index 9b6df7c5..c93aa29e 100644 --- a/JavaToCSharp/UsingsHelper.cs +++ b/JavaToCSharp/UsingsHelper.cs @@ -1,4 +1,5 @@ using com.github.javaparser.ast; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -9,8 +10,7 @@ public static class UsingsHelper public static IEnumerable GetUsings(ConversionContext context, IEnumerable imports, JavaConversionOptions? options, - IEnumerable rootMembers, - NamespaceDeclarationSyntax? namespaceSyntax) + NameSyntax? namespaceNameSyntax) { var usings = new List(); @@ -18,7 +18,7 @@ public static IEnumerable GetUsings(ConversionContext cont { // The import directive in Java will import a specific class. string importName = import.getNameAsString(); - var lastPartStartIndex = importName.LastIndexOf(".", StringComparison.Ordinal); + var lastPartStartIndex = importName.LastIndexOf('.'); var importNameWithoutClassName = lastPartStartIndex == -1 ? importName : importName[..lastPartStartIndex]; @@ -30,29 +30,38 @@ public static IEnumerable GetUsings(ConversionContext cont usingSyntax = CommentsHelper.AddUsingComments(usingSyntax, import); } - usings.Add(usingSyntax); + usings.Add(usingSyntax.NormalizeWhitespace().WithTrailingNewLines()); } if (options?.IncludeUsings == true) { usings.AddRange(options.Usings .Where(x => !string.IsNullOrWhiteSpace(x)) - .Select(ns => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)))); + .Select(ns => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)).NormalizeWhitespace().WithTrailingNewLines())); } - if (namespaceSyntax != null) + if (namespaceNameSyntax != null) { foreach (var staticUsing in options?.StaticUsingEnumNames ?? []) { var usingSyntax = SyntaxFactory - .UsingDirective(SyntaxFactory.ParseName($"{namespaceSyntax.Name}.{staticUsing}")) - .WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)); + .UsingDirective(SyntaxFactory.ParseName($"{namespaceNameSyntax}.{staticUsing}")) + .WithStaticKeyword(SyntaxFactory.Token(SyntaxKind.StaticKeyword)) + .NormalizeWhitespace() + .WithTrailingNewLines(); usings.Add(usingSyntax); } } - return usings.Distinct(new UsingDirectiveSyntaxComparer()).ToList(); + usings = usings.Distinct(new UsingDirectiveSyntaxComparer()).ToList(); + + if (usings.Count > 0) + { + usings[^1] = usings[^1].WithTrailingNewLines(2); + } + + return usings; } } diff --git a/JavaToCSharp/Whitespace.cs b/JavaToCSharp/Whitespace.cs new file mode 100644 index 00000000..2c3e48e8 --- /dev/null +++ b/JavaToCSharp/Whitespace.cs @@ -0,0 +1,11 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace JavaToCSharp; + +public static class Whitespace +{ + public static SyntaxTrivia NewLine => Environment.NewLine == "\r\n" + ? SyntaxFactory.CarriageReturnLineFeed + : SyntaxFactory.LineFeed; +} diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index a76f0770..4107ac80 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -48,6 +48,11 @@ public class Program description: "Convert System.out calls to Console", getDefaultValue: () => false); + private static readonly Option _fileScopedNamespacesOption = new( + name: "--file-scoped-namespaces", + description: "Use file-scoped namespaces in C# output", + getDefaultValue: () => false); + private static readonly Option _clearDefaultUsingsOption = new( name: "--clear-usings", description: "Remove all default usings provided by this app", @@ -84,6 +89,7 @@ public static async Task Main(string[] args) rootCommand.AddGlobalOption(_startInterfaceNamesWithIOption); rootCommand.AddGlobalOption(_commentUnrecognizedCodeOption); rootCommand.AddGlobalOption(_systemOutToConsoleOption); + rootCommand.AddGlobalOption(_fileScopedNamespacesOption); rootCommand.AddGlobalOption(_clearDefaultUsingsOption); rootCommand.AddGlobalOption(_addUsingsOption); @@ -131,7 +137,8 @@ private static JavaConversionOptions GetJavaConversionOptions(InvocationContext ConvertSystemOutToConsole = context.ParseResult.GetValueForOption(_systemOutToConsoleOption), StartInterfaceNamesWithI = context.ParseResult.GetValueForOption(_startInterfaceNamesWithIOption), UseDebugAssertForAsserts = context.ParseResult.GetValueForOption(_useDebugAssertOption), - UseUnrecognizedCodeToComment = context.ParseResult.GetValueForOption(_commentUnrecognizedCodeOption) + UseUnrecognizedCodeToComment = context.ParseResult.GetValueForOption(_commentUnrecognizedCodeOption), + UseFileScopedNamespaces = context.ParseResult.GetValueForOption(_fileScopedNamespacesOption), }; if (context.ParseResult.GetValueForOption(_clearDefaultUsingsOption)) diff --git a/JavaToCSharpGui/App.config b/JavaToCSharpGui/App.config index 7e16a1b6..070571fe 100644 --- a/JavaToCSharpGui/App.config +++ b/JavaToCSharpGui/App.config @@ -28,6 +28,9 @@ System;System.Collections.Generic;System.Collections.ObjectModel;System.Linq;System.Text + + False + \ No newline at end of file diff --git a/JavaToCSharpGui/CurrentOptions.cs b/JavaToCSharpGui/CurrentOptions.cs index 0c876194..4c421995 100644 --- a/JavaToCSharpGui/CurrentOptions.cs +++ b/JavaToCSharpGui/CurrentOptions.cs @@ -13,6 +13,7 @@ static CurrentOptions() Options.UseDebugAssertForAsserts = Settings.Default.UseDebugAssertPreference; Options.UseUnrecognizedCodeToComment = Settings.Default.UseUnrecognizedCodeToComment; Options.ConvertSystemOutToConsole = Settings.Default.ConvertSystemOutToConsole; + Options.UseFileScopedNamespaces = Settings.Default.UseFileScopedNamespaces; Options.SetUsings(Settings.Default.Usings.Split(';')); } @@ -27,6 +28,7 @@ public static void Persist() Settings.Default.UseDebugAssertPreference = Options.UseDebugAssertForAsserts; Settings.Default.UseUnrecognizedCodeToComment = Options.UseUnrecognizedCodeToComment; Settings.Default.ConvertSystemOutToConsole = Options.ConvertSystemOutToConsole; + Settings.Default.UseFileScopedNamespaces = Options.UseFileScopedNamespaces; Settings.Default.Usings = string.Join(";", Options.Usings); Settings.Default.Save(); diff --git a/JavaToCSharpGui/Properties/Settings.Designer.cs b/JavaToCSharpGui/Properties/Settings.Designer.cs index 1cc99752..85bf611e 100644 --- a/JavaToCSharpGui/Properties/Settings.Designer.cs +++ b/JavaToCSharpGui/Properties/Settings.Designer.cs @@ -106,5 +106,17 @@ public string Usings { this["Usings"] = value; } } + + [global::System.Configuration.UserScopedSettingAttribute()] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Configuration.DefaultSettingValueAttribute("False")] + public bool UseFileScopedNamespaces { + get { + return ((bool)(this["UseFileScopedNamespaces"])); + } + set { + this["UseFileScopedNamespaces"] = value; + } + } } } diff --git a/JavaToCSharpGui/Properties/Settings.settings b/JavaToCSharpGui/Properties/Settings.settings index 694de9ad..1f26626c 100644 --- a/JavaToCSharpGui/Properties/Settings.settings +++ b/JavaToCSharpGui/Properties/Settings.settings @@ -23,5 +23,8 @@ System;System.Collections.Generic;System.Collections.ObjectModel;System.Linq;System.Text + + False + diff --git a/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs b/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs index 2395ff20..5dd364fe 100644 --- a/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/SettingsWindowViewModel.cs @@ -24,6 +24,8 @@ public partial class SettingsWindowViewModel : ViewModelBase [ObservableProperty] private bool _convertSystemOutToConsole = CurrentOptions.Options.ConvertSystemOutToConsole; + [ObservableProperty] private bool _useFileScopedNamespaces = CurrentOptions.Options.UseFileScopedNamespaces; + public event EventHandler? CloseRequested; [RelayCommand] @@ -51,6 +53,7 @@ private void Save() CurrentOptions.Options.UseDebugAssertForAsserts = UseDebugAssertForAsserts; CurrentOptions.Options.UseUnrecognizedCodeToComment = UnrecognizedCodeToComment; CurrentOptions.Options.ConvertSystemOutToConsole = ConvertSystemOutToConsole; + CurrentOptions.Options.UseFileScopedNamespaces = UseFileScopedNamespaces; CurrentOptions.Options.SetUsings(Usings); diff --git a/JavaToCSharpGui/Views/SettingsWindow.axaml b/JavaToCSharpGui/Views/SettingsWindow.axaml index 69c8f08f..1b53d4a6 100644 --- a/JavaToCSharpGui/Views/SettingsWindow.axaml +++ b/JavaToCSharpGui/Views/SettingsWindow.axaml @@ -38,6 +38,10 @@ IsChecked="{CompiledBinding IncludeNamespace}"> Include namespace in output + + Use file-scoped namespaces + Include comments in output From 09e695dee51ce07932178ebead1d972bac36c71d Mon Sep 17 00:00:00 2001 From: Javier <10879637+javiertuya@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:49:05 +0200 Subject: [PATCH 17/25] Convert array and string length into a property accessor (#120) --- JavaToCSharp.Tests/ConvertExpressionTests.cs | 31 +++++++++++++++++++ .../FieldAccessExpressionVisitor.cs | 2 ++ JavaToCSharp/TypeHelper.cs | 11 +++++-- 3 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 JavaToCSharp.Tests/ConvertExpressionTests.cs diff --git a/JavaToCSharp.Tests/ConvertExpressionTests.cs b/JavaToCSharp.Tests/ConvertExpressionTests.cs new file mode 100644 index 00000000..1c7098bf --- /dev/null +++ b/JavaToCSharp.Tests/ConvertExpressionTests.cs @@ -0,0 +1,31 @@ +using com.github.javaparser; +using com.github.javaparser.ast.expr; +using JavaToCSharp.Expressions; + +namespace JavaToCSharp.Tests; + +public class ConvertExpressionTests +{ + [Theory] + [InlineData("lst.size()", "lst.Count")] + [InlineData("lst.get(i)", "lst[i]")] + [InlineData("lst.set(i, value)", "lst[i] = value")] + [InlineData("str.length()", "str.Length")] + [InlineData("arr.length", "arr.Length")] + + //Conversion not done if param number does not match the required + [InlineData("obj.size(i)", "obj.Size(i)")] + [InlineData("obj.get()", "obj.Get()")] + [InlineData("obj.get(i, j)", "obj.Get(i,j)")] + [InlineData("obj.set(i)", "obj.Set(i)")] + [InlineData("obj.set(i, j ,k)", "obj.Set(i,j,k)")] + [InlineData("obj.length(i)", "obj.Length(i)")] + public void Convert_DesignatedMethods_Into_PropertyAccessors(string javaExpr, string expectedCSharpExpr) + { + ParseResult parseResult = new JavaParser().parseExpression(javaExpr); + Expression parsedExpr = parseResult.getResult().FromRequiredOptional(); + var expr = ExpressionVisitor.VisitExpression(new ConversionContext(new JavaConversionOptions()), parsedExpr); + Assert.Equal(expectedCSharpExpr, expr?.ToString()); + } + +} diff --git a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs index 66e65bdf..4d7bc601 100644 --- a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs @@ -32,6 +32,8 @@ public class FieldAccessExpressionVisitor : ExpressionVisitor } var field = TypeHelper.EscapeIdentifier(fieldAccessExpr.getNameAsString()); + // array length accessor should be capitalized + field = field == "length" ? "Length" : field; return SyntaxFactory.MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, scopeSyntax, SyntaxFactory.IdentifierName(field)); } diff --git a/JavaToCSharp/TypeHelper.cs b/JavaToCSharp/TypeHelper.cs index 35945e10..d75d10f1 100644 --- a/JavaToCSharp/TypeHelper.cs +++ b/JavaToCSharp/TypeHelper.cs @@ -211,9 +211,14 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE switch (methodName.getIdentifier()) { + case "length" when args.size() == 0: + var scopeSyntaxLength = ExpressionVisitor.VisitExpression(context, scope); + transformedSyntax = ReplaceMethodByProperty(scopeSyntaxLength, "Length"); + return true; + case "size" when args.size() == 0: var scopeSyntaxSize = ExpressionVisitor.VisitExpression(context, scope); - transformedSyntax = ReplaceSizeByCount(scopeSyntaxSize); + transformedSyntax = ReplaceMethodByProperty(scopeSyntaxSize, "Count"); return true; case "get" when args.size() == 1: @@ -244,7 +249,7 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE return false; - static MemberAccessExpressionSyntax? ReplaceSizeByCount(ExpressionSyntax? scopeSyntax) + static MemberAccessExpressionSyntax? ReplaceMethodByProperty(ExpressionSyntax? scopeSyntax, string identifier) { if (scopeSyntax is null) { @@ -255,7 +260,7 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE return SyntaxFactory.MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, scopeSyntax, - SyntaxFactory.IdentifierName(SyntaxFactory.Identifier("Count"))); + SyntaxFactory.IdentifierName(SyntaxFactory.Identifier(identifier))); } static ExpressionSyntax ReplaceGetByIndexAccess(ConversionContext context, ExpressionSyntax scopeSyntax, From b84f9de184817a58f76853009d27313832cdf930 Mon Sep 17 00:00:00 2001 From: Javier <10879637+javiertuya@users.noreply.github.com> Date: Sun, 25 Aug 2024 20:53:17 +0200 Subject: [PATCH 18/25] Option to convert files excluding subdirectories (#123) --- JavaToCSharp/JavaConversionOptions.cs | 2 ++ JavaToCSharpCli/Program.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/JavaToCSharp/JavaConversionOptions.cs b/JavaToCSharp/JavaConversionOptions.cs index 2d0c0e57..6aa25cf6 100644 --- a/JavaToCSharp/JavaConversionOptions.cs +++ b/JavaToCSharp/JavaConversionOptions.cs @@ -14,6 +14,8 @@ public class JavaConversionOptions public IList StaticUsingEnumNames { get; } = new List(); + public bool IncludeSubdirectories { get; set; } = true; + public bool IncludeUsings { get; set; } = true; public bool IncludeNamespace { get; set; } = true; diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index 4107ac80..b61acef6 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -13,6 +13,11 @@ public class Program private static readonly ILoggerFactory _loggerFactory; private static readonly ILogger _logger; + private static readonly Option _includeSubdirectoriesOption = new( + name: "--include-subdirectories", + description: "When the command is dir, converts files in all subdirectories", + getDefaultValue: () => true); + private static readonly Option _includeUsingsOption = new( name: "--include-usings", description: "Include using directives in output", @@ -82,6 +87,7 @@ public static async Task Main(string[] args) rootCommand.AddCommand(CreateFileCommand()); rootCommand.AddCommand(CreateDirectoryCommand()); + rootCommand.AddGlobalOption(_includeSubdirectoriesOption); rootCommand.AddGlobalOption(_includeUsingsOption); rootCommand.AddGlobalOption(_includeNamespaceOption); rootCommand.AddGlobalOption(_includeCommentsOption); @@ -131,6 +137,7 @@ private static JavaConversionOptions GetJavaConversionOptions(InvocationContext { var options = new JavaConversionOptions { + IncludeSubdirectories = context.ParseResult.GetValueForOption(_includeSubdirectoriesOption), IncludeUsings = context.ParseResult.GetValueForOption(_includeUsingsOption), IncludeComments = context.ParseResult.GetValueForOption(_includeCommentsOption), IncludeNamespace = context.ParseResult.GetValueForOption(_includeNamespaceOption), @@ -193,7 +200,8 @@ private static void ConvertToCSharpDir(DirectoryInfo inputDirectory, DirectoryIn { if (inputDirectory.Exists) { - foreach (var f in inputDirectory.GetFiles("*.java", SearchOption.AllDirectories)) + var searchOption = options.IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly; + foreach (var f in inputDirectory.GetFiles("*.java", searchOption)) { string? directoryName = f.DirectoryName; if (string.IsNullOrWhiteSpace(directoryName)) From adae7865d3696c3a88be138ab7324aa609134d33 Mon Sep 17 00:00:00 2001 From: Javier <10879637+javiertuya@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:32:25 +0200 Subject: [PATCH 19/25] Add mappings from imports, methods and annotations to support JUnit test conversions (#124) * Add CLI option to read syntax mappings (yaml file) * Apply mappings to imports, methods and annotations * Document mappins in README --- JavaToCSharp.Tests/SyntaxMappingTests.cs | 116 ++++++++++++++++++ .../Declarations/MethodDeclarationVisitor.cs | 8 ++ .../MethodCallExpressionVisitor.cs | 23 ++++ JavaToCSharp/JavaConversionOptions.cs | 2 + JavaToCSharp/JavaToCSharp.csproj | 1 + JavaToCSharp/SyntaxMapping.cs | 43 +++++++ JavaToCSharp/UsingsHelper.cs | 11 ++ JavaToCSharpCli/Program.cs | 18 +++ README.md | 57 +++++++++ 9 files changed, 279 insertions(+) create mode 100644 JavaToCSharp.Tests/SyntaxMappingTests.cs create mode 100644 JavaToCSharp/SyntaxMapping.cs diff --git a/JavaToCSharp.Tests/SyntaxMappingTests.cs b/JavaToCSharp.Tests/SyntaxMappingTests.cs new file mode 100644 index 00000000..17117739 --- /dev/null +++ b/JavaToCSharp.Tests/SyntaxMappingTests.cs @@ -0,0 +1,116 @@ +using YamlDotNet.Core; + +namespace JavaToCSharp.Tests; + +public class SyntaxMappingTests +{ + [Fact] + public void Deserialize_Mappings() + { + var mappingString = """ + ImportMappings: + org.junit.Test : XUnit + java.util.List : "" + """; + + var mappings = SyntaxMapping.Deserialize(mappingString); + Assert.NotNull(mappings); + Assert.Equal(2, mappings.ImportMappings.Count); + Assert.Equal("XUnit", mappings.ImportMappings["org.junit.Test"]); + Assert.Equal("", mappings.ImportMappings["java.util.List"]); + Assert.False(mappings.ImportMappings.ContainsKey("other.Clazz")); + Assert.Empty(mappings.VoidMethodMappings); + Assert.Empty(mappings.NonVoidMethodMappings); + Assert.Empty(mappings.AnnotationMappings); + } + + [Fact] + public void Conversion_Options_Defaults_To_Empty_Mappings() + { + var options = new JavaConversionOptions(); + Assert.Empty(options.SyntaxMappings.ImportMappings); + Assert.Empty(options.SyntaxMappings.VoidMethodMappings); + Assert.Empty(options.SyntaxMappings.NonVoidMethodMappings); + Assert.Empty(options.SyntaxMappings.AnnotationMappings); + } + + [Theory] + [InlineData("VoidMethodMappings:\n org.junit.Assert.assertTrue : Assert.True")] + [InlineData("VoidMethodMappings:\n assertTrue : \"\"")] + [InlineData("NonVoidMethodMappings:\n org.junit.Assert.assertTrue : Assert.True")] + [InlineData("NonVoidMethodMappings:\n assertTrue : \"\"")] + public void Validation_Exceptions(string mappingString) + { + Assert.Throws(() => SyntaxMapping.Deserialize(mappingString)); + } + + // The use of mappings is tested using a typical JUnit4 test converted to Xunit: + // - Multiple java imports: rewritten and removed (empty value) + // - Multiple java methods: rewritten (void) and not rewritten (non void) + // - Multiple Java method annotations: rewritten and removed (no mapping) + // Not tested: + // - Qualified java method/annotation names (this would require a more elaborated handling of the scope) + // - No qualified CSharp methods (this would require CSharp static imports, that are not implemented) + // - Annotations with parameters + [Fact] + public void Conversion_With_Import_Method_And_Annotation_Mappings() + { + const string javaCode = """ + import static org.junit.Assert.assertEquals; + import static org.junit.Assert.assertTrue; + import org.junit.Test; + public class MappingsTest { + @Test @CustomJava @NotMapped + public void testAsserts() { + assertEquals("a", "a"); + assertTrue(true); + va.assertTrue(true); // non void is not mapped + } + } + + """; + var mappingsYaml = """ + ImportMappings: + org.junit.Test : Xunit + #to remove static imports + org.junit.Assert.assertEquals : "" + org.junit.Assert.assertTrue : "" + VoidMethodMappings: + assertEquals : Assert.Equal + assertTrue : Assert.True + AnnotationMappings: + Test : Fact + CustomJava : CustomCs + + """; + const string expectedCSharpCode = """ + using Xunit; + + public class MappingsTest + { + [Fact] + [CustomCs] + public virtual void TestAsserts() + { + Assert.Equal("a", "a"); + Assert.True(true); + va.AssertTrue(true); // non void is not mapped + } + } + + """; + + var parsed = GetParsed(javaCode, mappingsYaml); + Assert.Equal(expectedCSharpCode.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + } + + private static string GetParsed(string javaCode, string mappingsYaml) + { + var mappings = SyntaxMapping.Deserialize(mappingsYaml); + var options = new JavaConversionOptions { IncludeNamespace = false, IncludeUsings = false, SyntaxMappings = mappings }; + options.WarningEncountered += (_, eventArgs) + => Console.WriteLine("Line {0}: {1}", eventArgs.JavaLineNumber, eventArgs.Message); + var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + return parsed; + } +} diff --git a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs index b22b0cd5..d4b489f8 100644 --- a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs @@ -123,6 +123,14 @@ private static MemberDeclarationSyntax VisitInternal( methodSyntax = methodSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.OverrideKeyword)); isOverride = true; } + // add annotation if a mapping is found + else if (context.Options != null && context.Options.SyntaxMappings.AnnotationMappings.TryGetValue(name, out var mappedAnnotation)) + { + var attributeList = SyntaxFactory.AttributeList( + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.Attribute(SyntaxFactory.ParseName(mappedAnnotation)))); + methodSyntax = methodSyntax.AddAttributeLists(attributeList); + } } } diff --git a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs index 69b6ab6a..d50a5c3f 100644 --- a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs @@ -41,6 +41,12 @@ public class MethodCallExpressionVisitor : ExpressionVisitor } } + // Override methodName if a mapping is found + if (TryGetMappedMethodName(methodCallExpr.getNameAsString(), scope, context, out var mappedMethodName)) + { + methodName = mappedMethodName; + } + ExpressionSyntax methodExpression; if (scopeSyntax == null) @@ -61,4 +67,21 @@ public class MethodCallExpressionVisitor : ExpressionVisitor return SyntaxFactory.InvocationExpression(methodExpression, TypeHelper.GetSyntaxFromArguments(context, args)); } + + private static bool TryGetMappedMethodName(string methodName, Expression? scope, ConversionContext context, out string mappedMethodName) + { + var mappings = context.Options.SyntaxMappings; + if (scope == null && mappings.VoidMethodMappings.TryGetValue(methodName, out var voidMapping)) + { + mappedMethodName = voidMapping; + return true; + } + else if (scope != null && mappings.NonVoidMethodMappings.TryGetValue(methodName, out var nonVoidMapping)) + { + mappedMethodName = nonVoidMapping; + return true; + } + mappedMethodName = methodName; + return false; + } } diff --git a/JavaToCSharp/JavaConversionOptions.cs b/JavaToCSharp/JavaConversionOptions.cs index 6aa25cf6..85795cdf 100644 --- a/JavaToCSharp/JavaConversionOptions.cs +++ b/JavaToCSharp/JavaConversionOptions.cs @@ -35,6 +35,8 @@ public class JavaConversionOptions public bool UseFileScopedNamespaces { get; set; } + public SyntaxMapping SyntaxMappings { get; set; } = new SyntaxMapping(); + public ConversionState ConversionState { get; set; } public JavaConversionOptions AddPackageReplacement(string pattern, string replacement, RegexOptions options = RegexOptions.None) diff --git a/JavaToCSharp/JavaToCSharp.csproj b/JavaToCSharp/JavaToCSharp.csproj index bc7e8553..aa310b6e 100644 --- a/JavaToCSharp/JavaToCSharp.csproj +++ b/JavaToCSharp/JavaToCSharp.csproj @@ -19,6 +19,7 @@ + diff --git a/JavaToCSharp/SyntaxMapping.cs b/JavaToCSharp/SyntaxMapping.cs new file mode 100644 index 00000000..50e18c08 --- /dev/null +++ b/JavaToCSharp/SyntaxMapping.cs @@ -0,0 +1,43 @@ +namespace JavaToCSharp; + +public class SyntaxMapping +{ + public Dictionary ImportMappings { get; set; } = new(); + public Dictionary VoidMethodMappings { get; set; } = new(); + public Dictionary NonVoidMethodMappings { get; set; } = new(); + public Dictionary AnnotationMappings { get; set; } = new(); + + public static SyntaxMapping Deserialize(string yaml) + { + var deserializer = new YamlDotNet.Serialization.Deserializer(); + SyntaxMapping mapping = deserializer.Deserialize(yaml); + mapping.Validate(); + return mapping; + } + + private void Validate() + { + // Throw exception if any of the requirements are not meet + ValidateMethodMapping(VoidMethodMappings); + ValidateMethodMapping(NonVoidMethodMappings); + } + private static void ValidateMethodMapping(Dictionary mapping) + { + // Throw exception if any of the requirements are not meet + foreach (string key in mapping.Keys) + { + if (key.Contains('.')) + { + throw new YamlDotNet.Core.SemanticErrorException("Mappings from fully qualified java methods are not supported"); + } + } + foreach (string value in mapping.Values) + { + if (string.IsNullOrEmpty(value)) + { + throw new YamlDotNet.Core.SemanticErrorException("Mappings from java methods can not have an empty value"); + } + } + } + +} diff --git a/JavaToCSharp/UsingsHelper.cs b/JavaToCSharp/UsingsHelper.cs index c93aa29e..5b2a75e4 100644 --- a/JavaToCSharp/UsingsHelper.cs +++ b/JavaToCSharp/UsingsHelper.cs @@ -23,6 +23,17 @@ public static IEnumerable GetUsings(ConversionContext cont importName : importName[..lastPartStartIndex]; var nameSpace = TypeHelper.Capitalize(importNameWithoutClassName); + + // Override namespace if a non empty mapping is found (mapping to empty string removes the import) + if (options != null && options.SyntaxMappings.ImportMappings.TryGetValue(importName, out var mappedNamespace)) + { + if (string.IsNullOrEmpty(mappedNamespace)) + { + continue; + } + nameSpace = mappedNamespace; + } + var usingSyntax = SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(nameSpace)); if (context.Options.IncludeComments) diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index b61acef6..1301eb8d 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -70,6 +70,10 @@ public class Program ArgumentHelpName = "namespace" }; + private static readonly Option _mappingsFileNameOption = new( + name: "--mappings-file", + description: "A yaml file with syntax mappings from imports, methods and annotations"); + static Program() { _loggerFactory = LoggerFactory.Create(builder => @@ -98,6 +102,7 @@ public static async Task Main(string[] args) rootCommand.AddGlobalOption(_fileScopedNamespacesOption); rootCommand.AddGlobalOption(_clearDefaultUsingsOption); rootCommand.AddGlobalOption(_addUsingsOption); + rootCommand.AddGlobalOption(_mappingsFileNameOption); await rootCommand.InvokeAsync(args); @@ -166,9 +171,22 @@ private static JavaConversionOptions GetJavaConversionOptions(InvocationContext options.AddUsing(ns); } + var mappingsFile = context.ParseResult.GetValueForOption(_mappingsFileNameOption); + if (!string.IsNullOrEmpty(mappingsFile)) + { + options.SyntaxMappings = ReadMappingsFile(mappingsFile); + } + return options; } + private static SyntaxMapping ReadMappingsFile(string mappingsFile) + { + // Let fail if cannot be read or deserialized to display the exception message in the CLI + var mappingsStr = File.ReadAllText(mappingsFile); + return SyntaxMapping.Deserialize(mappingsStr); + } + private static Command CreateDirectoryCommand() { var inputArgument = new Argument( diff --git a/README.md b/README.md index 83175d87..c4867912 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,63 @@ from the command line. The core library is installable via NuGet at https://www.nuget.org/packages/JavaToCSharp/ +## Syntax Mappings + +By default, JavaToCSharp translates some usual Java classes and methods into their C# counterparts +(e.g. Java maps are converted into dictionaries). +You can specify additional mappings to fine tune the translation of the syntactic elements. + +The mappings are specified in a yaml file with root keys that represent the kind of mapping, +each having a set of key-value pairs that specify the java to C# mappings: + +- `ImportMappings`: Mappings from Java package names to the C# namespaces. + If a key-value pair has an empty value, the package will be removed from the resulting C#. +- `VoidMethodMappings`: Mappings from unqualified Java void methods to C#. +- `NonVoidMethodMappings`: Same as before, but for non-void java methods. +- `AnnotationMappings`: Mappings from Java method annotations to C#. + +For example, to convert *JUnit* tests into *xUnit* you can create this mapping file: +```yaml +ImportMappings: + org.junit.Test : Xunit + org.junit.Assert.assertEquals : "" + org.junit.Assert.assertTrue : "" +VoidMethodMappings: + assertEquals : Assert.Equal + assertTrue : Assert.True +AnnotationMappings: + Test : Fact +``` + +If you specify this file in the `--mappings-file` CLI argument, the conversion of this JUnit test: +```Java +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import org.junit.Test; +public class MappingsTest { + @Test + public void testAsserts() { + assertEquals("a", "a"); + assertTrue(true); + } +} +``` + +will produce this xUnit test: +```csharp +using Xunit; + +public class MappingsTest +{ + [Fact] + public virtual void TestAsserts() + { + Assert.Equal("a", "a"); + Assert.True(true); + } +} +``` + ## .NET Support Trunk will generally always target the latest LTS version of .NET for the core library and the CLI/GUI apps. From ebc600280de4962309061f3656ef78475a980839 Mon Sep 17 00:00:00 2001 From: Javier <10879637+javiertuya@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:59:47 +0100 Subject: [PATCH 20/25] Avoid repeated comments in subclasses, fix #88 (#126) --- JavaToCSharp.Tests/CommentTests.cs | 64 ++++++++++++++++++++++++++++++ JavaToCSharp/CommentsHelper.cs | 1 + 2 files changed, 65 insertions(+) diff --git a/JavaToCSharp.Tests/CommentTests.cs b/JavaToCSharp.Tests/CommentTests.cs index 115822b1..1b666ded 100644 --- a/JavaToCSharp.Tests/CommentTests.cs +++ b/JavaToCSharp.Tests/CommentTests.cs @@ -102,4 +102,68 @@ public class Foo Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); } + + [Theory] + [InlineData("Child", "Child")] + [InlineData("Child extends Parent", "Child : Parent")] + [InlineData("Child implements Parent", "Child : Parent")] + [InlineData("Child extends Parent implements IParent", "Child : Parent, IParent")] + + [InlineData("Parent", "Parent")] + [InlineData("Child>", "Child")] // issue #125, should add: where T : BoundType + [InlineData("Child extends Parent", "Child : Parent")] + public void CommentsInsideClass_ShouldNotBeDuplicated_Fix_88(string javaClass, string csharpClass) + { + string javaCode = $$""" + //class comment + public class {{javaClass}} { + //before comment 1 + public void method1() { + doSomething(); //after comment1 + } + //before comment 2 + public void method1() { + doSomething(); //after comment2 + } + //before comment 3 + public void method1() { + } + } + """; + var options = new JavaConversionOptions + { + IncludeUsings = false, + IncludeNamespace = false, + }; + + var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + + testOutputHelper.WriteLine(parsed); + + string expected = $$""" + //class comment + public class {{csharpClass}} + { + //before comment 1 + public virtual void Method1() + { + DoSomething(); //after comment1 + } + + //before comment 2 + public virtual void Method1() + { + DoSomething(); //after comment2 + } + + //before comment 3 + public virtual void Method1() + { + } + } + + """; + + Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + } } diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index 8aacd4a9..a0b74b97 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -232,6 +232,7 @@ private static bool HasNextSibling(JavaAst.Node parentNode, JavaParser.Position return parentNode.getChildNodes() .OfType() .Where(sibling => sibling is not JavaComments.Comment) + .OrderBy(sibling => sibling.getEnd().FromOptional()) // fix #88 .LastOrDefault(sibling => { var siblingEnd = sibling.getEnd().FromOptional(); From c93b08adfa30bfd6164851f2e3b5c7278b8bd1a0 Mon Sep 17 00:00:00 2001 From: Javier <10879637+javiertuya@users.noreply.github.com> Date: Mon, 13 Jan 2025 23:26:02 +0100 Subject: [PATCH 21/25] Convert class bounded type parameters (#127) * Convert class bounded type parameters, close #125 * Update comment test with class type bounded params --- JavaToCSharp.Tests/CommentTests.cs | 2 +- JavaToCSharp.Tests/ConvertTypeTests.cs | 31 +++++++++++++++++++ .../ClassOrInterfaceDeclarationVisitor.cs | 1 + JavaToCSharp/TypeHelper.cs | 25 +++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/JavaToCSharp.Tests/CommentTests.cs b/JavaToCSharp.Tests/CommentTests.cs index 1b666ded..c60d636e 100644 --- a/JavaToCSharp.Tests/CommentTests.cs +++ b/JavaToCSharp.Tests/CommentTests.cs @@ -110,7 +110,7 @@ public class Foo [InlineData("Child extends Parent implements IParent", "Child : Parent, IParent")] [InlineData("Parent", "Parent")] - [InlineData("Child>", "Child")] // issue #125, should add: where T : BoundType + [InlineData("Child>", "Child\n where T : BoundType")] [InlineData("Child extends Parent", "Child : Parent")] public void CommentsInsideClass_ShouldNotBeDuplicated_Fix_88(string javaClass, string csharpClass) { diff --git a/JavaToCSharp.Tests/ConvertTypeTests.cs b/JavaToCSharp.Tests/ConvertTypeTests.cs index b9cf4b79..558a8efc 100644 --- a/JavaToCSharp.Tests/ConvertTypeTests.cs +++ b/JavaToCSharp.Tests/ConvertTypeTests.cs @@ -113,4 +113,35 @@ public void ConvertTypeSyntax_GivenMismatchedRank_ShouldThrowException() Assert.Throws(() => TypeHelper.ConvertTypeSyntax(type, 1)); } + + [Theory] + [InlineData("GenericClass", "GenericClass")] + [InlineData("GenericClass>", "GenericClass\n where T : BoundType")] + [InlineData("GenericClass", "GenericClass\n where T : BoundType")] + + [InlineData("GenericClass", "GenericClass")] + [InlineData("GenericClass>", "GenericClass\n where U : BoundType")] + [InlineData("GenericClass, U extends BoundType2>", "GenericClass\n where T : BoundType1 where U : BoundType2")] + + [InlineData("GenericClass & BoundType2>", "GenericClass\n where T : BoundType1, BoundType2")] + public void ConvertClassTypeBoundedParameters(string javaClass, string csharpClass) + { + string javaCode = $$""" + public class {{javaClass}} { } + """; + var options = new JavaConversionOptions + { + IncludeUsings = false, + IncludeNamespace = false, + }; + var parsed = JavaToCSharpConverter.ConvertText(javaCode, options) ?? ""; + string expected = $$""" + public class {{csharpClass}} + { + } + + """; + + Assert.Equal(expected.ReplaceLineEndings(), parsed.ReplaceLineEndings()); + } } diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index 0c3532e9..23494ebe 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -128,6 +128,7 @@ public static ClassDeclarationSyntax VisitClassDeclaration(ConversionContext con { classSyntax = classSyntax.AddTypeParameterListParameters(typeParams .Select(i => SyntaxFactory.TypeParameter(i.getNameAsString())).ToArray()); + classSyntax = classSyntax.AddConstraintClauses(TypeHelper.GetTypeParameterListConstraints(typeParams).ToArray()); } var mods = classDecl.getModifiers().ToModifierKeywordSet(); diff --git a/JavaToCSharp/TypeHelper.cs b/JavaToCSharp/TypeHelper.cs index d75d10f1..0eedaa70 100644 --- a/JavaToCSharp/TypeHelper.cs +++ b/JavaToCSharp/TypeHelper.cs @@ -194,6 +194,31 @@ private static SeparatedSyntaxList GetSeparatedListFromArguments return SyntaxFactory.SeparatedList(argSyntaxes, separators); } + /// + /// Returns the list of C# type parameter constraints that must be added to a class syntax + /// to convert the java bounded type parameters of a class declaration. + /// e.g. to convert > ]]> into where T : Clazz ]]> + /// + public static IEnumerable GetTypeParameterListConstraints(List typeParams) + { + var typeParameterConstraints = new List(); + foreach (TypeParameter typeParam in typeParams) + { + if (typeParam.getTypeBound().size() > 0) + { + var typeConstraintsSyntax = new SeparatedSyntaxList(); + foreach (ClassOrInterfaceType bound in typeParam.getTypeBound()) + typeConstraintsSyntax = typeConstraintsSyntax.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName(bound.asString()))); + + var typeIdentifier = SyntaxFactory.IdentifierName(typeParam.getName().asString()); + var parameterConstraintClauseSyntax = SyntaxFactory.TypeParameterConstraintClause(typeIdentifier, typeConstraintsSyntax); + + typeParameterConstraints.Add(parameterConstraintClauseSyntax); + } + } + return typeParameterConstraints; + } + /// /// Transforms method calls into property and indexer accesses where appropriate. /// From be1eaa70421b3edd8497d43b7c9c9f049227349b Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Mon, 13 Oct 2025 08:56:53 -0600 Subject: [PATCH 22/25] Set GitHub Sponsors username to 'paulirwin' Updated GitHub Sponsors username in FUNDING.yml --- .github/FUNDING.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..af719b2e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: paulirwin +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From ef6865db6e62dba605956b15a147dde7e228ba0a Mon Sep 17 00:00:00 2001 From: Danial Ahmad Date: Tue, 9 Dec 2025 04:23:04 +0100 Subject: [PATCH 23/25] Add Paste button to Java Input pane (#136) (#139) * GUI: Add Paste button to Java Input pane, #136 * GUI: Fix Paste button positioning in file controls row, #136 * Update JavaToCSharpGui/Infrastructure/TextClipboard.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Danial Ahmad <108906119+iamdanialahmad@users.noreply.github.com> Co-authored-by: Paul Irwin Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../Infrastructure/ITextClipboard.cs | 6 ++++++ .../Infrastructure/TextClipboard.cs | 10 ++++++++++ .../ViewModels/MainWindowViewModel.cs | 20 +++++++++++++++++++ JavaToCSharpGui/Views/MainWindow.axaml | 9 ++++++++- 4 files changed, 44 insertions(+), 1 deletion(-) diff --git a/JavaToCSharpGui/Infrastructure/ITextClipboard.cs b/JavaToCSharpGui/Infrastructure/ITextClipboard.cs index 7057f18e..0c71366e 100644 --- a/JavaToCSharpGui/Infrastructure/ITextClipboard.cs +++ b/JavaToCSharpGui/Infrastructure/ITextClipboard.cs @@ -5,6 +5,12 @@ /// public interface ITextClipboard { + /// + /// Gets the clipboard's text. + /// + /// A Task representing the async operation that returns the clipboard text. + Task GetTextAsync(); + /// /// Sets the clipboard's text. /// diff --git a/JavaToCSharpGui/Infrastructure/TextClipboard.cs b/JavaToCSharpGui/Infrastructure/TextClipboard.cs index 043357ed..4c91edad 100644 --- a/JavaToCSharpGui/Infrastructure/TextClipboard.cs +++ b/JavaToCSharpGui/Infrastructure/TextClipboard.cs @@ -9,6 +9,16 @@ internal class TextClipboard : ITextClipboard public TextClipboard(IClipboard? clipboard) => _clipboard = clipboard; + /// + public async Task GetTextAsync() + { + if (_clipboard is null) + { + return null; + } + return await _clipboard.GetTextAsync(); + } + /// public async Task SetTextAsync(string? text) { diff --git a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs index 33242393..7372a7f9 100644 --- a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs @@ -313,6 +313,26 @@ private async Task OpenFileDialog() } } + [RelayCommand] + private async Task PasteInput() + { + if (_clipboard is null) + { + return; + } + + var text = await _clipboard.GetTextAsync(); + if (!string.IsNullOrEmpty(text)) + { + JavaText.Text = text; + ConversionStateLabel = "Pasted Java code from clipboard!"; + + await Task.Delay(2000); + + await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); + } + } + [RelayCommand] private async Task CopyOutput() { diff --git a/JavaToCSharpGui/Views/MainWindow.axaml b/JavaToCSharpGui/Views/MainWindow.axaml index 2a9ec3f2..167ce988 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml +++ b/JavaToCSharpGui/Views/MainWindow.axaml @@ -59,7 +59,7 @@ Java Source Code Input: - + File: + Date: Wed, 10 Dec 2025 22:51:06 -0500 Subject: [PATCH 24/25] Update to .NET 10 and upgrade dependencies (#140) * Bump to .NET 10 * Bump dependencies * Update Github workflows * Copilot PR feedback and code style formatting --------- Co-authored-by: Paul Irwin --- .github/workflows/build.yml | 8 +- JavaToCSharp.Tests/JavaToCSharp.Tests.csproj | 12 +- JavaToCSharp/JavaToCSharp.csproj | 10 +- JavaToCSharpCli/JavaToCSharpCli.csproj | 6 +- JavaToCSharpCli/Program.cs | 242 ++++++++++--------- JavaToCSharpGui/App.axaml | 5 +- JavaToCSharpGui/JavaToCSharpGui.csproj | 27 ++- 7 files changed, 166 insertions(+), 144 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5fec5114..cdff9d1c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,10 +11,10 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v1 - - name: Setup .NET 8 + - name: Setup .NET 10 uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.x + dotnet-version: 10.x - name: Build JavaToCSharp run: dotnet build ./JavaToCSharp/JavaToCSharp.csproj --configuration Release @@ -30,10 +30,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - - name: Setup .NET 8 + - name: Setup .NET 10 uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.x + dotnet-version: 10.x - name: Build JavaToCSharp run: dotnet build ./JavaToCSharp/JavaToCSharp.csproj --configuration Release diff --git a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj index a21d46f3..54e5aede 100644 --- a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj +++ b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 false enable latest @@ -10,17 +10,17 @@ - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + diff --git a/JavaToCSharp/JavaToCSharp.csproj b/JavaToCSharp/JavaToCSharp.csproj index aa310b6e..66903f14 100644 --- a/JavaToCSharp/JavaToCSharp.csproj +++ b/JavaToCSharp/JavaToCSharp.csproj @@ -1,7 +1,7 @@  4.0.0 - net8.0 + net10.0 enable enable nullable @@ -16,10 +16,10 @@ - - - - + + + + diff --git a/JavaToCSharpCli/JavaToCSharpCli.csproj b/JavaToCSharpCli/JavaToCSharpCli.csproj index a2a5b7cd..6c36c40a 100644 --- a/JavaToCSharpCli/JavaToCSharpCli.csproj +++ b/JavaToCSharpCli/JavaToCSharpCli.csproj @@ -2,15 +2,15 @@ 4.0.0 Exe - net8.0 + net10.0 latest enable enable nullable - - + + diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index 1301eb8d..5c831389 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -13,66 +13,76 @@ public class Program private static readonly ILoggerFactory _loggerFactory; private static readonly ILogger _logger; - private static readonly Option _includeSubdirectoriesOption = new( - name: "--include-subdirectories", - description: "When the command is dir, converts files in all subdirectories", - getDefaultValue: () => true); - - private static readonly Option _includeUsingsOption = new( - name: "--include-usings", - description: "Include using directives in output", - getDefaultValue: () => true); - - private static readonly Option _includeNamespaceOption = new( - name: "--include-namespace", - description: "Include namespace in output", - getDefaultValue: () => true); - - private static readonly Option _includeCommentsOption = new( - name: "--include-comments", - description: "Include comments in output", - getDefaultValue: () => true); - - private static readonly Option _useDebugAssertOption = new( - name: "--use-debug-assert", - description: "Use Debug.Assert for asserts", - getDefaultValue: () => false); - - private static readonly Option _startInterfaceNamesWithIOption = new( - name: "--start-interface-names-with-i", - description: "Prefix interface names with the letter I", - getDefaultValue: () => true); - - private static readonly Option _commentUnrecognizedCodeOption = new( - name: "--comment-unrecognized-code", - description: "Include unrecognized code in output as commented-out code", - getDefaultValue: () => true); - - private static readonly Option _systemOutToConsoleOption = new( - name: "--system-out-to-console", - description: "Convert System.out calls to Console", - getDefaultValue: () => false); - - private static readonly Option _fileScopedNamespacesOption = new( - name: "--file-scoped-namespaces", - description: "Use file-scoped namespaces in C# output", - getDefaultValue: () => false); - - private static readonly Option _clearDefaultUsingsOption = new( - name: "--clear-usings", - description: "Remove all default usings provided by this app", - getDefaultValue: () => false); - - private static readonly Option> _addUsingsOption = new( - name: "--add-using", - description: "Adds a using directive to the collection of usings") + private static readonly Option _includeSubdirectoriesOption = new("--include-subdirectories") { - ArgumentHelpName = "namespace" + Description = "When the command is dir, converts files in all subdirectories", + DefaultValueFactory = _ => true, }; - private static readonly Option _mappingsFileNameOption = new( - name: "--mappings-file", - description: "A yaml file with syntax mappings from imports, methods and annotations"); + private static readonly Option _includeUsingsOption = new("--include-usings") + { + Description = "Include using directives in output", + DefaultValueFactory = _ => true, + }; + + private static readonly Option _includeNamespaceOption = new("--include-namespace") + { + Description = "Include namespace in output", + DefaultValueFactory = _ => true, + }; + + private static readonly Option _includeCommentsOption = new("--include-comments") + { + Description = "Include comments in output", + DefaultValueFactory = _ => true, + }; + + private static readonly Option _useDebugAssertOption = new("--use-debug-assert") + { + Description = "Use Debug.Assert for asserts", + DefaultValueFactory = _ => false, + }; + + private static readonly Option _startInterfaceNamesWithIOption = new("--start-interface-names-with-i") + { + Description = "Prefix interface names with the letter I", + DefaultValueFactory = _ => true, + }; + + private static readonly Option _commentUnrecognizedCodeOption = new("--comment-unrecognized-code") + { + Description = "Include unrecognized code in output as commented-out code", + DefaultValueFactory = _ => true, + }; + + private static readonly Option _systemOutToConsoleOption = new("--system-out-to-console") + { + Description = "Convert System.out calls to Console", + DefaultValueFactory = _ => false, + }; + + private static readonly Option _fileScopedNamespacesOption = new("--file-scoped-namespaces") + { + Description = "Use file-scoped namespaces in C# output", + DefaultValueFactory = _ => false, + }; + + private static readonly Option _clearDefaultUsingsOption = new("--clear-usings") + { + Description = "Remove all default usings provided by this app", + DefaultValueFactory = _ => false, + }; + + private static readonly Option> _addUsingsOption = new("--add-using") + { + Description = "Adds a using directive to the collection of usings", + HelpName = "namespace", + }; + + private static readonly Option _mappingsFileNameOption = new("--mappings-file") + { + Description = "A yaml file with syntax mappings from imports, methods and annotations", + }; static Program() { @@ -85,26 +95,26 @@ public static async Task Main(string[] args) { var rootCommand = new RootCommand("Java to C# Converter") { - Description = "A syntactic transformer of source code from Java to C#." + Description = "A syntactic transformer of source code from Java to C#.", }; - rootCommand.AddCommand(CreateFileCommand()); - rootCommand.AddCommand(CreateDirectoryCommand()); + rootCommand.Subcommands.Add(CreateFileCommand()); + rootCommand.Subcommands.Add(CreateDirectoryCommand()); - rootCommand.AddGlobalOption(_includeSubdirectoriesOption); - rootCommand.AddGlobalOption(_includeUsingsOption); - rootCommand.AddGlobalOption(_includeNamespaceOption); - rootCommand.AddGlobalOption(_includeCommentsOption); - rootCommand.AddGlobalOption(_useDebugAssertOption); - rootCommand.AddGlobalOption(_startInterfaceNamesWithIOption); - rootCommand.AddGlobalOption(_commentUnrecognizedCodeOption); - rootCommand.AddGlobalOption(_systemOutToConsoleOption); - rootCommand.AddGlobalOption(_fileScopedNamespacesOption); - rootCommand.AddGlobalOption(_clearDefaultUsingsOption); - rootCommand.AddGlobalOption(_addUsingsOption); - rootCommand.AddGlobalOption(_mappingsFileNameOption); + rootCommand.Options.Add(_includeSubdirectoriesOption); + rootCommand.Options.Add(_includeUsingsOption); + rootCommand.Options.Add(_includeNamespaceOption); + rootCommand.Options.Add(_includeCommentsOption); + rootCommand.Options.Add(_useDebugAssertOption); + rootCommand.Options.Add(_startInterfaceNamesWithIOption); + rootCommand.Options.Add(_commentUnrecognizedCodeOption); + rootCommand.Options.Add(_systemOutToConsoleOption); + rootCommand.Options.Add(_fileScopedNamespacesOption); + rootCommand.Options.Add(_clearDefaultUsingsOption); + rootCommand.Options.Add(_addUsingsOption); + rootCommand.Options.Add(_mappingsFileNameOption); - await rootCommand.InvokeAsync(args); + await rootCommand.Parse(args).InvokeAsync(); // flush logs _loggerFactory.Dispose(); @@ -112,48 +122,50 @@ public static async Task Main(string[] args) private static Command CreateFileCommand() { - var inputArgument = new Argument( - name: "input", - description: "A Java source code file to convert"); + var inputArgument = new Argument("input") + { + Description = "A Java source code file to convert", + }; - var outputArgument = new Argument( - name: "output", - description: "Path to place the C# output file, or stdout if omitted", - getDefaultValue: () => null); + var outputArgument = new Argument("output") + { + Description = "Path to place the C# output file, or stdout if omitted", + DefaultValueFactory = _ => null, + }; var fileCommand = new Command("file", "Convert a Java file to C#"); - fileCommand.AddArgument(inputArgument); - fileCommand.AddArgument(outputArgument); + fileCommand.Arguments.Add(inputArgument); + fileCommand.Arguments.Add(outputArgument); - fileCommand.SetHandler(context => + fileCommand.SetAction(context => { - var input = context.ParseResult.GetValueForArgument(inputArgument); - var output = context.ParseResult.GetValueForArgument(outputArgument); + var input = context.GetValue(inputArgument); + var output = context.GetValue(outputArgument); var options = GetJavaConversionOptions(context); - ConvertToCSharpFile(input, output, options); + ConvertToCSharpFile(input!, output, options); }); return fileCommand; } - private static JavaConversionOptions GetJavaConversionOptions(InvocationContext context) + private static JavaConversionOptions GetJavaConversionOptions(ParseResult context) { var options = new JavaConversionOptions { - IncludeSubdirectories = context.ParseResult.GetValueForOption(_includeSubdirectoriesOption), - IncludeUsings = context.ParseResult.GetValueForOption(_includeUsingsOption), - IncludeComments = context.ParseResult.GetValueForOption(_includeCommentsOption), - IncludeNamespace = context.ParseResult.GetValueForOption(_includeNamespaceOption), - ConvertSystemOutToConsole = context.ParseResult.GetValueForOption(_systemOutToConsoleOption), - StartInterfaceNamesWithI = context.ParseResult.GetValueForOption(_startInterfaceNamesWithIOption), - UseDebugAssertForAsserts = context.ParseResult.GetValueForOption(_useDebugAssertOption), - UseUnrecognizedCodeToComment = context.ParseResult.GetValueForOption(_commentUnrecognizedCodeOption), - UseFileScopedNamespaces = context.ParseResult.GetValueForOption(_fileScopedNamespacesOption), + IncludeSubdirectories = context.GetValue(_includeSubdirectoriesOption), + IncludeUsings = context.GetValue(_includeUsingsOption), + IncludeComments = context.GetValue(_includeCommentsOption), + IncludeNamespace = context.GetValue(_includeNamespaceOption), + ConvertSystemOutToConsole = context.GetValue(_systemOutToConsoleOption), + StartInterfaceNamesWithI = context.GetValue(_startInterfaceNamesWithIOption), + UseDebugAssertForAsserts = context.GetValue(_useDebugAssertOption), + UseUnrecognizedCodeToComment = context.GetValue(_commentUnrecognizedCodeOption), + UseFileScopedNamespaces = context.GetValue(_fileScopedNamespacesOption), }; - if (context.ParseResult.GetValueForOption(_clearDefaultUsingsOption)) + if (context.GetValue(_clearDefaultUsingsOption)) { options.ClearUsings(); } @@ -166,12 +178,12 @@ private static JavaConversionOptions GetJavaConversionOptions(InvocationContext options.AddUsing("System.Text"); } - foreach (string ns in context.ParseResult.GetValueForOption(_addUsingsOption) ?? new List()) + foreach (string ns in context.GetValue(_addUsingsOption) ?? new List()) { options.AddUsing(ns); } - var mappingsFile = context.ParseResult.GetValueForOption(_mappingsFileNameOption); + var mappingsFile = context.GetValue(_mappingsFileNameOption); if (!string.IsNullOrEmpty(mappingsFile)) { options.SyntaxMappings = ReadMappingsFile(mappingsFile); @@ -189,26 +201,28 @@ private static SyntaxMapping ReadMappingsFile(string mappingsFile) private static Command CreateDirectoryCommand() { - var inputArgument = new Argument( - name: "input", - description: "A directory containing Java source code files to convert"); + var inputArgument = new Argument("input") + { + Description = "A directory containing Java source code files to convert" + }; - var outputArgument = new Argument( - name: "output", - description: "Path to place the C# output files"); + var outputArgument = new Argument("output") + { + Description = "Path to place the C# output files" + }; var dirCommand = new Command("dir", "Convert a directory containing Java files to C#"); - dirCommand.AddArgument(inputArgument); - dirCommand.AddArgument(outputArgument); + dirCommand.Arguments.Add(inputArgument); + dirCommand.Arguments.Add(outputArgument); - dirCommand.SetHandler(context => + dirCommand.SetAction(context => { - var input = context.ParseResult.GetValueForArgument(inputArgument); - var output = context.ParseResult.GetValueForArgument(outputArgument); + var input = context.GetValue(inputArgument); + var output = context.GetValue(outputArgument); var options = GetJavaConversionOptions(context); - ConvertToCSharpDir(input, output, options); + ConvertToCSharpDir(input!, output!, options); }); return dirCommand; @@ -239,13 +253,17 @@ private static void ConvertToCSharpDir(DirectoryInfo inputDirectory, DirectoryIn } } else + { _logger.LogError("Java input folder {path} doesn't exist!", inputDirectory); + } } private static void ConvertToCSharpFile(FileSystemInfo inputFile, FileSystemInfo? outputFile, JavaConversionOptions options, bool overwrite = true) { if (!overwrite && outputFile is { Exists: true }) + { _logger.LogInformation("{outputFilePath} exists, skip to next.", outputFile); + } else if (inputFile.Exists) { try @@ -283,7 +301,9 @@ private static void ConvertToCSharpFile(FileSystemInfo inputFile, FileSystemInfo } } else + { _logger.LogError("Java input file {filePath} doesn't exist!", inputFile.FullName); + } } private static void OutputFileOrPrint(string? fileName, string contents) diff --git a/JavaToCSharpGui/App.axaml b/JavaToCSharpGui/App.axaml index 3253d7b7..3128bd0d 100644 --- a/JavaToCSharpGui/App.axaml +++ b/JavaToCSharpGui/App.axaml @@ -1,5 +1,6 @@  @@ -9,7 +10,7 @@ - - + + diff --git a/JavaToCSharpGui/JavaToCSharpGui.csproj b/JavaToCSharpGui/JavaToCSharpGui.csproj index 7c2ed3c4..170f0084 100644 --- a/JavaToCSharpGui/JavaToCSharpGui.csproj +++ b/JavaToCSharpGui/JavaToCSharpGui.csproj @@ -4,7 +4,7 @@ true app.manifest WinExe - net8.0 + net10.0 latest enable enable @@ -16,21 +16,22 @@ - - - + + + - + - - - - + + + + - - - - + + + + + From 905cd5b279af2432b1d3de9866f5d83991f0c6d0 Mon Sep 17 00:00:00 2001 From: Paul Irwin Date: Thu, 11 Dec 2025 07:20:26 -0700 Subject: [PATCH 25/25] C# 14 and code style updates (#141) - Uses C# 14 features like extension blocks, and some older C# features - Converts the sln to the new slnx format - Turns on warnings as errors for all projects - Uses GeneratedRegexAttribute where Regex was used - Misc code cleanup --- JavaToCSharp.Tests/CommentTests.cs | 1 - JavaToCSharp.Tests/JavaToCSharp.Tests.csproj | 4 +- JavaToCSharp.sln | 55 -------- JavaToCSharp.slnx | 12 ++ ...tSettings => JavaToCSharp.slnx.DotSettings | 2 + JavaToCSharp/CommentsHelper.cs | 46 ++++--- JavaToCSharp/ConversionContext.cs | 18 +-- .../ConversionStateChangedEventArgs.cs | 13 +- JavaToCSharp/ConversionWarningEventArgs.cs | 16 +-- .../AnnotationDeclarationVisitor.cs | 4 +- .../Declarations/BodyDeclarationVisitor.cs | 4 +- .../ClassOrInterfaceDeclarationVisitor.cs | 16 +-- .../ConstructorDeclarationVisitor.cs | 2 +- .../Declarations/EnumDeclarationVisitor.cs | 2 +- .../Declarations/FieldDeclarationVisitor.cs | 2 +- .../InitializerDeclarationVisitor.cs | 4 +- .../Declarations/MethodDeclarationVisitor.cs | 25 ++-- .../ArrayAccessExpressionVisitor.cs | 6 +- .../ArrayCreationExpressionVisitor.cs | 24 ++-- .../ArrayInitializerExpressionVisitor.cs | 6 +- JavaToCSharp/Expressions/ExpressionVisitor.cs | 6 +- .../FieldAccessExpressionVisitor.cs | 2 +- .../MethodCallExpressionVisitor.cs | 10 +- .../MethodReferenceExpressionVisitor.cs | 6 +- .../ObjectCreationExpressionVisitor.cs | 12 +- JavaToCSharp/Extensions.cs | 128 ++++++++++-------- JavaToCSharp/JavaToCSharp.csproj | 51 +++---- JavaToCSharp/JavaToCSharpConverter.cs | 8 +- JavaToCSharp/Replacement.cs | 28 ++-- JavaToCSharp/SanitizingSyntaxRewriter.cs | 2 +- .../Statements/AssertStatementVisitor.cs | 14 +- .../Statements/ExpressionStatementVisitor.cs | 6 +- .../Statements/ForEachStatementVisitor.cs | 8 +- .../Statements/ForStatementVisitor.cs | 16 +-- JavaToCSharp/Statements/IfStatementVisitor.cs | 12 +- .../Statements/LabeledStatementVisitor.cs | 4 +- .../Statements/ReturnStatementVisitor.cs | 4 +- JavaToCSharp/Statements/StatementVisitor.cs | 14 +- .../Statements/SwitchStatementVisitor.cs | 15 +- .../Statements/TryStatementVisitor.cs | 30 ++-- .../TypeDeclarationStatementVisitor.cs | 5 +- .../Statements/WhileStatementVisitor.cs | 1 + JavaToCSharp/SyntaxMapping.cs | 2 +- JavaToCSharp/TypeHelper.cs | 27 ++-- JavaToCSharp/TypeNameParser.cs | 10 +- JavaToCSharp/UsingsHelper.cs | 8 +- JavaToCSharpCli/JavaToCSharpCli.csproj | 2 +- JavaToCSharpCli/Program.cs | 33 +++-- JavaToCSharpGui/App.axaml | 6 +- JavaToCSharpGui/App.axaml.cs | 6 +- .../Infrastructure/ITextClipboard.cs | 2 +- .../Infrastructure/IUIDispatcher.cs | 2 +- .../Infrastructure/TextClipboard.cs | 16 +-- .../Infrastructure/UIDispatcher.cs | 11 +- JavaToCSharpGui/JavaToCSharpGui.csproj | 19 ++- .../ViewModels/MainWindowViewModel.cs | 77 +++++++++-- JavaToCSharpGui/Views/AboutWindow.axaml.cs | 8 +- JavaToCSharpGui/Views/MainWindow.axaml | 36 +++-- README.md | 1 - 59 files changed, 456 insertions(+), 454 deletions(-) delete mode 100644 JavaToCSharp.sln create mode 100644 JavaToCSharp.slnx rename JavaToCSharp.sln.DotSettings => JavaToCSharp.slnx.DotSettings (65%) diff --git a/JavaToCSharp.Tests/CommentTests.cs b/JavaToCSharp.Tests/CommentTests.cs index c60d636e..3bd5514f 100644 --- a/JavaToCSharp.Tests/CommentTests.cs +++ b/JavaToCSharp.Tests/CommentTests.cs @@ -1,4 +1,3 @@ -using Microsoft.CodeAnalysis.CSharp; using Xunit.Abstractions; namespace JavaToCSharp.Tests; diff --git a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj index 54e5aede..51827f4d 100644 --- a/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj +++ b/JavaToCSharp.Tests/JavaToCSharp.Tests.csproj @@ -6,7 +6,7 @@ enable latest enable - nullable + true @@ -26,7 +26,7 @@ - + diff --git a/JavaToCSharp.sln b/JavaToCSharp.sln deleted file mode 100644 index ca90a449..00000000 --- a/JavaToCSharp.sln +++ /dev/null @@ -1,55 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.6.33829.357 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaToCSharp", "JavaToCSharp\JavaToCSharp.csproj", "{EDCE2F7C-299E-4FEA-870F-87C201FB2E43}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{7A18D136-8A66-4B98-8D8E-9C210E0C8D2D}" - ProjectSection(SolutionItems) = preProject - Lib\javaparser-core-3.25.4.jar = Lib\javaparser-core-3.25.4.jar - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaToCSharpCli", "JavaToCSharpCli\JavaToCSharpCli.csproj", "{8C56AE52-8E0F-438F-9B5B-87B891D70861}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaToCSharpGui", "JavaToCSharpGui\JavaToCSharpGui.csproj", "{DAA3F412-0460-40C3-98F7-3244649820F9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E8E0DCFE-ED61-442C-9F4B-36433D6949DF}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JavaToCSharp.Tests", "JavaToCSharp.Tests\JavaToCSharp.Tests.csproj", "{0CBBEF05-FD79-474A-A5F0-25B341B8B77D}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EDCE2F7C-299E-4FEA-870F-87C201FB2E43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EDCE2F7C-299E-4FEA-870F-87C201FB2E43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EDCE2F7C-299E-4FEA-870F-87C201FB2E43}.Release|Any CPU.Build.0 = Release|Any CPU - {EDCE2F7C-299E-4FEA-870F-87C201FB2E43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C56AE52-8E0F-438F-9B5B-87B891D70861}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C56AE52-8E0F-438F-9B5B-87B891D70861}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C56AE52-8E0F-438F-9B5B-87B891D70861}.Release|Any CPU.Build.0 = Release|Any CPU - {8C56AE52-8E0F-438F-9B5B-87B891D70861}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAA3F412-0460-40C3-98F7-3244649820F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DAA3F412-0460-40C3-98F7-3244649820F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAA3F412-0460-40C3-98F7-3244649820F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DAA3F412-0460-40C3-98F7-3244649820F9}.Release|Any CPU.Build.0 = Release|Any CPU - {0CBBEF05-FD79-474A-A5F0-25B341B8B77D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CBBEF05-FD79-474A-A5F0-25B341B8B77D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CBBEF05-FD79-474A-A5F0-25B341B8B77D}.Release|Any CPU.Build.0 = Release|Any CPU - {0CBBEF05-FD79-474A-A5F0-25B341B8B77D}.Debug|Any CPU.Build.0 = Debug|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {EB538484-5025-4753-B0A5-2C66A6C06193} - EndGlobalSection -EndGlobal diff --git a/JavaToCSharp.slnx b/JavaToCSharp.slnx new file mode 100644 index 00000000..a964be56 --- /dev/null +++ b/JavaToCSharp.slnx @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/JavaToCSharp.sln.DotSettings b/JavaToCSharp.slnx.DotSettings similarity index 65% rename from JavaToCSharp.sln.DotSettings rename to JavaToCSharp.slnx.DotSettings index 9e8d4492..fb2552b8 100644 --- a/JavaToCSharp.sln.DotSettings +++ b/JavaToCSharp.slnx.DotSettings @@ -1,5 +1,7 @@  <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="_" Suffix="" Style="aaBb" /></Policy> + True True True True diff --git a/JavaToCSharp/CommentsHelper.cs b/JavaToCSharp/CommentsHelper.cs index a0b74b97..06da9c9f 100644 --- a/JavaToCSharp/CommentsHelper.cs +++ b/JavaToCSharp/CommentsHelper.cs @@ -1,15 +1,15 @@ -using Microsoft.CodeAnalysis; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using JavaAst = com.github.javaparser.ast; using JavaComments = com.github.javaparser.ast.comments; using JavaParser = com.github.javaparser; -using SysRegex = System.Text.RegularExpressions; namespace JavaToCSharp; -public static class CommentsHelper +public static partial class CommentsHelper { private enum CommentPosition { @@ -18,8 +18,8 @@ private enum CommentPosition } // Regex: Optional *, capture optional @par, capture optional text (keep leading whitespaces, trim end). - private static readonly SysRegex.Regex _analyzeDocString = - new(@"^(\s*\*)?(\s*(?@[a-z]+))?\s?(?.*?)\s*$", SysRegex.RegexOptions.Compiled); + [GeneratedRegex(@"^(\s*\*)?(\s*(?@[a-z]+))?\s?(?.*?)\s*$", RegexOptions.Compiled)] + private static partial Regex AnalyzeDocStringRegex { get; } private static readonly Dictionary _knownTagsDict = new() { @@ -142,9 +142,14 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( private static List<(JavaComments.Comment c, CommentPosition pos)> GatherComments(JavaAst.Node? node) { var result = new List<(JavaComments.Comment c, CommentPosition pos)>(); - if (node == null) return result; + + if (node is null) + { + return result; + } var parentNode = node.getParentNode().FromOptional(); + if (parentNode is null) { if (node.getComment().FromOptional() is { } comment) @@ -155,6 +160,7 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( else { var unsortedComments = parentNode.getAllContainedComments(); + if (unsortedComments.size() != 0) { var comments = unsortedComments.OfType() @@ -163,7 +169,7 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( .ToList(); // Find leading comments - var nodeBegin = node.getBegin().FromOptional() + var nodeBegin = node.getBegin().FromOptional() ?? throw new InvalidOperationException("Node did not have a begin position"); var previousSibling = GetPreviousSibling(parentNode, nodeBegin); int previousPos = previousSibling?.getEnd().FromOptional()?.line ?? 0; @@ -173,7 +179,7 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( // Find trailing comments. // We consider only comments either appearing on the same line or, if no sibling nodes follow, // then also comments on the succeeding lines (because otherwise they belong to the next sibling). - var nodeEnd = node.getEnd().FromOptional() + var nodeEnd = node.getEnd().FromOptional() ?? throw new InvalidOperationException("Node did not have an end position"); var trailingComments = HasNextSibling(parentNode, nodeEnd) @@ -191,7 +197,7 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( comments.Where(c => { var commentBegin = c.getBegin().FromOptional(); - return commentBegin != null && (commentBegin.line == nodeEnd.line && commentBegin.column > nodeEnd.column || commentBegin.line > nodeEnd.line); + return commentBegin is not null && (commentBegin.line == nodeEnd.line && commentBegin.column > nodeEnd.column || commentBegin.line > nodeEnd.line); }) .Select(c => (c, CommentPosition.Trailing)); @@ -200,7 +206,7 @@ private static (SyntaxKind kind, string? pre, string? post) GetCommentInfo( .Where(c => { var commentBegin = c.getBegin().FromOptional(); - return commentBegin != null && commentBegin.line == nodeEnd.line && commentBegin.column > nodeEnd.column; + return commentBegin is not null && commentBegin.line == nodeEnd.line && commentBegin.column > nodeEnd.column; }) .Select(c => (c, CommentPosition.Trailing)); @@ -212,7 +218,7 @@ private static bool HasNextSibling(JavaAst.Node parentNode, JavaParser.Position .Any(sibling => { var siblingBegin = sibling.getBegin().FromOptional(); - return siblingBegin != null && (siblingBegin.line > nodeEnd.line || siblingBegin.line == nodeEnd.line && siblingBegin.column > nodeEnd.column); + return siblingBegin is not null && (siblingBegin.line > nodeEnd.line || siblingBegin.line == nodeEnd.line && siblingBegin.column > nodeEnd.column); }); } @@ -222,7 +228,7 @@ private static bool HasNextSibling(JavaAst.Node parentNode, JavaParser.Position { var commentBegin = c.getBegin().FromOptional(); var commentEnd = c.getEnd().FromOptional(); - return commentBegin != null && commentEnd != null && commentBegin.line > previousPos && (commentEnd.line < nodeBegin.line || commentEnd.line == nodeBegin.line && commentEnd.column < nodeBegin.column); + return commentBegin is not null && commentEnd is not null && commentBegin.line > previousPos && (commentEnd.line < nodeBegin.line || commentEnd.line == nodeBegin.line && commentEnd.column < nodeBegin.column); }) .Select(c => (c, CommentPosition.Leading)); } @@ -236,7 +242,7 @@ private static bool HasNextSibling(JavaAst.Node parentNode, JavaParser.Position .LastOrDefault(sibling => { var siblingEnd = sibling.getEnd().FromOptional(); - return siblingEnd != null && (siblingEnd.line < nodeBegin.line || siblingEnd.line == nodeBegin.line && siblingEnd.column < nodeBegin.column); + return siblingEnd is not null && (siblingEnd.line < nodeBegin.line || siblingEnd.line == nodeBegin.line && siblingEnd.column < nodeBegin.column); }); } @@ -252,7 +258,7 @@ public static IEnumerable ConvertToComment(IEnumerable(); foreach (var code in codes) { - string[] input = code.ToString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); + string[] input = code.ToString().Split([Environment.NewLine], StringSplitOptions.None); outputs.AddRange(input); } @@ -277,14 +283,14 @@ public static IEnumerable ConvertToComment(IEnumerable ConvertDocComment(JavaComments.Comment comment, string? post) { - string[] input = comment.getContent().Split(new[] { Environment.NewLine }, StringSplitOptions.None); + string[] input = comment.getContent().Split([Environment.NewLine], StringSplitOptions.None); var output = new List(); var remarks = new List(); // For Java tags unknown in C# var currentOutput = output; string? tag = null; foreach (string inputLine in input) { - var match = _analyzeDocString.Match(inputLine); + var match = AnalyzeDocStringRegex.Match(inputLine); if (match.Success) { string paramName = match.Groups["param"].Value; @@ -304,7 +310,7 @@ private static IEnumerable ConvertDocComment(JavaComments.Comment remarks.Add(paramName + text); tag = "remarks"; } - else if (tag == null) + else if (tag is null) { tag = "summary"; OpenSection(output, tag, text); @@ -358,14 +364,14 @@ private static void CloseSection(IList output, string? tag) } else { - output[output.Count - 1] += xmlEndTag; + output[^1] += xmlEndTag; } } } private static void TrimTrailingEmptyLines(IList lines) { - while (lines.Count > 0 && lines[lines.Count - 1].Trim() == "") + while (lines.Count > 0 && lines[^1].Trim() == "") { lines.RemoveAt(lines.Count - 1); } @@ -468,7 +474,7 @@ private static SyntaxNode AdjustBlockCommentIndentation(SyntaxNode node) if (t.IsKind(SyntaxKind.MultiLineCommentTrivia)) { int indentation = GetIndentation(leading, i) + 1; // Add one to align stars. - string[] lines = t.ToFullString().Split(new[] { Environment.NewLine }, StringSplitOptions.None); + string[] lines = t.ToFullString().Split([Environment.NewLine], StringSplitOptions.None); string indentString = new(' ', indentation); for (int l = 1; l < lines.Length; l++) { diff --git a/JavaToCSharp/ConversionContext.cs b/JavaToCSharp/ConversionContext.cs index a93053b7..2f565f24 100644 --- a/JavaToCSharp/ConversionContext.cs +++ b/JavaToCSharp/ConversionContext.cs @@ -1,22 +1,14 @@ -using System.Collections.Generic; -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace JavaToCSharp; -public class ConversionContext +public class ConversionContext(JavaConversionOptions options) { - public ConversionContext(JavaConversionOptions options) - { - PendingAnonymousTypes = new Queue(); - UsedAnonymousTypeNames = new HashSet(); - Options = options; - } + public Queue PendingAnonymousTypes { get; } = new(); - public Queue PendingAnonymousTypes { get; } + public ISet UsedAnonymousTypeNames { get; } = new HashSet(); - public ISet UsedAnonymousTypeNames { get; } - - public JavaConversionOptions Options { get; } + public JavaConversionOptions Options { get; } = options; public string? RootTypeName { get; set; } diff --git a/JavaToCSharp/ConversionStateChangedEventArgs.cs b/JavaToCSharp/ConversionStateChangedEventArgs.cs index d632e0b9..b69d8e2d 100644 --- a/JavaToCSharp/ConversionStateChangedEventArgs.cs +++ b/JavaToCSharp/ConversionStateChangedEventArgs.cs @@ -1,13 +1,6 @@ -using System; +namespace JavaToCSharp; -namespace JavaToCSharp; - -public sealed class ConversionStateChangedEventArgs : EventArgs +public sealed class ConversionStateChangedEventArgs(ConversionState newState) : EventArgs { - public ConversionStateChangedEventArgs(ConversionState newState) - { - NewState = newState; - } - - public ConversionState NewState { get; } + public ConversionState NewState { get; } = newState; } diff --git a/JavaToCSharp/ConversionWarningEventArgs.cs b/JavaToCSharp/ConversionWarningEventArgs.cs index 5313a511..dca58057 100644 --- a/JavaToCSharp/ConversionWarningEventArgs.cs +++ b/JavaToCSharp/ConversionWarningEventArgs.cs @@ -1,16 +1,8 @@ -using System; +namespace JavaToCSharp; -namespace JavaToCSharp; - -public class ConversionWarningEventArgs : EventArgs +public class ConversionWarningEventArgs(string message, int javaLineNumber) : EventArgs { - public ConversionWarningEventArgs(string message, int javaLineNumber) - { - Message = message; - JavaLineNumber = javaLineNumber; - } - - public string Message { get; } + public string Message { get; } = message; - public int JavaLineNumber { get; } + public int JavaLineNumber { get; } = javaLineNumber; } diff --git a/JavaToCSharp/Declarations/AnnotationDeclarationVisitor.cs b/JavaToCSharp/Declarations/AnnotationDeclarationVisitor.cs index 493a490c..dfe52342 100644 --- a/JavaToCSharp/Declarations/AnnotationDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/AnnotationDeclarationVisitor.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using com.github.javaparser; +using com.github.javaparser; using com.github.javaparser.ast.body; using com.github.javaparser.ast.type; using Microsoft.CodeAnalysis.CSharp.Syntax; diff --git a/JavaToCSharp/Declarations/BodyDeclarationVisitor.cs b/JavaToCSharp/Declarations/BodyDeclarationVisitor.cs index c5d7a382..a840de3b 100644 --- a/JavaToCSharp/Declarations/BodyDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/BodyDeclarationVisitor.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.type; using Microsoft.CodeAnalysis.CSharp.Syntax; using Range = com.github.javaparser.Range; diff --git a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs index 23494ebe..fe18368d 100644 --- a/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ClassOrInterfaceDeclarationVisitor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast; +using com.github.javaparser.ast; using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using com.github.javaparser.ast.type; @@ -71,7 +69,7 @@ public static InterfaceDeclarationSyntax VisitInterfaceDeclaration(ConversionCon var extends = interfaceDecl.getExtendedTypes().ToList(); - if (extends != null) + if (extends is not null) { foreach (var extend in extends) { @@ -81,7 +79,7 @@ public static InterfaceDeclarationSyntax VisitInterfaceDeclaration(ConversionCon var implements = interfaceDecl.getImplementedTypes().ToList(); - if (implements != null) + if (implements is not null) { foreach (var implement in implements) { @@ -98,7 +96,7 @@ public static InterfaceDeclarationSyntax VisitInterfaceDeclaration(ConversionCon var syntax = VisitBodyDeclarationForInterface(context, classSyntax, member); var memberWithComments = syntax?.WithJavaComments(context, member); - if (memberWithComments != null) + if (memberWithComments is not null) { classSyntax = classSyntax.AddMembers(memberWithComments); } @@ -144,14 +142,14 @@ public static ClassDeclarationSyntax VisitClassDeclaration(ConversionContext con if (mods.Contains(Modifier.Keyword.FINAL)) classSyntax = classSyntax.AddModifiers(SyntaxFactory.Token(SyntaxKind.SealedKeyword)); - var extends = classDecl.getExtendedTypes().ToList() ?? new List(); + var extends = classDecl.getExtendedTypes().ToList() ?? []; foreach (var extend in extends) { classSyntax = classSyntax.AddBaseListTypes(SyntaxFactory.SimpleBaseType(TypeHelper.GetSyntaxFromType(extend))); } - var implements = classDecl.getImplementedTypes().ToList() ?? new List(); + var implements = classDecl.getImplementedTypes().ToList() ?? []; foreach (var implement in implements) { @@ -184,7 +182,7 @@ public static ClassDeclarationSyntax VisitClassDeclaration(ConversionContext con var syntax = VisitBodyDeclarationForClass(context, classSyntax, member, extends, implements); var withJavaComments = syntax?.WithJavaComments(context, member); - if (withJavaComments != null) + if (withJavaComments is not null) { classSyntax = classSyntax.AddMembers(withJavaComments); } diff --git a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs index 39b28fb3..5566c8f6 100644 --- a/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/ConstructorDeclarationVisitor.cs @@ -72,7 +72,7 @@ public class ConstructorDeclarationVisitor : BodyDeclarationVisitor 0) + if (initArgs is not null && initArgs.size() > 0) { argsSyntax = TypeHelper.GetSyntaxFromArguments(context, initArgs); } diff --git a/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs b/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs index 16a34302..97a700cc 100644 --- a/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/EnumDeclarationVisitor.cs @@ -56,7 +56,7 @@ public static EnumDeclarationSyntax VisitEnumDeclaration(ConversionContext conte var classBody = itemConst.getClassBody(); if (!constArgs.isEmpty() || !classBody.isEmpty()) { - var bodyCodes = CommentsHelper.ConvertToComment(new[] { itemConst }, "enum member body", false); + var bodyCodes = CommentsHelper.ConvertToComment([itemConst], "enum member body", false); if (memberDecl.HasLeadingTrivia) { diff --git a/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs b/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs index 78e3a5fc..d37cc9ed 100644 --- a/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/FieldDeclarationVisitor.cs @@ -47,7 +47,7 @@ public override MemberDeclarationSyntax VisitForClass( var initExpr = item.getInitializer().FromOptional(); - if (initExpr != null) + if (initExpr is not null) { var initSyntax = ExpressionVisitor.VisitExpression(context, initExpr); diff --git a/JavaToCSharp/Declarations/InitializerDeclarationVisitor.cs b/JavaToCSharp/Declarations/InitializerDeclarationVisitor.cs index 99f4f115..1cbb03f8 100644 --- a/JavaToCSharp/Declarations/InitializerDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/InitializerDeclarationVisitor.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using com.github.javaparser; +using com.github.javaparser; using com.github.javaparser.ast.body; using com.github.javaparser.ast.type; using JavaToCSharp.Statements; diff --git a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs index d4b489f8..645757a2 100644 --- a/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs +++ b/JavaToCSharp/Declarations/MethodDeclarationVisitor.cs @@ -55,7 +55,7 @@ public override MemberDeclarationSyntax VisitForInterface(ConversionContext cont { var paramSyntax = parameters.Select(i => SyntaxFactory.Parameter( - attributeLists: new SyntaxList(), + attributeLists: [], modifiers: SyntaxFactory.TokenList(), type: SyntaxFactory.ParseTypeName(TypeHelper.ConvertTypeOf(i)), identifier: SyntaxFactory.ParseToken(TypeHelper.EscapeIdentifier(i.getNameAsString())), @@ -110,7 +110,7 @@ private static MemberDeclarationSyntax VisitInternal( bool isOverride = false; // TODO: figure out how to check for a non-interface base type - if (annotations is {Count: > 0}) + if (annotations is { Count: > 0 }) { foreach (var annotation in annotations) { @@ -124,7 +124,7 @@ private static MemberDeclarationSyntax VisitInternal( isOverride = true; } // add annotation if a mapping is found - else if (context.Options != null && context.Options.SyntaxMappings.AnnotationMappings.TryGetValue(name, out var mappedAnnotation)) + else if (context.Options is not null && context.Options.SyntaxMappings.AnnotationMappings.TryGetValue(name, out var mappedAnnotation)) { var attributeList = SyntaxFactory.AttributeList( SyntaxFactory.SingletonSeparatedList( @@ -180,7 +180,7 @@ private static MemberDeclarationSyntax VisitInternal( var block = methodDecl.getBody().FromOptional(); - if (block == null) + if (block is null) { // i.e. abstract method methodSyntax = methodSyntax.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)); @@ -196,20 +196,11 @@ private static MemberDeclarationSyntax VisitInternal( { var lockBlock = SyntaxFactory.Block(statementSyntax); - LockStatementSyntax? lockSyntax = null; - if (mods.Contains(Modifier.Keyword.STATIC)) - { - lockSyntax = SyntaxFactory.LockStatement(SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseTypeName(typeIdentifier)), lockBlock); - } - else - { - lockSyntax = SyntaxFactory.LockStatement(SyntaxFactory.ThisExpression(), lockBlock); - } + var lockSyntax = mods.Contains(Modifier.Keyword.STATIC) + ? SyntaxFactory.LockStatement(SyntaxFactory.TypeOfExpression(SyntaxFactory.ParseTypeName(typeIdentifier)), lockBlock) + : SyntaxFactory.LockStatement(SyntaxFactory.ThisExpression(), lockBlock); - if (lockSyntax is not null) - { - methodSyntax = methodSyntax.AddBodyStatements(lockSyntax); - } + methodSyntax = methodSyntax.AddBodyStatements(lockSyntax); } else { diff --git a/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs index d2bf676d..3c16710e 100644 --- a/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayAccessExpressionVisitor.cs @@ -22,9 +22,9 @@ public class ArrayAccessExpressionVisitor : ExpressionVisitor return null; } - return SyntaxFactory.ElementAccessExpression(nameSyntax, SyntaxFactory.BracketedArgumentList(SyntaxFactory.SeparatedList(new[] - { + return SyntaxFactory.ElementAccessExpression(nameSyntax, SyntaxFactory.BracketedArgumentList(SyntaxFactory.SeparatedList( + [ SyntaxFactory.Argument(indexSyntax) - }))); + ]))); } } diff --git a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs index 4f8d62c6..89f9d4f8 100644 --- a/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayCreationExpressionVisitor.cs @@ -26,30 +26,34 @@ protected override ExpressionSyntax Visit(ConversionContext context, ArrayCreati var rankSyntaxes = new List(); - if (rankDimensions != null) + if (rankDimensions is not null) { - var expressionSyntaxes = rankDimensions.Select(dimension => - VisitExpression(context, dimension.getDimension().FromOptional())) - .Where(syntax => syntax != null); - rankSyntaxes.AddRange(expressionSyntaxes!); + var expressionSyntaxes = rankDimensions + .Select(dimension => VisitExpression(context, dimension.getDimension().FromOptional())) + .OfType(); // filter out nulls + rankSyntaxes.AddRange(expressionSyntaxes); } var rankSpecifier = rankDimensions?.Count > 0 && rankSyntaxes.Count == 0 ? SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SingletonSeparatedList(SyntaxFactory.OmittedArraySizeExpression())) : SyntaxFactory.ArrayRankSpecifier(SyntaxFactory.SeparatedList(rankSyntaxes, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), rankSyntaxes.Count - 1))); - if (initializer == null) + if (initializer is null) + { return SyntaxFactory.ArrayCreationExpression(arrayTypeSyntax) .AddTypeRankSpecifiers(rankSpecifier); + } // todo: support multi-dimensional and jagged arrays - var values = initializer.getValues()?.ToList() ?? new List(); + var values = initializer.getValues()?.ToList() ?? []; + var syntaxes = values.Select(value => VisitExpression(context, value)) - .Where(syntax => syntax != null)! - .ToList(); + .OfType() // filter out nulls + .ToList(); + var initSyntax = - syntaxes.Any() + syntaxes.Count != 0 ? SyntaxFactory.InitializerExpression(SyntaxKind.ArrayInitializerExpression, SyntaxFactory.SeparatedList(syntaxes, Enumerable.Repeat(SyntaxFactory.Token(SyntaxKind.CommaToken), syntaxes.Count - 1))) diff --git a/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs b/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs index 667d8a01..31e575b7 100644 --- a/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ArrayInitializerExpressionVisitor.cs @@ -8,10 +8,10 @@ public class ArrayInitializerExpressionVisitor : ExpressionVisitor() ?? new List(); + var expressions = expr.getValues()?.ToList() ?? []; var syntaxes = expressions.Select(valueExpression => VisitExpression(context, valueExpression)) - .Where(syntax => syntax != null)! - .ToList(); + .OfType() // filter out nulls + .ToList(); return SyntaxFactory.ImplicitArrayCreationExpression( SyntaxFactory.InitializerExpression( diff --git a/JavaToCSharp/Expressions/ExpressionVisitor.cs b/JavaToCSharp/Expressions/ExpressionVisitor.cs index 51d67a09..0f26be13 100644 --- a/JavaToCSharp/Expressions/ExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ExpressionVisitor.cs @@ -55,7 +55,7 @@ static ExpressionVisitor() public static ExpressionSyntax? VisitExpression(ConversionContext context, Expression? expr) { - if (expr == null) + if (expr is null) { return null; } @@ -63,12 +63,12 @@ static ExpressionVisitor() ExpressionVisitor? visitor = null; var t = expr.GetType(); - while (t != null && !_visitors.TryGetValue(t, out visitor)) + while (t is not null && !_visitors.TryGetValue(t, out visitor)) { t = t.BaseType; } - if (visitor != null) + if (visitor is not null) { return visitor.Visit(context, expr); } diff --git a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs index 4d7bc601..69ef55ec 100644 --- a/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/FieldAccessExpressionVisitor.cs @@ -11,7 +11,7 @@ public class FieldAccessExpressionVisitor : ExpressionVisitor var scope = fieldAccessExpr.getScope(); ExpressionSyntax? scopeSyntax = null; - if (scope != null) + if (scope is not null) { scopeSyntax = VisitExpression(context, scope); diff --git a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs index d50a5c3f..2bc27069 100644 --- a/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/MethodCallExpressionVisitor.cs @@ -19,7 +19,7 @@ public class MethodCallExpressionVisitor : ExpressionVisitor var methodName = TypeHelper.Capitalize(methodCallExpr.getNameAsString()); methodName = TypeHelper.ReplaceCommonMethodNames(methodName); - if (scope != null) + if (scope is not null) { scopeSyntax = VisitExpression(context, scope); @@ -49,7 +49,7 @@ public class MethodCallExpressionVisitor : ExpressionVisitor ExpressionSyntax methodExpression; - if (scopeSyntax == null) + if (scopeSyntax is null) { methodExpression = SyntaxFactory.IdentifierName(methodName); } @@ -60,7 +60,7 @@ public class MethodCallExpressionVisitor : ExpressionVisitor var args = methodCallExpr.getArguments(); - if (args == null || args.size() == 0) + if (args is null || args.size() == 0) { return SyntaxFactory.InvocationExpression(methodExpression); } @@ -71,12 +71,12 @@ public class MethodCallExpressionVisitor : ExpressionVisitor private static bool TryGetMappedMethodName(string methodName, Expression? scope, ConversionContext context, out string mappedMethodName) { var mappings = context.Options.SyntaxMappings; - if (scope == null && mappings.VoidMethodMappings.TryGetValue(methodName, out var voidMapping)) + if (scope is null && mappings.VoidMethodMappings.TryGetValue(methodName, out var voidMapping)) { mappedMethodName = voidMapping; return true; } - else if (scope != null && mappings.NonVoidMethodMappings.TryGetValue(methodName, out var nonVoidMapping)) + else if (scope is not null && mappings.NonVoidMethodMappings.TryGetValue(methodName, out var nonVoidMapping)) { mappedMethodName = nonVoidMapping; return true; diff --git a/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs b/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs index bf980604..27af830a 100644 --- a/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/MethodReferenceExpressionVisitor.cs @@ -12,7 +12,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, MethodRefer var scope = expr.getScope(); ExpressionSyntax? scopeSyntax = null; - if (scope != null) + if (scope is not null) { scopeSyntax = VisitExpression(context, scope); } @@ -22,7 +22,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, MethodRefer ExpressionSyntax methodExpression; - if (scopeSyntax == null) + if (scopeSyntax is null) { methodExpression = SyntaxFactory.IdentifierName(methodName); } @@ -33,7 +33,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, MethodRefer var args = expr.getTypeArguments().FromOptional(); - if (args == null || args.size() == 0) + if (args is null || args.size() == 0) { return SyntaxFactory.InvocationExpression(methodExpression); } diff --git a/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs b/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs index a77708dd..c972d063 100644 --- a/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs +++ b/JavaToCSharp/Expressions/ObjectCreationExpressionVisitor.cs @@ -14,7 +14,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, ObjectCreat { var anonBody = newExpr.getAnonymousClassBody().FromOptional().ToList(); - if (anonBody is {Count: > 0}) + if (anonBody is { Count: > 0 }) { return VisitAnonymousClassCreationExpression(context, newExpr, anonBody); } @@ -23,7 +23,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, ObjectCreat //var scope = newExpr.getScope(); //ExpressionSyntax scopeSyntax = null; - //if (scope != null) { + //if (scope is not null) { // scopeSyntax = ExpressionVisitor.VisitExpression(context, scope); //} @@ -33,7 +33,7 @@ protected override ExpressionSyntax Visit(ConversionContext context, ObjectCreat var args = newExpr.getArguments(); - if (args == null || args.size() == 0) + if (args is null || args.size() == 0) { return SyntaxFactory.ObjectCreationExpression(typeSyntax).WithArgumentList(SyntaxFactory.ArgumentList()); } @@ -89,16 +89,16 @@ private static ObjectCreationExpressionSyntax VisitAnonymousClassCreationExpress // TODO.PI: do we need to pass extends/implements here? foreach (var memberSyntax in anonBody .Select(member => BodyDeclarationVisitor.VisitBodyDeclarationForClass(context, classSyntax, member, new List(), new List())) - .Where(memberSyntax => memberSyntax != null)) + .OfType()) { - classSyntax = classSyntax.AddMembers(memberSyntax!); + classSyntax = classSyntax.AddMembers(memberSyntax); } context.PendingAnonymousTypes.Enqueue(classSyntax); var args = newExpr.getArguments(); - if (args == null || args.size() == 0) + if (args is null || args.size() == 0) { return SyntaxFactory.ObjectCreationExpression(SyntaxFactory.ParseTypeName(anonTypeName)) .AddArgumentListArguments(SyntaxFactory.Argument(SyntaxFactory.ThisExpression())); diff --git a/JavaToCSharp/Extensions.cs b/JavaToCSharp/Extensions.cs index 8d959606..dcaa31dc 100644 --- a/JavaToCSharp/Extensions.cs +++ b/JavaToCSharp/Extensions.cs @@ -1,7 +1,6 @@ using System.Diagnostics.CodeAnalysis; using java.util; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using JavaAst = com.github.javaparser.ast; @@ -9,81 +8,98 @@ namespace JavaToCSharp; public static class Extensions { - /// - /// Converts a Java Iterable to a .NET IEnumerable<T> and filters the elements of type T. - /// - /// Type of items to be returned. /// The java Iterable to be enumerated. - /// A filtered enumeration of items of type T - public static IEnumerable OfType(this java.lang.Iterable iterable) + extension(java.lang.Iterable iterable) { - var iterator = iterable.iterator(); - while (iterator.hasNext()) + /// + /// Converts a Java Iterable to a .NET IEnumerable<T> and filters the elements of type T. + /// + /// Type of items to be returned. + /// A filtered enumeration of items of type T + public IEnumerable OfType() { - if (iterator.next() is T item) + var iterator = iterable.iterator(); + while (iterator.hasNext()) { - yield return item; + if (iterator.next() is T item) + { + yield return item; + } } } } - public static List? ToList(this java.util.List? list) + extension(java.util.List? list) { - if (list == null) - return null; + public List? ToList() + { + if (list is null) + { + return null; + } - var newList = new List(); + var newList = new List(); - for (int i = 0; i < list.size(); i++) - { - newList.Add((T)list.get(i)); - } + for (int i = 0; i < list.size(); i++) + { + newList.Add((T)list.get(i)); + } - return newList; + return newList; + } } - public static bool HasFlag(this java.util.EnumSet values, T flag) => values.contains(flag); - - [return: NotNullIfNotNull(nameof(node))] - public static TSyntax? WithJavaComments(this TSyntax? syntax, - ConversionContext context, - JavaAst.Node? node) - where TSyntax : SyntaxNode - => context.Options.IncludeComments - ? CommentsHelper.AddCommentsTrivias(syntax, node) - : syntax; + extension(java.util.EnumSet values) + { + public bool HasFlag(T flag) => values.contains(flag); + } - public static CompilationUnitSyntax WithPackageFileComments(this CompilationUnitSyntax syntax, - ConversionContext context, + extension(CompilationUnitSyntax syntax) + { + public CompilationUnitSyntax WithPackageFileComments(ConversionContext context, JavaAst.CompilationUnit compilationUnit, JavaAst.PackageDeclaration? packageDeclaration) - => context.Options.IncludeComments - ? CommentsHelper.AddPackageComments(syntax, compilationUnit, packageDeclaration) - : syntax; + => context.Options.IncludeComments + ? CommentsHelper.AddPackageComments(syntax, compilationUnit, packageDeclaration) + : syntax; + } - public static T? FromOptional(this Optional optional) - where T : class - => optional.isPresent() - ? optional.get() as T ?? - throw new InvalidOperationException($"Optional did not convert to {typeof(T)}") - : null; + extension(Optional optional) + { + public T? FromOptional() + where T : class + => optional.isPresent() + ? optional.get() as T ?? throw new InvalidOperationException($"Optional did not convert to {typeof(T)}") + : null; - public static T FromRequiredOptional(this Optional optional) - where T : class - => optional.isPresent() - ? 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 T FromRequiredOptional() + where T : class + => optional.isPresent() + ? 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(); + extension(JavaAst.NodeList nodeList) + { + public ISet ToModifierKeywordSet() + => nodeList.ToList()?.Select(i => i.getKeyword()).ToHashSet() ?? []; + } - public static TSyntax WithLeadingNewLines(this TSyntax syntax, int count = 1) - where TSyntax : SyntaxNode - => syntax.WithLeadingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); + extension(TSyntax syntax) where TSyntax : SyntaxNode + { + public TSyntax WithLeadingNewLines(int count = 1) + => syntax.WithLeadingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); + + public TSyntax WithTrailingNewLines(int count = 1) + => syntax.WithTrailingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); + } - public static TSyntax WithTrailingNewLines(this TSyntax syntax, int count = 1) - where TSyntax : SyntaxNode - => syntax.WithTrailingTrivia(Enumerable.Repeat(Whitespace.NewLine, count)); + extension(TSyntax? syntax) where TSyntax : SyntaxNode + { + [return: NotNullIfNotNull(nameof(node))] + public TSyntax? WithJavaComments(ConversionContext context, JavaAst.Node? node) + => context.Options.IncludeComments + ? CommentsHelper.AddCommentsTrivias(syntax, node) + : syntax; + } } diff --git a/JavaToCSharp/JavaToCSharp.csproj b/JavaToCSharp/JavaToCSharp.csproj index 66903f14..2cad3e2c 100644 --- a/JavaToCSharp/JavaToCSharp.csproj +++ b/JavaToCSharp/JavaToCSharp.csproj @@ -1,27 +1,28 @@  - - 4.0.0 - net10.0 - enable - enable - nullable - Paul Irwin - A Java to C# converter. - Copyright 2021-2023, Paul Irwin - MIT - https://github.com/paulirwin/JavaToCSharp - latest - - - - - - - - - - - - - + + 4.0.0 + net10.0 + enable + enable + true + Paul Irwin + A Java to C# converter. + Copyright 2021-2025, Paul Irwin + MIT + https://github.com/paulirwin/JavaToCSharp + latest + + + + + + + + + + + + + + diff --git a/JavaToCSharp/JavaToCSharpConverter.cs b/JavaToCSharp/JavaToCSharpConverter.cs index 3d11362e..676dbacf 100644 --- a/JavaToCSharp/JavaToCSharpConverter.cs +++ b/JavaToCSharp/JavaToCSharpConverter.cs @@ -49,8 +49,8 @@ public static class JavaToCSharpConverter options.ConversionStateChanged(ConversionState.BuildingCSharpAst); - var types = result.getTypes().ToList() ?? new List(); - var imports = result.getImports()?.ToList() ?? new List(); + var types = result.getTypes().ToList() ?? []; + var imports = result.getImports()?.ToList() ?? []; var package = result.getPackageDeclaration().FromOptional(); var rootMembers = new List(); @@ -67,7 +67,7 @@ public static class JavaToCSharpConverter continue; } - packageName = packageReplacement.Replace(packageName)!; + packageName = packageReplacement.Replace(packageName); } packageName = TypeHelper.Capitalize(packageName); @@ -105,7 +105,7 @@ public static class JavaToCSharpConverter } } - if (namespaceNameSyntax != null) + if (namespaceNameSyntax is not null) { if (options.UseFileScopedNamespaces && rootMembers.Count > 0) { diff --git a/JavaToCSharp/Replacement.cs b/JavaToCSharp/Replacement.cs index 18874428..00213b9e 100644 --- a/JavaToCSharp/Replacement.cs +++ b/JavaToCSharp/Replacement.cs @@ -2,24 +2,19 @@ namespace JavaToCSharp; -public class Replacement +public class Replacement(string pattern, string replacement, RegexOptions options = RegexOptions.None) { - public Replacement(string pattern, string replacement, RegexOptions options = RegexOptions.None) - { - Regex = new Regex(pattern, options); - ReplacementValue = replacement; - } + public Regex Regex { get; } = new(pattern, options); - public Regex? Regex { get; } + public string? ReplacementValue { get; } = replacement; - public string? ReplacementValue { get; } - - public string? Replace(string input) => string.IsNullOrWhiteSpace(ReplacementValue) ? null : Regex?.Replace(input, ReplacementValue); + public string Replace(string input) + => string.IsNullOrWhiteSpace(ReplacementValue) + ? string.Empty + : Regex.Replace(input, ReplacementValue); protected bool Equals(Replacement other) - { - return Equals(Regex, other.Regex) && ReplacementValue == other.ReplacementValue; - } + => Equals(Regex, other.Regex) && ReplacementValue == other.ReplacementValue; public override bool Equals(object? obj) { @@ -30,10 +25,5 @@ public override bool Equals(object? obj) } public override int GetHashCode() - { - unchecked - { - return ((Regex != null ? Regex.GetHashCode() : 0) * 397) ^ (ReplacementValue != null ? ReplacementValue.GetHashCode() : 0); - } - } + => HashCode.Combine(Regex, ReplacementValue); } diff --git a/JavaToCSharp/SanitizingSyntaxRewriter.cs b/JavaToCSharp/SanitizingSyntaxRewriter.cs index 37a1689f..d6b910f9 100644 --- a/JavaToCSharp/SanitizingSyntaxRewriter.cs +++ b/JavaToCSharp/SanitizingSyntaxRewriter.cs @@ -11,7 +11,7 @@ internal class SanitizingSyntaxRewriter : CSharpSyntaxRewriter { public override SyntaxNode? Visit(SyntaxNode? node) { - if (node != null) + if (node is not null) { // We must do this after whitespace normalization! node = CommentsHelper.FixCommentsWhitespaces(node); diff --git a/JavaToCSharp/Statements/AssertStatementVisitor.cs b/JavaToCSharp/Statements/AssertStatementVisitor.cs index b7c9b2cf..50cac058 100644 --- a/JavaToCSharp/Statements/AssertStatementVisitor.cs +++ b/JavaToCSharp/Statements/AssertStatementVisitor.cs @@ -11,7 +11,9 @@ public class AssertStatementVisitor : StatementVisitor public override StatementSyntax? Visit(ConversionContext context, AssertStmt assertStmt) { if (!context.Options.UseDebugAssertForAsserts) + { return null; + } var check = assertStmt.getCheck(); var checkSyntax = ExpressionVisitor.VisitExpression(context, check); @@ -22,14 +24,15 @@ public class AssertStatementVisitor : StatementVisitor var message = assertStmt.getMessage().FromOptional(); - if (message == null) + if (message is null) + { return SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName("Debug.Assert"), - SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] - { + SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList([ SyntaxFactory.Argument(checkSyntax) - })))); + ])))); + } var messageSyntax = ExpressionVisitor.VisitExpression(context, message); if (messageSyntax is null) @@ -40,6 +43,7 @@ public class AssertStatementVisitor : StatementVisitor return SyntaxFactory.ExpressionStatement( SyntaxFactory.InvocationExpression( SyntaxFactory.IdentifierName("Debug.Assert"), - SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { SyntaxFactory.Argument(checkSyntax), SyntaxFactory.Argument(messageSyntax) }, new[] { SyntaxFactory.Token(SyntaxKind.CommaToken) })))); + SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList([SyntaxFactory.Argument(checkSyntax), SyntaxFactory.Argument(messageSyntax) + ], [SyntaxFactory.Token(SyntaxKind.CommaToken)])))); } } diff --git a/JavaToCSharp/Statements/ExpressionStatementVisitor.cs b/JavaToCSharp/Statements/ExpressionStatementVisitor.cs index 2300f6b1..262cdbc7 100644 --- a/JavaToCSharp/Statements/ExpressionStatementVisitor.cs +++ b/JavaToCSharp/Statements/ExpressionStatementVisitor.cs @@ -15,7 +15,9 @@ public class ExpressionStatementVisitor : StatementVisitor // handle special case where AST is different if (expression is VariableDeclarationExpr expr) + { return VisitVariableDeclarationStatement(context, expr); + } var expressionSyntax = ExpressionVisitor.VisitExpression(context, expression); @@ -55,7 +57,7 @@ private static StatementSyntax VisitVariableDeclarationStatement(ConversionConte var initExpr = item.getInitializer().FromOptional(); - if (initExpr != null) + if (initExpr is not null) { var initSyntax = ExpressionVisitor.VisitExpression(context, initExpr); if (initSyntax is not null) @@ -65,7 +67,9 @@ private static StatementSyntax VisitVariableDeclarationStatement(ConversionConte } } else + { variables.Add(SyntaxFactory.VariableDeclarator(name)); + } } var typeSyntax = TypeHelper.ConvertTypeSyntax(commonType, arrayRank ?? 0); diff --git a/JavaToCSharp/Statements/ForEachStatementVisitor.cs b/JavaToCSharp/Statements/ForEachStatementVisitor.cs index f2b54250..19a43bec 100644 --- a/JavaToCSharp/Statements/ForEachStatementVisitor.cs +++ b/JavaToCSharp/Statements/ForEachStatementVisitor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.stmt; using JavaToCSharp.Expressions; using Microsoft.CodeAnalysis.CSharp; @@ -23,7 +21,7 @@ public class ForEachStatementVisitor : StatementVisitor var varType = varExpr.getCommonType(); var type = TypeHelper.ConvertType(varType); - var variableDeclarators = varExpr.getVariables()?.ToList()?? new List(); + var variableDeclarators = varExpr.getVariables()?.ToList()?? []; var vars = variableDeclarators .Select(i => SyntaxFactory.VariableDeclarator(i.toString())) .ToArray(); @@ -31,6 +29,6 @@ public class ForEachStatementVisitor : StatementVisitor var body = foreachStmt.getBody(); var bodySyntax = VisitStatement(context, body); - return bodySyntax == null ? null : SyntaxFactory.ForEachStatement(SyntaxFactory.ParseTypeName(type), vars[0].Identifier.ValueText, iterableSyntax, bodySyntax); + return bodySyntax is null ? null : SyntaxFactory.ForEachStatement(SyntaxFactory.ParseTypeName(type), vars[0].Identifier.ValueText, iterableSyntax, bodySyntax); } } diff --git a/JavaToCSharp/Statements/ForStatementVisitor.cs b/JavaToCSharp/Statements/ForStatementVisitor.cs index 33ca65c8..1f20c533 100644 --- a/JavaToCSharp/Statements/ForStatementVisitor.cs +++ b/JavaToCSharp/Statements/ForStatementVisitor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.body; +using com.github.javaparser.ast.body; using com.github.javaparser.ast.expr; using com.github.javaparser.ast.stmt; using JavaToCSharp.Expressions; @@ -18,7 +16,7 @@ public class ForStatementVisitor : StatementVisitor var initSyntaxes = new List(); VariableDeclarationSyntax? varSyntax = null; - if (inits != null) + if (inits is not null) { foreach (var init in inits) { @@ -26,7 +24,7 @@ public class ForStatementVisitor : StatementVisitor { var type = TypeHelper.ConvertType(varExpr.getCommonType()); - var variableDeclarators = varExpr.getVariables()?.ToList() ?? new List(); + var variableDeclarators = varExpr.getVariables()?.ToList() ?? []; var vars = variableDeclarators .Select(i => SyntaxFactory.VariableDeclarator(i.toString())) .ToArray(); @@ -50,17 +48,19 @@ public class ForStatementVisitor : StatementVisitor var increments = forStmt.getUpdate().ToList(); var incrementSyntaxes = new List(); - if (increments != null) + if (increments is not null) { var expressionSyntaxes = increments.Select(increment => ExpressionVisitor.VisitExpression(context, increment)); - incrementSyntaxes.AddRange(expressionSyntaxes.Where(expressionSyntax => expressionSyntax != null)!); + incrementSyntaxes.AddRange(expressionSyntaxes.OfType()); } var body = forStmt.getBody(); var bodySyntax = VisitStatement(context, body); - if (bodySyntax == null) + if (bodySyntax is null) + { return null; + } return SyntaxFactory.ForStatement(bodySyntax) .WithDeclaration(varSyntax) diff --git a/JavaToCSharp/Statements/IfStatementVisitor.cs b/JavaToCSharp/Statements/IfStatementVisitor.cs index f50f210c..1f5f8f67 100644 --- a/JavaToCSharp/Statements/IfStatementVisitor.cs +++ b/JavaToCSharp/Statements/IfStatementVisitor.cs @@ -11,6 +11,7 @@ public class IfStatementVisitor : StatementVisitor { var condition = ifStmt.getCondition(); var conditionSyntax = ExpressionVisitor.VisitExpression(context, condition); + if (conditionSyntax is null) { return null; @@ -19,20 +20,25 @@ public class IfStatementVisitor : StatementVisitor var thenStmt = ifStmt.getThenStmt(); var thenSyntax = VisitStatement(context, thenStmt); - if (thenSyntax == null) + if (thenSyntax is null) + { return null; + } var elseStmt = ifStmt.getElseStmt().FromOptional(); - if (elseStmt == null) + if (elseStmt is null) + { return SyntaxFactory.IfStatement(conditionSyntax, thenSyntax); + } var elseStatementSyntax = VisitStatement(context, elseStmt); + if (elseStatementSyntax is null) { return null; } - + var elseSyntax = SyntaxFactory.ElseClause(elseStatementSyntax); return SyntaxFactory.IfStatement(conditionSyntax, thenSyntax, elseSyntax); } diff --git a/JavaToCSharp/Statements/LabeledStatementVisitor.cs b/JavaToCSharp/Statements/LabeledStatementVisitor.cs index 7a5958c5..dbba5333 100644 --- a/JavaToCSharp/Statements/LabeledStatementVisitor.cs +++ b/JavaToCSharp/Statements/LabeledStatementVisitor.cs @@ -11,6 +11,6 @@ public class LabeledStatementVisitor : StatementVisitor var statement = labeledStmt.getStatement(); var syntax = VisitStatement(context, statement); - return syntax == null ? null : SyntaxFactory.LabeledStatement(labeledStmt.getLabel().asString(), syntax); + return syntax is null ? null : SyntaxFactory.LabeledStatement(labeledStmt.getLabel().asString(), syntax); } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Statements/ReturnStatementVisitor.cs b/JavaToCSharp/Statements/ReturnStatementVisitor.cs index d3542523..3b92de16 100644 --- a/JavaToCSharp/Statements/ReturnStatementVisitor.cs +++ b/JavaToCSharp/Statements/ReturnStatementVisitor.cs @@ -12,8 +12,10 @@ public override StatementSyntax Visit(ConversionContext context, ReturnStmt retu { var expr = returnStmt.getExpression().FromOptional(); - if (expr == null) + if (expr is null) + { return SyntaxFactory.ReturnStatement(); // i.e. "return" in a void method + } var exprSyntax = ExpressionVisitor.VisitExpression(context, expr); diff --git a/JavaToCSharp/Statements/StatementVisitor.cs b/JavaToCSharp/Statements/StatementVisitor.cs index 21ff885b..196cb9c9 100644 --- a/JavaToCSharp/Statements/StatementVisitor.cs +++ b/JavaToCSharp/Statements/StatementVisitor.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.stmt; +using com.github.javaparser.ast.stmt; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace JavaToCSharp.Statements; @@ -48,11 +45,12 @@ static StatementVisitor() protected abstract StatementSyntax? Visit(ConversionContext context, Statement statement); - public static List VisitStatements(ConversionContext context, IEnumerable? statements) => - statements == null - ? new List() + public static List VisitStatements(ConversionContext context, IEnumerable? statements) + => statements is null + ? [] : statements.Select(statement => VisitStatement(context, statement)) - .Where(syntax => syntax != null)!.ToList(); + .OfType() // filter out nulls + .ToList(); public static StatementSyntax? VisitStatement(ConversionContext context, Statement statement) { diff --git a/JavaToCSharp/Statements/SwitchStatementVisitor.cs b/JavaToCSharp/Statements/SwitchStatementVisitor.cs index 42e2db25..21612f24 100644 --- a/JavaToCSharp/Statements/SwitchStatementVisitor.cs +++ b/JavaToCSharp/Statements/SwitchStatementVisitor.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser.ast.expr; +using com.github.javaparser.ast.expr; using com.github.javaparser.ast.stmt; using JavaToCSharp.Expressions; using Microsoft.CodeAnalysis.CSharp; @@ -14,6 +12,7 @@ public class SwitchStatementVisitor : StatementVisitor { var selector = switchStmt.getSelector(); var selectorSyntax = ExpressionVisitor.VisitExpression(context, selector); + if (selectorSyntax is null) { return null; @@ -21,8 +20,10 @@ public class SwitchStatementVisitor : StatementVisitor var cases = switchStmt.getEntries().ToList(); - if (cases == null) + if (cases is null) + { return SyntaxFactory.SwitchStatement(selectorSyntax, SyntaxFactory.List()); + } var caseSyntaxes = new List(); @@ -58,9 +59,9 @@ public class SwitchStatementVisitor : StatementVisitor { var labelSyntaxes = labels .Select(i => ExpressionVisitor.VisitExpression(context, i)) - .Where(i => i != null) - .Select(i => (SwitchLabelSyntax)SyntaxFactory.CaseSwitchLabel(i!)); - + .OfType() + .Select(SwitchLabelSyntax (i) => SyntaxFactory.CaseSwitchLabel(i)); + var caseSyntax = SyntaxFactory.SwitchSection( SyntaxFactory.List(labelSyntaxes.ToList()), SyntaxFactory.List(syntaxes.AsEnumerable())); diff --git a/JavaToCSharp/Statements/TryStatementVisitor.cs b/JavaToCSharp/Statements/TryStatementVisitor.cs index 1650d408..e842134b 100644 --- a/JavaToCSharp/Statements/TryStatementVisitor.cs +++ b/JavaToCSharp/Statements/TryStatementVisitor.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using com.github.javaparser; +using com.github.javaparser; using com.github.javaparser.ast.expr; using com.github.javaparser.ast.stmt; using com.github.javaparser.ast.type; @@ -15,7 +12,7 @@ public class TryStatementVisitor : StatementVisitor { public override StatementSyntax Visit(ConversionContext context, TryStmt tryStmt) { - var resources = tryStmt.getResources().ToList() ?? new List(); + var resources = tryStmt.getResources().ToList() ?? []; var tryBlock = tryStmt.getTryBlock(); var tryStatements = tryBlock.getStatements().ToList(); @@ -57,19 +54,18 @@ public override StatementSyntax Visit(ConversionContext context, TryStmt tryStmt var variable = varDecl.getVariable(0); var variableInit = variable.getInitializer().FromRequiredOptional(); - + var initSyntax = ExpressionVisitor.VisitExpression(context, variableInit) ?? throw new InvalidOperationException("Unable to parse try-with-resources variable initializer"); - + result = SyntaxFactory.UsingStatement(result) .WithDeclaration( SyntaxFactory.VariableDeclaration( SyntaxFactory.ParseTypeName(TypeHelper.ConvertType(variable.getType())), - SyntaxFactory.SeparatedList(new[] - { + SyntaxFactory.SeparatedList([ SyntaxFactory.VariableDeclarator(variable.getNameAsString()) .WithInitializer(SyntaxFactory.EqualsValueClause(initSyntax)) - }) + ]) )); } else @@ -81,13 +77,13 @@ public override StatementSyntax Visit(ConversionContext context, TryStmt tryStmt if (tryStmt.getFinallyBlock().isPresent() || tryStmt.getCatchClauses().ToList()?.Count > 0) { - result = TransformTryBlock(context, tryStmt, new[] { result }); + result = TransformTryBlock(context, tryStmt, [result]); } return result; } - private static StatementSyntax TransformTryBlock(ConversionContext context, TryStmt tryStmt, + private static TryStatementSyntax TransformTryBlock(ConversionContext context, TryStmt tryStmt, IEnumerable tryBlockStatements) { var catches = tryStmt.getCatchClauses().ToList(); @@ -95,14 +91,14 @@ private static StatementSyntax TransformTryBlock(ConversionContext context, TryS var trySyn = SyntaxFactory.TryStatement() .AddBlockStatements(tryBlockStatements.ToArray()); - if (catches != null) + if (catches is not null) { foreach (var catchClause in catches) { var paramType = catchClause.getParameter().getType(); if (paramType is UnionType) { - var nodes = paramType.getChildNodes()?.ToList() ?? new List(); + var nodes = paramType.getChildNodes()?.ToList() ?? []; foreach (var node in nodes) { var referenceTypeName = node.getElementType().ToString(); @@ -119,8 +115,10 @@ private static StatementSyntax TransformTryBlock(ConversionContext context, TryS var finallyBlock = tryStmt.getFinallyBlock().FromOptional(); - if (finallyBlock == null) + if (finallyBlock is null) + { return trySyn; + } var finallyStatements = finallyBlock.getStatements().ToList(); var finallyConverted = VisitStatements(context, finallyStatements); @@ -154,4 +152,4 @@ private static TryStatementSyntax AddCatches(ConversionContext context, CatchCla return trySyn; } -} \ No newline at end of file +} diff --git a/JavaToCSharp/Statements/TypeDeclarationStatementVisitor.cs b/JavaToCSharp/Statements/TypeDeclarationStatementVisitor.cs index 4fd737d6..666460a3 100644 --- a/JavaToCSharp/Statements/TypeDeclarationStatementVisitor.cs +++ b/JavaToCSharp/Statements/TypeDeclarationStatementVisitor.cs @@ -13,11 +13,12 @@ public class TypeDeclarationStatementVisitor : StatementVisitor { var expr = whileStmt.getCondition(); var syntax = ExpressionVisitor.VisitExpression(context, expr); + if (syntax is null) { return null; diff --git a/JavaToCSharp/SyntaxMapping.cs b/JavaToCSharp/SyntaxMapping.cs index 50e18c08..08d8f5dc 100644 --- a/JavaToCSharp/SyntaxMapping.cs +++ b/JavaToCSharp/SyntaxMapping.cs @@ -21,6 +21,7 @@ private void Validate() ValidateMethodMapping(VoidMethodMappings); ValidateMethodMapping(NonVoidMethodMappings); } + private static void ValidateMethodMapping(Dictionary mapping) { // Throw exception if any of the requirements are not meet @@ -39,5 +40,4 @@ private static void ValidateMethodMapping(Dictionary mapping) } } } - } diff --git a/JavaToCSharp/TypeHelper.cs b/JavaToCSharp/TypeHelper.cs index 0eedaa70..6bb5f6bb 100644 --- a/JavaToCSharp/TypeHelper.cs +++ b/JavaToCSharp/TypeHelper.cs @@ -62,16 +62,7 @@ public static string ConvertType(Type type) } public static string ConvertType(string typeName) - { - return TypeNameParser.ParseTypeName(typeName, s => - { - if (_typeNameConversions.TryGetValue(s, out string? converted)) - { - return converted; - } - return s; - }); - } + => TypeNameParser.ParseTypeName(typeName, s => _typeNameConversions.TryGetValue(s, out string? converted) ? converted : s); public static TypeSyntax ConvertTypeSyntax(Type type, int arrayRank) { @@ -202,13 +193,17 @@ private static SeparatedSyntaxList GetSeparatedListFromArguments public static IEnumerable GetTypeParameterListConstraints(List typeParams) { var typeParameterConstraints = new List(); + foreach (TypeParameter typeParam in typeParams) { if (typeParam.getTypeBound().size() > 0) { var typeConstraintsSyntax = new SeparatedSyntaxList(); + foreach (ClassOrInterfaceType bound in typeParam.getTypeBound()) + { typeConstraintsSyntax = typeConstraintsSyntax.Add(SyntaxFactory.TypeConstraint(SyntaxFactory.ParseTypeName(bound.asString()))); + } var typeIdentifier = SyntaxFactory.IdentifierName(typeParam.getName().asString()); var parameterConstraintClauseSyntax = SyntaxFactory.TypeParameterConstraintClause(typeIdentifier, typeConstraintsSyntax); @@ -216,6 +211,7 @@ public static IEnumerable GetTypeParameterL typeParameterConstraints.Add(parameterConstraintClauseSyntax); } } + return typeParameterConstraints; } @@ -237,16 +233,21 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE switch (methodName.getIdentifier()) { case "length" when args.size() == 0: + { var scopeSyntaxLength = ExpressionVisitor.VisitExpression(context, scope); transformedSyntax = ReplaceMethodByProperty(scopeSyntaxLength, "Length"); return true; + } case "size" when args.size() == 0: + { var scopeSyntaxSize = ExpressionVisitor.VisitExpression(context, scope); transformedSyntax = ReplaceMethodByProperty(scopeSyntaxSize, "Count"); return true; + } case "get" when args.size() == 1: + { var scopeSyntaxGet = ExpressionVisitor.VisitExpression(context, scope); if (scopeSyntaxGet is null) { @@ -256,8 +257,10 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE transformedSyntax = ReplaceGetByIndexAccess(context, scopeSyntaxGet, args); return true; + } case "set" when args.size() == 2: + { var scopeSyntaxSet = ExpressionVisitor.VisitExpression(context, scope); if (scopeSyntaxSet is null) { @@ -267,13 +270,13 @@ public static bool TryTransformMethodCall(ConversionContext context, MethodCallE transformedSyntax = ReplaceSetByIndexAccess(context, scopeSyntaxSet, args); return true; + } } } transformedSyntax = null; return false; - static MemberAccessExpressionSyntax? ReplaceMethodByProperty(ExpressionSyntax? scopeSyntax, string identifier) { if (scopeSyntax is null) @@ -304,7 +307,7 @@ static ExpressionSyntax ReplaceGetByIndexAccess(ConversionContext context, Expre java.util.List args) { // Replace expr.Set(i,v) by expr[i] = v - var argsList = args.ToList() ?? new List(); + var argsList = args.ToList() ?? []; var left = SyntaxFactory.ElementAccessExpression( scopeSyntax, SyntaxFactory.BracketedArgumentList(GetSeparatedListFromArguments(context, argsList.Take(1))) diff --git a/JavaToCSharp/TypeNameParser.cs b/JavaToCSharp/TypeNameParser.cs index ada68811..353bab20 100644 --- a/JavaToCSharp/TypeNameParser.cs +++ b/JavaToCSharp/TypeNameParser.cs @@ -1,10 +1,9 @@ -using System; -using System.Text; +using System.Text; using System.Text.RegularExpressions; namespace JavaToCSharp; -public static class TypeNameParser +public static partial class TypeNameParser { private enum TokenType { @@ -20,7 +19,8 @@ private enum TokenType QuestionMark } - private static readonly Regex _tokenizePattern = new(@"\w+|\[|\]|<|>|,|\?", RegexOptions.Compiled); + [GeneratedRegex(@"\w+|\[|\]|<|>|,|\?", RegexOptions.Compiled)] + private static partial Regex TokenizePattern { get; } private static (string, TokenType)[]? _tokens; private static (string text, TokenType type) _token; @@ -53,7 +53,7 @@ internal static string ParseTypeName(string typename, Func trans private static (string, TokenType)[] Tokenize(string typeName) { - var matches = _tokenizePattern.Matches(typeName); + var matches = TokenizePattern.Matches(typeName); var tokens = new (string, TokenType)[matches.Count]; for (int i = 0; i < matches.Count; i++) { diff --git a/JavaToCSharp/UsingsHelper.cs b/JavaToCSharp/UsingsHelper.cs index 5b2a75e4..a4630ff6 100644 --- a/JavaToCSharp/UsingsHelper.cs +++ b/JavaToCSharp/UsingsHelper.cs @@ -25,7 +25,7 @@ public static IEnumerable GetUsings(ConversionContext cont var nameSpace = TypeHelper.Capitalize(importNameWithoutClassName); // Override namespace if a non empty mapping is found (mapping to empty string removes the import) - if (options != null && options.SyntaxMappings.ImportMappings.TryGetValue(importName, out var mappedNamespace)) + if (options is not null && options.SyntaxMappings.ImportMappings.TryGetValue(importName, out var mappedNamespace)) { if (string.IsNullOrEmpty(mappedNamespace)) { @@ -51,7 +51,7 @@ public static IEnumerable GetUsings(ConversionContext cont .Select(ns => SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(ns)).NormalizeWhitespace().WithTrailingNewLines())); } - if (namespaceNameSyntax != null) + if (namespaceNameSyntax is not null) { foreach (var staticUsing in options?.StaticUsingEnumNames ?? []) { @@ -90,7 +90,5 @@ public bool Equals(UsingDirectiveSyntax? x, UsingDirectiveSyntax? y) } public int GetHashCode(UsingDirectiveSyntax obj) - { - return HashCode.Combine(obj.Alias?.ToString() ?? "", obj.Name?.ToString() ?? ""); - } + => HashCode.Combine(obj.Alias?.ToString() ?? "", obj.Name?.ToString() ?? ""); } diff --git a/JavaToCSharpCli/JavaToCSharpCli.csproj b/JavaToCSharpCli/JavaToCSharpCli.csproj index 6c36c40a..564e41a9 100644 --- a/JavaToCSharpCli/JavaToCSharpCli.csproj +++ b/JavaToCSharpCli/JavaToCSharpCli.csproj @@ -6,7 +6,7 @@ latest enable enable - nullable + true diff --git a/JavaToCSharpCli/Program.cs b/JavaToCSharpCli/Program.cs index 5c831389..d74a0fc3 100644 --- a/JavaToCSharpCli/Program.cs +++ b/JavaToCSharpCli/Program.cs @@ -1,5 +1,4 @@ using System.CommandLine; -using System.CommandLine.Invocation; using JavaToCSharp; using Microsoft.Extensions.Logging; @@ -133,18 +132,20 @@ private static Command CreateFileCommand() DefaultValueFactory = _ => null, }; - var fileCommand = new Command("file", "Convert a Java file to C#"); - fileCommand.Arguments.Add(inputArgument); - fileCommand.Arguments.Add(outputArgument); + var fileCommand = new Command("file", "Convert a Java file to C#") + { + inputArgument, + outputArgument, + }; fileCommand.SetAction(context => { - var input = context.GetValue(inputArgument); + var input = context.GetRequiredValue(inputArgument); var output = context.GetValue(outputArgument); var options = GetJavaConversionOptions(context); - ConvertToCSharpFile(input!, output, options); + ConvertToCSharpFile(input, output, options); }); return fileCommand; @@ -178,7 +179,7 @@ private static JavaConversionOptions GetJavaConversionOptions(ParseResult contex options.AddUsing("System.Text"); } - foreach (string ns in context.GetValue(_addUsingsOption) ?? new List()) + foreach (string ns in context.GetValue(_addUsingsOption) ?? []) { options.AddUsing(ns); } @@ -203,26 +204,28 @@ private static Command CreateDirectoryCommand() { var inputArgument = new Argument("input") { - Description = "A directory containing Java source code files to convert" + Description = "A directory containing Java source code files to convert", }; var outputArgument = new Argument("output") { - Description = "Path to place the C# output files" + Description = "Path to place the C# output files", }; - var dirCommand = new Command("dir", "Convert a directory containing Java files to C#"); - dirCommand.Arguments.Add(inputArgument); - dirCommand.Arguments.Add(outputArgument); + var dirCommand = new Command("dir", "Convert a directory containing Java files to C#") + { + inputArgument, + outputArgument, + }; dirCommand.SetAction(context => { - var input = context.GetValue(inputArgument); - var output = context.GetValue(outputArgument); + var input = context.GetRequiredValue(inputArgument); + var output = context.GetRequiredValue(outputArgument); var options = GetJavaConversionOptions(context); - ConvertToCSharpDir(input!, output!, options); + ConvertToCSharpDir(input, output, options); }); return dirCommand; diff --git a/JavaToCSharpGui/App.axaml b/JavaToCSharpGui/App.axaml index 3128bd0d..29a53652 100644 --- a/JavaToCSharpGui/App.axaml +++ b/JavaToCSharpGui/App.axaml @@ -6,11 +6,11 @@ RequestedThemeVariant="Default"> 14 - Cascadia Code,SF Mono,DejaVu Sans Mono,Menlo,Consolas + Cascadia Code,SF Mono,DejaVu Sans Mono,Menlo,Consolas - - + + diff --git a/JavaToCSharpGui/App.axaml.cs b/JavaToCSharpGui/App.axaml.cs index fc990f8e..5ab33082 100644 --- a/JavaToCSharpGui/App.axaml.cs +++ b/JavaToCSharpGui/App.axaml.cs @@ -15,13 +15,13 @@ public override void Initialize() public override void OnFrameworkInitializationCompleted() { - if(ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { // Line below is needed to remove Avalonia data validation. // Without this line you will get duplicate validations from both Avalonia and CT BindingPlugins.DataValidators.RemoveAt(0); - desktop.MainWindow = new MainWindow(); + desktop.MainWindow = new MainWindow(); base.OnFrameworkInitializationCompleted(); } } -} \ No newline at end of file +} diff --git a/JavaToCSharpGui/Infrastructure/ITextClipboard.cs b/JavaToCSharpGui/Infrastructure/ITextClipboard.cs index 0c71366e..9a5cb9d6 100644 --- a/JavaToCSharpGui/Infrastructure/ITextClipboard.cs +++ b/JavaToCSharpGui/Infrastructure/ITextClipboard.cs @@ -8,7 +8,7 @@ public interface ITextClipboard /// /// Gets the clipboard's text. /// - /// A Task representing the async operation that returns the clipboard text. + /// A Task representing the async operation. The result is the clipboard text, or null if unavailable. Task GetTextAsync(); /// diff --git a/JavaToCSharpGui/Infrastructure/IUIDispatcher.cs b/JavaToCSharpGui/Infrastructure/IUIDispatcher.cs index 44e8d0bf..33b31d20 100644 --- a/JavaToCSharpGui/Infrastructure/IUIDispatcher.cs +++ b/JavaToCSharpGui/Infrastructure/IUIDispatcher.cs @@ -3,7 +3,7 @@ namespace JavaToCSharpGui.Infrastructure; /// -/// Provides acces to the UI thread. +/// Provides access to the UI thread. /// public interface IUIDispatcher { diff --git a/JavaToCSharpGui/Infrastructure/TextClipboard.cs b/JavaToCSharpGui/Infrastructure/TextClipboard.cs index 4c91edad..3f5302d2 100644 --- a/JavaToCSharpGui/Infrastructure/TextClipboard.cs +++ b/JavaToCSharpGui/Infrastructure/TextClipboard.cs @@ -3,29 +3,27 @@ namespace JavaToCSharpGui.Infrastructure; /// -internal class TextClipboard : ITextClipboard +internal class TextClipboard(IClipboard? clipboard) : ITextClipboard { - private readonly IClipboard? _clipboard; - - public TextClipboard(IClipboard? clipboard) => _clipboard = clipboard; - /// public async Task GetTextAsync() { - if (_clipboard is null) + if (clipboard is null) { return null; } - return await _clipboard.GetTextAsync(); + + return await clipboard.TryGetTextAsync(); } /// public async Task SetTextAsync(string? text) { - if(_clipboard is null) + if (clipboard is null) { return; } - await _clipboard.SetTextAsync(text); + + await clipboard.SetTextAsync(text); } } diff --git a/JavaToCSharpGui/Infrastructure/UIDispatcher.cs b/JavaToCSharpGui/Infrastructure/UIDispatcher.cs index efc1bbd4..08da38e1 100644 --- a/JavaToCSharpGui/Infrastructure/UIDispatcher.cs +++ b/JavaToCSharpGui/Infrastructure/UIDispatcher.cs @@ -5,19 +5,12 @@ using Avalonia.Threading; /// -public class UIDispatcher : IUIDispatcher +public class UIDispatcher(IDispatcher dispatcher) : IUIDispatcher { - private readonly IDispatcher _dispatcher; - - public UIDispatcher(IDispatcher dispatcher) - { - _dispatcher = dispatcher; - } - /// public async Task InvokeAsync(Action callback, DispatcherPriority priority) { - if (_dispatcher is Dispatcher avaloniaDispatcher) + if (dispatcher is Dispatcher avaloniaDispatcher) { await avaloniaDispatcher.InvokeAsync(callback, priority); } diff --git a/JavaToCSharpGui/JavaToCSharpGui.csproj b/JavaToCSharpGui/JavaToCSharpGui.csproj index 170f0084..334d7f4c 100644 --- a/JavaToCSharpGui/JavaToCSharpGui.csproj +++ b/JavaToCSharpGui/JavaToCSharpGui.csproj @@ -8,26 +8,23 @@ latest enable enable - Nullable + true true + 11.3.8 - + + + + + + - - - - - - - - - diff --git a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs index 7372a7f9..ac058707 100644 --- a/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs +++ b/JavaToCSharpGui/ViewModels/MainWindowViewModel.cs @@ -188,6 +188,7 @@ await Task.Run(async () => foreach (var jFile in FolderInputFiles.Where(static x => x.Directory is not null)) { + // ! null checked above string jPath = jFile.Directory!.FullName; string jOutPath = $"{outDirFullName}{jPath[subStartIndex..]}"; string jOutFileName = Path.GetFileNameWithoutExtension(jFile.Name) + ".cs"; @@ -314,23 +315,83 @@ private async Task OpenFileDialog() } [RelayCommand] - private async Task PasteInput() + private async Task PasteJavaCode() { if (_clipboard is null) { return; } - var text = await _clipboard.GetTextAsync(); - if (!string.IsNullOrEmpty(text)) + string? clipboardText = await _clipboard.GetTextAsync(); + if (clipboardText is null) { - JavaText.Text = text; - ConversionStateLabel = "Pasted Java code from clipboard!"; + ShowMessage("Clipboard is empty or unavailable.", "Paste Error"); + return; + } - await Task.Delay(2000); + JavaText.Text = clipboardText; + ConversionStateLabel = "Pasted Java code from clipboard!"; + + await Task.Delay(2000); + + await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); + } - await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); + [RelayCommand] + private async Task ClipboardConvert() + { + if (_clipboard is null) + { + return; } + + // Get clipboard text + string? clipboardText = await _clipboard.GetTextAsync(); + if (clipboardText is null) + { + ShowMessage("Clipboard is empty or unavailable.", "Clipboard Convert Error"); + return; + } + + // Set Java text + JavaText.Text = clipboardText; + + // Perform conversion + CurrentOptions.Options.WarningEncountered += Options_WarningEncountered; + CurrentOptions.Options.StateChanged += Options_StateChanged; + + IsConvertEnabled = false; + _usingFolderConvert = false; + + await Task.Run(async () => + { + try + { + string? csharp = JavaToCSharpConverter.ConvertText(clipboardText, CurrentOptions.Options); + await DispatcherInvoke(() => CSharpText.Text = csharp ?? ""); + + // Copy result to clipboard + await _clipboard.SetTextAsync(csharp ?? ""); + ConversionStateLabel = "Conversion complete! C# code copied to clipboard."; + + await Task.Delay(2000); + await _dispatcher.InvokeAsync(() => { ConversionStateLabel = ""; }, DispatcherPriority.Background); + } + catch (Exception ex) + { + await DispatcherInvoke(() => + ShowMessage($"There was an error converting the text to C#: {ex.GetBaseException().Message}", + "Conversion Error")); + + ConversionStateLabel = ""; + } + finally + { + await DispatcherInvoke(() => IsConvertEnabled = true); + CurrentOptions.Options.WarningEncountered -= Options_WarningEncountered; + CurrentOptions.Options.StateChanged -= Options_StateChanged; + } + }); } [RelayCommand] @@ -356,7 +417,7 @@ private async Task SaveOutput() { IStorageFolder? startLocation = null; - if (Path.GetDirectoryName(OpenPath) is string dir) + if (Path.GetDirectoryName(OpenPath) is { } dir) { startLocation = await _storageProvider.TryGetFolderFromPathAsync(dir); } diff --git a/JavaToCSharpGui/Views/AboutWindow.axaml.cs b/JavaToCSharpGui/Views/AboutWindow.axaml.cs index 626d9114..ec5a2e93 100644 --- a/JavaToCSharpGui/Views/AboutWindow.axaml.cs +++ b/JavaToCSharpGui/Views/AboutWindow.axaml.cs @@ -7,22 +7,18 @@ namespace JavaToCSharpGui.Views; public partial class AboutWindow : Window { - private readonly string _version; - public AboutWindow() { var assembly = Assembly.GetExecutingAssembly(); - _version = assembly.GetCustomAttribute()?.InformationalVersion + VersionString = assembly.GetCustomAttribute()?.InformationalVersion ?? assembly.GetName().Version?.ToString() ?? "Unknown"; InitializeComponent(); DataContext = this; - - } - public string VersionString => $"Version {_version}"; + public string VersionString => $"Version {field}"; private void GitHubLinkTapped(object? sender, TappedEventArgs e) => Process.Start(new ProcessStartInfo { diff --git a/JavaToCSharpGui/Views/MainWindow.axaml b/JavaToCSharpGui/Views/MainWindow.axaml index 167ce988..ab9e6a54 100644 --- a/JavaToCSharpGui/Views/MainWindow.axaml +++ b/JavaToCSharpGui/Views/MainWindow.axaml @@ -59,27 +59,27 @@ Java Source Code Input: - - File: + + + File: - - - + + + + C# Output: