/*- * -\-\- * Helios Services * -- * Copyright (C) 2016 Spotify AB * -- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * -/-/- */ package com.spotify.helios.rollingupdate; /* * Copyright 2014 Daniel Sawano * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import static java.nio.CharBuffer.wrap; import static java.util.Objects.requireNonNull; import java.nio.CharBuffer; import java.text.Collator; import java.util.Comparator; import java.util.Locale; public class AlphaNumericComparator implements Comparator<CharSequence> { private final Collator collator; /** * Creates a comparator that will use lexicographical sorting of the non-numerical parts of the * compared strings. */ public AlphaNumericComparator() { collator = null; } /** * Creates a comparator that will use locale-sensitive sorting of the non-numerical parts of the * compared strings. * * @param locale * the locale to use */ public AlphaNumericComparator(final Locale locale) { this(Collator.getInstance(requireNonNull(locale))); } /** * Creates a comparator that will use the given collator to sort the non-numerical parts of the * compared strings. * * @param collator * the collator to use */ public AlphaNumericComparator(final Collator collator) { this.collator = requireNonNull(collator); } @Override public int compare(final CharSequence s1, final CharSequence s2) { final CharBuffer b1 = wrap(s1); final CharBuffer b2 = wrap(s2); while (b1.hasRemaining() && b2.hasRemaining()) { moveWindow(b1); moveWindow(b2); final int result = compare(b1, b2); if (result != 0) { return result; } prepareForNextIteration(b1); prepareForNextIteration(b2); } return s1.length() - s2.length(); } private int compare(final CharBuffer b1, final CharBuffer b2) { if (isNumerical(b1) && isNumerical(b2)) { return compareNumerically(b1, b2); } return compareAsStrings(b1, b2); } private void moveWindow(final CharBuffer buffer) { int start = buffer.position(); int end = buffer.position(); final boolean isNumerical = isDigit(buffer.get(start)); while (end < buffer.limit() && isNumerical == isDigit(buffer.get(end))) { ++end; if (isNumerical && (start + 1 < buffer.limit()) && isZero(buffer.get(start)) && isDigit(buffer.get(end))) { ++start; // trim leading zeros } } buffer.position(start) .limit(end); } private boolean isNumerical(final CharBuffer buffer) { return isDigit(buffer.charAt(0)); } private boolean isDigit(final char ch) { if (collator == null) { final int intValue = (int) ch; return intValue >= 48 && intValue <= 57; } return Character.isDigit(ch); } private int compareNumerically(final CharBuffer b1, final CharBuffer b2) { final int diff = b1.length() - b2.length(); if (diff != 0) { return diff; } for (int i = 0; i < b1.remaining() && i < b2.remaining(); ++i) { final int result = Character.compare(b1.charAt(i), b2.charAt(i)); if (result != 0) { return result; } } return 0; } private void prepareForNextIteration(final CharBuffer buffer) { buffer.position(buffer.limit()) .limit(buffer.capacity()); } private int compareAsStrings(final CharBuffer b1, final CharBuffer b2) { if (collator != null) { return collator.compare(b1.toString(), b2.toString()); } return b1.toString().compareTo(b2.toString()); } private boolean isZero(final char ch) { return ch == '0'; } }