package org.apache.cassandra.io.sstable;
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 java.io.File;
import java.util.StringTokenizer;
import com.google.common.base.Objects;
import org.apache.cassandra.utils.Pair;
/**
* A SSTable is described by the keyspace and column family it contains data
* for, a generation (where higher generations contain more recent data) and
* an alphabetic version string.
*
* A descriptor can be marked as temporary, which influences generated filenames.
*/
public class Descriptor
{
public static final String LEGACY_VERSION = "a";
public static final String CURRENT_VERSION = "f";
public final File directory;
public final String version;
public final String ksname;
public final String cfname;
public final int generation;
public final boolean temporary;
private final int hashCode;
public final boolean hasStringsInBloomFilter;
public final boolean hasIntRowSize;
public final boolean hasEncodedKeys;
public final boolean isLatestVersion;
public final boolean usesOldBloomFilter;
/**
* A descriptor that assumes CURRENT_VERSION.
*/
public Descriptor(File directory, String ksname, String cfname, int generation, boolean temp)
{
this(CURRENT_VERSION, directory, ksname, cfname, generation, temp);
}
public Descriptor(String version, File directory, String ksname, String cfname, int generation, boolean temp)
{
assert version != null && directory != null && ksname != null && cfname != null;
this.version = version;
this.directory = directory;
this.ksname = ksname;
this.cfname = cfname;
this.generation = generation;
temporary = temp;
hashCode = Objects.hashCode(directory, generation, ksname, cfname);
hasStringsInBloomFilter = version.compareTo("c") < 0;
hasIntRowSize = version.compareTo("d") < 0;
hasEncodedKeys = version.compareTo("e") < 0;
usesOldBloomFilter = version.compareTo("f") < 0;
isLatestVersion = version.compareTo(CURRENT_VERSION) == 0;
}
public String filenameFor(Component component)
{
return filenameFor(component.name());
}
private String baseFilename()
{
StringBuilder buff = new StringBuilder();
buff.append(directory).append(File.separatorChar);
buff.append(cfname).append("-");
if (temporary)
buff.append(SSTable.TEMPFILE_MARKER).append("-");
if (!LEGACY_VERSION.equals(version))
buff.append(version).append("-");
buff.append(generation);
return buff.toString();
}
/**
* @param suffix A component suffix, such as 'Data.db'/'Index.db'/etc
* @return A filename for this descriptor with the given suffix.
*/
public String filenameFor(String suffix)
{
return baseFilename() + "-" + suffix;
}
/**
* @see #fromFilename(File directory, String name)
*/
public static Descriptor fromFilename(String filename)
{
int separatorPos = filename.lastIndexOf(File.separatorChar);
assert separatorPos != -1 : "Filename must include parent directory.";
File directory = new File(filename.substring(0, separatorPos));
String name = filename.substring(separatorPos+1, filename.length());
return fromFilename(directory, name).left;
}
/**
* Filename of the form "<ksname>/<cfname>-[tmp-][<version>-]<gen>-<component>"
* @return A Descriptor for the SSTable, and the Component remainder.
*/
public static Pair<Descriptor,String> fromFilename(File directory, String name)
{
// name of parent directory is keyspace name
String ksname = directory.getName();
// tokenize the filename
StringTokenizer st = new StringTokenizer(name, "-");
String nexttok = null;
// all filenames must start with a column family
String cfname = st.nextToken();
// optional temporary marker
nexttok = st.nextToken();
boolean temporary = false;
if (nexttok.equals(SSTable.TEMPFILE_MARKER))
{
temporary = true;
nexttok = st.nextToken();
}
// optional version string
String version = LEGACY_VERSION;
if (versionValidate(nexttok))
{
version = nexttok;
nexttok = st.nextToken();
}
int generation = Integer.parseInt(nexttok);
// component suffix
String component = st.nextToken();
return new Pair<Descriptor,String>(new Descriptor(version, directory, ksname, cfname, generation, temporary), component);
}
/**
* @return A clone of this descriptor with the given 'temporary' status.
*/
public Descriptor asTemporary(boolean temporary)
{
return new Descriptor(version, directory, ksname, cfname, generation, temporary);
}
/**
* @return True if the given version string is not empty, and
* contains all lowercase letters, as defined by java.lang.Character.
*/
static boolean versionValidate(String ver)
{
if (ver.length() < 1) return false;
for (char ch : ver.toCharArray())
if (!Character.isLetter(ch) || !Character.isLowerCase(ch))
return false;
return true;
}
public boolean isFromTheFuture()
{
return version.compareTo(CURRENT_VERSION) > 0;
}
@Override
public String toString()
{
return baseFilename();
}
@Override
public boolean equals(Object o)
{
if (o == this)
return true;
if (!(o instanceof Descriptor))
return false;
Descriptor that = (Descriptor)o;
return that.directory.equals(this.directory) && that.generation == this.generation && that.ksname.equals(this.ksname) && that.cfname.equals(this.cfname);
}
@Override
public int hashCode()
{
return hashCode;
}
}