From b2f6ebbba054ea71ace4409fa32bd91da8efc951 Mon Sep 17 00:00:00 2001 From: Rob Austin Date: Sun, 21 Dec 2014 17:28:35 +0000 Subject: [PATCH] HCOLL-259 fixed JSON<->CHM to support $$Native like BondVOInterface --- .../chronicle/map/VanillaChronicleMap.java | 124 +++++++++++++++--- .../openhft/chronicle/map/XStreamHelper.java | 63 +++++++-- .../map/ChronicleMapImportExportTest.java | 24 ++-- 3 files changed, 172 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java index 2740a19a5..78069b0cb 100755 --- a/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/VanillaChronicleMap.java @@ -47,6 +47,10 @@ import org.jetbrains.annotations.Nullable; 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.io.*; import java.lang.reflect.Array; import java.lang.reflect.Method; @@ -58,6 +62,7 @@ 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.Class.forName; import static java.lang.Long.numberOfTrailingZeros; import static java.lang.Math.max; @@ -771,19 +776,17 @@ class VanillaChronicleMap, String canonicalName = aClass.getCanonicalName(); - if (canonicalName.startsWith("net.openhft.lang.values")) { - return true; - } + return canonicalName.startsWith("net.openhft.lang.values") + || canonicalName.endsWith("$$Native"); - return false; } @Override public void marshal(Object o, HierarchicalStreamWriter writer, MarshallingContext marshallingContext) { if (WriteThroughEntry.class.isAssignableFrom(o.getClass())) { - final SimpleEntry e = (SimpleEntry) o; + final SimpleEntry e = (SimpleEntry) o; writer.startNode("chronicle-key"); Object key = e.getKey(); @@ -797,6 +800,7 @@ class VanillaChronicleMap, writeType(writer, value, vClass); marshallingContext.convertAnother(value); writer.endNode(); + } else if (EntrySet.class .isAssignableFrom(o.getClass())) { for (Entry e : (EntrySet) o) { @@ -805,29 +809,114 @@ class VanillaChronicleMap, writer.endNode(); } } + if (o.getClass().getCanonicalName().startsWith("net.openhft.lang.values") && (o.getClass().getCanonicalName().endsWith("$$Native") || o.getClass().getCanonicalName().endsWith("$$Heap"))) { + Method[] methods = o.getClass().getMethods(); + for (Method method : methods) { - if (method.getName().equals("getValue") && method.getParameterTypes() - .length == 0) { + if (method.getName().equals("getValue") && + method.getParameterTypes().length == 0) { try { - Object result = method.invoke(o); - writer.setValue(result.toString()); + final Object result = method.invoke(o); + // writer.startNode(o.getClass().getCanonicalName()); + marshallingContext.convertAnother(result); + // writer.endNode(); return; } catch (Exception e) { throw new RuntimeException("class=" + o.getClass().getCanonicalName(), e); } } } + + } else { + if (o.getClass().getCanonicalName().endsWith("$$Native")) { + + 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=" + o.getClass().getCanonicalName(), e); + } + } } } @Override public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext unmarshallingContext) { + + String canonicalName = unmarshallingContext.getRequiredType().getCanonicalName(); + if (canonicalName.endsWith("$$Native")) { + + final String nodeName = canonicalName.substring(0, canonicalName.length() - + "$$Native".length()); + Object o; + + try { + o = DataValueClasses.newDirectInstance(Class.forName(nodeName)); + + 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(); + String type = reader.getAttribute("type"); switch (nodeName) { case "chronicle-entries": @@ -859,11 +948,14 @@ class VanillaChronicleMap, reader.moveUp(); } - break; + + return null; case "chronicle-key": if (kClass.getCanonicalName().startsWith("net.openhft.lang.values")) { - return to$$Native(reader, vClass, true, VanillaChronicleMap.this); + return to$$Native(reader, vClass, true, VanillaChronicleMap.this, unmarshallingContext); + } else if (type.endsWith("$$Native")) { + return XStreamHelper.to$$Native2(reader, vClass, false, VanillaChronicleMap.this, unmarshallingContext, type); } else { long keySize = keySizeMarshaller.readSize(buffer); ThreadLocalCopies copies = keyReaderProvider.getCopies(null); @@ -873,7 +965,9 @@ class VanillaChronicleMap, case "chronicle-value": if (vClass.getCanonicalName().startsWith("net.openhft.lang.values")) { - return to$$Native(reader, vClass, false, VanillaChronicleMap.this); + return to$$Native(reader, vClass, false, VanillaChronicleMap.this, unmarshallingContext); + } else if (type.endsWith("$$Native")) { + return XStreamHelper.to$$Native2(reader, vClass, false, VanillaChronicleMap.this, unmarshallingContext, type); } else { long valueSize = valueSizeMarshaller.readSize(buffer); ThreadLocalCopies copies = valueReaderProvider.getCopies(null); @@ -884,7 +978,6 @@ class VanillaChronicleMap, } - return null; } @@ -916,13 +1009,14 @@ class VanillaChronicleMap, return; String simpleName = o.getClass().getCanonicalName(); - if (simpleName.endsWith("$$Native")) { + if (simpleName.startsWith("net.openhft.lang.values")) { String nodeName = simpleName.substring(0, simpleName.length() - "$$Native".length()); if (!nodeName.equals(clazz.getCanonicalName())) writer.addAttribute("type", nodeName); - } else if (!clazz.equals(o.getClass())) + } else if (simpleName.endsWith("$$Native") || !clazz.equals(o.getClass())) { writer.addAttribute("type", o.getClass().getCanonicalName()); + } } } diff --git a/src/main/java/net/openhft/chronicle/map/XStreamHelper.java b/src/main/java/net/openhft/chronicle/map/XStreamHelper.java index db6b3fe8f..0372353be 100644 --- a/src/main/java/net/openhft/chronicle/map/XStreamHelper.java +++ b/src/main/java/net/openhft/chronicle/map/XStreamHelper.java @@ -17,27 +17,65 @@ 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 java.beans.BeanInfo; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; import java.lang.reflect.Method; /** * Created by Rob on 18/12/14. */ class XStreamHelper { + static Object to$$Native2(HierarchicalStreamReader reader, final Class aClass2, final boolean isKey, + ChronicleMap map, UnmarshallingContext unmarshallingContext, String type) { - static Object to$$Native(HierarchicalStreamReader reader, final Class aClass, final boolean isKey, ChronicleMap map) { + + if (type.endsWith("$$Native")) { + + Class aClass = null; + try { + aClass = Class.forName(type); + } catch (ClassNotFoundException e) { + String clazz = type.substring(0, type.length() - + "$$Native".length()); + try { + DataValueClasses.newDirectInstance(Class.forName(clazz)); + } catch (ClassNotFoundException e1) { + throw new ConversionException(e1); + } + try { + aClass = Class.forName(type); + } catch (ClassNotFoundException e1) { + throw new ConversionException(e1); + } + } + + + return unmarshallingContext.convertAnother(null, aClass); + } + + throw new ConversionException("setValue(..) method not found in class=" + type); + } + + static Object to$$Native(HierarchicalStreamReader reader, final Class aClass, final boolean isKey, ChronicleMap map, UnmarshallingContext unmarshallingContext) { final Object o = isKey ? map.newKeyInstance() : map.newValueInstance(); + try { - for (Method $$nativeMethod : o.getClass().getMethods()) { - if ($$nativeMethod.getName().equals("setValue") && $$nativeMethod.getParameterTypes() - .length == 1) { - Class parameterType = $$nativeMethod.getParameterTypes()[0]; - String value = reader.getValue(); + 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 (parameterType.isPrimitive()) { @@ -59,17 +97,18 @@ class XStreamHelper { final Method valueOf = parameterType.getMethod("valueOf", String.class); - final Object invoke = valueOf.invoke(null, value); - $$nativeMethod.invoke(o, invoke); - return o; + final Object boxedValue = valueOf.invoke(null, value); + p.getWriteMethod().invoke(o, boxedValue); + return o; } } + } catch (Exception e) { - throw new ConversionException("class=" + aClass.getCanonicalName(), e); + // } + throw new ConversionException("setValue(..) method not found in class=" + aClass - .getCanonicalName - ()); + .getCanonicalName()); } } diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java index e6cd266ff..ab6a1721b 100644 --- a/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapImportExportTest.java @@ -2,6 +2,7 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.replication.TcpTransportAndNetworkConfig; import net.openhft.chronicle.map.fromdocs.BondVOInterface; +import net.openhft.lang.model.DataValueClasses; import net.openhft.lang.values.LongValue; import org.junit.Assert; import org.junit.Ignore; @@ -186,7 +187,7 @@ public class ChronicleMapImportExportTest { public void testWithLongValue() throws IOException, InterruptedException { File file = new File(TMP + "/chronicle-map-" + System.nanoTime() + ".json"); - file.deleteOnExit(); + //file.deleteOnExit(); System.out.println(file.getAbsolutePath()); try (ChronicleMap expected = ChronicleMapBuilder.of(CharSequence.class, LongValue @@ -213,11 +214,11 @@ public class ChronicleMapImportExportTest { Assert.assertEquals(expected, actual); } } finally { - file.delete(); + // file.delete(); } } - @Ignore("HCOLL-259 JSON<->CHM to support $$Native like BondVOInterface") + @Test public void testBondVOInterface() throws IOException, InterruptedException { @@ -225,27 +226,26 @@ public class ChronicleMapImportExportTest { file.deleteOnExit(); System.out.println(file.getAbsolutePath()); - try (ChronicleMap expected = ChronicleMapBuilder.of(CharSequence.class, BondVOInterface - .class) - .create()) { - BondVOInterface value = expected.newValueInstance(); + try (ChronicleMap expected = + ChronicleMapBuilder.of(CharSequence.class, BondVOInterface.class).create()) { + + final BondVOInterface value = expected.newValueInstance(); // this will add the entry try (WriteContext context = expected.acquireUsingLocked("one", value)) { value.setCoupon(8.98); assert value == context.value(); - } expected.getAll(file); - try (ChronicleMap actual = ChronicleMapBuilder.of(CharSequence.class, LongValue - .class) - .create()) { + try (ChronicleMap actual = + ChronicleMapBuilder.of(CharSequence.class, + DataValueClasses.directClassFor(BondVOInterface.class)).create()) { actual.putAll(file); - Assert.assertEquals(expected, actual); + Assert.assertEquals(expected.get("one").getCoupon(), actual.get("one").getCoupon(), 0); } } finally { file.delete();