< prev index next >

src/java.base/share/classes/java/util/Comparators.java

Print this page
rev 17471 : [mq]: 8134512-provide-Alpha-Numeric-logical-Comparator

@@ -1,7 +1,7 @@
 /*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.  Oracle designates this

@@ -93,6 +93,192 @@
         @Override
         public Comparator<T> reversed() {
             return new NullComparator<>(!nullFirst, real == null ? null : real.reversed());
         }
     }
+
+    /**
+     * Compares char sequences, taking into account their numeric part if one exists.
+     *
+     * @see Comparator.comparingNumerically()
+     * @see Comparator.comparingNumericallyLeadingZerosAhead()
+     */
+    static class NumericComparator<T extends CharSequence> implements Comparator<T>, Serializable {
+
+        private static final long serialVersionUID = 0x77164074580FAC97L;
+
+        static final Comparator<?> INSTANCE_LEADING_ZEROS_AHEAD_NATURAL =
+                new NumericComparator<CharSequence>(true)
+        {
+            @Override
+            @SuppressWarnings("unchecked")
+            public Comparator<CharSequence> reversed() {
+                return (Comparator<CharSequence>)INSTANCE_LEADING_ZEROS_AHEAD_REVERSED;
+            }
+        };
+
+        static final Comparator<?> INSTANCE_LEADING_ZEROS_AHEAD_REVERSED =
+                new NumericComparator<CharSequence>(true)
+        {
+            @Override
+            public int compare(CharSequence o1, CharSequence o2) {
+                return super.compare(o2, o1);
+            }
+
+            @Override
+            @SuppressWarnings("unchecked")
+            public Comparator<CharSequence> reversed() {
+                return (Comparator<CharSequence>)INSTANCE_LEADING_ZEROS_AHEAD_NATURAL;
+            }
+        };
+
+        static final Comparator<?> INSTANCE_LEADING_ZEROS_BEHIND_NATURAL =
+                new NumericComparator<CharSequence>(false)
+        {
+            @Override
+            @SuppressWarnings("unchecked")
+            public Comparator<CharSequence> reversed() {
+                return (Comparator<CharSequence>)INSTANCE_LEADING_ZEROS_BEHIND_REVERSED;
+            }
+        };
+
+        static final Comparator<?> INSTANCE_LEADING_ZEROS_BEHIND_REVERSED =
+                new NumericComparator<CharSequence>(false)
+        {
+            @Override
+            public int compare(CharSequence o1, CharSequence o2) {
+                return super.compare(o2, o1);
+            }
+
+            @Override
+            @SuppressWarnings("unchecked")
+            public Comparator<CharSequence> reversed() {
+                return (Comparator<CharSequence>)INSTANCE_LEADING_ZEROS_BEHIND_NATURAL;
+            }
+        };
+
+        /**
+         * If true, the string representation of a number with
+         * more leading zeros will be treated as smaller.
+         * E.g.: "0001" < "001" < "1".
+         */
+        private final boolean leadingZerosAhead;
+
+        /**
+         * Constructs a new comparator with possibility to specify
+         * how it should deal with leading zeros in numbers.
+         *
+         * @param leadingZerosAhead  if {@code true}, numbers with
+         *        more leading zeros are treated as smaller.
+         */
+        private NumericComparator(boolean leadingZerosAhead) {
+            this.leadingZerosAhead = leadingZerosAhead;
+        }
+
+        /**
+         * Counts how many consequtive characters in the string {@code o}
+         * are digits, starting from the position {@code from}.
+         */
+        private int countDigits(CharSequence o, int from) {
+            for (int i = from, len = o.length(); ; ++i) {
+                if (i == len || !Character.isDigit(o.charAt(i))) {
+                    return i - from;
+                }
+            }
+        }
+
+        /**
+         * Checks if all {@code cnt} chars of the sequence {@code o},
+         * starting with {@code pos} are zeros.
+         *
+         * @param pos  position of the first digit of numeric part.
+         * @param cnt  number of digits to check;  must be > 0.
+         *
+         * @return {@code true} if the numeric part of the string {@code o}
+         * can be reduced in length by {@code cnt} without changing its
+         * numerical value.
+         */
+        private boolean skipLeadingZeros(CharSequence o, int pos, int cnt) {
+            do {
+                if (Character.digit(o.charAt(pos++), 10) != 0) {
+                    // Leading digit of numeric part of o isn't zero
+                    return false;
+                }
+            } while (--cnt > 0);
+            return true;
+        }
+
+        @Override
+        public int compare(T o1, T o2) {
+            int minLen = Math.min(o1.length(), o2.length());
+            int numStart1 = 0;
+            int index = 0;
+            int res0 = 0;
+
+            // Skip common prefix of the arguments
+            char ch;
+            while (index < minLen &&
+                    (res0 = Character.compare(ch = o1.charAt(index),
+                                              o2.charAt(index))) == 0) {
+                ++index;
+                if (!Character.isDigit(ch)) {
+                    numStart1 = index;
+                }
+            }
+
+            int numLen1 = index - numStart1;
+            if (numLen1 > 0 ||
+                    (index < minLen &&
+                        Character.isDigit(o1.charAt(index)) &&
+                        Character.isDigit(o2.charAt(index))))
+            {
+                // The difference is inside or at the boundary of
+                // numerical part
+                int numLen2 = numLen1;
+                int numStart2 = numStart1;
+
+                numLen1 += countDigits(o1, index);
+                numLen2 += countDigits(o2, index);
+
+                int diffNumLen = numLen2 - numLen1;
+
+                if (diffNumLen > 0) {
+                    if (!skipLeadingZeros(o2, numStart2, diffNumLen)) {
+                        // Length of numeric part of o2 is greater
+                        return -1;
+                    }
+                    numStart2 += diffNumLen;
+                    numLen2 = numLen1;
+                } else if (diffNumLen < 0) {
+                    if (!skipLeadingZeros(o1, numStart1, -diffNumLen)) {
+                        // Length of numeric part of o1 is greater
+                        return 1;
+                    }
+                    numStart1 -= diffNumLen;
+                    numLen1 = numLen2;
+                }
+
+                // Numeric parts are of the same length, so they can
+                // be compared literally
+                while (numLen1-- > 0) {
+                    int res1 = Character.compare(o1.charAt(numStart1++),
+                            o2.charAt(numStart2++));
+                    if (res1 != 0) {
+                        return res1;
+                    }
+                }
+
+                // Numeric parts are numerically equal
+                if (diffNumLen != 0) {
+                    // If leadingZerosAhead == true, treat the number
+                    // with more leading zeros as smaller
+                    return (leadingZerosAhead ^ (diffNumLen > 0)) ? -1 : 1;
+                }
+            }
+
+            // Otherwise, compare literally
+            return (index < o1.length())
+                    ? ((index < o2.length()) ? res0 : 1)
+                    : ((index < o2.length()) ? -1 : 0);
+        }
+    }
 }
< prev index next >