/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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.cinchapi.concourse.util; import java.io.File; import java.util.Comparator; import com.google.common.base.Strings; /** * A {@link Comparator} that sorts files with strictly numerical names between 0 * and 2^63 - 1 (i.e. a timestamp). * * @author Jeff Nelson */ public enum NaturalSorter implements Comparator<File> { INSTANCE; private static AlphaNumericSorter COMPARATOR = new AlphaNumericSorter(); @Override public int compare(File f1, File f2) { return COMPARATOR.compare(f1.getName(), f2.getName()); } /** * A comparator that sorts string using natural alphanumeric ordering. * * @author Jeff Nelson */ private static class AlphaNumericSorter implements Comparator<String> { @Override public int compare(String a, String b) { int aPos = 0; int bPos = 0; int aPeekPos = 0; int bPeekPos = 0; for (;;) { char aPeek = '\0'; String aChunk = ""; // Try to create chunks of all digits while (aPos < a.length()) { aPeek = a.charAt(aPos); aPeekPos = aPos; ++aPos; if(Character.isDigit(aPeek)) { aChunk += String.valueOf(aPeek); } else { break; } } char bPeek = '\0'; String bChunk = ""; // Try to create chunks of all digits while (bPos < b.length()) { bPeek = b.charAt(bPos); bPeekPos = bPos; ++bPos; if(Character.isDigit(bPeek)) { bChunk += String.valueOf(bPeek); } else { break; } } if(Strings.isNullOrEmpty(aChunk) || Strings.isNullOrEmpty(bChunk)) { // If either chunk is empty, then it means we aren't doing a // numeric v. numeric comparison, in which case we should // compare the values at which we peeked int comp = aPeek - bPeek; aPos = aPeekPos + 1; bPos = bPeekPos + 1; if(comp != 0 || aPos >= a.length() || bPos >= b.length()) { return comp; } else { continue; } } else { int aChunkSize = aChunk.length(); int bChunkSize = bChunk.length(); if(aChunkSize < bChunkSize) { return -1; } else if(bChunkSize < aChunkSize) { return 1; } else { // Chunks are the same size, so comparison is based off // first digit that is different, if any for (int i = 0; i < aChunkSize; ++i) { int comp = aChunk.charAt(i) - bChunk.charAt(i); if(comp != 0) { return comp; } } if(aPos >= a.length() || bPos >= b.length()) { return 0; } else { continue; } } } } } } }