blob: f2eea992a3de7661aa981a3fdcd90cd7da9fd020 [file] [log] [blame]
/*
* 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.
*/
package org.apache.cassandra.io.sstable;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Pattern;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import org.apache.cassandra.io.sstable.format.SSTableFormat;
import org.apache.cassandra.io.sstable.format.SSTableFormat.Components.Types;
/**
* SSTables are made up of multiple components in separate files. Components are
* identified by a type and an id, but required unique components (such as the Data
* and Index files) may have implicit ids assigned to them.
*/
public class Component
{
public static final char separator = '-';
/**
* WARNING: Be careful while changing the names or string representation of the enum
* members. Streaming code depends on the names during streaming (Ref: CASSANDRA-14556).
*/
public final static class Type
{
private final static CopyOnWriteArrayList<Type> typesCollector = new CopyOnWriteArrayList<>();
public static final List<Type> all = Collections.unmodifiableList(typesCollector);
public final int id;
public final String name;
public final String repr;
private final Component singleton;
@SuppressWarnings("rawtypes")
public final Class<? extends SSTableFormat> formatClass;
/**
* Creates a new non-singleton type and registers it a global type registry - see {@link #registerType(Type)}.
*
* @param name type name, must be unique for this and all parent formats
* @param repr the regular expression to be used to recognize a name represents this type
* @param formatClass format class for which this type is defined for
*/
public static Type create(String name, String repr, Class<? extends SSTableFormat<?, ?>> formatClass)
{
return new Type(name, repr, false, formatClass);
}
/**
* Creates a new singleton type and registers it in a global type registry - see {@link #registerType(Type)}.
*
* @param name type name, must be unique for this and all parent formats
* @param repr the regular expression to be used to recognize a name represents this type
* @param formatClass format class for which this type is defined for
*/
public static Type createSingleton(String name, String repr, Class<? extends SSTableFormat<?, ?>> formatClass)
{
return new Type(name, repr, true, formatClass);
}
private Type(String name, String repr, boolean isSingleton, Class<? extends SSTableFormat<?, ?>> formatClass)
{
this.name = Objects.requireNonNull(name);
this.repr = repr;
this.id = typesCollector.size();
this.formatClass = formatClass == null ? SSTableFormat.class : formatClass;
this.singleton = isSingleton ? new Component(this) : null;
registerType(this);
}
/**
* If you have two formats registered, they may both define a type say `INDEX`. It is allowed even though
* they have the same name because they are not in the same branch. Though, we cannot let a custom type
* define a type `TOC` which is declared on the top level.
* So, e.g. given we have `TOC@SSTableFormat`, and `BigFormat` tries to define `TOC@BigFormat`, we should
* forbid that; but, given we have `INDEX@BigFormat`, we should allow to define `INDEX@TrieFormat` as those
* types are be distinguishable via format type.
*
* @param type a type to be registered
*/
private static void registerType(Type type)
{
synchronized (typesCollector)
{
if (typesCollector.stream().anyMatch(t -> (Objects.equals(t.name, type.name) || Objects.equals(t.repr, type.repr)) && (t.formatClass.isAssignableFrom(type.formatClass))))
throw new AssertionError("Type named " + type.name + " is already registered");
typesCollector.add(type);
}
}
@VisibleForTesting
public static Type fromRepresentation(String repr, SSTableFormat<?, ?> format)
{
for (Type type : Type.all)
{
if (type.repr != null && Pattern.matches(type.repr, repr) && type.formatClass.isAssignableFrom(format.getClass()))
return type;
}
return Types.CUSTOM;
}
public static Component createComponent(String repr, SSTableFormat<?, ?> format)
{
Type type = fromRepresentation(repr, format);
if (type.singleton != null)
return type.singleton;
else
return new Component(type, repr);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Type type = (Type) o;
return id == type.id;
}
@Override
public int hashCode()
{
return id;
}
@Override
public String toString()
{
return name;
}
public Component getSingleton()
{
return Objects.requireNonNull(singleton);
}
public Component createComponent(String repr)
{
Preconditions.checkArgument(singleton == null);
return new Component(this, repr);
}
}
public final Type type;
public final String name;
public final int hashCode;
private Component(Type type)
{
this(type, type.repr);
}
private Component(Type type, String name)
{
assert name != null : "Component name cannot be null";
this.type = type;
this.name = name;
this.hashCode = Objects.hash(type, name);
}
/**
* @return The unique (within an sstable) name for this component.
*/
public String name()
{
return name;
}
/**
* Parse the component part of a sstable filename into a {@code Component} object.
*
* @param name a string representing a sstable component.
* @return the component corresponding to {@code name}. Note that this always return a component as an unrecognized
* name is parsed into a CUSTOM component.
*/
public static Component parse(String name, SSTableFormat<?, ?> format)
{
return Type.createComponent(name, format);
}
public static Iterable<Component> getSingletonsFor(SSTableFormat<?, ?> format)
{
return Iterables.transform(Iterables.filter(Type.all, t -> t.singleton != null && t.formatClass.isAssignableFrom(format.getClass())), t -> t.singleton);
}
public static Iterable<Component> getSingletonsFor(Class<? extends SSTableFormat<?, ?>> formatClass)
{
return Iterables.transform(Iterables.filter(Type.all, t -> t.singleton != null && t.formatClass.isAssignableFrom(formatClass)), t -> t.singleton);
}
public boolean isValidFor(Descriptor descriptor)
{
return type.formatClass.isAssignableFrom(descriptor.version.format.getClass());
}
@Override
public String toString()
{
return this.name();
}
@Override
public boolean equals(Object o)
{
if (o == this)
return true;
if (!(o instanceof Component))
return false;
Component that = (Component) o;
return this.hashCode == that.hashCode && this.type == that.type && this.name.equals(that.name);
}
@Override
public int hashCode()
{
return hashCode;
}
}