Kaynağa Gözat

ObjectWalker: collector & printer

Mariusz Czarnowski 5 yıl önce
ebeveyn
işleme
a5f4ff567f

+ 39 - 15
assira/src/main/java/net/ranides/assira/reflection/walker/ObjectVisitor.java

@@ -5,7 +5,9 @@ import net.ranides.assira.reflection.*;
 import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;
 
 import java.io.PrintWriter;
+import java.util.Collection;
 import java.util.Map;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 public interface ObjectVisitor {
@@ -23,7 +25,7 @@ public interface ObjectVisitor {
     boolean visitMap(WalkerContexts.MapContext<?> ctx);
 
     static ObjectVisitor of(Predicate<ObjectContext<?>> predicate) {
-        return new AbstractVisitor() {
+        return new SimpleVisitor() {
             @Override
             public boolean visitObject(ObjectContext<?> ctx) {
                 return predicate.test(ctx);
@@ -31,7 +33,7 @@ public interface ObjectVisitor {
         };
     }
 
-    abstract class AbstractVisitor implements ObjectVisitor {
+    abstract class SimpleVisitor implements ObjectVisitor {
 
         @Override
         public ResolveContext scope() {
@@ -63,21 +65,15 @@ public interface ObjectVisitor {
 
     }
 
-    abstract class PrintVisitor implements ObjectVisitor {
-        private final PrintWriter target;
+    abstract class AbstractVisitor implements ObjectVisitor {
         private final PrototypeMap scope;
 
-        protected PrintVisitor(PrintWriter target, Map<Object, ?> map) {
-            this.target = target;
+        protected AbstractVisitor(Map<Object, ?> map) {
             this.scope = new PrototypeMap(map);
         }
 
-        public PrintWriter writer() {
-            return target;
-        }
-
         @Override
-        public ResolveContext scope() {
+        public final ResolveContext scope() {
             return () -> scope;
         }
 
@@ -85,24 +81,52 @@ public interface ObjectVisitor {
         public abstract boolean visitObject(ObjectContext<?> context);
 
         @Override
-        public boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {
+        public final boolean visitArray(WalkerContexts.ArrayContext<?> ctx) {
             return visitObject(ctx);
         }
 
         @Override
-        public boolean visitCollection(WalkerContexts.CollectionContext<?> ctx) {
+        public final boolean visitCollection(WalkerContexts.CollectionContext<?> ctx) {
             return visitObject(ctx);
         }
 
         @Override
-        public boolean visitList(WalkerContexts.ListContext<?> ctx) {
+        public final boolean visitList(WalkerContexts.ListContext<?> ctx) {
             return visitObject(ctx);
         }
 
         @Override
-        public boolean visitMap(WalkerContexts.MapContext<?> ctx) {
+        public final boolean visitMap(WalkerContexts.MapContext<?> ctx) {
             return visitObject(ctx);
         }
     }
 
+    abstract class PrintVisitor extends AbstractVisitor {
+        private final PrintWriter target;
+
+        protected PrintVisitor(PrintWriter target, Map<Object, ?> map) {
+            super(map);
+            this.target = target;
+        }
+
+        public PrintWriter writer() {
+            return target;
+        }
+
+    }
+
+    abstract class ConsumeVisitor<T> extends AbstractVisitor {
+        private final Consumer<T> target;
+
+        protected ConsumeVisitor(Consumer<T> target, Map<Object, ?> map) {
+            super(map);
+            this.target = target;
+        }
+
+        public void accept(T value) {
+            target.accept(value);
+        }
+
+    }
+
 }

+ 5 - 262
assira/src/main/java/net/ranides/assira/reflection/walker/WalkerRules.java

@@ -2,19 +2,15 @@ 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.walker.ObjectVisitor.PrintVisitor;
 import net.ranides.assira.reflection.walker.WalkerContexts.ArrayContext;
 import net.ranides.assira.reflection.walker.WalkerContexts.ObjectContext;
-import net.ranides.assira.text.Charsets;
+import net.ranides.assira.reflection.walker.rules.PrintRules;
 import net.ranides.assira.text.FormatHex;
 import net.ranides.assira.text.ResolveFormat;
 
 import javax.xml.datatype.XMLGregorianCalendar;
-import java.io.OutputStream;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
-import java.io.Writer;
 import java.time.temporal.Temporal;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -28,7 +24,7 @@ import java.util.function.Function;
 
 public class WalkerRules {
 
-    public static final WalkerRules DEFAULT = new Builder()
+    public static final PrintRules PRINTER = new PrintRules.Builder()
         .tag("PATH", "{id,text,4}.{namePath,text,-50}")
         .rule()
             .match(ObjectContext::isVisited)
@@ -71,261 +67,8 @@ public class WalkerRules {
             .hint("{PATH} {actual.shortname} = {value}")
         .prepare();
 
-    private final Map<Object, Object> tags;
-    private final List<RuleEntry> rules;
-
-    private WalkerRules(Map<Object, Object> 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 static PrintRules.Builder printer() {
+        return new PrintRules.Builder();
     }
 
-    public PrintVisitor visitor(Writer target) {
-        return new RuleVisitor(target);
-    }
-
-    public static class Builder {
-
-        private final Map<Object, Object> tags = new HashMap<>();
-        private final List<RuleEntry> rules = new ArrayList<>();
-
-        public WalkerRules prepare() {
-            return new WalkerRules(tags, rules);
-        }
-
-        public Builder tag(String name, Object value) {
-            this.tags.put(name, value);
-            return this;
-        }
-
-        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;
-            }
-
-            public Builder text(String text) {
-                ResolveFormat fmt = ResolveFormat.compile(text);
-                return text((target, ctx) -> fmt.format(ctx));
-            }
-
-            public Builder text(Function<ObjectContext<F>, String> handler) {
-                return text((target, ctx) -> handler.apply(ctx));
-            }
-
-            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 Builder hint(String text) {
-                ResolveFormat fmt = ResolveFormat.compile(text);
-                return hint((target, ctx) -> target.writer().println(fmt.format(ctx)));
-            }
-
-            public Builder hint(Consumer<ObjectContext<F>> handler) {
-                return hint((target, ctx) -> handler.accept(ctx));
-            }
-
-            public Builder hint(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-                return rule(predicates, RuleType.HINT, asHandler(handler));
-            }
-
-            public Builder term(String text) {
-                ResolveFormat fmt = ResolveFormat.compile(text);
-                return term((target, ctx) -> target.writer().println(fmt.format(ctx)));
-            }
-
-            public Builder term(Consumer<ObjectContext<F>> handler) {
-                return term((target, ctx) -> handler.accept(ctx));
-            }
-
-            public Builder term(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-                return rule(predicates, RuleType.TERM, asHandler(handler));
-            }
-
-            private RuleHandler<F> asHandler(BiConsumer<PrintVisitor, ObjectContext<F>> handler) {
-                return (target, ctx) -> {
-                    formatter.accept(ctx);
-                    handler.accept(target, ctx);
-                };
-            }
-
-            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 class RootRule extends AbstractRule<Object> {
-
-            public RootRule() {
-                super(new ArrayList<>());
-            }
-
-            public RootRule format(String text) {
-                return format(ResolveFormat.compile(text)::format);
-            }
-
-            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 <T> TypeRule<T> match(IClass<T> type) {
-                predicates.add(ctx -> ctx.isSubclass(type));
-                return new TypeRule<>(this);
-            }
-
-            public MoreRule match(RulePredicate<?> predicate) {
-                predicates.add(predicate);
-                return new MoreRule(this);
-            }
-
-        }
-
-        public class TypeRule<F> extends AbstractRule<F> {
-
-            public TypeRule(AbstractRule<?> source) {
-                super(source.predicates);
-            }
-
-            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 MoreRule match(Class<?> type) {
-                return match(IClass.typeinfo(type));
-            }
-
-            public MoreRule match(IClass<?> type) {
-                predicates.add(ctx -> ctx.isSubclass(type));
-                return new MoreRule(this);
-            }
-
-            public MoreRule match(RulePredicate<?> predicate) {
-                predicates.add(predicate);
-                return new MoreRule(this);
-            }
-
-        }
-
-        public class MoreRule extends AbstractRule<Object> {
-
-            public MoreRule(AbstractRule<?> source) {
-                super(source.predicates);
-            }
-
-            public MoreRule format(String text) {
-                return format(ResolveFormat.compile(text)::format);
-            }
-
-            public MoreRule format(Function<ObjectContext<Object>, String> formatter) {
-                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
-                return this;
-            }
-
-            public MoreRule match(Class<?> type) {
-                return match(IClass.typeinfo(type));
-            }
-
-            public MoreRule match(IClass<?> type) {
-                predicates.add(ctx -> ctx.isSubclass(type));
-                return this;
-            }
-
-            public MoreRule match(RulePredicate<?> predicate) {
-                predicates.add(predicate);
-                return this;
-            }
-
-        }
-
-    }
-
-    private class RuleVisitor extends PrintVisitor {
-
-        public RuleVisitor(Writer target) {
-            super(new PrintWriter(target), 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"})
-    @Data
-    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 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> {
-        void apply(PrintVisitor printVisitor, ObjectContext<U> context);
-    }
 }

+ 264 - 0
assira/src/main/java/net/ranides/assira/reflection/walker/rules/CollectRules.java

@@ -0,0 +1,264 @@
+package net.ranides.assira.reflection.walker.rules;
+
+import lombok.Data;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.walker.ObjectVisitor;
+import net.ranides.assira.reflection.walker.WalkerContexts;
+import net.ranides.assira.text.ResolveFormat;
+
+import java.util.*;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+public class CollectRules<T> {
+
+    private final Map<Object, Object> tags;
+
+    private final List<RuleEntry<T>> rules;
+
+    private CollectRules(Map<Object, Object> tags, List<RuleEntry<T>> rules) {
+        this.tags = new HashMap<>(tags);
+        this.rules = new ArrayList<>(rules);
+    }
+
+    public ObjectVisitor.ConsumeVisitor<T> visitor(Collection<T> target) {
+        return new ConsumeRuleVisitor(target::add);
+    }
+
+    public ObjectVisitor.ConsumeVisitor<T> visitor(Consumer<T> consumer) {
+        return new ConsumeRuleVisitor(consumer);
+    }
+
+    public static class Builder<T> {
+
+        private final Map<Object, Object> tags = new HashMap<>();
+        private final List<RuleEntry<T>> rules = new ArrayList<>();
+
+        public CollectRules<T> prepare() {
+            return new CollectRules<>(tags, rules);
+        }
+
+        public Builder<T> tag(String name, Object value) {
+            this.tags.put(name, value);
+            return this;
+        }
+
+        public Builder<T> tag(String name, String value) {
+            this.tags.put(name, ResolveFormat.compile(value));
+            return this;
+        }
+
+        public RootRule rule() {
+            return new RootRule();
+        }
+
+        public Builder<T> rules(CollectRules<T> parent) {
+            this.tags.putAll(parent.tags);
+            this.rules.addAll(parent.rules);
+            return this;
+        }
+
+        public abstract class AbstractRule<F> {
+
+            protected final List<RulePredicate<?>> predicates;
+
+            protected BiConsumer<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>> before = (v, c) -> { };
+
+            protected AbstractRule(List<RulePredicate<?>> predicates) {
+                this.predicates = predicates;
+            }
+
+            public Builder<T> exec(Consumer<WalkerContexts.ObjectContext<F>> handler) {
+                return exec((target, ctx) -> handler.accept(ctx));
+            }
+
+            public Builder<T> exec(BiConsumer<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>> handler) {
+                before = before.andThen(handler);
+                return rule(predicates, RuleType.APPLY, asHandler((v, c) -> null));
+            }
+
+            public Builder<T> hint(Function<WalkerContexts.ObjectContext<F>, T> handler) {
+                return hint((target, ctx) -> handler.apply(ctx));
+            }
+
+            public Builder<T> hint(BiFunction<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>, T> handler) {
+                return rule(predicates, RuleType.HINT, asHandler(handler));
+            }
+
+            public Builder<T> term(Function<WalkerContexts.ObjectContext<F>, T> handler) {
+                return term((target, ctx) -> handler.apply(ctx));
+            }
+
+            public Builder<T> term(BiFunction<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>, T> handler) {
+                return rule(predicates, RuleType.TERM, asHandler(handler));
+            }
+
+            private RuleHandler<ObjectVisitor.ConsumeVisitor<T>, F, T> asHandler(BiFunction<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>, T> handler) {
+                return (target, ctx) -> {
+                    before.accept(target, ctx);
+                    return handler.apply(target, ctx);
+                };
+            }
+
+            private <U> Builder<T> rule(List<RulePredicate<?>> predicates, RuleType type, RuleHandler<ObjectVisitor.ConsumeVisitor<T>, U, T> handler) {
+                predicates.forEach(predicate -> rules.add(new RuleEntry<>(predicate, handler, type)));
+                return Builder.this;
+            }
+
+        }
+
+        public class RootRule extends AbstractRule<Object> {
+
+            public RootRule() {
+                super(new ArrayList<>());
+            }
+
+            public RootRule before(Consumer<WalkerContexts.ObjectContext<Object>> handler) {
+                return before((target, ctx) ->  handler.accept(ctx));
+            }
+
+            public RootRule before(BiConsumer<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<Object>> handler) {
+                before = before.andThen(handler);
+                return this;
+            }
+
+            public <Q> TypeRule<Q> match(Class<Q> type) {
+                return match(IClass.typeinfo(type));
+            }
+
+            public <Q> TypeRule<Q> match(IClass<Q> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new TypeRule<>(this);
+            }
+
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return new MoreRule(this);
+            }
+
+        }
+
+        public class TypeRule<F> extends AbstractRule<F> {
+
+            public TypeRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
+
+            public TypeRule<F> before(Consumer<WalkerContexts.ObjectContext<F>> handler) {
+                return before((target, ctx) ->  handler.accept(ctx));
+            }
+
+            public TypeRule<F> before(BiConsumer<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<F>> handler) {
+                before = before.andThen(handler);
+                return this;
+            }
+
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
+
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new MoreRule(this);
+            }
+
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return new MoreRule(this);
+            }
+
+        }
+
+        public class MoreRule extends AbstractRule<Object> {
+
+            public MoreRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
+
+            public MoreRule before(Consumer<WalkerContexts.ObjectContext<Object>> handler) {
+                return before((target, ctx) ->  handler.accept(ctx));
+            }
+
+            public MoreRule before(BiConsumer<ObjectVisitor.ConsumeVisitor<T>, WalkerContexts.ObjectContext<Object>> handler) {
+                before = before.andThen(handler);
+                return this;
+            }
+
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
+
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return this;
+            }
+
+            public MoreRule match(RulePredicate<?> predicate) {
+                predicates.add(predicate);
+                return this;
+            }
+
+        }
+
+    }
+
+    private class ConsumeRuleVisitor extends ObjectVisitor.ConsumeVisitor<T> {
+
+        public ConsumeRuleVisitor(Consumer<T> target) {
+            super(target, tags);
+        }
+
+        @Override
+        public boolean visitObject(WalkerContexts.ObjectContext<?> context) {
+
+            for (RuleEntry<T> rule : rules) {
+                if (rule.test(context)) {
+                    T item = rule.consume(this, context);
+                    if(item != null) {
+                        accept(item);
+                    }
+                    switch (rule.type) {
+                        case HINT:
+                            return true;
+                        case TERM:
+                            return false;
+                        case APPLY:
+                            // continue processing
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    @Data
+    private static class RuleEntry<T> {
+        private final RulePredicate predicate;
+        private final RuleHandler handler;
+        private final RuleType type;
+
+        public boolean test(WalkerContexts.ObjectContext<?> context) {
+            return predicate.test(context);
+        }
+
+        public T consume(ObjectVisitor.ConsumeVisitor<T> visitor, WalkerContexts.ObjectContext<?> context) {
+            return (T)handler.apply(visitor, context);
+        }
+    }
+
+    private interface RuleHandler<V, U, T> {
+        T apply(V visitor, WalkerContexts.ObjectContext<U> context);
+    }
+
+    private interface RulePredicate<U> {
+        boolean test(WalkerContexts.ObjectContext<U> context);
+    }
+
+    private enum RuleType {
+        TERM, HINT, APPLY
+    }
+
+}

+ 278 - 0
assira/src/main/java/net/ranides/assira/reflection/walker/rules/PrintRules.java

@@ -0,0 +1,278 @@
+package net.ranides.assira.reflection.walker.rules;
+
+import lombok.Data;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.walker.ObjectVisitor;
+import net.ranides.assira.reflection.walker.WalkerContexts;
+import net.ranides.assira.text.Charsets;
+import net.ranides.assira.text.ResolveFormat;
+
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.*;
+
+public class PrintRules {
+
+    private final Map<Object, Object> tags;
+
+    private final List<RuleEntry<ObjectVisitor.PrintVisitor>> rules;
+
+    private PrintRules(Map<Object, Object> tags, List<RuleEntry<ObjectVisitor.PrintVisitor>> rules) {
+        this.tags = new HashMap<>(tags);
+        this.rules = new ArrayList<>(rules);
+    }
+
+    public ObjectVisitor.PrintVisitor visitor(OutputStream target) {
+        return visitor(new OutputStreamWriter(target, Charsets.UTF8));
+    }
+
+    public ObjectVisitor.PrintVisitor visitor(Writer target) {
+        return new PrintRuleVisitor(target);
+    }
+
+    public static class Builder {
+
+        private final Map<Object, Object> tags = new HashMap<>();
+        private final List<RuleEntry<ObjectVisitor.PrintVisitor>> rules = new ArrayList<>();
+
+        public PrintRules prepare() {
+            return new PrintRules(tags, rules);
+        }
+
+        public Builder tag(String name, Object value) {
+            this.tags.put(name, value);
+            return this;
+        }
+
+        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(PrintRules 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<WalkerContexts.ObjectContext<F>> formatter = x -> { };
+
+            protected AbstractRule(List<RulePredicate> predicates) {
+                this.predicates = predicates;
+            }
+
+            public Builder text(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return text((target, ctx) -> fmt.format(ctx));
+            }
+
+            public Builder text(Function<WalkerContexts.ObjectContext<F>, String> handler) {
+                return text((target, ctx) -> handler.apply(ctx));
+            }
+
+            public Builder text(BiFunction<ObjectVisitor.PrintVisitor, WalkerContexts.ObjectContext<F>, String> handler) {
+                return rule(predicates, RuleType.APPLY, asHandler((v, c) -> c.custom("value", handler.apply(v, c))));
+            }
+
+            public Builder hint(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return hint((target, ctx) -> target.writer().println(fmt.format(ctx)));
+            }
+
+            public Builder hint(Consumer<WalkerContexts.ObjectContext<F>> handler) {
+                return hint((target, ctx) -> handler.accept(ctx));
+            }
+
+            public Builder hint(BiConsumer<ObjectVisitor.PrintVisitor, WalkerContexts.ObjectContext<F>> handler) {
+                return rule(predicates, RuleType.HINT, asHandler(handler));
+            }
+
+            public Builder term(String text) {
+                ResolveFormat fmt = ResolveFormat.compile(text);
+                return term((target, ctx) -> target.writer().println(fmt.format(ctx)));
+            }
+
+            public Builder term(Consumer<WalkerContexts.ObjectContext<F>> handler) {
+                return term((target, ctx) -> handler.accept(ctx));
+            }
+
+            public Builder term(BiConsumer<ObjectVisitor.PrintVisitor, WalkerContexts.ObjectContext<F>> handler) {
+                return rule(predicates, RuleType.TERM, asHandler(handler));
+            }
+
+            private RuleHandler<ObjectVisitor.PrintVisitor, F> asHandler(BiConsumer<ObjectVisitor.PrintVisitor, WalkerContexts.ObjectContext<F>> handler) {
+                return (target, ctx) -> {
+                    formatter.accept(ctx);
+                    handler.accept(target, ctx);
+                };
+            }
+
+            private <U> Builder rule(List<RulePredicate> predicates, RuleType type, RuleHandler<ObjectVisitor.PrintVisitor, U> handler) {
+                predicates.forEach(predicate -> rules.add(new RuleEntry<>(predicate, handler, type)));
+                return Builder.this;
+            }
+
+        }
+
+        public class RootRule extends AbstractRule<Object> {
+
+            public RootRule() {
+                super(new ArrayList<>());
+            }
+
+            public RootRule format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
+
+            public RootRule format(Function<WalkerContexts.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 <T> TypeRule<T> match(IClass<T> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new TypeRule<>(this);
+            }
+
+            public MoreRule match(Predicate<WalkerContexts.ObjectContext<?>> predicate) {
+                predicates.add(predicate::test);
+                return new MoreRule(this);
+            }
+
+        }
+
+        public class TypeRule<F> extends AbstractRule<F> {
+
+            public TypeRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
+
+            public TypeRule<F> format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
+
+            public TypeRule<F> format(Function<WalkerContexts.ObjectContext<F>, String> formatter) {
+                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
+                return this;
+            }
+
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
+
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return new MoreRule(this);
+            }
+
+            public MoreRule match(Predicate<WalkerContexts.ObjectContext<?>> predicate) {
+                predicates.add(predicate::test);
+                return new MoreRule(this);
+            }
+
+        }
+
+        public class MoreRule extends AbstractRule<Object> {
+
+            public MoreRule(AbstractRule<?> source) {
+                super(source.predicates);
+            }
+
+            public MoreRule format(String text) {
+                return format(ResolveFormat.compile(text)::format);
+            }
+
+            public MoreRule format(Function<WalkerContexts.ObjectContext<Object>, String> formatter) {
+                this.formatter = ctx -> ctx.custom("value", formatter.apply(ctx));
+                return this;
+            }
+
+            public MoreRule match(Class<?> type) {
+                return match(IClass.typeinfo(type));
+            }
+
+            public MoreRule match(IClass<?> type) {
+                predicates.add(ctx -> ctx.isSubclass(type));
+                return this;
+            }
+
+            public MoreRule match(Predicate<WalkerContexts.ObjectContext<?>> predicate) {
+                predicates.add(predicate::test);
+                return this;
+            }
+
+        }
+
+    }
+
+    private class PrintRuleVisitor extends ObjectVisitor.PrintVisitor {
+
+        public PrintRuleVisitor(Writer target) {
+            super(new PrintWriter(target), tags);
+        }
+
+        @Override
+        public boolean visitObject(WalkerContexts.ObjectContext<?> context) {
+            for (RuleEntry<PrintVisitor> rule : rules) {
+                if (rule.test(context)) {
+                    rule.consume(this, context);
+                    switch (rule.type) {
+                        case HINT:
+                            return true;
+                        case TERM:
+                            return false;
+                        case APPLY:
+                            // continue processing
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    @SuppressWarnings({"rawtypes", "unchecked"})
+    @Data
+    private static class RuleEntry<V> {
+        private final RulePredicate predicate;
+        private final RuleHandler handler;
+        private final RuleType type;
+
+        public boolean test(WalkerContexts.ObjectContext<?> context) {
+            return predicate.test(context);
+        }
+
+        public void consume(V visitor, WalkerContexts.ObjectContext<?> context) {
+            handler.apply(visitor, context);
+        }
+    }
+
+    private interface RuleHandler<V, U> {
+        void apply(V visitor, WalkerContexts.ObjectContext<U> context);
+    }
+
+    private interface RulePredicate {
+        boolean test(WalkerContexts.ObjectContext<?> context);
+    }
+
+    private enum RuleType {
+        TERM, HINT, APPLY
+    }
+}

+ 22 - 11
assira/src/test/java/net/ranides/assira/reflection/walker/ObjectWalkerTest.java

@@ -4,6 +4,7 @@ import lombok.Builder;
 import lombok.Data;
 import lombok.Singular;
 import net.ranides.assira.junit.NewAssert;
+import net.ranides.assira.reflection.walker.rules.PrintRules;
 import net.ranides.assira.trace.TraceUtils;
 import org.jetbrains.annotations.Debug;
 import org.junit.Test;
@@ -65,11 +66,16 @@ public class ObjectWalkerTest {
         .lines("one two three four five six".split(" "))
         .build();
 
+    Quaz quaz1 = Quaz.builder()
+        .uid("quaz")
+        .numbers(new int[]{1,7,13,9})
+        .build();
+
     @Test
     public void basic() {
         StringWriter writer = new StringWriter();
         PrintWriter printer = new PrintWriter(writer);
-        ObjectWalker.walk(foo1, new ObjectVisitor.AbstractVisitor() {
+        ObjectWalker.walk(foo1, new ObjectVisitor.SimpleVisitor() {
             @Override
             public boolean visitObject(ObjectContext ctx) {
                 printer.printf(
@@ -94,7 +100,7 @@ public class ObjectWalkerTest {
         StringWriter writer = new StringWriter();
         PrintWriter printer = new PrintWriter(writer);
 
-        ObjectWalker.walk(foo1, new ObjectVisitor.AbstractVisitor() {
+        ObjectWalker.walk(foo1, new ObjectVisitor.SimpleVisitor() {
             @Override
             public boolean visitObject(ObjectContext ctx) {
                 printer.printf(" - [%-12s] %-40s | %s = %s%n",
@@ -146,14 +152,14 @@ public class ObjectWalkerTest {
     public void format() {
         StringWriter writer = new StringWriter();
 
-        WalkerRules rules = new WalkerRules.Builder()
+        PrintRules rules = WalkerRules.printer()
             .rule().match(ObjectContext::isNull).term("{namePath,text} = <null>")
             .rule().match(String.class).term("{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));
+        ObjectWalker.walk(foo1, "foo2", WalkerRules.PRINTER.visitor(writer));
 
         NewAssert.assertLineEquals(TestFiles.WALK_FORMAT, writer.toString());
     }
@@ -162,7 +168,7 @@ public class ObjectWalkerTest {
     public void formatRules() {
         StringWriter writer = new StringWriter();
 
-        PrintVisitor visitor = new WalkerRules.Builder()
+        PrintVisitor visitor = WalkerRules.printer()
             .rule()
                 .match(ObjectContext::isNull)
                 .term("{namePath,text} = <null>")
@@ -197,7 +203,7 @@ public class ObjectWalkerTest {
         List<Foo> list = Arrays.asList(foo2, foo3, foo2, foo4);
 
         StringWriter writer = new StringWriter();
-        PrintVisitor visitor = new WalkerRules.Builder()
+        PrintVisitor visitor = WalkerRules.printer()
             .rule()
                 .match(List.class)
                 .match(Collection.class)
@@ -227,9 +233,9 @@ public class ObjectWalkerTest {
     public void arrays() {
         StringWriter writer = new StringWriter();
 
-        List<Object> list = Arrays.asList(record1, foo1);
+        List<Object> list = Arrays.asList(record1, foo1, quaz1);
 
-        ObjectWalker.walk(list, "record", WalkerRules.DEFAULT.visitor(writer));
+        ObjectWalker.walk(list, "record", WalkerRules.PRINTER.visitor(writer));
 
         NewAssert.assertLineEquals(TestFiles.WALK_ARRAYS, writer.toString());
     }
@@ -238,14 +244,14 @@ public class ObjectWalkerTest {
     public void matchText() {
         StringWriter writer = new StringWriter();
 
-        PrintVisitor visitor = new WalkerRules.Builder()
+        PrintVisitor visitor = WalkerRules.printer()
             .rule()
                 .match(Record.class)
                 .match(Foo.class)
                 .match(Bar.class)
                 .match(Point.class)
                 .text("<native>")
-            .rules(WalkerRules.DEFAULT)
+            .rules(WalkerRules.PRINTER)
             .tag("TEST", TraceUtils.getTestFrame().orElse(null))
             .tag("HOME", "#{TEST.methodName}")
             .tag("PATH", "{HOME}: {typeName,text,20} {namePath,text,-50}")
@@ -266,7 +272,6 @@ public class ObjectWalkerTest {
         private final String[] lines;
     }
 
-
     @Data
     @Builder
     @Debug.Renderer(text ="\"very interesting text<\" + bar.hex + \">\"")
@@ -292,5 +297,11 @@ public class ObjectWalkerTest {
         private final int z;
     }
 
+    @Data
+    @Builder
+    public static class Quaz {
+        private final String uid;
+        private final int[] numbers;
+    }
 
 }

+ 8 - 1
assira/src/test/resources/reflective/walker/arrays.txt

@@ -1,4 +1,4 @@
-   1.record                                             List<E>.size = 2
+   1.record                                             List<E>.size = 3
    2.record/[0]                                         Record = ObjectWalkerTest.Record(uid=556, binary=[50, 86, -96, -13, 71, -118], sequence=[h, e, l, l, o,  , w, o, r, l, d], lines=[one, two, three, four, five, six])
    3.record/[0]/binary                                  byte[] = '32 56 A0 F3 47 8A'
    4.record/[0]/lines                                   String[].size = 6
@@ -44,3 +44,10 @@
   44.record/[1]/points/[88]/x                           Integer = 21
   45.record/[1]/points/[88]/y                           Integer = 6
   46.record/[1]/points/[88]/z                           Integer = 4
+  47.record/[2]                                         Quaz = ObjectWalkerTest.Quaz(uid=quaz, numbers=[1, 7, 13, 9])
+  48.record/[2]/numbers                                 int[].size = 4
+  49.record/[2]/numbers/[0]                             Integer = 1
+  50.record/[2]/numbers/[1]                             Integer = 7
+  51.record/[2]/numbers/[2]                             Integer = 13
+  52.record/[2]/numbers/[3]                             Integer = 9
+  53.record/[2]/uid                                     String = 'quaz'