学无先后达者为师!
不忘初心,砥砺前行。

我是如何把 Java 项目移植到 .NET 5.0 的

伴随着 IP 位置库 的上线,笔者的“童年梦想”又成真了一个。为了分发这份来之不易的数据库,笔者找到了 ip2region 项目。该项目提供了一种体积小且查询速度极快的离线IP位置数据库文件格式,同时提供了多种语言支持的查询客户端。但 ip2region 项目的作者并未提供除 Java 以外的数据库文件生成代码,笔者打算为该项目移植 .NET 5.0 的数据库文件生成器,并在本文中记录下移植过程。

移植前准备

ip2region 的 Java 版数据库生成器 代码并不复杂,源代码文件只有 8 个。以笔者粗浅的 Java 经验来看,因为 C# 与 Java 大体相似,移植过程中无需对程序的结构和命名进行变更,也无需对处理逻辑进行调整。移植需要做的就是让程序可以编译通过,基本上就算成功。

开始移植

笔者新建了一个名为 IP2RegionDotNetDbMaker 的 .NET 5.0 控制台应用程序,删掉 Program.cs 文件并将所有的 Java 文件复制到项目中。

下一步操作很暴力,就是直接将源代码的后缀从 .java 改为 .cs 。为此,笔者在 LINQPad 中写了一段小代码,来完成这个操作:

var dir = @"D:\coderbusy.com\demo\IP2RegionDotNetDbMaker\IP2RegionDotNetDbMaker";
var javaFiles = Directory.GetFiles(dir, "*.java");
foreach (var javaFile in javaFiles)
{
	var _ = Path.GetDirectoryName(javaFile);
	var fileName = Path.GetFileNameWithoutExtension(javaFile);
	var csFile = Path.Combine(_, fileName + ".cs");
	File.Move(javaFile, csFile);
}

在暴力改名之后的源代码文件里,不出意外的报了很多错误:

需要先把 package 和 import 这两种语句去掉,然后把缺失的命名空间给加上。

var dir = @"D:\coderbusy.com\demo\IP2RegionDotNetDbMaker\IP2RegionDotNetDbMaker";
var files = Directory.GetFiles(dir, "*.cs");
foreach (var file in files)
{
	var lines = File.ReadAllLines(file, Encoding.UTF8);
	var builder = new StringBuilder();
	builder.AppendLine($"using System;{Environment.NewLine}namespace IP2RegionDotNetDbMaker{Environment.NewLine}{{");
	foreach (var line in lines)
	{
		if (line.StartsWith("package "))
		{
			continue;
		}
		if (line.StartsWith("import "))
		{
			continue;
		}
		builder.AppendLine(line);
	}
	builder.AppendLine("}");
	var content = builder.ToString();
	File.WriteAllText(file, content, Encoding.UTF8);
}

异常声明在 C# 中不支持,可以通过正则将其替换掉:

在替换时,确定开启“使用正则表达式”,查找项为:throws ([\w ,]+)Exception 替换项保持为空。之后,替换掉所有的 @Overridefinal 关键字。

在 C# 中 out 是一个关键字不能被当作类型使用,Java 编程中常用的 System.out.println 方法需要被替换成 Console.WriteLine ,直接全局替换搞定。

重构 DbMakerConfigException 类型:

using System;
namespace IP2RegionDotNetDbMaker
{

    /**
     * configuration exception
     * 
     * @author chenxin<chenxin619315@gmail.com>
     */
    public class DbMakerConfigException : Exception
    {
        public DbMakerConfigException(string info) : base(info)
        {

        }
    }
}

在 .NET 5.0 中 Mock 实现 Java 所需的 API

新建 Mock.cs 文件,用于存放 Java API 到 C# API 的Mock 代码。使用扩展方法对 String 类型进行扩展,并实现 Java API 所用的方法:

    public static class Extensions
    {
        public static Int32 length(this string str)
        {
            if (String.IsNullOrWhiteSpace(str))
            {
                return 0;
            }
            return str.Length;
        }
        public static string trim(this string str)
        {
            return str.Trim();
        }
        public static char charAt(this string str, Int32 i)
        {
            return str[i];
        }
        public static int indexOf(this string str, string value)
        {
            return str.IndexOf(value);
        }
        public static int indexOf(this string str, char value, Int32 start)
        {
            return str.IndexOf(value, start);
        }
        public static string substring(this string str, Int32 startIndex)
        {
            return str.Substring(startIndex);
        }
        public static string substring(this string str, Int32 startIndex, Int32 endIndex)
        {
            return str.Substring(startIndex, endIndex - startIndex);
        }
        public static bool equals(this string str1, string str2)
        {
            return String.Equals(str1, str2, StringComparison.InvariantCultureIgnoreCase);
        }
        public static string[] split(this string str, string separator)
        {
            return str.Split(separator);
        }
        public static byte[] getBytes(this string str)
        {
            return Encoding.UTF8.GetBytes(str);
        }
        public static byte[] getBytes(this string str, string encoding)
        {
            return Encoding.GetEncoding(encoding).GetBytes(str);
        }
        public static bool endsWith(this string str, string value)
        {
            return str.EndsWith(value);
        }
    }

