using System.CommandLine;
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 _includeSubdirectoriesOption = new("--include-subdirectories")
{
Description = "When the command is dir, converts files in all subdirectories",
DefaultValueFactory = _ => true,
};
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()
{
_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.Subcommands.Add(CreateFileCommand());
rootCommand.Subcommands.Add(CreateDirectoryCommand());
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.Parse(args).InvokeAsync();
// flush logs
_loggerFactory.Dispose();
}
private static Command CreateFileCommand()
{
var inputArgument = new Argument("input")
{
Description = "A Java source code file to convert",
};
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#")
{
inputArgument,
outputArgument,
};
fileCommand.SetAction(context =>
{
var input = context.GetRequiredValue(inputArgument);
var output = context.GetValue(outputArgument);
var options = GetJavaConversionOptions(context);
ConvertToCSharpFile(input, output, options);
});
return fileCommand;
}
private static JavaConversionOptions GetJavaConversionOptions(ParseResult context)
{
var options = new JavaConversionOptions
{
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.GetValue(_clearDefaultUsingsOption))
{
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.GetValue(_addUsingsOption) ?? [])
{
options.AddUsing(ns);
}
var mappingsFile = context.GetValue(_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("input")
{
Description = "A directory containing Java source code files to convert",
};
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#")
{
inputArgument,
outputArgument,
};
dirCommand.SetAction(context =>
{
var input = context.GetRequiredValue(inputArgument);
var output = context.GetRequiredValue(outputArgument);
var options = GetJavaConversionOptions(context);
ConvertToCSharpDir(input, output, options);
});
return dirCommand;
}
private static void ConvertToCSharpDir(DirectoryInfo inputDirectory, DirectoryInfo outputDirectory, JavaConversionOptions options)
{
if (inputDirectory.Exists)
{
var searchOption = options.IncludeSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
foreach (var f in inputDirectory.GetFiles("*.java", searchOption))
{
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);
}
}
}