From c033f6ecb175d448c4b88fd359166287fca5218c Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Mon, 22 Dec 2014 19:50:15 +0000 Subject: [PATCH] HCOLL-268 refactored the JSON convertor --- .../chronicle/map/ChronicleMapConvector.java | 375 ++++++++++++++++++ .../chronicle/map/VanillaChronicleMap.java | 262 +----------- .../openhft/chronicle/map/XStreamHelper.java | 155 -------- .../chronicle/map/CHMUseCasesTest.java | 1 - 4 files changed, 377 insertions(+), 416 deletions(-) create mode 100644 src/main/java/net/openhft/chronicle/map/ChronicleMapConvector.java delete mode 100644 src/main/java/net/openhft/chronicle/map/XStreamHelper.java diff --git a/src/main/java/net/openhft/chronicle/map/ChronicleMapConvector.java b/src/main/java/net/openhft/chronicle/map/ChronicleMapConvector.java new file mode 100644 index 000000000..f900b8ee2 --- /dev/null +++ b/src/main/java/net/openhft/chronicle/map/ChronicleMapConvector.java @@ -0,0 +1,375 @@ +package net.openhft.chronicle.map; + +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.MarshallingContext; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; +import com.thoughtworks.xstream.io.HierarchicalStreamWriter; +import net.openhft.lang.model.DataValueClasses; +import net.openhft.lang.model.constraints.NotNull; +import net.openhft.lang.values.StringValue; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.Map; + +import static java.beans.Introspector.getBeanInfo; + +/** + * @author Rob Austin. + */ +public class ChronicleMapConvector implements Converter { + + private static final Logger LOG = LoggerFactory.getLogger(VanillaChronicleMap.class); + + private final Class entrySetClass; + private final Class writeThroughEntryClass; + private final Map map; + + public ChronicleMapConvector(@NotNull Map map) { + + final String vanillaChronicleMap = VanillaChronicleMap.class.getCanonicalName(); + + try { + this.entrySetClass = Class.forName(vanillaChronicleMap + "$EntrySet"); + this.writeThroughEntryClass = Class.forName(vanillaChronicleMap + "$WriteThroughEntry"); + } catch (ClassNotFoundException e) { + throw new ConversionException("", e); + } + + this.map = map; + } + + + @Override + public boolean canConvert(Class aClass) { + + if (entrySetClass.isAssignableFrom(aClass) || + writeThroughEntryClass.isAssignableFrom(aClass)) + return true; + + String canonicalName = aClass.getCanonicalName(); + + return canonicalName.startsWith("net.openhft.lang.values") || + canonicalName.endsWith("$$Native") || + canonicalName.endsWith("$$Heap"); + } + + @Override + public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext + marshallingContext) { + + if (writeThroughEntryClass.isAssignableFrom(o.getClass())) { + + final AbstractMap.SimpleEntry e = (AbstractMap.SimpleEntry) o; + Object key = e.getKey(); + writer.startNode(key.getClass().getCanonicalName()); + + + marshallingContext.convertAnother(key); + writer.endNode(); + + Object value = e.getValue(); + writer.startNode(value.getClass().getCanonicalName()); + + + marshallingContext.convertAnother(value); + writer.endNode(); + + } else if (entrySetClass.isAssignableFrom(o.getClass())) { + + for (Map.Entry e : (Iterable) o) { + writer.startNode("entry"); + marshallingContext.convertAnother(e); + writer.endNode(); + } + + } + + final String canonicalName = o.getClass().getCanonicalName(); + + boolean isNative = canonicalName.endsWith("$$Native"); + boolean isHeap = canonicalName.endsWith("$$Heap"); + + if (isNative || isHeap) { + if (canonicalName.startsWith("net.openhft.lang.values")) { + + Method[] methods = o.getClass().getMethods(); + + for (Method method : methods) { + + if (!method.getName().equals("getValue") || + method.getParameterTypes().length != 0) { + continue; + } + + try { + marshallingContext.convertAnother(method.invoke(o)); + return; + } catch (Exception e) { + throw new ConversionException("class=" + canonicalName, e); + } + } + + throw new ConversionException("class=" + canonicalName); + + } + + + try { + + final BeanInfo info = getBeanInfo(o.getClass()); // new BeanGenerator + + for (PropertyDescriptor p : info.getPropertyDescriptors()) { + + if (p.getName().equals("Class") || + p.getReadMethod() == null || + p.getWriteMethod() == null) + continue; + + try { + + final Method readMethod = p.getReadMethod(); + final Object value = readMethod.invoke(o); + + if (value == null) + return; + + writer.startNode(p.getDisplayName()); + marshallingContext.convertAnother(value); + writer.endNode(); + + } catch (Exception e) { + LOG.error("class=" + p.getName(), e); + } + + + } + } catch (IntrospectionException e) { + throw new ConversionException("class=" + canonicalName, e); + } + + } + } + + @Override + public Object unmarshal(HierarchicalStreamReader reader, + UnmarshallingContext context) { + + + if ("cmap".equals(reader.getNodeName())) { + + while (reader.hasMoreChildren()) { + reader.moveDown(); + final String nodeName0 = reader.getNodeName(); + + if (!nodeName0.equals("entry")) + throw new ConversionException("unable to convert node " + + "named=" + nodeName0); + final K k; + final V v; + + reader.moveDown(); + k = (K) deserialize(context, reader); + reader.moveUp(); + + reader.moveDown(); + v = (V) deserialize(context, reader); + reader.moveUp(); + + + if (k != null) + map.put(k, v); + + reader.moveUp(); + } + + return null; + + } + + final String canonicalName = context.getRequiredType().getCanonicalName(); + + boolean isNative = canonicalName.endsWith("$$Native"); + boolean isHeap = canonicalName.endsWith("$$Heap"); + + if (!isNative && !isHeap) + return null; + + if (context.getRequiredType().getCanonicalName().startsWith + ("net.openhft.lang.values")) + return toNativeValueObjects(reader, context.getRequiredType()); + + final String nodeName = isNative ? + + canonicalName.substring(0, canonicalName.length() - + "$$Native".length()) : + + canonicalName.substring(0, canonicalName.length() - + "$$Heap".length()); + + try { + + final Class interfaceClass = Class.forName(nodeName); + + final Object result = (isNative) ? + DataValueClasses.newDirectInstance(interfaceClass) : + DataValueClasses.newInstance(interfaceClass); + + final BeanInfo beanInfo = getBeanInfo(result.getClass()); + + while (reader.hasMoreChildren()) { + reader.moveDown(); + + final String name = reader.getNodeName(); + + for (PropertyDescriptor descriptor : beanInfo.getPropertyDescriptors()) { + + if (!descriptor.getName().equals(name)) + continue; + + final Class[] parameterTypes = descriptor.getWriteMethod() + .getParameterTypes(); + + if (parameterTypes.length != 1) + continue; + + final Object object = context.convertAnother(null, parameterTypes[0]); + + try { + descriptor.getWriteMethod().invoke(result, object); + } catch (Exception e) { + LOG.error("", e); + } + + break; + } + + reader.moveUp(); + } + + return result; + + } catch (Exception e) { + throw new ConversionException("class=" + canonicalName, e); + } + + + } + + + static Object deserialize(@NotNull UnmarshallingContext unmarshallingContext, + @NotNull HierarchicalStreamReader reader) { + + final String clazz = reader.getNodeName(); + + switch (clazz) { + + case "java.util.Collections$EmptySet": + return Collections.EMPTY_SET; + + case "java.util.Collections$EmptyList": + return Collections.EMPTY_LIST; + + case "java.util.Collections$EmptyMap": + case "java.util.Collections.EmptyMap": + return Collections.EMPTY_MAP; + + } + + return unmarshallingContext.convertAnother(null, forName(clazz)); + } + + static Class forName(String clazz) { + + try { + + return Class.forName(clazz); + + } catch (ClassNotFoundException e) { + + boolean isNative = clazz.endsWith("$$Native"); + boolean isHeap = clazz.endsWith("$$Heap"); + + if (!isNative && !isHeap) + throw new ConversionException("class=" + clazz, e); + + final String nativeInterface = isNative ? clazz.substring(0, clazz.length() - + "$$Native".length()) : clazz.substring(0, clazz.length() - + "$$Heap".length()); + try { + DataValueClasses.newDirectInstance(Class.forName(clazz)); + return Class.forName(nativeInterface); + } catch (Exception e1) { + throw new ConversionException("class=" + clazz, e1); + } + + } + } + + + static Object toNativeValueObjects(HierarchicalStreamReader reader, final Class aClass) { + + final Object o = DataValueClasses.newDirectInstance(aClass); + + try { + + final BeanInfo info = Introspector.getBeanInfo(o.getClass()); // new BeanGenerator + + for (PropertyDescriptor p : info.getPropertyDescriptors()) { + + if (!p.getName().equals("value")) { + continue; + } + + final String value = reader.getValue(); + Class parameterType = p.getPropertyType(); + + if (StringValue.class.isAssignableFrom(o.getClass())) { + ((StringValue) o).setValue(value); + return o; + } + + if (parameterType.isPrimitive()) { + + // convert the primitive to their boxed type + + if (parameterType == int.class) + parameterType = Integer.class; + else if (parameterType == char.class) + parameterType = Character.class; + else { + final String name = parameterType.getSimpleName(); + + final String properName = "java.lang." + Character.toString + (name.charAt(0)).toUpperCase() + + name.substring(1); + parameterType = Class.forName(properName); + } + } + + final Method valueOf = parameterType.getMethod("valueOf", String.class); + final Object boxedValue = valueOf.invoke(null, value); + + p.getWriteMethod().invoke(o, boxedValue); + return o; + + } + + } catch (Exception e) { + // + } + + throw new ConversionException("setValue(..) method not found in class=" + + aClass.getCanonicalName()); + } + +} diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 1568ddf38..7aab4051e 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -19,12 +19,6 @@ package net.openhft.chronicle.map; import com.thoughtworks.xstream.XStream; -import com.thoughtworks.xstream.converters.ConversionException; -import com.thoughtworks.xstream.converters.Converter; -import com.thoughtworks.xstream.converters.MarshallingContext; -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; -import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.io.json.JettisonMappedXmlDriver; import net.openhft.chronicle.hash.ChronicleHashErrorListener; import net.openhft.chronicle.hash.hashing.Hasher; @@ -47,13 +41,8 @@ import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.PropertyDescriptor; import java.io.*; import java.lang.reflect.Array; -import java.lang.reflect.Method; -import java.nio.ByteOrder; import java.nio.channels.FileChannel; import java.util.*; import java.util.concurrent.TimeUnit; @@ -61,13 +50,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; -import static java.beans.Introspector.getBeanInfo; import static java.lang.Long.numberOfTrailingZeros; import static java.lang.Math.max; import static java.lang.Thread.currentThread; -import static java.nio.ByteBuffer.allocateDirect; import static net.openhft.chronicle.map.Asserts.assertNotNull; -import static net.openhft.chronicle.map.XStreamHelper.deserialize; import static net.openhft.lang.MemoryUnit.*; class VanillaChronicleMap, @@ -673,7 +659,7 @@ class VanillaChronicleMap, final XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.setMode(XStream.NO_REFERENCES); xstream.alias("cmap", VanillaChronicleMap.EntrySet.class); - lazyXStreamConverter().registerConverter(xstream); + xstream.registerConverter(new ChronicleMapConvector(this)); OutputStream outputStream = new FileOutputStream(toFile); if (toFile.getName().toLowerCase().endsWith(".gz")) @@ -688,9 +674,7 @@ class VanillaChronicleMap, final XStream xstream = new XStream(new JettisonMappedXmlDriver()); xstream.setMode(XStream.NO_REFERENCES); xstream.alias("cmap", VanillaChronicleMap.EntrySet.class); - - // ideally we will use java serialization if we can - lazyXStreamConverter().registerConverter(xstream); + xstream.registerConverter(new ChronicleMapConvector(this)); InputStream inputStream = new FileInputStream(fromFile); if (fromFile.getName().toLowerCase().endsWith(".gz")) @@ -757,248 +741,6 @@ class VanillaChronicleMap, } } - private transient XStreamConverter xStreamConverter; - - - /** - * @return a lazily created XStreamConverter - */ - private XStreamConverter lazyXStreamConverter() { - return xStreamConverter != null ? xStreamConverter : - (xStreamConverter = new XStreamConverter()); - } - - private class XStreamConverter { - private Bytes buffer; - - public XStreamConverter() { - this.buffer = new ByteBufferBytes(allocateDirect((int) entrySize) - .order(ByteOrder.nativeOrder())); - } - - private void registerConverter(XStream xstream) { - final Converter converter = new Converter() { - - - @Override - public boolean canConvert(Class aClass) { - - if (EntrySet.class.isAssignableFrom(aClass) - || WriteThroughEntry.class.isAssignableFrom(aClass)) - return true; - - String canonicalName = aClass.getCanonicalName(); - - return canonicalName.startsWith("net.openhft.lang.values") - || canonicalName.endsWith("$$Native") || canonicalName.endsWith - ("$$Heap"); - - } - - @Override - public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext - marshallingContext) { - if (WriteThroughEntry.class.isAssignableFrom(o.getClass())) { - - final SimpleEntry e = (SimpleEntry) o; - Object key = e.getKey(); - writer.startNode(key.getClass().getCanonicalName()); - - - marshallingContext.convertAnother(key); - writer.endNode(); - - Object value = e.getValue(); - writer.startNode(value.getClass().getCanonicalName()); - - - marshallingContext.convertAnother(value); - writer.endNode(); - - } else if (EntrySet.class - .isAssignableFrom(o.getClass())) { - for (Entry e : (EntrySet) o) { - writer.startNode("entry"); - marshallingContext.convertAnother(e); - writer.endNode(); - } - } - - final String canonicalName = o.getClass().getCanonicalName(); - - if (canonicalName.startsWith("net.openhft.lang.values") - && (canonicalName.endsWith("$$Native") || - canonicalName.endsWith("$$Heap"))) { - - Method[] methods = o.getClass().getMethods(); - - for (Method method : methods) { - if (method.getName().equals("getValue") && - method.getParameterTypes().length == 0) { - try { - marshallingContext.convertAnother(method.invoke(o)); - return; - } catch (Exception e) { - throw new RuntimeException("class=" + canonicalName, e); - } - } - } - - } else { - if (canonicalName.endsWith("$$Native") || - canonicalName.endsWith("$$Heap")) { - - try { - BeanInfo info = getBeanInfo(o.getClass()); // new BeanGenerator - - for (PropertyDescriptor p : info.getPropertyDescriptors()) { - - if (p.getName().equals("Class")) { - continue; - } - - if (p.getReadMethod() != null && p.getWriteMethod() != null) { - - try { - - final Method readMethod = p.getReadMethod(); - - Object value = readMethod.invoke(o); - if (value != null) { - writer.startNode(p.getDisplayName()); - marshallingContext.convertAnother(value); - writer.endNode(); - } - } catch (Exception e) { - LOG.error("class=" + p.getName(), e); - } - } - - } - } catch (IntrospectionException e) { - throw new RuntimeException("class=" + canonicalName, e); - } - } - } - } - - @Override - public Object unmarshal(HierarchicalStreamReader reader, - UnmarshallingContext unmarshallingContext) { - - String canonicalName = unmarshallingContext.getRequiredType().getCanonicalName(); - - boolean isNative = canonicalName.endsWith("$$Native"); - boolean isHeap = canonicalName.endsWith("$$Heap"); - - if (isNative || isHeap) { - - - if (unmarshallingContext.getRequiredType().getCanonicalName().startsWith - ("net.openhft.lang.values")) - return XStreamHelper.toBuiltIn$$(reader, unmarshallingContext.getRequiredType() - ); - - - final String nodeName = isNative ? - canonicalName.substring(0, canonicalName.length() - - "$$Native".length()) : - - canonicalName.substring(0, canonicalName.length() - - "$$Heap".length()); - - Object o; - - - try { - Class interfaceClass = Class.forName(nodeName); - o = (isNative) ? - DataValueClasses.newDirectInstance(interfaceClass) : - DataValueClasses.newInstance(interfaceClass); - - final BeanInfo beanInfo = getBeanInfo(o.getClass()); - - while (reader.hasMoreChildren()) { - reader.moveDown(); - String name = reader.getNodeName(); - - for (PropertyDescriptor methodDescriptor : beanInfo.getPropertyDescriptors()) { - if (methodDescriptor.getName().equals(name)) { - Class[] parameterTypes = methodDescriptor.getWriteMethod().getParameterTypes(); - - if (parameterTypes.length == 1) { - Object o1 = unmarshallingContext.convertAnother(null, parameterTypes[0]); - try { - methodDescriptor.getWriteMethod().invoke(o, o1); - } catch (Exception e) { - LOG.error("", e); - } - break; - } - - } - } - - reader.moveUp(); - } - return o; - } catch (Exception e) { - throw new ConversionException("class=" + canonicalName, e); - } - - } - - - final String nodeName = reader.getNodeName(); - - switch (nodeName) { - case "cmap": - - while (reader.hasMoreChildren()) { - reader.moveDown(); - final String nodeName0 = reader.getNodeName(); - - if (!nodeName0.equals("entry")) - throw new ConversionException("unable to convert node " + - "named=" + nodeName0); - final K k; - final V v; - - - reader.moveDown(); - { - k = (K) deserialize(unmarshallingContext, reader); - } - reader.moveUp(); - reader.moveDown(); - { - v = (V) deserialize(unmarshallingContext, reader); - } - reader.moveUp(); - - - if (k != null) - VanillaChronicleMap.this.put(k, v); - - reader.moveUp(); - } - - return null; - - - } - - return null; - } - - - }; - - xstream.registerConverter(converter); - } - - - } @NotNull @Override diff --git a/src/main/java/net/openhft/chronicle/map/XStreamHelper.java b/src/main/java/net/openhft/chronicle/map/XStreamHelper.java deleted file mode 100644 index 2323931bf..000000000 --- a/src/main/java/net/openhft/chronicle/map/XStreamHelper.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * 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 com.thoughtworks.xstream.converters.ConversionException; -import com.thoughtworks.xstream.converters.UnmarshallingContext; -import com.thoughtworks.xstream.io.HierarchicalStreamReader; -import net.openhft.lang.model.DataValueClasses; -import net.openhft.lang.values.StringValue; - -import java.beans.BeanInfo; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.Method; -import java.util.Collections; - -/** - * Created by Rob on 18/12/14. - */ -class XStreamHelper { - - - static Object deserialize(UnmarshallingContext unmarshallingContext, HierarchicalStreamReader - reader) { - String type = reader.getNodeName(); - - switch (type) { - - case "java.util.Collections$EmptySet": - return Collections.EMPTY_SET; - - case "java.util.Collections$EmptyList": - return Collections.EMPTY_LIST; - - case "java.util.Collections$EmptyMap": - case "java.util.Collections.EmptyMap": - return Collections.EMPTY_MAP; - - - } - - - Class o = XStreamHelper.forName(type); - return unmarshallingContext.convertAnother(null, o); - } - - static Class forName(String type) { - - - boolean isNative = type.endsWith("$$Native"); - if (!isNative && !type.endsWith("$$Heap")) { - try { - return Class.forName(type); - } catch (ClassNotFoundException e1) { - throw new ConversionException(e1); - } - } - - - try { - return Class.forName(type); - } catch (ClassNotFoundException e) { - - String clazz = isNative ? type.substring(0, type.length() - - "$$Native".length()) : type.substring(0, type.length() - - "$$Heap".length()); - try { - DataValueClasses.newDirectInstance(Class.forName(clazz)); - } catch (ClassNotFoundException e1) { - throw new ConversionException(e1); - } - try { - return Class.forName(type); - } catch (ClassNotFoundException e1) { - throw new ConversionException(e1); - } - } - - - } - - - static Object toBuiltIn$$(HierarchicalStreamReader reader, final Class aClass) { - - - final Object o = DataValueClasses.newDirectInstance(aClass); - - try { - - final BeanInfo info = Introspector.getBeanInfo(o.getClass()); // new BeanGenerator - - for (PropertyDescriptor p : info.getPropertyDescriptors()) { - - if (p.getName().equals("value")) { - - final String value = reader.getValue(); - Class parameterType = p.getPropertyType(); - - - if (StringValue.class.isAssignableFrom(o.getClass())) { - ((StringValue) o).setValue(value); - return o; - } - - if (parameterType.isPrimitive()) { - - // convert the primitive to their boxed type - - if (parameterType == int.class) - parameterType = Integer.class; - else if (parameterType == char.class) - parameterType = Character.class; - else { - final String name = parameterType.getSimpleName(); - - final String properName = "java.lang." + Character.toString - (name.charAt(0)).toUpperCase() - + name.substring(1); - parameterType = Class.forName(properName); - } - } - - - final Method valueOf = parameterType.getMethod("valueOf", String.class); - final Object boxedValue = valueOf.invoke(null, value); - - p.getWriteMethod().invoke(o, boxedValue); - return o; - } - } - - } catch (Exception e) { - // - } - - throw new ConversionException("setValue(..) method not found in class=" + aClass - .getCanonicalName()); - } - - -} diff --git a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java index 24227abfd..e9e63f241 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java +++ b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java @@ -2383,7 +2383,6 @@ public class CHMUseCasesTest { } } - // @Ignore("HCOLL-268 JSON serialisation issue") @Test public void testMapStringIntegerValueWithoutListMarshallers() throws IOException { ChronicleMapBuilder> builder = ChronicleMapBuilder