feat: 实现 Java → C# 转换器 (Task 2.5)
新增组件: - JavaToCSharpStrategy: Java 到 C# 转换策略 - JavaToCSharpConverter: Java 到 C# 转换器 - JavaParser: Java 简化解析器(基于文本处理) - CSharpCodeGenerator: C# 代码生成器 功能实现: - 18 种类型映射(Java → C#) - package → namespace 转换 - import → using 转换 - JavaDoc → XML Doc 转换 - Stream API 检测并添加 TODO - CompletableFuture 检测并添加 TODO 测试覆盖: - 添加 4 个 JavaToCSharpConverterTests 测试用例 - 总测试数 17 个,全部通过 Co-authored-by: monkeycode-ai <monkeycode-ai@chaitin.com>
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using System.Text;
|
||||
using CodePlay.Core.Interfaces;
|
||||
using CodePlay.Core.Common;
|
||||
|
||||
namespace CodePlay.Core.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// C# 代码生成器
|
||||
/// </summary>
|
||||
public class CSharpCodeGenerator : ICodeGenerator
|
||||
{
|
||||
private readonly StringBuilder _output = new();
|
||||
private int _indentLevel;
|
||||
|
||||
/// <summary>
|
||||
/// 从语法树生成 C# 代码
|
||||
/// </summary>
|
||||
public string Generate(Interfaces.SyntaxTree syntaxTree)
|
||||
{
|
||||
_output.Clear();
|
||||
_indentLevel = 0;
|
||||
|
||||
// 生成 using 指令
|
||||
GenerateUsings(syntaxTree);
|
||||
|
||||
// 生成代码
|
||||
GenerateNode(syntaxTree.Root);
|
||||
|
||||
return _output.ToString();
|
||||
}
|
||||
|
||||
private void GenerateUsings(Interfaces.SyntaxTree tree)
|
||||
{
|
||||
_output.AppendLine("using System;");
|
||||
_output.AppendLine("using System.Collections.Generic;");
|
||||
_output.AppendLine();
|
||||
}
|
||||
|
||||
private void GenerateNode(Interfaces.SyntaxNode node)
|
||||
{
|
||||
switch (node.Type)
|
||||
{
|
||||
case SyntaxNodeType.CompilationUnit:
|
||||
GenerateCompilationUnit(node);
|
||||
break;
|
||||
case SyntaxNodeType.Namespace:
|
||||
GenerateNamespace(node);
|
||||
break;
|
||||
case SyntaxNodeType.Class:
|
||||
GenerateClass(node);
|
||||
break;
|
||||
case SyntaxNodeType.Method:
|
||||
GenerateMethod(node);
|
||||
break;
|
||||
case SyntaxNodeType.Property:
|
||||
GenerateProperty(node);
|
||||
break;
|
||||
case SyntaxNodeType.Field:
|
||||
GenerateField(node);
|
||||
break;
|
||||
default:
|
||||
GenerateDefault(node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateCompilationUnit(Interfaces.SyntaxNode node)
|
||||
{
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
GenerateNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateNamespace(Interfaces.SyntaxNode node)
|
||||
{
|
||||
var namespaceName = ExtractNamespaceName(node.Text);
|
||||
_output.AppendLine($"namespace {namespaceName}");
|
||||
_output.AppendLine("{");
|
||||
_indentLevel++;
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (child.Type != SyntaxNodeType.Unknown)
|
||||
{
|
||||
GenerateNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
_indentLevel--;
|
||||
_output.AppendLine("}");
|
||||
_output.AppendLine();
|
||||
}
|
||||
|
||||
private void GenerateClass(Interfaces.SyntaxNode node)
|
||||
{
|
||||
var classDeclaration = node.Text.Split('{')[0].Trim();
|
||||
_output.AppendLine(Indent(classDeclaration));
|
||||
_output.AppendLine(Indent("{"));
|
||||
_indentLevel++;
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
if (child.Type != SyntaxNodeType.Unknown && child.Type != SyntaxNodeType.CompilationUnit)
|
||||
{
|
||||
GenerateNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
_indentLevel--;
|
||||
_output.AppendLine(Indent("}"));
|
||||
_output.AppendLine();
|
||||
}
|
||||
|
||||
private void GenerateMethod(Interfaces.SyntaxNode node)
|
||||
{
|
||||
var methodSignature = ExtractMethodSignature(node.Text);
|
||||
_output.AppendLine(Indent(methodSignature));
|
||||
_output.AppendLine(Indent("{"));
|
||||
_indentLevel++;
|
||||
|
||||
var methodBody = ExtractMethodBody(node.Text);
|
||||
if (!string.IsNullOrWhiteSpace(methodBody))
|
||||
{
|
||||
var lines = methodBody.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line.Trim()))
|
||||
{
|
||||
_output.AppendLine(Indent(line.Trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_indentLevel--;
|
||||
_output.AppendLine(Indent("}"));
|
||||
_output.AppendLine();
|
||||
}
|
||||
|
||||
private void GenerateProperty(Interfaces.SyntaxNode node)
|
||||
{
|
||||
var propertyDeclaration = node.Text.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(propertyDeclaration))
|
||||
{
|
||||
_output.AppendLine(Indent(propertyDeclaration.EndsWith(";") ? propertyDeclaration : propertyDeclaration + ";"));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateField(Interfaces.SyntaxNode node)
|
||||
{
|
||||
var fieldDeclaration = node.Text.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(fieldDeclaration))
|
||||
{
|
||||
_output.AppendLine(Indent(fieldDeclaration.EndsWith(";") ? fieldDeclaration : fieldDeclaration + ";"));
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateDefault(Interfaces.SyntaxNode node)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(node.Text) && node.Type != SyntaxNodeType.Comment)
|
||||
{
|
||||
var lines = node.Text.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (!string.IsNullOrWhiteSpace(trimmed) &&
|
||||
trimmed != "{" &&
|
||||
trimmed != "}")
|
||||
{
|
||||
_output.AppendLine(Indent(trimmed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
GenerateNode(child);
|
||||
}
|
||||
}
|
||||
|
||||
private string Indent(string text)
|
||||
{
|
||||
var indent = new string(' ', _indentLevel * 4);
|
||||
return indent + text;
|
||||
}
|
||||
|
||||
private string ExtractNamespaceName(string text)
|
||||
{
|
||||
if (text.StartsWith("namespace "))
|
||||
{
|
||||
var match = System.Text.RegularExpressions.Regex.Match(text, @"namespace\s+([\w.]+)");
|
||||
return match.Success ? match.Groups[1].Value : "CodePlay.Converted";
|
||||
}
|
||||
return "CodePlay.Converted";
|
||||
}
|
||||
|
||||
private string ExtractMethodSignature(string text)
|
||||
{
|
||||
var lines = text.Split('\n');
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var trimmed = line.Trim();
|
||||
if (trimmed.Length > 0 &&
|
||||
!trimmed.StartsWith("{") &&
|
||||
!trimmed.StartsWith("}") &&
|
||||
(trimmed.Contains("(") || trimmed.Contains("void") || trimmed.Contains("public") || trimmed.Contains("private")))
|
||||
{
|
||||
return trimmed.EndsWith("{") ? trimmed.Substring(0, trimmed.Length - 1).Trim() : trimmed;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private string ExtractMethodBody(string text)
|
||||
{
|
||||
var startIndex = text.IndexOf('{');
|
||||
var endIndex = text.LastIndexOf('}');
|
||||
|
||||
if (startIndex >= 0 && endIndex > startIndex)
|
||||
{
|
||||
return text.Substring(startIndex + 1, endIndex - startIndex - 1).Trim();
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using CodePlay.Core.Interfaces;
|
||||
using CodePlay.Core.Models;
|
||||
using CodePlay.Core.Common;
|
||||
|
||||
namespace CodePlay.Core.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Java 到 C# 代码转换器
|
||||
/// </summary>
|
||||
public class JavaToCSharpConverter : IConverter
|
||||
{
|
||||
private readonly JavaToCSharpStrategy _strategy;
|
||||
private readonly CSharpCodeGenerator _codeGenerator;
|
||||
|
||||
public JavaToCSharpConverter()
|
||||
{
|
||||
_strategy = new JavaToCSharpStrategy();
|
||||
_codeGenerator = new CSharpCodeGenerator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换语法树
|
||||
/// </summary>
|
||||
public async Task<ConversionResult> ConvertAsync(
|
||||
Interfaces.SyntaxTree syntaxTree,
|
||||
LanguageType targetLanguage,
|
||||
ConversionOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new ConversionResult
|
||||
{
|
||||
Success = false,
|
||||
Warnings = new List<ConversionWarning>(),
|
||||
Report = new ConversionReport()
|
||||
};
|
||||
|
||||
if (targetLanguage != LanguageType.CSharp)
|
||||
{
|
||||
result.ErrorMessage = "This converter only supports Java to C# conversion";
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var context = new ConversionContext
|
||||
{
|
||||
SourceLanguage = LanguageType.Java,
|
||||
TargetLanguage = LanguageType.CSharp,
|
||||
Options = options
|
||||
};
|
||||
|
||||
// 转换根节点
|
||||
var convertedRoot = _strategy.ConvertNode(syntaxTree.Root, context);
|
||||
|
||||
// 创建新的语法树
|
||||
var convertedTree = new Interfaces.SyntaxTree
|
||||
{
|
||||
Language = LanguageType.CSharp,
|
||||
Root = convertedRoot,
|
||||
SourceCode = syntaxTree.SourceCode
|
||||
};
|
||||
|
||||
// 保留注释和文档
|
||||
if (options?.KeepComments == true)
|
||||
{
|
||||
convertedTree.Documentation = syntaxTree.Documentation
|
||||
.Select(d => new SyntaxDocumentation
|
||||
{
|
||||
ElementName = d.ElementName,
|
||||
Content = ConvertDocumentation(d.Content),
|
||||
Format = DocFormat.XmlDoc
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
// 生成 C# 代码
|
||||
var generatedCode = _codeGenerator.Generate(convertedTree);
|
||||
|
||||
result.TransformedCode = generatedCode;
|
||||
result.Success = true;
|
||||
|
||||
// 生成报告
|
||||
if (result.Report != null)
|
||||
{
|
||||
result.Report.LinesConverted = syntaxTree.SourceCode?.Split('\n').Length ?? 0;
|
||||
result.Report.ClassesConverted = CountClasses(syntaxTree.Root);
|
||||
result.Report.MethodsConverted = CountMethods(syntaxTree.Root);
|
||||
result.Report.TodoItems = context.TodoItems;
|
||||
result.Report.Issues = context.Issues;
|
||||
result.Report.TransformationLog = context.Logs;
|
||||
}
|
||||
|
||||
context.Logs.Add(new TransformationLog
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Operation = "Conversion",
|
||||
Details = "Java to C# conversion completed",
|
||||
Level = LogLevel.Info
|
||||
});
|
||||
|
||||
result.Warnings = context.Issues
|
||||
.Select(i => new ConversionWarning
|
||||
{
|
||||
Code = $"WARN_{i.Type}",
|
||||
Message = i.Description,
|
||||
Suggestion = i.Suggestion
|
||||
}).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.ErrorMessage = ex.Message;
|
||||
result.Success = false;
|
||||
}
|
||||
|
||||
return await Task.FromResult(result);
|
||||
}
|
||||
|
||||
private string ConvertDocumentation(string javaDocContent)
|
||||
{
|
||||
// JavaDoc 到 XML Doc 转换
|
||||
var content = javaDocContent
|
||||
.Replace("/**", "///")
|
||||
.Replace("*/", "")
|
||||
.Replace("*", "///")
|
||||
.Replace("@param ", "<param name=\"")
|
||||
.Replace("@return", "<returns>")
|
||||
.Replace("@throws ", "<exception cref=\"")
|
||||
.Replace("@see ", "<seealso cref=\"");
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
private int CountClasses(Interfaces.SyntaxNode node)
|
||||
{
|
||||
int count = 0;
|
||||
if (node.Type == SyntaxNodeType.Class) count++;
|
||||
count += node.Children.Sum(CountClasses);
|
||||
return count;
|
||||
}
|
||||
|
||||
private int CountMethods(Interfaces.SyntaxNode node)
|
||||
{
|
||||
int count = 0;
|
||||
if (node.Type == SyntaxNodeType.Method) count++;
|
||||
count += node.Children.Sum(CountMethods);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using CodePlay.Core.Interfaces;
|
||||
using CodePlay.Core.Models;
|
||||
using CodePlay.Core.Common;
|
||||
|
||||
namespace CodePlay.Core.Converters;
|
||||
|
||||
/// <summary>
|
||||
/// Java 到 C# 转换策略
|
||||
/// </summary>
|
||||
public class JavaToCSharpStrategy : IConversionStrategy
|
||||
{
|
||||
/// <summary>
|
||||
/// 源语言
|
||||
/// </summary>
|
||||
public LanguageType SourceLanguage => LanguageType.Java;
|
||||
|
||||
/// <summary>
|
||||
/// 目标语言
|
||||
/// </summary>
|
||||
public LanguageType TargetLanguage => LanguageType.CSharp;
|
||||
|
||||
private readonly List<TypeMapping> _typeMappings = new();
|
||||
|
||||
public JavaToCSharpStrategy()
|
||||
{
|
||||
InitializeTypeMappings();
|
||||
}
|
||||
|
||||
private void InitializeTypeMappings()
|
||||
{
|
||||
_typeMappings.AddRange(new[]
|
||||
{
|
||||
new TypeMapping("java.lang.String", "string"),
|
||||
new TypeMapping("java.lang.Object", "object"),
|
||||
new TypeMapping("java.lang.Integer", "int"),
|
||||
new TypeMapping("java.lang.Long", "long"),
|
||||
new TypeMapping("java.lang.Boolean", "bool"),
|
||||
new TypeMapping("java.lang.Double", "double"),
|
||||
new TypeMapping("java.lang.Float", "float"),
|
||||
new TypeMapping("java.util.ArrayList", "List"),
|
||||
new TypeMapping("java.util.List", "IEnumerable"),
|
||||
new TypeMapping("java.util.HashMap", "Dictionary"),
|
||||
new TypeMapping("java.util.Map", "IDictionary"),
|
||||
new TypeMapping("java.util.stream.Stream", "IEnumerable"),
|
||||
new TypeMapping("java.util.Arrays", "Array"),
|
||||
new TypeMapping("java.time.LocalDateTime", "DateTime"),
|
||||
new TypeMapping("java.time.Duration", "TimeSpan"),
|
||||
new TypeMapping("java.lang.Exception", "Exception"),
|
||||
new TypeMapping("java.lang.IllegalArgumentException", "ArgumentException"),
|
||||
new TypeMapping("java.lang.IllegalStateException", "InvalidOperationException"),
|
||||
new TypeMapping("java.lang.NullPointerException", "NullReferenceException"),
|
||||
new TypeMapping("java.util.concurrent.CompletableFuture", "Task"),
|
||||
new TypeMapping("System.out.println", "Console.WriteLine"),
|
||||
new TypeMapping("public static void main", "static void Main"),
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 映射类型
|
||||
/// </summary>
|
||||
public string MapType(string sourceType)
|
||||
{
|
||||
var result = sourceType;
|
||||
|
||||
foreach (var mapping in _typeMappings)
|
||||
{
|
||||
result = result.Replace(mapping.SourceType, mapping.TargetType);
|
||||
}
|
||||
|
||||
// Java 到 C# 的特定转换
|
||||
result = result
|
||||
.Replace("var ", "var ")
|
||||
.Replace("final ", "")
|
||||
.Replace(".size()", ".Count")
|
||||
.Replace(".length", ".Length")
|
||||
.Replace(".equals(", ".Equals(")
|
||||
.Replace(".toString()", ".ToString()")
|
||||
.Replace(".hashCode()", ".GetHashCode()");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 转换语法节点
|
||||
/// </summary>
|
||||
public Interfaces.SyntaxNode ConvertNode(Interfaces.SyntaxNode node, ConversionContext context)
|
||||
{
|
||||
var newNode = new Interfaces.SyntaxNode
|
||||
{
|
||||
Type = node.Type,
|
||||
Text = ConvertText(node.Text, context),
|
||||
Metadata = new Dictionary<string, object?>(node.Metadata),
|
||||
Parent = node.Parent,
|
||||
Children = new List<Interfaces.SyntaxNode>(),
|
||||
IsUnconvertible = node.IsUnconvertible,
|
||||
TodoDescription = node.TodoDescription
|
||||
};
|
||||
|
||||
foreach (var child in node.Children)
|
||||
{
|
||||
var convertedChild = ConvertNode(child, context);
|
||||
convertedChild.Parent = newNode;
|
||||
newNode.Children.Add(convertedChild);
|
||||
}
|
||||
|
||||
// 检测不可转换的语法
|
||||
CheckUnconvertibleSyntax(node.Text, context);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
private string ConvertText(string text, ConversionContext context)
|
||||
{
|
||||
var result = text;
|
||||
|
||||
// package 转 namespace
|
||||
if (result.StartsWith("package "))
|
||||
{
|
||||
result = result.Replace("package ", "namespace ")
|
||||
.Replace(";", " {");
|
||||
}
|
||||
|
||||
// import 转 using
|
||||
if (result.StartsWith("import "))
|
||||
{
|
||||
result = result.Replace("import ", "using ")
|
||||
.Replace(";", ";");
|
||||
}
|
||||
|
||||
// 类型映射
|
||||
result = MapType(result);
|
||||
|
||||
// Java 特定语法处理
|
||||
result = result
|
||||
.Replace("super.", "base.")
|
||||
.Replace("System.out.println", "Console.WriteLine")
|
||||
.Replace("@Override", "[Override]")
|
||||
.Replace("extends", ":")
|
||||
.Replace("implements", ":");
|
||||
|
||||
// 方法声明转换
|
||||
result = System.Text.RegularExpressions.Regex.Replace(
|
||||
result,
|
||||
@"public\s+(\w+)\s+get(\w+)\(\)",
|
||||
"public $1 Get$2 { get; }"
|
||||
);
|
||||
|
||||
result = System.Text.RegularExpressions.Regex.Replace(
|
||||
result,
|
||||
@"public\s+void\s+set(\w+)\(\1\s+\w+\)",
|
||||
"public void Set$1 { set; }"
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CheckUnconvertibleSyntax(string text, ConversionContext context)
|
||||
{
|
||||
// 检测 Stream API
|
||||
if (text.Contains(".stream(") || text.Contains("Stream."))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = IssueType.UnconvertibleSyntax,
|
||||
Description = "Java Stream API 需要转换为 LINQ",
|
||||
OriginalCode = text,
|
||||
Suggestion = "使用 LINQ 替代:stream().filter() → .Where(), stream().map() → .Select()"
|
||||
});
|
||||
|
||||
context.TodoItems.Add(new TodoItem
|
||||
{
|
||||
Description = "将 Stream API 转换为 LINQ",
|
||||
OriginalSyntax = "Stream API",
|
||||
WhyNotDirect = "Java Stream 和 LINQ 语法不同,需要手动调整",
|
||||
RecommendedAlternative = "使用 .Where(), .Select(), .Aggregate() 等 LINQ 方法"
|
||||
});
|
||||
}
|
||||
|
||||
// 检测 CompletableFuture
|
||||
if (text.Contains("CompletableFuture") || text.Contains("thenApply") || text.Contains("thenAccept"))
|
||||
{
|
||||
context.Issues.Add(new ConversionIssue
|
||||
{
|
||||
Type = IssueType.UnconvertibleSyntax,
|
||||
Description = "CompletableFuture 需要转换为 async/await",
|
||||
OriginalCode = text,
|
||||
Suggestion = "使用 async/await 模式:completableFuture.thenApply() → await Task"
|
||||
});
|
||||
|
||||
context.TodoItems.Add(new TodoItem
|
||||
{
|
||||
Description = "将 CompletableFuture 转换为 async/await",
|
||||
OriginalSyntax = "CompletableFuture",
|
||||
WhyNotDirect = "Java CompletableFuture 和 C# async/await 模式不同",
|
||||
RecommendedAlternative = "使用 Task<T> 和 async/await 关键字"
|
||||
});
|
||||
}
|
||||
|
||||
// 检测接口默认方法
|
||||
if (text.Contains("default ") && text.Contains(" interface "))
|
||||
{
|
||||
context.Logs.Add(new TransformationLog
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Operation = "Warning",
|
||||
Details = "Java 接口默认方法需要特殊处理",
|
||||
Level = LogLevel.Warning
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user