View Javadoc
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 }