/*
* 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.hash.serialization;
import net.openhft.chronicle.bytes.Bytes;
import net.openhft.chronicle.wire.WireIn;
import net.openhft.chronicle.wire.WireOut;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import static net.openhft.chronicle.hash.serialization.StatefulCopyable.copyIfNeeded;
/**
* Marshaller of {@code Set<T>}. Uses {@link HashSet} (hence default element objects' equality and
* {@code hashCode()}) as the set implementation to deserialize into.
*
* <p>This marshaller supports multimap emulation on top of Chronicle Map, that is possible but
* inefficient. See <a href="https://github.com/OpenHFT/Chronicle-Map#chronicle-map-is-not">the
* README section</a>.
*
* <p>Usage: <pre>{@code
* SetMarshaller<String> valueMarshaller = SetMarshaller.of(
* new StringBytesReader(), CharSequenceBytesWriter.INSTANCE);
* ChronicleMap<String, Set<String>> map = ChronicleMap
* .of(String.class, (Class<Set<String>>) (Class) Set.class)
* .averageKey("fruits")
* .valueMarshaller(valueMarshaller)
* .averageValue(ImmutableSet.of("apples", "bananas", "grapes"))
* .entries(10_000)
* .create();
* }</pre>
*
* <p>Look for pre-defined element marshallers in {@link
* net.openhft.chronicle.hash.serialization.impl} package. This package is not included into
* Javadocs, but present in Chronicle Map distribution. If there are no existing marshallers for
* your {@code Set} element type, define {@link BytesReader} and {@link BytesWriter} yourself.
*
* @param <T> the element type of serialized Sets
* @see ListMarshaller
* @see MapMarshaller
*/
public final class SetMarshaller<T>
implements BytesReader<Set<T>>, BytesWriter<Set<T>>, StatefulCopyable<SetMarshaller<T>> {
/**
* Returns a {@code SetMarshaller} which uses the given set elements' serializers.
*
* @param elementReader set elements' reader
* @param elementWriter set elements' writer
* @param <T> type of set elements
* @return a {@code SetMarshaller} which uses the given set elements' serializers
*/
public static <T> SetMarshaller<T> of(
BytesReader<T> elementReader, BytesWriter<? super T> elementWriter) {
return new SetMarshaller<>(elementReader, elementWriter);
}
/**
* Returns a {@code SetMarshaller} which uses the given marshaller as both reader and writer of
* set elements. Example: <pre><code>
* ChronicleMap
* .of(String.class,{@code (Class<Set<Integer>>)} ((Class) Set.class))
* .valueMarshaller(SetMarshaller.of(IntegerMarshaller.INSTANCE))
* ...</code></pre>
* @param elementMarshaller set elements' marshaller
* @param <T> type of set elements
* @param <M> type of set elements' marshaller
* @return a {@code SetMarshaller} which uses the given set elements' marshaller
*/
public static <T, M extends BytesReader<T> & BytesWriter<? super T>> SetMarshaller<T> of(
M elementMarshaller) {
return of(elementMarshaller, elementMarshaller);
}
// Config fields
private BytesReader<T> elementReader;
private BytesWriter<? super T> elementWriter;
/** Cache field */
private transient Deque<T> orderedElements;
/**
* Constructs a {@code SetMarshaller} with the given set elements' serializers.
*
* <p>Use static factory {@link #of(BytesReader, BytesWriter)} instead of this constructor
* directly.
*
* @param elementReader set elements' reader
* @param elementWriter set elements' writer
*/
public SetMarshaller(BytesReader<T> elementReader, BytesWriter<? super T> elementWriter) {
this.elementReader = elementReader;
this.elementWriter = elementWriter;
initTransients();
}
private void initTransients() {
orderedElements = new ArrayDeque<>();
}
@NotNull
@Override
public Set<T> read(Bytes in, @Nullable Set<T> using) {
int size = in.readInt();
if (using == null) {
using = new HashSet<>((int) (size / 0.75));
for (int i = 0; i < size; i++) {
using.add(elementReader.read(in, null));
}
} else {
orderedElements.addAll(using);
using.clear();
for (int i = 0; i < size; i++) {
using.add(elementReader.read(in, orderedElements.pollFirst()));
}
orderedElements.clear(); // for GC, avoid zombie object links
}
return using;
}
@Override
public void write(Bytes out, @NotNull Set<T> toWrite) {
out.writeInt(toWrite.size());
toWrite.forEach(e -> elementWriter.write(out, e));
}
@Override
public SetMarshaller<T> copy() {
return new SetMarshaller<>(copyIfNeeded(elementReader), copyIfNeeded(elementWriter));
}
@Override
public void readMarshallable(@NotNull WireIn wireIn) {
elementReader = wireIn.read(() -> "elementReader").typedMarshallable();
elementWriter = wireIn.read(() -> "elementWriter").typedMarshallable();
initTransients();
}
@Override
public void writeMarshallable(@NotNull WireOut wireOut) {
wireOut.write(() -> "elementReader").typedMarshallable(elementReader);
wireOut.write(() -> "elementWriter").typedMarshallable(elementWriter);
}
}