diff --git a/src/main/java/net/openhft/chronicle/map/ChroncileMapJsonSerializer.java b/src/main/java/net/openhft/chronicle/map/ChroncileMapJsonSerializer.java index 9813cdaae..89830e1b8 100644 --- a/src/main/java/net/openhft/chronicle/map/ChroncileMapJsonSerializer.java +++ b/src/main/java/net/openhft/chronicle/map/ChroncileMapJsonSerializer.java @@ -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 void getAll(File toFile, Map map) throws IOException { - final XStream xstream = xStream(map); + static synchronized void getAll(File toFile, Map 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 void putAll(File fromFile, Map map) throws IOException { - final XStream xstream = xStream(map); + static synchronized void putAll(File fromFile, Map 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 XStream xStream(Map map) { + private static XStream xStream(Map 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); diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java index f5a51af34..4864b0664 100755 --- a/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapBuilder.java @@ -80,16 +80,16 @@ import static net.openhft.lang.model.DataValueGenerator.firstPrimitiveFieldType; * {@code ChronicleMap} implementations with completely different properties. * *

{@code ChronicleMap} ("ChronicleMaps, created by {@code ChronicleMapBuilder}") currently - * doesn't support resizing. That is why you must configure {@linkplain - * #entries(long) number of entries} you are going to insert into the created map at most. - * See {@link #entries(long)} method documentation for more information on this. + * doesn't support resizing. That is why you must configure {@linkplain #entries(long) number + * of entries} you are going to insert into the created map at most. See {@link + * #entries(long)} method documentation for more information on this. * - *

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 must 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. + *

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 must 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 key type of the maps, produced by this builder * @param value type of the maps, produced by this builder @@ -112,6 +112,9 @@ public final class ChronicleMapBuilder 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 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 averageKeySize(double averageKeySize) { @@ -280,6 +283,30 @@ public final class ChronicleMapBuilder 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 addJsonConverter(Object converter) { + this.jsonConverters.add(converter); + return this; + } + + private double averageKeySize() { return averageKeyOrValueSize(averageKeySize, keyBuilder); } @@ -289,12 +316,12 @@ public final class ChronicleMapBuilder implements Cloneable, * created by this builder. If value size is always the same, call {@link * #constantValueSizeBySample(Object)} method instead of this one. * - *

{@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. + *

{@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. * *

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 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 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 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) diff --git a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java index 7c4ee9ea3..a6d2d0449 100644 --- a/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/StatelessChronicleMap.java @@ -59,7 +59,7 @@ class StatelessChronicleMap implements ChronicleMap, 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 implements ChronicleMap, 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 implements ChronicleMap, 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 diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 516588da3..0c95601d0 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -102,6 +102,7 @@ class VanillaChronicleMap, final Class nativeValueClass; final MultiMapFactory multiMapFactory; final int maxChunksPerEntry; + private final List jsonConverters; transient Provider> keyReaderProvider; transient Provider keyInteropProvider; transient Provider> valueReaderProvider; @@ -119,6 +120,7 @@ class VanillaChronicleMap, keySizeMarshaller = keyBuilder.sizeMarshaller(); originalKeyReader = keyBuilder.reader(); dataFileVersion = BuildVersion.version(); + jsonConverters = builder.jsonConverters(); originalKeyInterop = (KI) keyBuilder.interop(); @@ -339,8 +341,8 @@ class VanillaChronicleMap, 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, @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