< 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 **** /* ! * Copyright (c) 2012, 2013, 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 --- 1,7 ---- /* ! * 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,98 **** --- 93,284 ---- @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 >