/*
* Copyright (C) 2012, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.set;
import net.openhft.chronicle.hash.ChronicleHashBuilder;
import net.openhft.chronicle.hash.ChronicleHashBuilderPrivateAPI;
import net.openhft.chronicle.hash.ChronicleHashCorruption;
import net.openhft.chronicle.hash.Data;
import net.openhft.chronicle.hash.serialization.*;
import net.openhft.chronicle.map.*;
import net.openhft.chronicle.map.replication.MapRemoteOperations;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
/**
* {@code ChronicleSetBuilder} manages the whole set of {@link ChronicleSet} configurations, could
* be used as a classic builder and/or factory.
* <p>
* <p>{@code ChronicleMapBuilder} is mutable, see a note in {@link
* ChronicleHashBuilder} interface documentation.
*
* @param <K> element type of the sets, created by this builder
* @see ChronicleSet
* @see ChronicleMapBuilder
*/
public final class ChronicleSetBuilder<K>
implements ChronicleHashBuilder<K, ChronicleSet<K>, ChronicleSetBuilder<K>> {
private static final Logger chronicleSetLogger = LoggerFactory.getLogger(ChronicleSet.class);
private static final ChronicleHashCorruption.Listener defaultChronicleSetCorruptionListener =
corruption -> {
if (corruption.exception() != null) {
chronicleSetLogger.error(corruption.message(), corruption.exception());
} else {
chronicleSetLogger.error(corruption.message());
}
};
private ChronicleMapBuilder<K, DummyValue> chronicleMapBuilder;
private ChronicleSetBuilderPrivateAPI<K> privateAPI;
ChronicleSetBuilder(Class<K> keyClass) {
chronicleMapBuilder = ChronicleMapBuilder.of(keyClass, DummyValue.class)
.valueReaderAndDataAccess(
DummyValueMarshaller.INSTANCE, DummyValueMarshaller.INSTANCE)
.valueSizeMarshaller(SizeMarshaller.constant(0));
//noinspection deprecation,unchecked
privateAPI = new ChronicleSetBuilderPrivateAPI<>(
(ChronicleHashBuilderPrivateAPI<K, MapRemoteOperations<K, DummyValue, ?>>)
chronicleMapBuilder.privateAPI());
}
/**
* Returns a new {@code ChronicleSetBuilder} instance which is able to {@linkplain #create()
* create} sets with the specified key class.
*
* @param keyClass class object used to infer key type and discover it's properties via
* reflection
* @param <K> key type of the sets, created by the returned builder
* @return a new builder for the given key class
*/
public static <K> ChronicleSetBuilder<K> of(Class<K> keyClass) {
return new ChronicleSetBuilder<>(keyClass);
}
@Override
public ChronicleSetBuilder<K> clone() {
try {
@SuppressWarnings("unchecked")
final ChronicleSetBuilder<K> result = (ChronicleSetBuilder<K>) super.clone();
result.chronicleMapBuilder = chronicleMapBuilder.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
@Override
public ChronicleSetBuilder<K> name(String name) {
chronicleMapBuilder.name(name);
return this;
}
@Override
public ChronicleSetBuilder<K> actualSegments(int actualSegments) {
chronicleMapBuilder.actualSegments(actualSegments);
return this;
}
@Override
public ChronicleSetBuilder<K> minSegments(int minSegments) {
chronicleMapBuilder.minSegments(minSegments);
return this;
}
@Override
public ChronicleSetBuilder<K> entriesPerSegment(long entriesPerSegment) {
chronicleMapBuilder.entriesPerSegment(entriesPerSegment);
return this;
}
@Override
public ChronicleSetBuilder<K> actualChunksPerSegmentTier(long actualChunksPerSegmentTier) {
chronicleMapBuilder.actualChunksPerSegmentTier(actualChunksPerSegmentTier);
return this;
}
/**
* {@inheritDoc}
*
* <p>Example: if keys in your set(s) are English words in {@link String} form, average English
* word length is 5.1, configure average key size of 6: <pre>{@code
* ChronicleSet<String> uniqueWords = ChronicleSetBuilder.of(String.class)
* .entries(50000)
* .averageKeySize(6)
* .create();}</pre>
*
* <p>(Note that 6 is chosen as average key size in bytes despite strings in Java are UTF-16
* encoded (and each character takes 2 bytes on-heap), because default off-heap {@link String}
* encoding is UTF-8 in {@code ChronicleSet}.)
*
* @param averageKeySize the average size in bytes of the key
* @see #constantKeySizeBySample(Object)
* @see #actualChunkSize(int)
*/
@Override
public ChronicleSetBuilder<K> averageKeySize(double averageKeySize) {
chronicleMapBuilder.averageKeySize(averageKeySize);
return this;
}
@Override
public ChronicleSetBuilder<K> averageKey(K averageKey) {
chronicleMapBuilder.averageKey(averageKey);
return this;
}
/**
* {@inheritDoc}
* <p>
* <p>For example, if your keys are Git commit hashes:<pre>{@code
* Set<byte[]> gitCommitsOfInterest = ChronicleSetBuilder.of(byte[].class)
* .constantKeySizeBySample(new byte[20])
* .create();}</pre>
*
* @see ChronicleHashBuilder#averageKeySize(double)
*/
@Override
public ChronicleSetBuilder<K> constantKeySizeBySample(K sampleKey) {
chronicleMapBuilder.constantKeySizeBySample(sampleKey);
return this;
}
@Override
public ChronicleSetBuilder<K> actualChunkSize(int actualChunkSize) {
chronicleMapBuilder.actualChunkSize(actualChunkSize);
return this;
}
@Override
public ChronicleSetBuilder<K> maxChunksPerEntry(int maxChunksPerEntry) {
chronicleMapBuilder.maxChunksPerEntry(maxChunksPerEntry);
return this;
}
@Override
public ChronicleSetBuilder<K> entries(long entries) {
chronicleMapBuilder.entries(entries);
return this;
}
@Override
public ChronicleSetBuilder<K> maxBloatFactor(double maxBloatFactor) {
chronicleMapBuilder.maxBloatFactor(maxBloatFactor);
return this;
}
@Override
public ChronicleSetBuilder<K> allowSegmentTiering(boolean allowSegmentTiering) {
chronicleMapBuilder.allowSegmentTiering(allowSegmentTiering);
return this;
}
@Override
public ChronicleSetBuilder<K> nonTieredSegmentsPercentile(double nonTieredSegmentsPercentile) {
chronicleMapBuilder.nonTieredSegmentsPercentile(nonTieredSegmentsPercentile);
return this;
}
@Override
public String toString() {
return " ChronicleSetBuilder{" +
"chronicleMapBuilder=" + chronicleMapBuilder +
'}';
}
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
@Override
public boolean equals(Object o) {
return o instanceof ChronicleSetBuilder &&
chronicleMapBuilder.equals(((ChronicleSetBuilder) o).chronicleMapBuilder);
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public ChronicleSetBuilder<K> keyReaderAndDataAccess(
SizedReader<K> keyReader, @NotNull DataAccess<K> keyDataAccess) {
chronicleMapBuilder.keyReaderAndDataAccess(keyReader, keyDataAccess);
return this;
}
@Override
public ChronicleSetBuilder<K> keyMarshallers(
@NotNull BytesReader<K> keyReader, @NotNull BytesWriter<? super K> keyWriter) {
chronicleMapBuilder.keyMarshallers(keyReader, keyWriter);
return this;
}
@Override
public <M extends BytesReader<K> & BytesWriter<? super K>>
ChronicleSetBuilder<K> keyMarshaller(@NotNull M marshaller) {
chronicleMapBuilder.keyMarshaller(marshaller);
return this;
}
@Override
public ChronicleSetBuilder<K> keyMarshallers(
@NotNull SizedReader<K> keyReader, @NotNull SizedWriter<? super K> keyWriter) {
chronicleMapBuilder.keyMarshallers(keyReader, keyWriter);
return this;
}
@Override
public <M extends SizedReader<K> & SizedWriter<? super K>>
ChronicleSetBuilder<K> keyMarshaller(@NotNull M sizedMarshaller) {
chronicleMapBuilder.keyMarshaller(sizedMarshaller);
return this;
}
@Override
public ChronicleSetBuilder<K> keySizeMarshaller(@NotNull SizeMarshaller keySizeMarshaller) {
chronicleMapBuilder.keySizeMarshaller(keySizeMarshaller);
return this;
}
@Override
public ChronicleSetBuilder<K> aligned64BitMemoryOperationsAtomic(
boolean aligned64BitMemoryOperationsAtomic) {
chronicleMapBuilder.aligned64BitMemoryOperationsAtomic(aligned64BitMemoryOperationsAtomic);
return this;
}
@Override
public ChronicleSetBuilder<K> checksumEntries(boolean checksumEntries) {
chronicleMapBuilder.checksumEntries(checksumEntries);
return this;
}
/**
* Inject your SPI code around basic {@code ChronicleSet}'s operations with entries:
* removing entries and inserting new entries.
*
* <p>This affects behaviour of ordinary set.add(), set.remove(), calls, as well as removes
* <i>during iterations</i>, updates during <i>remote calls</i> and
* <i>internal replication operations</i>.
*/
public ChronicleSetBuilder<K> entryOperations(SetEntryOperations<K, ?> entryOperations) {
chronicleMapBuilder.entryOperations(new MapEntryOperations<K, DummyValue, Object>() {
@Override
public Object remove(@NotNull MapEntry<K, DummyValue> entry) {
//noinspection unchecked
return entryOperations.remove((SetEntry<K>) entry);
}
@Override
public Object insert(
@NotNull MapAbsentEntry<K, DummyValue> absentEntry, Data<DummyValue> value) {
//noinspection unchecked
return entryOperations.insert((SetAbsentEntry<K>) absentEntry);
}
});
return this;
}
@Override
public ChronicleSet<K> create() {
final ChronicleMap<K, DummyValue> map = chronicleMapBuilder.create();
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
}
@Override
public ChronicleSet<K> createPersistedTo(File file) throws IOException {
ChronicleMap<K, DummyValue> map = chronicleMapBuilder.createPersistedTo(file);
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
}
@Override
public ChronicleSet<K> createOrRecoverPersistedTo(File file) throws IOException {
return createOrRecoverPersistedTo(file, true);
}
@Override
public ChronicleSet<K> createOrRecoverPersistedTo(File file, boolean sameLibraryVersion)
throws IOException {
return createOrRecoverPersistedTo(file, sameLibraryVersion,
defaultChronicleSetCorruptionListener);
}
@Override
public ChronicleSet<K> createOrRecoverPersistedTo(
File file, boolean sameLibraryVersion,
ChronicleHashCorruption.Listener corruptionListener) throws IOException {
ChronicleMap<K, DummyValue> map = chronicleMapBuilder.createOrRecoverPersistedTo(
file, sameLibraryVersion, corruptionListener);
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
}
@Override
public ChronicleSet<K> recoverPersistedTo(File file, boolean sameBuilderConfigAndLibraryVersion)
throws IOException {
return recoverPersistedTo(file, sameBuilderConfigAndLibraryVersion,
defaultChronicleSetCorruptionListener);
}
@Override
public ChronicleSet<K> recoverPersistedTo(
File file, boolean sameBuilderConfigAndLibraryVersion,
ChronicleHashCorruption.Listener corruptionListener) throws IOException {
ChronicleMap<K, DummyValue> map = chronicleMapBuilder.recoverPersistedTo(
file, sameBuilderConfigAndLibraryVersion, corruptionListener);
return new SetFromMap<>((VanillaChronicleMap<K, DummyValue, ?>) map);
}
/**
* @deprecated don't use private API in the client code
*/
@Deprecated
@Override
public Object privateAPI() {
return privateAPI;
}
}