Mock 实现 StringBuilder 类型:

    public class StringBuilder
    {
        private readonly System.Text.StringBuilder _builder = new System.Text.StringBuilder();

        internal StringBuilder append(object value)
        {
            _builder.Append(value);
            return this;
        }

        internal string toString()
        {
            return _builder.ToString();
        }
    }

Mock 实现 File 类型:

    public class File
    {
        public File(string ipSrcFile)
        {
            _fileInfo = new FileInfo(ipSrcFile);
        }
        private FileInfo _fileInfo;
        public FileInfo FileInfo => _fileInfo;
        internal bool exists()
        {
            return _fileInfo.Exists;
        }
    }

Mock 实现 LinkedList 类型:

    public class LinkedList<T> : List<T>
    {
        internal T getFirst()
        {
            return this[0];
        }

        internal void add(T item)
        {
            this.Add(item);
        }

        internal T getLast()
        {
            return this[this.Count - 1];
        }

        internal IEnumerable<T> iterator()
        {
            return this;
        }
    }

Mock 实现 HashMap 类型:

    public class HashMap<K, V> : Dictionary<K, V>
    {
        internal void put(K k, V v)
        {
            this[k] = v;
        }

        internal bool containsKey(K key)
        {
            return this.ContainsKey(key);
        }

        internal V get(K k)
        {
            if (containsKey(k))
            {
                return this[k];
            }
            return default;
        }

    }

Mock 实现 FileReader 类型:

    public class FileReader
    {
        private File globalRegionFile;
        private Queue<String> _lines = new Queue<string>();
        public FileReader(File file)
        {
            this.globalRegionFile = file;
            using (var fs = file.FileInfo.OpenRead())
            {
                using (var sr = new StreamReader(fs))
                {
                    while (!sr.EndOfStream)
                    {
                        var line = sr.ReadLine();
                        _lines.Enqueue(line);
                    }
                }
            }
        }

        internal string readLine()
        {
            if (_lines.TryDequeue(out var line))
            {
                return line;
            }
            return null;
        }

        internal void close()
        {
        }
    }

Mock 实现 BufferedReader 类型:

    public class BufferedReader
    {
        private FileReader fileReader;

        public BufferedReader(FileReader fileReader)
        {
            this.fileReader = fileReader;
        }

        internal string readLine()
        {
            return fileReader.readLine();
        }

        internal void close()
        {
            fileReader.close();
        }
    }

Mock 实现 RandomAccessFile 类型:

    public class RandomAccessFile
    {
        private string dbFile;
        private Stream stream;

        internal void seek(long v)
        {
            stream.Seek(v, SeekOrigin.Begin);
        }

        internal void write(byte[] vs)
        {
            stream.Write(vs);
        }

        internal void readFully(byte[] dbBinStr, int v, int length)
        {
            stream.Read(dbBinStr, v, length);
        }

        private string v;

        public RandomAccessFile(string dbFile, string v)
        {
            this.dbFile = dbFile;
            this.v = v;
            this.stream = new FileStream(dbFile, FileMode.OpenOrCreate, FileAccess.ReadWrite);
        }
        public long length()
        {
            return this.stream.Length;
        }

        internal void close()
        {
            if (stream == null)
            {
                return;
            }
            this.stream.Flush();
            this.stream.Close();
            this.stream = null;
        }

        internal long getFilePointer()
        {
            return stream.Position;
        }

        internal void write(int v)
        {
            var bytes = BitConverter.GetBytes(v);
            stream.Write(bytes);
        }
    }

语法与属性修正

经过以上的 Mock 操作,报错部分便仅仅涉及语法和部分属性。

C# 中并不存在“扩展属性”类似的东西,所以 Java 中以“小驼峰”命名的 length 字段调用,据需要改为“大驼峰”方式的 Length 。位运算符 >>> 也需要改为 >> ,同时,还有几个语法错误需要修正。比如:C# 中并不支持 Java 中的 for(Type e:collection) 语法,需要用 foreach 来替代。之后,项目就可以编译通过了。

结果验证

将 data 目录拷贝至 bin 目录,使用以下命令便可启动生成:

dbMaker -src ./data/ip.merge.txt -region ./data/global_region.csv

伴随着大量的控制台输出,笔者似乎找到了黑客帝国的感觉。经过一小会儿的等待,生成已经成功执行。

通过二进制对比,该结果文件仅在行尾的日期存储部分与源文件不同:

通过阅读代码,文件末尾部分的数据是生成的时间戳和一小段声明信息。文件尾的不一致并不会对使用造成影响。这表示,这次移植是成功的。

开源地址

目前,该代码已经上传至 Gitee ,地址是:

https://gitee.com/coderbusy/demo/tree/master/IP2RegionDotNetDbMaker

赞(3) 打赏
未经允许不得转载:码农很忙 » 我是如何把 Java 项目移植到 .NET 5.0 的

评论 2

  1. 反向操作最为致命。

    Soar、毅3年前 (2020-12-03)
  2. #1

    可以可以,这动作很骚

    书情3年前 (2020-12-03)

给作者买杯咖啡

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册