HCOLL-280 Allow users to define their own xStream serialisers

This commit is contained in:
Rob Austin
2014-12-27 21:30:02 +00:00
parent add9a46d85
commit 6c9e68f646
4 changed files with 80 additions and 32 deletions
@@ -28,6 +28,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.List;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
@@ -39,8 +40,8 @@ class JsonSerializer {
private static final Logger LOG = LoggerFactory.getLogger(StatelessChronicleMap.class);
static synchronized <K, V> void getAll(File toFile, Map<K, V> map) throws IOException {
final XStream xstream = xStream(map);
static synchronized <K, V> void getAll(File toFile, Map<K, V> map, List jsonConverters) throws IOException {
final XStream xstream = xStream(map, jsonConverters);
OutputStream outputStream = new FileOutputStream(toFile);
if (toFile.getName().toLowerCase().endsWith(".gz"))
outputStream = new GZIPOutputStream(outputStream);
@@ -50,8 +51,9 @@ class JsonSerializer {
}
static synchronized <K, V> void putAll(File fromFile, Map<K, V> map) throws IOException {
final XStream xstream = xStream(map);
static synchronized <K, V> void putAll(File fromFile, Map<K, V> map, List jsonConverters)
throws IOException {
final XStream xstream = xStream(map, jsonConverters);
InputStream inputStream = new FileInputStream(fromFile);
if (fromFile.getName().toLowerCase().endsWith(".gz"))
@@ -61,7 +63,7 @@ class JsonSerializer {
}
}
private static <K, V> XStream xStream(Map<K, V> map) {
private static <K, V> XStream xStream(Map<K, V> map, List jsonConverters) {
try {
final XStream xstream = new XStream(new JettisonMappedXmlDriver());
xstream.setMode(XStream.NO_REFERENCES);
@@ -70,6 +72,18 @@ class JsonSerializer {
registerChronicleMapConverter(map, xstream);
xstream.registerConverter(new ByteBufferConverter());
xstream.registerConverter(new DataValueConverter());
for (Object c : jsonConverters) {
if (c instanceof Converter) {
xstream.registerConverter((Converter) c);
} else {
LOG.warn("Skipping Converter of type class=" + c.getClass().getName() + " as " +
" expecting an object of type com.thoughtworks.xstream.converters" +
".Converter");
}
}
return xstream;
} catch (NoClassDefFoundError e) {
logErrorSuggestXStreem(e);
@@ -80,16 +80,16 @@ import static net.openhft.lang.model.DataValueGenerator.firstPrimitiveFieldType;
* {@code ChronicleMap} implementations with completely different properties.
*
* <p>{@code ChronicleMap} ("ChronicleMaps, created by {@code ChronicleMapBuilder}") currently
* doesn't support resizing. That is why you <i>must</i> configure {@linkplain
* #entries(long) number of entries} you are going to insert into the created map <i>at most</i>.
* See {@link #entries(long)} method documentation for more information on this.
* doesn't support resizing. That is why you <i>must</i> configure {@linkplain #entries(long) number
* of entries} you are going to insert into the created map <i>at most</i>. See {@link
* #entries(long)} method documentation for more information on this.
*
* <p>If you key or value type is not constantly sized and known to {@code ChronicleHashBuilder},
* i. e. it is not a boxed primitive, data value generated interface, {@link Byteable}, etc. (see
* the complete list TODO insert the link to the complete list),
* you <i>must</i> provide the {@code ChronicleHashBuilder} with some information about you keys
* or values: if they are constantly-sized, call {@link #constantKeySizeBySample(Object)}, otherwise
* {@link ChronicleHashBuilder#averageKeySize(double)} method, accordingly for values.
* <p>If you key or value type is not constantly sized and known to {@code ChronicleHashBuilder}, i.
* e. it is not a boxed primitive, data value generated interface, {@link Byteable}, etc. (see the
* complete list TODO insert the link to the complete list), you <i>must</i> provide the {@code
* ChronicleHashBuilder} with some information about you keys or values: if they are
* constantly-sized, call {@link #constantKeySizeBySample(Object)}, otherwise {@link
* ChronicleHashBuilder#averageKeySize(double)} method, accordingly for values.
*
* @param <K> key type of the maps, produced by this builder
* @param <V> value type of the maps, produced by this builder
@@ -112,6 +112,9 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
private static final double UNDEFINED_DOUBLE_CONFIG = Double.NaN;
private List jsonConverters = new ArrayList();
private static boolean isDefined(double config) {
return !Double.isNaN(config);
}
@@ -246,12 +249,12 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
* encoded (and each character takes 2 bytes on-heap), because default off-heap {@link String}
* encoding is UTF-8 in {@code ChronicleMap}.)
*
* @param averageKeySize
* @throws IllegalStateException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @see #constantKeySizeBySample(Object)
* @see #averageValueSize(double)
* @see #actualChunkSize(int)
* @param averageKeySize
*/
@Override
public ChronicleMapBuilder<K, V> averageKeySize(double averageKeySize) {
@@ -280,6 +283,30 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
return this;
}
/**
* Used to a add a custom xStream converter, this converter may be used while chronicle maps
* serialises/deserialized and object(s) to and from JSON. You should add a custom converter if
* you wish to customise the JSON format, or support object(s) that can not be serialised into
* JSON.
*
* The method signature purposely has a loose coupling to xStream, it exposes {@code Object}
* parameter, rather than the com.thoughtworks.xstream.jsonConverters.Converter this is so that
* the the {@code ChronicleMapBuilder} class does not require xStream artifact/jars, however the
* parameter pass must be of type com.thoughtworks.xstream.jsonConverters.Converter as no other
* type is currently accepted.
*
* @param converter an xStream converter of type com.thoughtworks.xstream.jsonConverters.Converter
* @return this builder back
* @see ChronicleMap#putAll(java.io.File)
* @see ChronicleMap#putAll(java.io.File)
*/
public ChronicleMapBuilder<K, V> addJsonConverter(Object converter) {
this.jsonConverters.add(converter);
return this;
}
private double averageKeySize() {
return averageKeyOrValueSize(averageKeySize, keyBuilder);
}
@@ -289,12 +316,12 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
* created by this builder. If value size is always the same, call {@link
* #constantValueSizeBySample(Object)} method instead of this one.
*
* <p>{@code ChronicleHashBuilder} implementation heuristically chooses
* {@linkplain #actualChunkSize(int) the actual chunk size} based on this configuration and
* the key size, that, however, might result to quite high internal fragmentation,
* i. e. losses because only integral number of chunks could be allocated for the entry. If you
* want to avoid this, you should manually configure the actual chunk size in addition to this
* average value size configuration, which is anyway needed.
* <p>{@code ChronicleHashBuilder} implementation heuristically chooses {@linkplain
* #actualChunkSize(int) the actual chunk size} based on this configuration and the key size,
* that, however, might result to quite high internal fragmentation, i. e. losses because only
* integral number of chunks could be allocated for the entry. If you want to avoid this, you
* should manually configure the actual chunk size in addition to this average value size
* configuration, which is anyway needed.
*
* <p>If values are of boxed primitive type or {@link Byteable} subclass, i. e. if value size is
* known statically, it is automatically accounted and shouldn't be specified by user.
@@ -366,8 +393,8 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
* {@inheritDoc}
*
* @throws IllegalStateException is sizes of both keys and values of maps created by this
* builder are constant, hence chunk size shouldn't be
* configured by user
* builder are constant, hence chunk size shouldn't be configured
* by user
* @see #entryAndValueAlignment(Alignment)
* @see #entries(long)
* @see #maxChunksPerEntry(int)
@@ -385,6 +412,10 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
return this;
}
public List jsonConverters() {
return this.jsonConverters;
}
static class EntrySizeInfo {
final double averageEntrySize;
final int worstAlignment;
@@ -444,8 +475,8 @@ public final class ChronicleMapBuilder<K, V> implements Cloneable,
}
/**
* This is needed, if chunkSize = constant entry size is not aligned, for entry alignment to
* be always the same, we should _misalign_ the first chunk.
* This is needed, if chunkSize = constant entry size is not aligned, for entry alignment to be
* always the same, we should _misalign_ the first chunk.
*/
int specialEntrySpaceOffset(boolean replicated) {
if (!constantlySizedEntries() || valueAlignment() == Alignment.NO_ALIGNMENT)
@@ -59,7 +59,7 @@ class StatelessChronicleMap<K, V> implements ChronicleMap<K, V>, Closeable, Clon
private final ByteBuffer connectionOutBuffer = ByteBuffer.wrap(connectionByte);
private final String name;
private final List jsonConverters;
private ByteBuffer outBuffer;
private ByteBufferBytes outBytes;
@@ -139,6 +139,7 @@ class StatelessChronicleMap<K, V> implements ChronicleMap<K, V>, Closeable, Clon
@NotNull final ChronicleMapBuilder chronicleMapBuilder) {
this.config = config;
this.jsonConverters = chronicleMapBuilder.jsonConverters();
final BufferResizer outBufferResizer = new BufferResizer() {
@Override
@@ -191,12 +192,12 @@ class StatelessChronicleMap<K, V> implements ChronicleMap<K, V>, Closeable, Clon
@Override
public void getAll(File toFile) throws IOException {
JsonSerializer.getAll(toFile, this);
JsonSerializer.getAll(toFile, this, jsonConverters);
}
@Override
public void putAll(File fromFile) throws IOException {
JsonSerializer.putAll(fromFile, this);
JsonSerializer.putAll(fromFile, this, jsonConverters);
}
@Override
@@ -102,6 +102,7 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, ? super KI>,
final Class nativeValueClass;
final MultiMapFactory multiMapFactory;
final int maxChunksPerEntry;
private final List jsonConverters;
transient Provider<BytesReader<K>> keyReaderProvider;
transient Provider<KI> keyInteropProvider;
transient Provider<BytesReader<V>> valueReaderProvider;
@@ -119,6 +120,7 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, ? super KI>,
keySizeMarshaller = keyBuilder.sizeMarshaller();
originalKeyReader = keyBuilder.reader();
dataFileVersion = BuildVersion.version();
jsonConverters = builder.jsonConverters();
originalKeyInterop = (KI) keyBuilder.interop();
@@ -339,8 +341,8 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, ? super KI>,
private long segmentSize() {
long ss = (CACHE_LINES.align(sizeOfMultiMap() + sizeOfMultiMapBitSet(), BYTES)
* multiMapsPerSegment())
// the free list and 0+ dirty lists.
+ numberOfBitSets() * sizeOfSegmentFreeListBitSets()
// the free list and 0+ dirty lists.
+ numberOfBitSets() * sizeOfSegmentFreeListBitSets()
+ sizeOfEntrySpaceInSegment();
if ((ss & 63L) != 0)
throw new AssertionError();
@@ -671,12 +673,12 @@ class VanillaChronicleMap<K, KI, MKI extends MetaBytesInterop<K, ? super KI>,
@Override
public synchronized void getAll(File toFile) throws IOException {
JsonSerializer.getAll(toFile, this);
JsonSerializer.getAll(toFile, this, jsonConverters);
}
@Override
public synchronized void putAll(File fromFile) throws IOException {
JsonSerializer.putAll(fromFile, this);
JsonSerializer.putAll(fromFile, this, jsonConverters);
}
@Override