/**
* Copyright 2016 LinkedIn Corp. All rights reserved.
*
* 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.
*/
package com.github.ambry.store;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Comparator;
/**
* Helper for working with log segment names.
* <p/>
* Log segments will have a name "pos_gen" where pos represents their relative position and "gen" represents the
* generation number of the log segment at "pos".
* <p/>
* The file name is a combination of the segment name and a suffix "_log"
* <p/>
* If the file name format changes, the version of {@link LogSegment} has to be updated and this class updated to
* handle the new and old versions.
*/
class LogSegmentNameHelper {
static final String SUFFIX = BlobStore.SEPARATOR + "log";
/**
* {@link Comparator} for two log segment names.
*/
static final Comparator<String> COMPARATOR = new Comparator<String>() {
@Override
public int compare(String name1, String name2) {
// special case for log_current (one segment logs)
if (name1.isEmpty() && name2.isEmpty()) {
return 0;
}
long pos1 = getPosition(name1);
long pos2 = getPosition(name2);
int compare = Long.compare(pos1, pos2);
if (compare == 0) {
long gen1 = getGeneration(name1);
long gen2 = getGeneration(name2);
compare = Long.compare(gen1, gen2);
}
return compare;
}
};
/**
* Filter for getting all log files from a particular directory.
*/
static final FilenameFilter LOG_FILE_FILTER = new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.endsWith(LogSegmentNameHelper.SUFFIX) || name.equals(SINGLE_SEGMENT_LOG_FILE_NAME);
}
};
// for backwards compatibility, if the log contains only a single segment, the segment will have a special name.
private static final String SINGLE_SEGMENT_LOG_FILE_NAME = "log_current";
/**
* @param name the name of the log segment.
* @return the hashcode of {@code name}.
*/
static int hashcode(String name) {
return name.hashCode();
}
/**
* @param name the name of the log segment.
* @return the relative position of the log segment.
*/
static long getPosition(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException("Name provided cannot be empty");
}
return Long.parseLong(name.substring(0, name.indexOf(BlobStore.SEPARATOR)));
}
/**
* @param name the name of the log segment.
* @return the generation number of the log segment.
*/
static long getGeneration(String name) {
if (name.isEmpty()) {
throw new IllegalArgumentException("Name provided cannot be empty");
}
return Long.parseLong(name.substring(name.indexOf(BlobStore.SEPARATOR) + 1));
}
/**
* @param pos the relative position of the log segment.
* @param gen the generation of the log segment.
* @return the name of a log segment with position {@code pos} and generation number {@code gen}.
*/
static String getName(long pos, long gen) {
return pos + BlobStore.SEPARATOR + gen;
}
/**
* @param name the name of the log segment.
* @return what should be the name of the log segment that is exactly one position higher than {@code name}. The
* generation of the returned name will start from the lowest generation number.
*/
static String getNextPositionName(String name) {
long pos = getPosition(name);
return getName(pos + 1, 0);
}
/**
* @param name the name of the log segment.
* @return what should be the name of the log segment that is exactly one generation higher than {@code name}.
*/
static String getNextGenerationName(String name) {
long pos = getPosition(name);
long gen = getGeneration(name);
return getName(pos, gen + 1);
}
/**
* @param isLogSegmented {@code true} if the log is segmented, {@code false} otherwise.
* @return what should be the name of the first segment.
*/
static String generateFirstSegmentName(boolean isLogSegmented) {
return isLogSegmented ? getName(0, 0) : "";
}
/**
* @param name the name of the log segment.
* @return the name of the file that backs the log segment.
*/
static String nameToFilename(String name) {
if (name.isEmpty()) {
return SINGLE_SEGMENT_LOG_FILE_NAME;
}
return name + SUFFIX;
}
/**
* @param filename the name of the file that backs the log segment.
* @return the name of the log segment.
*/
static String nameFromFilename(String filename) {
if (filename.equals(SINGLE_SEGMENT_LOG_FILE_NAME)) {
return "";
}
if (!filename.endsWith(SUFFIX)) {
throw new IllegalArgumentException("The filename of the log segment does not end with [" + SUFFIX + "]");
}
String name = filename.substring(0, filename.length() - SUFFIX.length());
validate(name);
return name;
}
/**
* Validates that the name provided is a valid log segment name.
* @param name the log segment name.
*/
private static void validate(String name) {
if (!name.equals(getName(getPosition(name), getGeneration(name)))) {
throw new IllegalArgumentException("Invalid name: " + name);
}
}
}