Make error code and invalid method invocation sorted. (#4)

* Sort illegal call locations, and error codes.

* Apache License Header.

* Optimize README.

* Add README in root path.

* Javadoc.
diff --git a/.gitignore b/.gitignore
index 8fed2fa..0ee178e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -48,4 +48,4 @@
 
 # global registry center
 .tmp
-/dubbo-error-code-inspector/error-inspection-result.txt
+error-inspection-result.txt
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4dee40a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# Apache Dubbo Testing Tools
+
+This repository holds some utilities used by CI Testing.
+
+Currently, it only has a single submodule: Dubbo Error Code Inspector. 
+
+You may read its README [here](https://github.com/apache/dubbo-test-tools/blob/main/dubbo-error-code-inspector/README.md).
\ No newline at end of file
diff --git a/dubbo-error-code-inspector/README.md b/dubbo-error-code-inspector/README.md
index 7e3d0ef..110e2df 100644
--- a/dubbo-error-code-inspector/README.md
+++ b/dubbo-error-code-inspector/README.md
@@ -29,7 +29,6 @@
 2. 方式 2
 
    a. 切换到 dubbo-error-code-inspector 目录下执行 `mvn package` 打包。<br /><br />
-
    b. 之后在 `target` 目录下运行 `java -jar dubbo-error-code-inspector-1.0.0-SNAPSHOT.jar <Dubbo 源码路径>` 即可。
 
 ### English
@@ -41,7 +40,6 @@
 2. Method 2
 
    (a). Change directory to `dubbo-error-code-inspector` and execute `mvn package`. <br /><br />
-
    (b). In the `target` directory, execute `java -jar dubbo-error-code-inspector-1.0.0-SNAPSHOT.jar <Path to Dubbo Source>`
 
 # 原先在 dubbo 仓库的关于此的 PR / Related PR in 'dubbo' repository
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/Main.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/Main.java
index 6209d61..01640f1 100644
--- a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/Main.java
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/Main.java
@@ -26,6 +26,7 @@
 import org.apache.dubbo.errorcode.model.LoggerMethodInvocation;
 import org.apache.dubbo.errorcode.model.MethodDefinition;
 import org.apache.dubbo.errorcode.reporter.InspectionResult;
+import org.apache.dubbo.errorcode.util.ErrorCodeStringComparator;
 import org.apache.dubbo.errorcode.util.FileUtils;
 
 import java.nio.file.Path;
@@ -150,9 +151,17 @@
         inspectionResult.setAllErrorCodes(
             codes.stream()
                 .distinct()
-                .sorted().collect(Collectors.toList()));
+                .sorted(ErrorCodeStringComparator.getInstance())
+                .collect(Collectors.toList())
+        );
 
-        inspectionResult.setLinkNotReachableErrorCodes(linksNotReachable);
+        inspectionResult.setLinkNotReachableErrorCodes(
+            linksNotReachable.stream()
+                .distinct()
+                .sorted(ErrorCodeStringComparator.getInstance())
+                .collect(Collectors.toList())
+        );
+
         inspectionResult.setIllegalInvocations(illegalInvocationClassesAndLoggerMethods);
         inspectionResult.setInvalidLoggerMethodInvocationLocations(invalidLoggerMethodInvocationLocations);
 
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/ConsoleOutputReporter.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/ConsoleOutputReporter.java
index 2287683..6d25922 100644
--- a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/ConsoleOutputReporter.java
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/ConsoleOutputReporter.java
@@ -26,10 +26,6 @@
 public class ConsoleOutputReporter implements Reporter {
     @Override
     public void report(InspectionResult inspectionResult) {
-        System.out.println("All error codes: " + inspectionResult.getAllErrorCodes());
-        System.out.println();
-        System.out.println("Error codes which document links are not reachable: " + inspectionResult.getLinkNotReachableErrorCodes());
-        System.out.println();
-        System.out.println(StringifyUtil.generateIllegalInvocationString(inspectionResult));
+        StringifyUtil.outputStringifyText(inspectionResult, System.out);
     }
 }
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/FileOutputReporter.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/FileOutputReporter.java
index 3ad6436..3ad5555 100644
--- a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/FileOutputReporter.java
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/FileOutputReporter.java
@@ -32,12 +32,7 @@
     @Override
     public void report(InspectionResult inspectionResult) {
         try (PrintStream printStream = new PrintStream(Files.newOutputStream(Paths.get(System.getProperty("user.dir"), "error-inspection-result.txt")))) {
-
-            printStream.println("All error codes: " + inspectionResult.getAllErrorCodes());
-            printStream.println();
-            printStream.println("Error codes which document links are not reachable: " + inspectionResult.getLinkNotReachableErrorCodes());
-            printStream.println();
-            printStream.println(StringifyUtil.generateIllegalInvocationString(inspectionResult));
+            StringifyUtil.outputStringifyText(inspectionResult, printStream);
 
         } catch (IOException e) {
             throw new RuntimeException(e);
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/StringifyUtil.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/StringifyUtil.java
index c308966..4c526c7 100644
--- a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/StringifyUtil.java
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/reporter/impl/StringifyUtil.java
@@ -18,12 +18,13 @@
 package org.apache.dubbo.errorcode.reporter.impl;
 
 import org.apache.dubbo.errorcode.model.LoggerMethodInvocation;
-import org.apache.dubbo.errorcode.model.MethodDefinition;
 import org.apache.dubbo.errorcode.reporter.InspectionResult;
+import org.apache.dubbo.errorcode.util.FilePathComparator;
 import org.apache.dubbo.errorcode.util.FileUtils;
 
+import java.io.PrintStream;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Map;
 
 /**
  * Utility of generating string content.
@@ -33,14 +34,26 @@
         throw new UnsupportedOperationException("No instance of StringifyUtil for you! ");
     }
 
+    static void outputStringifyText(InspectionResult inspectionResult, PrintStream outputStream) {
+        List<String> errorCodes = inspectionResult.getAllErrorCodes();
+        List<String> notReachable = inspectionResult.getLinkNotReachableErrorCodes();
+
+        outputStream.println("All error codes: " + errorCodes + ", totally " + errorCodes.size() + " codes.");
+        outputStream.println();
+        outputStream.println("Error codes which document links are not reachable: " + notReachable + ", totally " + notReachable.size() + " codes.");
+        outputStream.println();
+        outputStream.println(StringifyUtil.generateIllegalInvocationString(inspectionResult));
+    }
+
     static String generateIllegalInvocationString(InspectionResult inspectionResult) {
         StringBuilder illegalInvocationReportStringBuilder = new StringBuilder();
         illegalInvocationReportStringBuilder.append("Illegal logger method invocations: ");
         illegalInvocationReportStringBuilder.append('\n');
 
-        for (Map.Entry<String, List<MethodDefinition>> entry : inspectionResult.getIllegalInvocations().entrySet()) {
+        List<String> sortedFileNameKeys = new ArrayList<>(inspectionResult.getIllegalInvocations().keySet());
+        sortedFileNameKeys.sort(FilePathComparator.getInstance());
 
-            String key = entry.getKey();
+        for (String key : sortedFileNameKeys) {
 
             illegalInvocationReportStringBuilder.append(FileUtils.getSourceFilePathFromClassFilePath(key));
 
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/ErrorCodeStringComparator.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/ErrorCodeStringComparator.java
new file mode 100644
index 0000000..43bd4c7
--- /dev/null
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/ErrorCodeStringComparator.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.errorcode.util;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+/**
+ * Comparator for Error codes.
+ */
+public final class ErrorCodeStringComparator implements Comparator<String> {
+
+    private ErrorCodeStringComparator() {}
+
+    private static class Holder {
+        static final ErrorCodeStringComparator instance = new ErrorCodeStringComparator();
+    }
+
+    public static ErrorCodeStringComparator getInstance() {
+        return Holder.instance;
+    }
+
+    @Override
+    public int compare(String o1, String o2) {
+        int[] firstCodeSegments = Arrays.stream(o1.split("-")).mapToInt(Integer::parseInt).toArray();
+        int[] secondCodeSegments = Arrays.stream(o2.split("-")).mapToInt(Integer::parseInt).toArray();
+
+        if (firstCodeSegments[0] == secondCodeSegments[0]) {
+            return Integer.compare(firstCodeSegments[1], secondCodeSegments[1]);
+        }
+
+        return Integer.compare(firstCodeSegments[0], secondCodeSegments[0]);
+    }
+}
diff --git a/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/FilePathComparator.java b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/FilePathComparator.java
new file mode 100644
index 0000000..3fea17f
--- /dev/null
+++ b/dubbo-error-code-inspector/src/main/java/org/apache/dubbo/errorcode/util/FilePathComparator.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.dubbo.errorcode.util;
+
+import java.io.File;
+import java.util.Comparator;
+
+/**
+ * Comparator for File paths.
+ */
+public final class FilePathComparator implements Comparator<String> {
+
+    private static final String SEPARATOR = File.separatorChar == '\\' ? "\\\\" : File.separator;
+
+    private FilePathComparator() {}
+
+    private static class Holder {
+        static final FilePathComparator instance = new FilePathComparator();
+    }
+
+    public static FilePathComparator getInstance() {
+        return Holder.instance;
+    }
+
+    @Override
+    public int compare(String o1, String o2) {
+
+        String[] segments1 = o1.split(SEPARATOR);
+        String[] segments2 = o2.split(SEPARATOR);
+
+        for (int i = 0; i < Math.min(segments1.length, segments2.length); i++) {
+            if (!segments1[i].equals(segments2[i])) {
+                return segments1[i].compareTo(segments2[i]);
+            }
+        }
+
+        return o1.compareTo(o2);
+    }
+}