From 5b7ad0f9318f3119446e27e36463fcfb7edbac81 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Fri, 10 Oct 2014 12:07:52 +0100 Subject: [PATCH] HCOLL-164 Create a ChronicleSet --- .../chronicle/map/ChronicleSetBuilder.java | 348 ++++++++++++++++++ .../map/ChronicleSetBuilderTest.java | 24 ++ 2 files changed, 372 insertions(+) create mode 100644 src/main/java/net/openhft/chronicle/map/ChronicleSetBuilder.java create mode 100644 src/test/java/net/openhft/chronicle/map/ChronicleSetBuilderTest.java diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleSetBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleSetBuilder.java new file mode 100644 index 000000000..fa593bc40 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/ChronicleSetBuilder.java @@ -0,0 +1,348 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * 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 net.openhft.chronicle.map; + +import net.openhft.lang.io.serialization.BytesMarshaller; +import net.openhft.lang.io.serialization.BytesMarshallerFactory; +import net.openhft.lang.io.serialization.ObjectSerializer; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static net.openhft.chronicle.map.Objects.builderEquals; + +public class ChronicleSetBuilder implements Cloneable { + + ChronicleMapBuilder chronicleMapBuilder; + + ChronicleSetBuilder(Class keyClass) { + chronicleMapBuilder = new ChronicleMapBuilder(keyClass, Boolean.class); + chronicleMapBuilder.constantValueSizeBySample(Boolean.TRUE); + } + + public static ChronicleSetBuilder of(Class keyClass) { + return new ChronicleSetBuilder(keyClass); + } + + @Override + public ChronicleSetBuilder clone() { + try { + @SuppressWarnings("unchecked") + final ChronicleSetBuilder result = (ChronicleSetBuilder) super.clone(); + result.chronicleMapBuilder = chronicleMapBuilder.clone(); + return result; + } catch (CloneNotSupportedException e) { + throw new AssertionError(e); + } + } + + + public ChronicleSetBuilder actualSegments(int actualSegments) { + chronicleMapBuilder.actualSegments(actualSegments); + return this; + } + + public int actualSegments() { + return chronicleMapBuilder.actualSegments(); + } + + /** + * Set minimum number of segments. See concurrencyLevel in {@link java.util.concurrent.ConcurrentHashMap}. + * + * @param minSegments the minimum number of segments in maps, constructed by this builder + * @return this builder object back + */ + public ChronicleSetBuilder minSegments(int minSegments) { + chronicleMapBuilder.minSegments(minSegments); + return this; + } + + public int minSegments() { + return chronicleMapBuilder.minSegments(); + } + + + public ChronicleSetBuilder actualElementsPerSegment(int actualEntriesPerSegment) { + chronicleMapBuilder.actualEntriesPerSegment(actualEntriesPerSegment); + return this; + } + + public int actualElementsPerSegment() { + return chronicleMapBuilder.actualEntriesPerSegment(); + } + + + /** + * Configures the optimal number of bytes for an element. If elementSize is always the same, call {@link + * #constantElementsSizeBySample(Object)} method instead of this one. + * + *

If the size varies moderately, specify the size higher than average, but lower than the maximum + * possible, to minimize average memory overuse. If the size varies in a wide range, it's better to use + * + * + *

Example: if element in your set(s) are English words in {@link String} form, keys size 10 + * (a bit more than average English word length) would be a good choice:

{@code
+     * ChronicleSetBuilder wordFrequencies = ChronicleSetBuilder
+     *     .of(String.class)
+     *     .entries(50000)
+     *     .elementSize(10)
+     *     .create();}
+ * + * @param elementSize number of bytes, taken by serialized form of the element + * @return this {@code ChronicleSetBuilder} back + * @see #constantElementsSizeBySample(Object) + */ + public ChronicleSetBuilder elementSize(int elementSize) { + chronicleMapBuilder.keySize(elementSize); + return this; + } + + + /** + * Configures the constant number of bytes, taken by serialized form of elements, created by this builder. + * This is done by providing the {@code element}, all elements should take the same number of bytes in + * serialized form, as this sample object. + * + *

For example, if your elements are Git commit hashes:

{@code
+     * Map gitCommitMessagesByHash =
+     *     ChronicleSetBuilder.of(byte[].class)
+     *     .constantElementsSizeBySample(new byte[20])
+     *     .create();
+     * }
+ * + *

If elements are of boxed primitive type or {@link net.openhft.lang.model.Byteable} subclass, i. e. + * if element size is known statically, it is automatically accounted and this method shouldn't be + * called. + * + * @param element the sample element + * @return this builder back + */ + public ChronicleSetBuilder constantElementsSizeBySample(K element) { + chronicleMapBuilder.constantKeySizeBySample(element); + return this; + } + + + /** + * Configures alignment strategy of address in memory of elements + * + *

Useful when elements are updated intensively, particularly fields with volatile access, because it + * doesn't work well if the value elements cache lines. Also, on some (nowadays rare) architectures any + * misaligned memory access is more expensive than aligned. + * + * @param alignment the new alignment of the seta constructed by this builder + * @return this {@code ChronicleSetBuilder} back + */ + public ChronicleSetBuilder alignment(Alignment alignment) { + chronicleMapBuilder.entryAndValueAlignment(alignment); + return this; + } + + /** + * * Returns the alignment memory strategy used for addresses of elements + * + *

Default is {@link net.openhft.chronicle.map.Alignment#OF_4_BYTES}. + * + * @return alignment memory strategy + * @see #alignment(net.openhft.chronicle.map.Alignment) + */ + public Alignment alignment() { + return chronicleMapBuilder.entryAndValueAlignment(); + } + + public ChronicleSetBuilder elements(long entries) { + chronicleMapBuilder.entries(entries); + return this; + } + + public long elements() { + return chronicleMapBuilder.entries(); + } + + public ChronicleSetBuilder replicas(int replicas) { + chronicleMapBuilder.replicas(replicas); + return this; + } + + public int replicas() { + return chronicleMapBuilder.replicas(); + } + + + /** + * Not supported yet. + * + * @param transactional if the built map should be transactional + * @return this {@code ChronicleMapBuilder} back + */ + public ChronicleSetBuilder transactional(boolean transactional) { + chronicleMapBuilder.transactional(transactional); + return this; + } + + public boolean transactional() { + return chronicleMapBuilder.transactional(); + } + + public ChronicleSetBuilder lockTimeOut(long lockTimeOut, TimeUnit unit) { + chronicleMapBuilder.lockTimeOut(lockTimeOut, unit); + return this; + } + + public long lockTimeOut(TimeUnit unit) { + return chronicleMapBuilder.lockTimeOut(unit); + } + + + public ChronicleSetBuilder errorListener(MapErrorListener errorListener) { + chronicleMapBuilder.errorListener(errorListener); + return this; + } + + public MapErrorListener errorListener() { + return chronicleMapBuilder.errorListener(); + } + + + public boolean largeSegments() { + return chronicleMapBuilder.largeSegments(); + } + + public ChronicleSetBuilder largeSegments(boolean largeSegments) { + chronicleMapBuilder.largeSegments(largeSegments); + return this; + } + + public ChronicleSetBuilder metaDataBytes(int metaDataBytes) { + chronicleMapBuilder.metaDataBytes(metaDataBytes); + return this; + } + + public int metaDataBytes() { + return chronicleMapBuilder.metaDataBytes(); + } + + @Override + public String toString() { + return " ChronicleMapBuilder{" + + "actualSegments=" + actualSegments() + + ", minSegments=" + minSegments() + + ", actualEntriesPerSegment=" + actualElementsPerSegment() + + ", alignment=" + alignment() + + ", entries=" + elements() + + ", replicas=" + replicas() + + ", transactional=" + transactional() + + ", metaDataBytes=" + metaDataBytes() + + ", errorListener=" + errorListener() + + ", largeSegments=" + largeSegments() + + ", timeProvider=" + timeProvider() + + ", bytesMarshallerfactory=" + bytesMarshallerFactory() + + '}'; + } + + @SuppressWarnings("EqualsWhichDoesntCheckParameterClass") + @Override + public boolean equals(Object o) { + return builderEquals(this, o); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + public ChronicleSetBuilder addReplicator(Replicator replicator) { + chronicleMapBuilder.addReplicator(replicator); + return this; + } + + public ChronicleSetBuilder setReplicators(Replicator... replicators) { + chronicleMapBuilder.setReplicators(replicators); + return this; + } + + public ChronicleSetBuilder timeProvider(TimeProvider timeProvider) { + chronicleMapBuilder.timeProvider(timeProvider); + return this; + } + + public TimeProvider timeProvider() { + return chronicleMapBuilder.timeProvider(); + } + + public BytesMarshallerFactory bytesMarshallerFactory() { + return chronicleMapBuilder.bytesMarshallerFactory(); + } + + public ChronicleSetBuilder bytesMarshallerFactory(BytesMarshallerFactory bytesMarshallerFactory) { + chronicleMapBuilder.bytesMarshallerFactory(bytesMarshallerFactory); + return this; + } + + public ObjectSerializer objectSerializer() { + return chronicleMapBuilder.objectSerializer(); + } + + public ChronicleSetBuilder objectSerializer(ObjectSerializer objectSerializer) { + chronicleMapBuilder.objectSerializer(objectSerializer); + return this; + } + + /** + * For testing + */ + ChronicleSetBuilder forceReplicatedImpl() { + chronicleMapBuilder.forceReplicatedImpl(); + return this; + } + + public ChronicleSetBuilder keyMarshaller(@NotNull BytesMarshaller keyMarshaller) { + chronicleMapBuilder.keyMarshaller(keyMarshaller); + return this; + } + + /** + * Specifies that elements, are immutable. {@code ChronicleSet} implementations can benefit from the + * knowledge that elements are not mutated between queries. By default, {@code ChronicleMapBuilder} + * detects immutability automatically only for very few standard JDK types (for example, for {@link + * String}), it is not recommended to rely on {@code ChronicleSetBuilder} to be smart enough about this. + * + * @return this builder back + */ + public ChronicleSetBuilder immutableKeys() { + chronicleMapBuilder.immutableKeys(); + return this; + } + + + public Set create(File file) throws IOException { + final ChronicleMap map = chronicleMapBuilder.create(file); + return Collections.newSetFromMap(map); + } + + public Set create() throws IOException { + final ChronicleMap map = chronicleMapBuilder.create(); + return Collections.newSetFromMap(map); + } + + +} + diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleSetBuilderTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleSetBuilderTest.java new file mode 100644 index 000000000..d07183eed --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/ChronicleSetBuilderTest.java @@ -0,0 +1,24 @@ +package net.openhft.chronicle.map; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Set; + +public class ChronicleSetBuilderTest { + + @Test + public void test() throws Exception { + + Set integers = ChronicleSetBuilder.of(Integer.class).create(); + + for (int i = 0; i < 10; i++) { + integers.add(i); + } + + Assert.assertTrue(integers.contains(5)); + Assert.assertEquals(10, integers.size()); + } + + +}