diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollate.java b/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollate.java new file mode 100644 index 00000000000..bed055069f9 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollate.java @@ -0,0 +1,84 @@ +package com.orientechnologies.orient.core.collate; + +import java.util.Comparator; + +public class NaturalStringCollate implements OCollate{ + + + public static final String NAME = "NATURAL"; + + private final Comparator naturalComparator = new NaturalStringComparator(); + + @Override + public String getName() { + return NAME; + } + + @Override + public Object transform(Object obj) { + return obj; + } + + @Override + public int compareForOrderBy(Object o1, Object o2) { + return naturalComparator.compare(o1, o2); + } + + // 自然字符串比较器实现 + private static class NaturalStringComparator implements Comparator { + @Override + public int compare(Object o1, Object o2) { + if (o1 == null && o2 == null) return 0; + if (o1 == null) return -1; + if (o2 == null) return 1; + + String s1 = o1.toString(); + String s2 = o2.toString(); + + return naturalCompare(s1, s2); + } + + private int naturalCompare(String a, String b) { + int[] na = new int[1]; + int[] nb = new int[1]; + + while (true) { + // 跳过前导零 + while (isZero(a, na) && isZero(b, nb)) { + a = a.substring(na[0]); + b = b.substring(nb[0]); + na[0] = 0; + nb[0] = 0; + } + + // 提取数字部分 + int diff = getNum(a, na) - getNum(b, nb); + if (diff != 0) return diff; + + // 如果都到达末尾,则相等 + if (na[0] >= a.length() && nb[0] >= b.length()) return 0; + + // 处理边界情况 + if (na[0] >= a.length()) return -1; + if (nb[0] >= b.length()) return 1; + + // 提取非数字部分 + diff = a.charAt(na[0]++) - b.charAt(nb[0]++); + if (diff != 0) return diff; + } + } + + private boolean isZero(String s, int[] pos) { + pos[0] = 0; + while (pos[0] < s.length() && s.charAt(pos[0]) == '0') pos[0]++; + return pos[0] < s.length() && Character.isDigit(s.charAt(pos[0])); + } + + private int getNum(String s, int[] pos) { + int start = pos[0]; + while (pos[0] < s.length() && Character.isDigit(s.charAt(pos[0]))) pos[0]++; + return pos[0] == start ? 0 : Integer.parseInt(s.substring(start, pos[0])); + } + } + +} diff --git a/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollateFactory.java b/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollateFactory.java new file mode 100644 index 00000000000..7972bfed023 --- /dev/null +++ b/core/src/main/java/com/orientechnologies/orient/core/collate/NaturalStringCollateFactory.java @@ -0,0 +1,31 @@ + + +package com.orientechnologies.orient.core.collate; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class NaturalStringCollateFactory implements OCollateFactory { + + + private final Set collateNames = new HashSet<>(); + + public NaturalStringCollateFactory() { + collateNames.add(NaturalStringCollate.NAME); + } + + + @Override + public Set getNames() { + return Collections.unmodifiableSet(collateNames); + } + + @Override + public OCollate getCollate(String name) { + if (NaturalStringCollate.NAME.equalsIgnoreCase(name)) { + return new NaturalStringCollate(); + } + return null; + } +} \ No newline at end of file diff --git a/core/src/test/java/com/orientechnologies/orient/core/collate/NaturalStringCollateTest.java b/core/src/test/java/com/orientechnologies/orient/core/collate/NaturalStringCollateTest.java new file mode 100644 index 00000000000..29108cdb24d --- /dev/null +++ b/core/src/test/java/com/orientechnologies/orient/core/collate/NaturalStringCollateTest.java @@ -0,0 +1,155 @@ +package com.orientechnologies.orient.core.collate; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.*; + +public class NaturalStringCollateTest { + + @Test + public void testNaturalSortOrder() { + NaturalStringCollate collate = new NaturalStringCollate(); + + // 测试基本的自然排序 + List testData = new ArrayList<>(); + testData.add("M10"); + testData.add("M2"); + testData.add("M1"); + testData.add("M20"); + testData.add("M3"); + + // 使用自然排序进行排序 + Collections.sort(testData, (a, b) -> collate.compareForOrderBy(a, b)); + + // 验证排序结果 + assertEquals("M1", testData.get(0)); + assertEquals("M2", testData.get(1)); + assertEquals("M3", testData.get(2)); + assertEquals("M10", testData.get(3)); + assertEquals("M20", testData.get(4)); + } + + @Test + public void testComplexNaturalSortOrder() { + NaturalStringCollate collate = new NaturalStringCollate(); + + List testData = new ArrayList<>(); + testData.add("item100"); + testData.add("item2"); + testData.add("item1"); + testData.add("item20"); + testData.add("item3"); + testData.add("item10"); + + Collections.sort(testData, (a, b) -> collate.compareForOrderBy(a, b)); + + assertEquals("item1", testData.get(0)); + assertEquals("item2", testData.get(1)); + assertEquals("item3", testData.get(2)); + assertEquals("item10", testData.get(3)); + assertEquals("item20", testData.get(4)); + assertEquals("item100", testData.get(5)); + } + + @Test + public void testMixedContentNaturalSort() { + NaturalStringCollate collate = new NaturalStringCollate(); + + List testData = new ArrayList<>(); + testData.add("version2.10"); + testData.add("version2.2"); + testData.add("version2.1"); + testData.add("version10.1"); + testData.add("version1.10"); + + Collections.sort(testData, (a, b) -> collate.compareForOrderBy(a, b)); + + assertEquals("version1.10", testData.get(0)); + assertEquals("version2.1", testData.get(1)); + assertEquals("version2.2", testData.get(2)); + assertEquals("version2.10", testData.get(3)); + assertEquals("version10.1", testData.get(4)); + } + + @Test + public void testLeadingZeros() { + NaturalStringCollate collate = new NaturalStringCollate(); + + List testData = new ArrayList<>(); + testData.add("test0003"); + testData.add("test02"); + testData.add("test1"); + testData.add("test001"); + + Collections.sort(testData, (a, b) -> collate.compareForOrderBy(a, b)); + + assertEquals("test1", testData.get(0)); + assertEquals("test02", testData.get(1)); + assertEquals("test001", testData.get(2)); + assertEquals("test0003", testData.get(3)); + } + + @Test + public void testNullValues() { + NaturalStringCollate collate = new NaturalStringCollate(); + + // 测试 null 值处理 + assertTrue(collate.compareForOrderBy(null, null) == 0); + assertTrue(collate.compareForOrderBy(null, "test") < 0); + assertTrue(collate.compareForOrderBy("test", null) > 0); + } + + @Test + public void testEqualValues() { + NaturalStringCollate collate = new NaturalStringCollate(); + + // 测试相等值 + assertEquals(0, collate.compareForOrderBy("test", "test")); + assertEquals(0, collate.compareForOrderBy("item10", "item10")); + } + + @Test + public void testGetName() { + NaturalStringCollate collate = new NaturalStringCollate(); + assertEquals("NATURAL", collate.getName()); + } + + @Test + public void testTransform() { + NaturalStringCollate collate = new NaturalStringCollate(); + + // 测试 transform 方法(应该返回原始对象) + String testString = "test"; + assertEquals(testString, collate.transform(testString)); + + Integer testInt = 123; + assertEquals(testInt, collate.transform(testInt)); + + assertNull(collate.transform(null)); + } + + @Test + public void testFactoryIntegration() { + NaturalStringCollateFactory factory = new NaturalStringCollateFactory(); + + // 测试工厂是否能正确创建 NaturalStringCollate 实例 + OCollate collate = factory.getCollate("NATURAL"); + assertNotNull("Factory should return a collate instance for NATURAL", collate); + assertEquals("NATURAL", collate.getName()); + + // 测试大小写不敏感 + OCollate collate2 = factory.getCollate("natural"); + assertNotNull("Factory should work case-insensitively", collate2); + + // 测试不存在的 collate + OCollate collate3 = factory.getCollate("NONEXISTENT"); + assertNull("Factory should return null for non-existent collate", collate3); + + // 测试 getNames 方法 + assertTrue("Factory should support NATURAL collate", factory.getNames().contains("NATURAL")); + } +}