using System.CommandLine; using System.CommandLine.Invocation; using JavaToCSharp; using Microsoft.Extensions.Logging; namespace JavaToCSharpCli; /// /// The main JavaToCSharpCli program class. /// public class Program { private static readonly ILoggerFactory _loggerFactory; private static readonly ILogger _logger; 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 _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") { ArgumentHelpName = "namespace" }; static Program() { _loggerFactory = LoggerFactory.Create(builder => builder.AddSimpleConsole().SetMinimumLevel(LogLevel.Information)); _logger = _loggerFactory.CreateLogger(); } 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#." }; rootCommand.AddCommand(CreateFileCommand()); rootCommand.AddCommand(CreateDirectoryCommand()); rootCommand.AddGlobalOption(_includeUsingsOption); rootCommand.AddGlobalOption(_includeNamespaceOption); rootCommand.AddGlobalOption(_includeCommentsOption); rootCommand.AddGlobalOption(_useDebugAssertOption); rootCommand.AddGlobalOption(_startInterfaceNamesWithIOption); rootCommand.AddGlobalOption(_commentUnrecognizedCodeOption); rootCommand.AddGlobalOption(_systemOutToConsoleOption); rootCommand.AddGlobalOption(_clearDefaultUsingsOption); rootCommand.AddGlobalOption(_addUsingsOption); await rootCommand.InvokeAsync(args); // flush logs _loggerFactory.Dispose(); } private static Command CreateFileCommand() { var inputArgument = new Argument( name: "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 fileCommand = new Command("file", "Convert a Java file to C#"); fileCommand.AddArgument(inputArgument); fileCommand.AddArgument(outputArgument); fileCommand.SetHandler(context => { var input = context.ParseResult.GetValueForArgument(inputArgument); var output = context.ParseResult.GetValueForArgument(outputArgument); var options = GetJavaConversionOptions(context); ConvertToCSharpFile(input, output, options); }); return fileCommand; } private static JavaConversionOptions GetJavaConversionOptions(InvocationContext context) { var options = new JavaConversionOptions { 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) }; if (context.ParseResult.GetValueForOption(_clearDefaultUsingsOption)) { options.ClearUsings(); } foreach (string ns in context.ParseResult.GetValueForOption(_addUsingsOption) ?? new List()) { options.AddUsing(ns); } return options; } private static Command CreateDirectoryCommand() { var inputArgument = new Argument( name: "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 dirCommand = new Command("dir", "Convert a directory containing Java files to C#"); dirCommand.AddArgument(inputArgument); dirCommand.AddArgument(outputArgument); dirCommand.SetHandler(context => { var input = context.ParseResult.GetValueForArgument(inputArgument); var output = context.ParseResult.GetValueForArgument(outputArgument); var options = GetJavaConversionOptions(context); ConvertToCSharpDir(input, output, options); }); return dirCommand; } private static void ConvertToCSharpDir(DirectoryInfo inputDirectory, DirectoryInfo outputDirectory, JavaConversionOptions options) { if (inputDirectory.Exists) { foreach (var f in inputDirectory.GetFiles("*.java", SearchOption.AllDirectories)) { string? directoryName = f.DirectoryName; if (string.IsNullOrWhiteSpace(directoryName)) { continue; } if (!outputDirectory.Exists) { outputDirectory.Create(); } ConvertToCSharpFile(f, new FileInfo(Path.Combine(outputDirectory.FullName, Path.ChangeExtension(f.Name, ".cs"))), options, false); } } 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 { string javaText = File.ReadAllText(inputFile.FullName); options.WarningEncountered += (_, eventArgs) => { if (outputFile != null) { _logger.LogWarning("Line {JavaLineNumber}: {Message}", eventArgs.JavaLineNumber, eventArgs.Message); } OutputFileOrPrint(outputFile != null ? Path.ChangeExtension(outputFile.FullName, ".warning") : null, eventArgs.Message + Environment.NewLine); }; string? parsed = JavaToCSharpConverter.ConvertText(javaText, options); OutputFileOrPrint(outputFile?.FullName, parsed ?? string.Empty); if (outputFile != null) { _logger.LogInformation("{filePath} converted!", inputFile.Name); } } catch (Exception ex) { _logger.LogError("{filePath} failed! {type}: {message}", inputFile.Name, ex.GetType().Name, ex); if (outputFile != null) { File.WriteAllText(Path.ChangeExtension(outputFile.FullName, ".error"), ex.ToString()); } } } else _logger.LogError("Java input file {filePath} doesn't exist!", inputFile.FullName); } private static void OutputFileOrPrint(string? fileName, string contents) { if (fileName == null) { Console.Out.WriteLine(contents); } else { File.WriteAllText(fileName, contents); } } }