1 package fr.ifremer.quadrige3.core.dao.technical;
2
3 /*-
4 * #%L
5 * Quadrige3 Core :: Shared
6 * %%
7 * Copyright (C) 2017 - 2019 Ifremer
8 * %%
9 * This program is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU Affero General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU Affero General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 * #L%
22 */
23 import org.apache.commons.lang3.ArrayUtils;
24 import org.apache.commons.lang3.StringUtils;
25
26 import java.text.Collator;
27 import java.util.Comparator;
28 import java.util.regex.Matcher;
29 import java.util.regex.Pattern;
30
31 /**
32 * Compares two alphanumeric strings by first check common prefixes and compare following numeric values, if any
33 * else compare strings directly if no numeric part found
34 *
35 * @author peck7 on 13/05/2019.
36 */
37 public class AlphanumericComparator implements Comparator<String> {
38
39 private static AlphanumericComparator INSTANCE = new AlphanumericComparator();
40
41 public static AlphanumericComparator instance() {
42 return INSTANCE;
43 }
44
45 private AlphanumericComparator() {
46 }
47
48 @Override
49 public int compare(String string1, String string2) {
50 // Fast compare if equals
51 if (StringUtils.equals(string1, string2)) return 0;
52
53 // Get all numerals
54 String[] numerals1 = getNumerals(string1);
55 String[] numerals2 = getNumerals(string2);
56
57 if (numerals1.length > 0 && numerals2.length > 0) {
58
59 int prefixIndex1 = 0;
60 int prefixIndex2 = 0;
61 for (int i = 0; i < numerals1.length; i++) {
62
63 // Get prefix of first string
64 int index1 = string1.indexOf(numerals1[i], prefixIndex1);
65 String prefix1 = string1.substring(prefixIndex1, index1);
66
67 // Get prefix on second string
68 int index2 = string2.indexOf(numerals2[i], prefixIndex2);
69 String prefix2 = string2.substring(prefixIndex2, index2);
70
71 if (prefix1.equals(prefix2)) {
72 // If same prefix, compare numerals
73 int compareResult = Integer.valueOf(numerals1[i]).compareTo(Integer.valueOf(numerals2[i]));
74 if (compareResult == 0) {
75
76 if (i == numerals1.length - 1 || i == numerals2.length - 1) {
77 // At this point, the rest of numerals cannot be compared, get the comparison of numeral counts
78 int numeralsCountCompare = Integer.compare(numerals1.length, numerals2.length);
79 if (numeralsCountCompare == 0) {
80 // compare string lengths, a longer string can means it have a suffix
81 String suffix1 = string1.substring(Math.min(index1 + numerals1[i].length(), string1.length()));
82 String suffix2 = string2.substring(Math.min(index2 + numerals2[i].length(), string2.length()));
83 return suffix1.compareTo(suffix2);
84 }
85 // return the numerals count compare result by default
86 return numeralsCountCompare;
87 }
88
89 // Numerals are equals, continue with next
90 prefixIndex1 = index1 + numerals1[i].length();
91 prefixIndex2 = index2 + numerals2[i].length();
92
93 } else {
94
95 // Comparision loop finished
96 return compareResult;
97 }
98 } else {
99 // Not same prefix, fallback to default comparator
100 break;
101 }
102 }
103 }
104
105 // return default string comparison
106 return Collator.getInstance().compare(string1, string2);
107 }
108
109 private String[] getNumerals(String string) {
110 String[] numerals = new String[0];
111 if (StringUtils.isNotBlank(string)) {
112 Pattern pattern = Pattern.compile("\\d+");
113 Matcher matcher = pattern.matcher(string);
114 while (matcher.find()) {
115 numerals = ArrayUtils.add(numerals, matcher.group());
116 }
117 }
118 return numerals;
119 }
120
121 }