瀏覽代碼

ObjectWalker: non-terminal #text ObjectWalker: fix context bug

Signed-off-by: Ranides Atterwim <ranides@gmail.com>
Mariusz Czarnowski 5 年之前
父節點
當前提交
1371f25f2d

+ 19 - 2
assira/src/main/java/net/ranides/assira/reflection/ResolveContext.java

@@ -1,16 +1,19 @@
 package net.ranides.assira.reflection;
 
+import java.util.Collections;
 import java.util.Map;
 import java.util.Optional;
 
 public interface ResolveContext {
 
+    ResolveContext EMPTY = of(Collections.emptyMap());
+
     ResolveContext getResolveParent();
 
-    Map<String, Object> getResolveMap();
+    Map<String, ?> getResolveMap();
 
     default Optional<Object> getResolveProperty(String name) {
-        Map<String, Object> map = getResolveMap();
+        Map<String, ?> map = getResolveMap();
         if(map.containsKey(name)) {
             return Optional.of(map.get(name));
         }
@@ -25,4 +28,18 @@ public interface ResolveContext {
         return getResolveProperty(name).isPresent();
     }
 
+    static ResolveContext of(Map<String, ?> map) {
+        return new ResolveContext() {
+            @Override
+            public ResolveContext getResolveParent() {
+                return null;
+            }
+
+            @Override
+            public Map<String, ?> getResolveMap() {
+                return map;
+            }
+        };
+    }
+
 }

+ 13 - 10
assira/src/main/java/net/ranides/assira/reflection/walker/ObjectVisitor.java

@@ -1,5 +1,6 @@
 package net.ranides.assira.reflection.walker;
 
+import net.ranides.assira.reflection.ResolveContext;
 import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;
 
 import java.io.PrintWriter;
@@ -7,7 +8,7 @@ import java.util.function.Predicate;
 
 public interface ObjectVisitor {
 
-    boolean visitRoot(ObjectContext<?> ctx);
+    ResolveContext scope();
 
     boolean visitObject(ObjectContext<?> ctx);
 
@@ -31,12 +32,12 @@ public interface ObjectVisitor {
     abstract class AbstractVisitor implements ObjectVisitor {
 
         @Override
-        public abstract boolean visitObject(ObjectContext<?> ctx);
+        public ResolveContext scope() {
+            return ResolveContext.EMPTY;
+        }
 
         @Override
-        public boolean visitRoot(ObjectContext<?> ctx) {
-            return true;
-        }
+        public abstract boolean visitObject(ObjectContext<?> ctx);
 
         @Override
         public boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {
@@ -62,9 +63,11 @@ public interface ObjectVisitor {
 
     abstract class PrintVisitor implements ObjectVisitor {
         private final PrintWriter target;
+        private final ResolveContext scope;
 
-        protected PrintVisitor(PrintWriter target) {
+        protected PrintVisitor(PrintWriter target, ResolveContext scope) {
             this.target = target;
+            this.scope = scope;
         }
 
         public PrintWriter writer() {
@@ -72,12 +75,12 @@ public interface ObjectVisitor {
         }
 
         @Override
-        public abstract boolean visitObject(ObjectContext<?> context);
+        public ResolveContext scope() {
+            return scope;
+        }
 
         @Override
-        public boolean visitRoot(ObjectContext<?> ctx) {
-            return true;
-        }
+        public abstract boolean visitObject(ObjectContext<?> context);
 
         @Override
         public boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {

+ 3 - 5
assira/src/main/java/net/ranides/assira/reflection/walker/ObjectWalker.java

@@ -48,10 +48,8 @@ public class ObjectWalker {
 
     private void start(String rootname) {
         CObject<Object> bootstrap = new CObject<>(null, null, IClass.OBJECT, null);
-        ObjectContext<Object> root = bootstrap.context(rootname, IClass.typefor(this.root), this.root);
-        if(visitor.visitRoot(root)) {
-            root.accept(visitor);
-        }
+        ObjectContext<Object> context = bootstrap.context(rootname, IClass.typefor(root), root);
+        context.accept(visitor);
     }
 
     private void mark(ObjectContext<?> ctx) {
@@ -189,7 +187,7 @@ public class ObjectWalker {
 
         @Override
         public ResolveContext getResolveParent() {
-            return parent;
+            return visitor.scope();
         }
 
         @Override

+ 186 - 129
assira/src/main/java/net/ranides/assira/reflection/walker/WalkerRules.java

@@ -2,7 +2,8 @@ package net.ranides.assira.reflection.walker;
 
 import lombok.Data;
 
-import net.ranides.assira.reflection.*;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.ResolveContext;
 import net.ranides.assira.reflection.walker.ObjectVisitor.PrintVisitor;
 import net.ranides.assira.reflection.walker.WalkerContexts.ArrayContext;
 import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;
@@ -22,12 +23,13 @@ import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
 public class WalkerRules {
 
-    public static final WalkerRules DEFAULT = new WalkerRules()
+    public static final WalkerRules DEFAULT = new Builder()
         .tag("PATH", "{id,text,4}.{namePath,text,-50}")
         .rule()
             .match(ObjectContext::isVisited)
@@ -43,11 +45,11 @@ public class WalkerRules {
             .term("{PATH} {actual.shortname} = '{value}'")
         .rule()
             .match(byte[].class)
-            .text(ctx -> FormatHex.format(ctx.value()))
+            .format(ctx -> FormatHex.format(ctx.value()))
             .term("{PATH} {actual.shortname} = '{value}'")
         .rule()
             .match(char[].class)
-            .text(ctx -> String.valueOf(ctx.value()))
+            .format(ctx -> String.valueOf(ctx.value()))
             .term("{PATH} {actual.shortname} = '{value}'")
         .rule()
             .match(ArrayContext.class::isInstance)
@@ -68,181 +70,231 @@ public class WalkerRules {
         .rule()
             .match(Object.class)
             .hint("{PATH} {actual.shortname} = {value}")
-        ;
+        .prepare();
 
-    private final Map<String, ResolveFormat> tags = new HashMap<>();
+    private final Map<String, ResolveFormat> tags;
+    private final List<RuleEntry> rules;
 
-    private final List<RuleEntry> rules = new ArrayList<>();
+    private WalkerRules(Map<String, ResolveFormat> tags, List<RuleEntry> rules) {
+        this.tags = new HashMap<>(tags);
+        this.rules = new ArrayList<>(rules);
+    }
+
+    public static Builder builder() {
+        return new Builder();
+    }
 
     public PrintVisitor visitor(OutputStream target) {
         return visitor(new OutputStreamWriter(target, Charsets.UTF8));
     }
 
     public PrintVisitor visitor(Writer target) {
-        return new PrintVisitor(new PrintWriter(target)){
-            @Override
-            public boolean visitRoot(ObjectContext<?> context) {
-                context.custom(tags);
-                return true;
+        return new RuleVisitor(target);
+    }
+
+    public static class Builder {
+
+        private final Map<String, ResolveFormat> tags = new HashMap<>();
+        private final List<RuleEntry> rules = new ArrayList<>();
+
+        public WalkerRules prepare() {
+            return new WalkerRules(tags, rules);
+        }
+
+        public Builder tag(String name, String value) {
+            this.tags.put(name, ResolveFormat.compile(value));
+            return this;
+        }
+
+        public RootRule rule() {
+            return new RootRule();
+        }
+
+        public Builder rules(WalkerRules parent) {
+            this.tags.putAll(parent.tags);
+            this.rules.addAll(parent.rules);
+            return this;
+        }
+
+        public abstract class AbstractRule<F> {
+
+            protected final List<RulePredicate<?>> predicates;
+
+            protected Consumer<ObjectContext<F>> formatter = x -> { };
+
+            protected AbstractRule(List<RulePredicate<?>> predicates) {
+                this.predicates = predicates;
             }
 
-            @Override
-            public boolean visitObject(ObjectContext<?> context) {
-                for (RuleEntry rule : rules) {
-                    if(rule.test(context)) {
-                        return rule.consume(this, context);
-                    }
-                }
-                return true;
+            public Builder text(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return text((target, ctx) -> fmt.format(ctx));
             }
-        };
-    }
 
-    public WalkerRules inherit(WalkerRules parent) {
-        this.tags.putAll(parent.tags);
-        this.rules.addAll(parent.rules);
-        return this;
-    }
+            public Builder text(Function<ObjectContext<F>, String> handler) {
+                return text((target, ctx) -> handler.apply(ctx));
+            }
 
-    public WalkerRules tag(String name, String value) {
-        this.tags.put(name, ResolveFormat.compile(value));
-        return this;
-    }
+            public Builder text(BiFunction<PrintVisitor, ObjectContext<F>, String> handler) {
+                return rule(predicates, RuleType.FORMAT, asHandler((v,c) -> c.custom("value", handler.apply(v, c))));
+            }
 
-    public RuleBuilderRoot rule() {
-        return new RuleBuilderRoot();
-    }
+            public Builder hint(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return hint((target, ctx) -> target.writer().println(fmt.format(ctx)));
+            }
 
-    public abstract class RuleBuilder<F> {
+            public Builder hint(Consumer<ObjectContext<F>> handler) {
+                return hint((target, ctx) -> handler.accept(ctx));
+            }
 
-        protected final List<RulePredicate<?>> predicates;
+            public Builder hint(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
+                return rule(predicates, RuleType.HINT, asHandler(handler));
+            }
 
-        protected Consumer<ObjectContext<F>> formatter = x -> { };
+            public Builder term(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return term((target, ctx) -> target.writer().println(fmt.format(ctx)));
+            }
 
-        protected RuleBuilder(List<RulePredicate<?>> predicates) {
-            this.predicates = predicates;
-        }
+            public Builder term(Consumer<ObjectContext<F>> handler) {
+                return term((target, ctx) -> handler.accept(ctx));
+            }
 
-        public WalkerRules hint(String text) {
-            ResolveFormat fmt = ResolveFormat.compile(text);
-            return hint((target, ctx) -> target.writer().println(fmt.format(ctx)));
-        }
+            public Builder term(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
+                return rule(predicates, RuleType.TERM, asHandler(handler));
+            }
 
-        public WalkerRules hint(Consumer<ObjectContext<F>> handler) {
-            return hint((target, ctx) -> handler.accept(ctx));
-        }
+            private RuleHandler<F> asHandler(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
+                return (target, ctx) -> {
+                    formatter.accept(ctx);
+                    handler.accept(target, ctx);
+                };
+            }
 
-        public WalkerRules hint(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-            return rule(predicates, asHandler(true, handler));
-        }
+            private <U> Builder rule(List<RulePredicate<?>> predicates, RuleType type, RuleHandler<U> handler) {
+                predicates.forEach(predicate -> rules.add(new RuleEntry(predicate, handler, type)));
+                return Builder.this;
+            }
 
-        public WalkerRules term(String text) {
-            ResolveFormat fmt = ResolveFormat.compile(text);
-            return term((target, ctx) -> target.writer().println(fmt.format(ctx)));
         }
 
-        public WalkerRules term(Consumer<ObjectContext<F>> handler) {
-            return term((target, ctx) -> handler.accept(ctx));
-        }
+        public class RootRule extends AbstractRule<Object> {
 
-        public WalkerRules term(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-            return rule(predicates, asHandler(false, handler));
-        }
+            public RootRule() {
+                super(new ArrayList<>());
+            }
 
-        private RuleHandler<F> asHandler(boolean follow, BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-            return (target, ctx) -> {
-                formatter.accept(ctx);
-                handler.accept(target, ctx);
-                return follow;
-            };
-        }
+            public RootRule format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
 
-        private <U> WalkerRules rule(List<RulePredicate<?>> predicates, RuleHandler<U> handler) {
-            predicates.forEach(predicate -> rules.add(new RuleEntry(predicate, handler)));
-            return WalkerRules.this;
-        }
+            public RootRule format(Function<ObjectContext<Object>, String> formatter) {
+                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
+                return this;
+            }
 
-    }
+            public <T> TypeRule<T> match(Class<T> type) {
+                return match(IClass.typeinfo(type));
+            }
 
-    public class RuleBuilderRoot extends RuleBuilder<Object> {
+            public <T> TypeRule<T> match(IClass<T> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new TypeRule<>(this);
+            }
 
-        public RuleBuilderRoot() {
-            super(new ArrayList<>());
-        }
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return new MoreRule(this);
+            }
 
-        public RuleBuilderRoot text(Function<ObjectContext<Object>, String> formatter) {
-            this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
-            return this;
         }
 
-        public <T> RuleBuilderType<T> match(Class<T> type) {
-            return match(IClass.typeinfo(type));
-        }
+        public class TypeRule<F> extends AbstractRule<F> {
 
-        public <T> RuleBuilderType<T> match(IClass<T> type) {
-            predicates.add(ctx -> ctx.isSubclass(type));
-            return new RuleBuilderType<>(this);
-        }
+            public TypeRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
 
-        public RuleBuilderMore match(RulePredicate<?> predicate){
-            predicates.add(predicate);
-            return new RuleBuilderMore(this);
-        }
+            public TypeRule<F> format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
 
-    }
+            public TypeRule<F> format(Function<ObjectContext<F>, String> formatter) {
+                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
+                return this;
+            }
 
-    public class RuleBuilderType<F> extends RuleBuilder<F> {
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
 
-        public RuleBuilderType(RuleBuilder<?> source) {
-            super(source.predicates);
-        }
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new MoreRule(this);
+            }
 
-        public RuleBuilderType<F> text(Function<ObjectContext<F>, String> formatter) {
-            this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
-            return this;
-        }
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return new MoreRule(this);
+            }
 
-        public RuleBuilderMore match(Class<?> type) {
-            return match(IClass.typeinfo(type));
         }
 
-        public RuleBuilderMore match(IClass<?> type) {
-            predicates.add(ctx -> ctx.isSubclass(type));
-            return new RuleBuilderMore(this);
-        }
+        public class MoreRule extends AbstractRule<Object> {
 
-        public RuleBuilderMore match(RulePredicate<?> predicate) {
-            predicates.add(predicate);
-            return new RuleBuilderMore(this);
-        }
+            public MoreRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
 
-    }
+            public MoreRule format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
 
-    public class RuleBuilderMore extends RuleBuilder<Object> {
+            public MoreRule format(Function<ObjectContext<Object>, String> formatter) {
+                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
+                return this;
+            }
 
-        public RuleBuilderMore(RuleBuilder<?> source) {
-            super(source.predicates);
-        }
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
 
-        public RuleBuilderMore text(Function<ObjectContext<Object>, String> formatter) {
-            this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
-            return this;
-        }
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return this;
+            }
 
-        public RuleBuilderMore match(Class<?> type) {
-            return match(IClass.typeinfo(type));
-        }
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return this;
+            }
 
-        public RuleBuilderMore match(IClass<?> type) {
-            predicates.add(ctx -> ctx.isSubclass(type));
-            return this;
         }
 
-        public RuleBuilderMore match(RulePredicate<?> predicate) {
-            predicates.add(predicate);
-            return this;
+    }
+
+    private class RuleVisitor extends PrintVisitor {
+
+        public RuleVisitor(Writer target) {
+            super(new PrintWriter(target), ResolveContext.of(tags));
         }
 
+        @Override
+        public boolean visitObject(ObjectContext<?> context) {
+            for (RuleEntry rule : rules) {
+                if(rule.test(context)) {
+                    rule.consume(this, context);
+                    switch (rule.type) {
+                        case HINT: return true;
+                        case TERM: return false;
+                        case FORMAT:
+                            // continue processing
+                    }
+                }
+            }
+            return true;
+        }
     }
 
     @SuppressWarnings({"rawtypes", "unchecked"})
@@ -250,21 +302,26 @@ public class WalkerRules {
     private static class RuleEntry {
         private final RulePredicate predicate;
         private final RuleHandler consumer;
+        private final RuleType type;
 
         public boolean test(ObjectContext<?> context) {
             return predicate.test(context);
         }
 
-        public boolean consume(PrintVisitor visitor, ObjectContext<?> context) {
-            return consumer.test(visitor, context);
+        public void consume(PrintVisitor visitor, ObjectContext<?> context) {
+            consumer.apply(visitor, context);
         }
     }
 
+    private enum RuleType {
+        TERM, HINT, FORMAT
+    }
+
     public interface RulePredicate<U> {
         boolean test(ObjectContext<U> context);
     }
 
     public interface RuleHandler<U> {
-        boolean test(PrintVisitor printVisitor, ObjectContext<U> context);
+        void apply(PrintVisitor printVisitor, ObjectContext<U> context);
     }
 }

+ 1 - 0
assira/src/test/java/net/ranides/assira/TestFiles.java

@@ -87,6 +87,7 @@ public final class TestFiles {
     public static final File WALK_FORMAT_RULES = new File(WALK_DIR,"formatRules.txt");
     public static final File WALK_FORMAT_RECURSIVE = new File(WALK_DIR,"formatRecursive.txt");
     public static final File WALK_ARRAYS = new File(WALK_DIR,"arrays.txt");
+    public static final File WALK_MATCH_TEXT = new File(WALK_DIR,"matchText.txt");
 
 
 }

+ 34 - 10
assira/src/test/java/net/ranides/assira/reflection/walker/ObjectWalkerTest.java

@@ -11,11 +11,8 @@ import net.ranides.assira.TestFiles;
 import net.ranides.assira.reflection.*;
 import net.ranides.assira.reflection.walker.ObjectVisitor.PrintVisitor;
 import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;
-import net.ranides.assira.text.Charsets;
 import net.ranides.assira.text.FormatHex;
-import net.ranides.assira.text.IOStrings;
 
-import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.Arrays;
@@ -23,8 +20,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 
-import static org.junit.Assert.assertEquals;
-
 public class ObjectWalkerTest {
 
     Foo foo1 = Foo.builder()
@@ -150,10 +145,11 @@ public class ObjectWalkerTest {
     public void format() {
         StringWriter writer = new StringWriter();
 
-        WalkerRules rules = new WalkerRules()
+        WalkerRules rules = new WalkerRules.Builder()
             .rule().match(ObjectContext::isNull).term("{namePath,text} = <null>")
             .rule().match(String.class).term("{namePath,text} = '{value}'")
-            .rule().match(Object.class).hint("{namePath,text} = {value}");
+            .rule().match(Object.class).hint("{namePath,text} = {value}")
+            .prepare();
 
         ObjectWalker.walk(foo1, "foo1", rules.visitor(writer));
         ObjectWalker.walk(foo1, "foo2", WalkerRules.DEFAULT.visitor(writer));
@@ -165,7 +161,7 @@ public class ObjectWalkerTest {
     public void formatRules() {
         StringWriter writer = new StringWriter();
 
-        PrintVisitor visitor = new WalkerRules()
+        PrintVisitor visitor = new WalkerRules.Builder()
             .rule()
                 .match(ObjectContext::isNull)
                 .term("{namePath,text} = <null>")
@@ -187,6 +183,7 @@ public class ObjectWalkerTest {
             .rule()
                 .match(Object.class)
                 .hint("{namePath,text} = {value}")
+            .prepare()
             .visitor(writer);
 
         ObjectWalker.walk(foo1, "foo1", visitor);
@@ -199,7 +196,7 @@ public class ObjectWalkerTest {
         List<Foo> list = Arrays.asList(foo2, foo3, foo2, foo4);
 
         StringWriter writer = new StringWriter();
-        PrintVisitor visitor = new WalkerRules()
+        PrintVisitor visitor = new WalkerRules.Builder()
             .rule()
                 .match(List.class)
                 .match(Collection.class)
@@ -217,6 +214,7 @@ public class ObjectWalkerTest {
             .rule()
                 .match(Object.class)
                 .hint("#{kind.shortname,text,16}:{depth} {id,text,4}.{namePath,text,-50} {actual.shortname} = {value}")
+            .prepare()
             .visitor(writer);
 
         ObjectWalker.walk(list, "list", visitor);
@@ -225,15 +223,41 @@ public class ObjectWalkerTest {
     }
 
     @Test
-    public void arrays() throws IOException {
+    public void arrays() {
         StringWriter writer = new StringWriter();
 
         List<Object> list = Arrays.asList(record1, foo1);
+
         ObjectWalker.walk(list, "record", WalkerRules.DEFAULT.visitor(writer));
 
         NewAssert.assertLineEquals(TestFiles.WALK_ARRAYS, writer.toString());
     }
 
+    @Test
+    public void matchText() {
+        StringWriter writer = new StringWriter();
+
+        PrintVisitor visitor = new WalkerRules.Builder()
+            .rule()
+                .match(Record.class)
+                .match(Foo.class)
+                .match(Bar.class)
+                .match(Point.class)
+                .text("<native>")
+            .rules(WalkerRules.DEFAULT)
+            .tag("PATH", "{typeName,text,20} {namePath,text,-50}")
+            .prepare()
+            .visitor(writer);
+
+        List<Object> list = Arrays.asList(record1, foo1);
+
+        ObjectWalker.walk(list, "record", visitor);
+
+        System.out.println(writer.toString());
+
+        NewAssert.assertLineEquals(TestFiles.WALK_MATCH_TEXT, writer.toString());
+    }
+
     @Data
     @Builder
     public static class Record {

+ 46 - 0
assira/src/test/resources/reflective/walker/matchText.txt

@@ -0,0 +1,46 @@
+             List<E> record                                             List<E>.size = 2
+                   E record/[0]                                         Record = <native>
+              byte[] record/[0]/binary                                  byte[] = '32 56 A0 F3 47 8A'
+            String[] record/[0]/lines                                   String[].size = 6
+              String record/[0]/lines/[0]                               String = 'one'
+              String record/[0]/lines/[1]                               String = 'two'
+              String record/[0]/lines/[2]                               String = 'three'
+              String record/[0]/lines/[3]                               String = 'four'
+              String record/[0]/lines/[4]                               String = 'five'
+              String record/[0]/lines/[5]                               String = 'six'
+              char[] record/[0]/sequence                                char[] = 'hello world'
+                 int record/[0]/uid                                     Integer = 556
+                   E record/[1]                                         Foo = <native>
+                 Bar record/[1]/bar                                     Bar = <native>
+              String record/[1]/bar/hex                                 String = '0xFAD'
+              String record/[1]/bar/uid                                 String = '456'
+                 Bar record/[1]/empty                                   Bar = <null>
+        List<String> record/[1]/names                                   List<String>.size = 3
+              String record/[1]/names/[0]                               String = 'John'
+              String record/[1]/names/[1]                               String = 'Jenny'
+              String record/[1]/names/[2]                               String = 'Adam'
+  Map<Integer,Point> record/[1]/points                                  Map<Integer,Point>.size = 6
+               Point record/[1]/points/[17]                             Point = <native>
+                 int record/[1]/points/[17]/x                           Integer = 21
+                 int record/[1]/points/[17]/y                           Integer = 6
+                 int record/[1]/points/[17]/z                           Integer = 4
+               Point record/[1]/points/[18]                             Point = <native>
+                 int record/[1]/points/[18]/x                           Integer = 21
+                 int record/[1]/points/[18]/y                           Integer = 6
+                 int record/[1]/points/[18]/z                           Integer = 4
+               Point record/[1]/points/[21]                             Point = <native>
+                 int record/[1]/points/[21]/x                           Integer = 9
+                 int record/[1]/points/[21]/y                           Integer = 7
+                 int record/[1]/points/[21]/z                           Integer = 5
+               Point record/[1]/points/[3]                              Point = <native>
+                 int record/[1]/points/[3]/x                            Integer = 9
+                 int record/[1]/points/[3]/y                            Integer = 19
+                 int record/[1]/points/[3]/z                            Integer = 11
+               Point record/[1]/points/[8]                              Point = <native>
+                 int record/[1]/points/[8]/x                            Integer = 9
+                 int record/[1]/points/[8]/y                            Integer = 32
+                 int record/[1]/points/[8]/z                            Integer = 2
+               Point record/[1]/points/[88]                             Point = <native>
+                 int record/[1]/points/[88]/x                           Integer = 21
+                 int record/[1]/points/[88]/y                           Integer = 6
+                 int record/[1]/points/[88]/z                           Integer = 4