Pārlūkot izejas kodu

ObjectVisitor draft

Signed-off-by: Ranides Atterwim <ranides@gmail.com>
Mariusz Czarnowski 5 gadi atpakaļ
vecāks
revīzija
ad7c6c38f2

+ 2 - 3
assira.drafts/src/main/java/net/ranides/assira/credentials/PWDBStore.java

@@ -5,7 +5,6 @@ import net.ranides.assira.collection.maps.GenericEntry;
 import net.ranides.assira.collection.maps.GenericKey;
 import net.ranides.assira.collection.maps.GenericMap;
 import net.ranides.assira.collection.query.CQuery;
-import net.ranides.assira.collection.query.CQueryAbstract;
 import net.ranides.assira.collection.sets.SetUtils;
 import net.ranides.assira.generic.TypeToken;
 import net.ranides.assira.reflection.IClass;
@@ -50,7 +49,7 @@ public class PWDBStore implements UserStore {
             .stream()
             .map(PWDMap::new)
             .collect(Collectors.toList());
-        return CQueryAbstract.Builder.fromCollection(list);
+        return CQuery.from().collection(list);
     }
 
     @Override
@@ -60,7 +59,7 @@ public class PWDBStore implements UserStore {
             .stream()
             .map(PWDMap::new)
             .collect(Collectors.toList());
-        return CQueryAbstract.Builder.fromCollection(list);
+        return CQuery.from().collection(list);
     }
 
     private static class PWDMap extends AGenericMap<String> {

+ 2 - 2
assira.drafts/src/main/java/net/ranides/assira/reflection/impl/WSInpsector.java

@@ -20,7 +20,7 @@ import javax.jws.WebService;
 
 import net.ranides.assira.collection.maps.CacheMap;
 import net.ranides.assira.collection.maps.MapCollectors;
-import net.ranides.assira.collection.query.CQueryAbstract;
+import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.generic.LazyReference;
 import net.ranides.assira.reflection.*;
 import net.ranides.assira.text.StringTraits;
@@ -164,7 +164,7 @@ public class WSInpsector implements WSModel {
 
         @Override
         public IArguments collect() {
-            return FElements.newArguments(AHints.EMPTY, 1, CQueryAbstract.Builder.of(this));
+            return FElements.newArguments(AHints.EMPTY, 1, CQuery.from().value(this));
         }
 
         @Override

+ 0 - 269
assira/src/main/java/net/ranides/assira/reflection/ObjectScanner.java

@@ -1,269 +0,0 @@
-package net.ranides.assira.reflection;
-
-import net.ranides.assira.reflection.impl.scanner.ScanWalker;
-
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Predicate;
-
-public class ObjectScanner {
-
-    public static void walk(Object input, Predicate<ObjectContext> visitor) {
-        walk(input, new SimpleListener(visitor));
-    }
-
-    public static void walk(Object input, ObjectVisitor visitor) {
-        walk(input, new SimpleListener(visitor));
-    }
-
-    public static void walk(Object input, ObjectListener listener) {
-        new ScanWalker(input, listener).walk();
-    }
-
-    public static class SimpleListener extends AbstractObjectListener {
-
-        private final ObjectVisitor visitor;
-
-        public SimpleListener(ObjectVisitor visitor) {
-            this.visitor = visitor;
-        }
-
-        public SimpleListener(Predicate<ObjectContext> predicate) {
-            this(new AbstractObjectVisitor() {
-                @Override
-                public boolean visitObject(ObjectContext value) {
-                    return predicate.test(value);
-                }
-            });
-        }
-
-        @Override
-        public boolean enterObject(ObjectContext value) {
-            return visitor.visitObject(value);
-        }
-
-        @Override
-        public void leaveObject(ObjectContext value) {
-            // do nothing
-        }
-
-        @Override
-        public boolean enterArray(ArrayContext value) {
-            return visitor.visitArray(value);
-        }
-
-        @Override
-        public boolean enterCollection(CollectionContext value) {
-            return visitor.visitCollection(value);
-        }
-
-        @Override
-        public boolean enterList(ListContext value) {
-            return visitor.visitList(value);
-        }
-
-        @Override
-        public boolean enterMap(MapContext value) {
-            return visitor.visitMap(value);
-        }
-    }
-
-    public static abstract class AbstractObjectListener implements ObjectListener {
-        @Override
-        public abstract boolean enterObject(ObjectContext value);
-
-        @Override
-        public abstract void leaveObject(ObjectContext value);
-
-        @Override
-        public boolean enterArray(ArrayContext value) {
-            return enterObject(value);
-        }
-
-        @Override
-        public void leaveArray(ArrayContext value) {
-            leaveObject(value);
-        }
-
-        @Override
-        public boolean enterCollection(CollectionContext value) {
-            return enterObject(value);
-        }
-
-        @Override
-        public void leaveCollection(CollectionContext value) {
-            leaveObject(value);
-        }
-
-        @Override
-        public boolean enterList(ListContext value) {
-            return enterObject(value);
-        }
-
-        @Override
-        public void leaveList(ListContext value) {
-            leaveObject(value);
-        }
-
-        @Override
-        public boolean enterMap(MapContext value) {
-            return enterObject(value);
-        }
-
-        @Override
-        public void leaveMap(MapContext value) {
-            leaveObject(value);
-        }
-
-    }
-
-    public static abstract class AbstractObjectVisitor implements ObjectVisitor {
-
-        @Override
-        public abstract boolean visitObject(ObjectContext value);
-
-        @Override
-        public boolean visitArray(ArrayContext value) {
-            return visitObject(value);
-        }
-
-        @Override
-        public boolean visitCollection(CollectionContext value) {
-            return visitObject(value);
-        }
-
-        @Override
-        public boolean visitList(ListContext value) {
-            return visitObject(value);
-        }
-
-        @Override
-        public boolean visitMap(MapContext value) {
-            return visitObject(value);
-        }
-
-    }
-
-    public interface ObjectVisitor {
-
-        boolean visitObject(ObjectContext value);
-
-        boolean visitArray(ArrayContext value);
-
-        boolean visitCollection(CollectionContext value);
-
-        boolean visitList(ListContext value);
-
-        boolean visitMap(MapContext value);
-
-    }
-
-    public interface ObjectListener {
-
-        boolean enterObject(ObjectContext value);
-
-        void leaveObject(ObjectContext value);
-
-        boolean enterArray(ArrayContext value);
-
-        void leaveArray(ArrayContext value);
-
-        boolean enterCollection(CollectionContext value);
-
-        void leaveCollection(CollectionContext value);
-
-        boolean enterList(ListContext value);
-
-        void leaveList(ListContext value);
-
-        boolean enterMap(MapContext value);
-
-        void leaveMap(MapContext value);
-
-    }
-
-    public interface ObjectContext {
-
-        void accept(ObjectListener listener);
-
-        ObjectContext parent();
-
-        String typeName();
-
-        String name();
-
-        IClass<?> type();
-
-        IClass<?> actual();
-
-        Object value();
-
-        long id();
-
-        long ref();
-
-        String typePath();
-
-        String namePath();
-
-        String idPath();
-
-        boolean isRoot();
-
-        boolean isField();
-
-        boolean isVisited();
-
-        boolean isDefined();
-
-        boolean isPrimitive();
-
-        boolean isNull();
-
-        boolean isSubclass();
-    }
-
-    public interface ArrayContext extends ObjectContext {
-
-        IClass<?> component();
-
-        int size();
-
-        Object at(int index);
-
-    }
-
-    public interface ListContext extends ObjectContext {
-
-        IClass<?> component();
-
-        int size();
-
-        Object at(int index);
-
-    }
-
-    public interface CollectionContext extends ObjectContext {
-
-        IClass<?> component();
-
-        int size();
-
-        Iterator<?> iterator();
-
-    }
-
-    public interface MapContext extends ObjectContext {
-
-        IClass<?> keyComponent();
-
-        IClass<?> valueComponent();
-
-        int size();
-
-        Set<? extends Map.Entry<?, ?>> entries();
-
-    }
-
-}

+ 0 - 59
assira/src/main/java/net/ranides/assira/reflection/impl/scanner/ScanSupport.java

@@ -1,59 +0,0 @@
-package net.ranides.assira.reflection.impl.scanner;
-
-import net.ranides.assira.generic.TypeToken;
-import net.ranides.assira.reflection.IClass;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-public class ScanSupport {
-
-    public static final Undefined UNDEFINED = new Undefined();
-
-    private static final IClass<?> LIST = new TypeToken<List<?>>() {};
-
-    private static final IClass<?> COLLECTION = new TypeToken<Collection<?>>() {};
-
-    private static final IClass<?> MAP = new TypeToken<Map<?,?>>() {};
-
-    public static boolean isList(IClass<?> actual) {
-        return actual.isSubclass(LIST);
-    }
-
-    public static boolean isList(IClass<?> declared, IClass<?> actual) {
-        return isList(declared) || isList(actual);
-    }
-
-    public static boolean isCollection(IClass<?> actual) {
-        return actual.isSubclass(COLLECTION);
-    }
-
-    public static boolean isCollection(IClass<?> declared, IClass<?> actual) {
-        return isCollection(declared) || isCollection(actual);
-    }
-
-    public static boolean isMap(IClass<?> actual) {
-        return actual.isSubclass(MAP);
-    }
-
-    public static boolean isMap(IClass<?> declared, IClass<?> actual) {
-        return isMap(declared) || isMap(actual);
-    }
-
-    public static boolean isArray(IClass<?> actual) {
-        return actual.isArray();
-    }
-
-    public static boolean isArray(IClass<?> declared, IClass<?> actual) {
-        return isArray(declared) || isArray(actual);
-    }
-
-    public static class Undefined {
-        @Override
-        public String toString() {
-            return "<undefined>";
-        }
-    }
-
-}

+ 0 - 7
assira/src/main/java/net/ranides/assira/reflection/impl/scanner/ScanVisitors.java

@@ -1,7 +0,0 @@
-package net.ranides.assira.reflection.impl.scanner;
-
-public class ScanVisitors {
-
-
-
-}

+ 174 - 0
assira/src/main/java/net/ranides/assira/reflection/visit/ObjectVisitor.java

@@ -0,0 +1,174 @@
+package net.ranides.assira.reflection.visit;
+
+import lombok.RequiredArgsConstructor;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.visit.WalkerContexts.ObjectContext;
+import net.ranides.assira.text.ResolveFormat;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public interface ObjectVisitor {
+
+    boolean visitObject(ObjectContext ctx);
+
+    boolean visitArray(WalkerContexts.ArrayContext ctx);
+
+    boolean visitCollection(WalkerContexts.CollectionContext ctx);
+
+    boolean visitList(WalkerContexts.ListContext ctx);
+
+    boolean visitMap(WalkerContexts.MapContext ctx);
+
+    @RequiredArgsConstructor
+    class SimpleVisitor extends AbstractVisitor {
+
+        private final Predicate<ObjectContext> predicate;
+
+        @Override
+        public boolean visitObject(ObjectContext ctx) {
+            return predicate.test(ctx);
+        }
+    }
+
+    abstract class AbstractVisitor implements ObjectVisitor {
+
+        @Override
+        public abstract boolean visitObject(ObjectContext ctx);
+
+        @Override
+        public boolean visitArray(WalkerContexts.ArrayContext ctx) {
+            return true;
+        }
+
+        @Override
+        public boolean visitCollection(WalkerContexts.CollectionContext ctx) {
+            return true;
+        }
+
+        @Override
+        public boolean visitList(WalkerContexts.ListContext ctx) {
+            return true;
+        }
+
+        @Override
+        public boolean visitMap(WalkerContexts.MapContext ctx) {
+            return true;
+        }
+
+    }
+
+    class FormatRules {
+        private final List<Predicate<ObjectContext>> termC = new ArrayList<>();
+        private final List<ResolveFormat> termF = new ArrayList<>();
+
+        private final List<Predicate<ObjectContext>> hintC = new ArrayList<>();
+        private final List<ResolveFormat> hintF = new ArrayList<>();
+
+        public FormatVisitor visitor() {
+            return visitor(new StringWriter());
+        }
+
+        public FormatVisitor visitor(Appendable target) {
+            return new FormatVisitor(this, target);
+        }
+
+        public FormatRules term(Class<?> type, String text) {
+            return term(IClass.typeinfo(type), text);
+        }
+
+        public FormatRules term(IClass<?> type, String text) {
+            return term(ctx -> ctx.isSubclass(type), text);
+        }
+
+        public FormatRules term(Predicate<ObjectContext> predicate, String text) {
+            this.termC.add(predicate);
+            this.termF.add(ResolveFormat.compile(text));
+            return this;
+        }
+
+        public FormatRules hint(Class<?> type, String text) {
+            return hint(IClass.typeinfo(type), text);
+        }
+
+        public FormatRules hint(IClass<?> type, String text) {
+            return hint(ctx -> ctx.isSubclass(type), text);
+        }
+
+        public FormatRules hint(Predicate<ObjectContext> predicate, String text) {
+            this.hintC.add(predicate);
+            this.hintF.add(ResolveFormat.compile(text));
+            return this;
+        }
+
+        private boolean apply(Appendable target, ObjectContext ctx) {
+            for(int i = 0, n = termC.size(); i<n; i++) {
+                if(termC.get(i).test(ctx)) {
+                    append(target, termF.get(i).format(ctx));
+                    return false;
+                }
+            }
+
+            for(int i = 0, n = hintC.size(); i<n; i++) {
+                if(hintC.get(i).test(ctx)) {
+                    append(target, hintF.get(i).format(ctx));
+                    return true;
+                }
+            }
+
+            return true;
+        }
+
+        private void append(Appendable target, String text) {
+            try {
+                target.append(text).append("\n");
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        }
+    }
+
+    class FormatVisitor implements ObjectVisitor {
+        private final FormatRules rules;
+        private final Appendable target;
+
+        protected FormatVisitor(FormatRules rules, Appendable target) {
+            this.rules = rules;
+            this.target = target;
+        }
+
+        @Override
+        public String toString() {
+            return target.toString();
+        }
+
+        @Override
+        public boolean visitObject(ObjectContext ctx) {
+            return rules.apply(target, ctx);
+        }
+
+        @Override
+        public boolean visitArray(WalkerContexts.ArrayContext ctx) {
+            return visitObject(ctx);
+        }
+
+        @Override
+        public boolean visitCollection(WalkerContexts.CollectionContext ctx) {
+            return visitObject(ctx);
+        }
+
+        @Override
+        public boolean visitList(WalkerContexts.ListContext ctx) {
+            return visitObject(ctx);
+        }
+
+        @Override
+        public boolean visitMap(WalkerContexts.MapContext ctx) {
+            return visitObject(ctx);
+        }
+    }
+}

+ 111 - 87
assira/src/main/java/net/ranides/assira/reflection/impl/scanner/ScanWalker.java

@@ -1,4 +1,4 @@
-package net.ranides.assira.reflection.impl.scanner;
+package net.ranides.assira.reflection.visit;
 
 import net.ranides.assira.collection.arrays.NativeArray;
 import net.ranides.assira.functional.special.LazyFunction;
@@ -8,24 +8,26 @@ import net.ranides.assira.reflection.IAttribute;
 import net.ranides.assira.reflection.IClass;
 import net.ranides.assira.reflection.IElement;
 import net.ranides.assira.reflection.IField;
-import net.ranides.assira.reflection.ObjectScanner;
+import net.ranides.assira.reflection.visit.WalkerContexts.ArrayContext;
+import net.ranides.assira.reflection.visit.WalkerContexts.CollectionContext;
+import net.ranides.assira.reflection.visit.WalkerContexts.ListContext;
+import net.ranides.assira.reflection.visit.WalkerContexts.MapContext;
+import net.ranides.assira.reflection.visit.WalkerContexts.ObjectContext;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.SortedMap;
+import java.util.function.Predicate;
 
-public class ScanWalker {
-
-    private static final String TYPE_PATH = "/";
-
-    private static final String NAME_PATH = "/";
-
-    private static final String ID_PATH = ".";
+public class ObjectWalker {
 
     private final Map<Object, Long> visited = new IdentityHashMap<>();
 
@@ -33,25 +35,41 @@ public class ScanWalker {
 
     private final Object root;
 
-    private final ObjectScanner.ObjectListener listener;
+    private final ObjectVisitor visitor;
 
-    public ScanWalker(Object root, ObjectScanner.ObjectListener listener) {
+    private ObjectWalker(Object root, ObjectVisitor visitor) {
         this.root = root;
-        this.listener = listener;
+        this.visitor = visitor;
+    }
+
+    public static void walk(Object input, Predicate<ObjectContext> visitor) {
+        walk(input, "var", visitor);
+    }
+
+    public static void walk(Object input, ObjectVisitor visitor) {
+        walk(input, "var", visitor);
+    }
+
+    public static void walk(Object input, String root, Predicate<ObjectContext> visitor) {
+        walk(input, root, new ObjectVisitor.SimpleVisitor(visitor));
+    }
+
+    public static void walk(Object input, String root, ObjectVisitor visitor) {
+        new ObjectWalker(input, visitor).start(root);
     }
 
-    public void walk() {
-        context(null, null, IClass.OBJECT, root).accept(listener);
+    private void start(String rootname) {
+        context(null, rootname, IClass.OBJECT, root).accept(visitor);
     }
 
-    private void mark(ObjectScanner.ObjectContext ctx) {
+    private void mark(ObjectContext ctx) {
         if(!ctx.isPrimitive()) {
             visited.put(ctx.value(), ctx.id());
         }
     }
 
-    private ObjectScanner.ObjectContext context(ObjectScanner.ObjectContext parent, IField field) {
-        Object value = ScanSupport.UNDEFINED;
+    private ObjectContext context(ObjectContext parent, IField field) {
+        Object value = WalkerUtils.UNDEFINED;
         if(!parent.isNull()) {
             try {
                 value = field.accessible().get(parent.value());
@@ -62,42 +80,42 @@ public class ScanWalker {
         IClass<?> declared = field.type();
         IClass<?> actual = IClass.typefor(value);
 
-        if(ScanSupport.isList(declared, actual)) {
+        if(WalkerUtils.isList(declared, actual)) {
             return new CList(parent, field, value);
         }
-        if(ScanSupport.isCollection(declared, actual)) {
+        if(WalkerUtils.isCollection(declared, actual)) {
             return new CCollection(parent, field, value);
         }
-        if(ScanSupport.isMap(declared, actual)) {
+        if(WalkerUtils.isMap(declared, actual)) {
             return new CMap(parent, field, value);
         }
-        if(ScanSupport.isArray(declared, actual)) {
+        if(WalkerUtils.isArray(declared, actual)) {
             return new CArray(parent, field, value);
         }
         return new CObject(parent, field, value);
     }
 
-    private ObjectScanner.ObjectContext context(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
+    private ObjectContext context(ObjectContext parent, String name, IClass<?> declared, Object value) {
         IClass<?> actual = IClass.typefor(value);
 
-        if(ScanSupport.isList(declared, actual)) {
+        if(WalkerUtils.isList(declared, actual)) {
             return new CList(parent, name, declared, value);
         }
-        if(ScanSupport.isCollection(declared, actual)) {
+        if(WalkerUtils.isCollection(declared, actual)) {
             return new CCollection(parent, name, declared, value);
         }
-        if(ScanSupport.isMap(declared, actual)) {
+        if(WalkerUtils.isMap(declared, actual)) {
             return new CMap(parent, name, declared, value);
         }
-        if(ScanSupport.isArray(declared, actual)) {
+        if(WalkerUtils.isArray(declared, actual)) {
             return new CArray(parent, name, declared, value);
         }
         return new CObject(parent, name, declared, value);
     }
 
 
-    private abstract class CAbstract implements ObjectScanner.ObjectContext {
-        private final ObjectScanner.ObjectContext parent;
+    private abstract class CAbstract implements ObjectContext {
+        private final ObjectContext parent;
         private final String name;
         private final IClass<?> declared;
         private final IClass<?> actual;
@@ -110,7 +128,7 @@ public class ScanWalker {
         private final LazyFunction<String> namePath = LazyReference.concurrent();
         private final LazyFunction<String> idPath = LazyReference.concurrent();
 
-        public CAbstract(ObjectScanner.ObjectContext parent, IField field, Object value) {
+        public CAbstract(ObjectContext parent, IField field, Object value) {
             this.field = Optional.of(field);
             this.parent = parent;
             this.name = field.name();
@@ -121,7 +139,7 @@ public class ScanWalker {
 
         }
 
-        public CAbstract(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
+        public CAbstract(ObjectContext parent, String name, IClass<?> declared, Object value) {
             this.field = Optional.empty();
             this.parent = parent;
             this.name = name;
@@ -132,7 +150,7 @@ public class ScanWalker {
         }
 
         @Override
-        public ObjectScanner.ObjectContext parent() {
+        public ObjectContext parent() {
             return parent;
         }
 
@@ -173,25 +191,17 @@ public class ScanWalker {
 
         @Override
         public String typePath() {
-            return typePath.apply(() -> isRoot() ? typeName() : parent.typePath() + TYPE_PATH + typeName());
+            return typePath.apply(() -> WalkerUtils.typePath(this));
         }
 
         @Override
         public String namePath() {
-            return namePath.apply(() -> {
-                if(parent != null) {
-                    String head = parent.namePath();
-                    if(head != null) {
-                        return head + NAME_PATH + name();
-                    }
-                }
-                return name();
-            });
+            return namePath.apply(() -> WalkerUtils.namePath(this));
         }
 
         @Override
         public String idPath() {
-            return idPath.apply(() -> isRoot() ? String.valueOf(id()) : parent.idPath() + ID_PATH + id());
+            return idPath.apply(() -> WalkerUtils.idPath(this));
         }
 
         @Override
@@ -211,7 +221,7 @@ public class ScanWalker {
 
         @Override
         public boolean isDefined() {
-            return value == ScanSupport.UNDEFINED;
+            return value == WalkerUtils.UNDEFINED;
         }
 
         @Override
@@ -231,19 +241,19 @@ public class ScanWalker {
 
     }
 
-    private class CObject extends CAbstract implements ObjectScanner.ObjectContext {
-        public CObject(ObjectScanner.ObjectContext parent, IField field, Object value) {
+    private class CObject extends CAbstract implements ObjectContext {
+        public CObject(ObjectContext parent, IField field, Object value) {
             super(parent, field, value);
         }
 
-        public CObject(ObjectScanner.ObjectContext parent, String name, IClass<?> declared, Object value) {
+        public CObject(ObjectContext parent, String name, IClass<?> declared, Object value) {
             super(parent, name, declared, value);
         }
 
         @Override
-        public void accept(ObjectScanner.ObjectListener listener) {
+        public void accept(ObjectVisitor visitor) {
             try {
-                if (!listener.enterObject(this)) {
+                if (!visitor.visitObject(this)) {
                     return;
                 }
 
@@ -255,26 +265,25 @@ public class ScanWalker {
                         .stream()
                         .sort(Comparator.comparing(IElement::name))
                         .map(field -> context(this, field))
-                        .each(context -> context.accept(listener));
+                        .each(context -> context.accept(visitor));
                 }
             } finally {
                 mark(this);
-                listener.leaveObject(this);
             }
         }
 
     }
 
-    private class CArray extends CAbstract implements ObjectScanner.ArrayContext {
+    private class CArray extends CAbstract implements ArrayContext {
 
         private final NativeArray array;
 
-        public CArray(ObjectScanner.ObjectContext parent, IField field, Object value) {
+        public CArray(ObjectContext parent, IField field, Object value) {
             super(parent, field, value);
             this.array = NativeArray.wrap(value());
         }
 
-        public CArray(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
+        public CArray(ObjectContext parent, String name, IClass<?> type, Object value) {
             super(parent, name, type, value);
             this.array = NativeArray.wrap(value());
         }
@@ -295,35 +304,34 @@ public class ScanWalker {
         }
 
         @Override
-        public void accept(ObjectScanner.ObjectListener listener) {
+        public void accept(ObjectVisitor visitor) {
             try {
-                if (!listener.enterArray(this)) {
+                if (!visitor.visitArray(this)) {
                     return;
                 }
                 if (!isNull()) {
                     IClass<?> cmp = component();
                     for (int index = 0, n = size(); index < n; index++) {
-                        context(this, "[" + index + "]", cmp, at(index)).accept(listener);
+                        context(this, "[" + index + "]", cmp, at(index)).accept(visitor);
                     }
                 }
             } finally {
                 mark(this);
-                listener.leaveArray(this);
             }
         }
     }
 
-    private class CList extends CAbstract implements ObjectScanner.ListContext {
+    private class CList extends CAbstract implements ListContext {
 
         private final List<?> list;
         private final LazyFunction<IClass<?>> component = LazyReference.concurrent();
 
-        public CList(ObjectScanner.ObjectContext parent, IField field, Object value) {
+        public CList(ObjectContext parent, IField field, Object value) {
             super(parent, field, value);
             this.list = (List<?>)value();
         }
 
-        public CList(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
+        public CList(ObjectContext parent, String name, IClass<?> type, Object value) {
             super(parent, name, type, value);
             this.list = (List<?>)value();
         }
@@ -336,10 +344,10 @@ public class ScanWalker {
         @Override
         public IClass<?> component() {
             return component.apply(() -> {
-                if(ScanSupport.isList(type())) {
+                if(WalkerUtils.isList(type())) {
                     return type().params().get(0);
                 }
-                if(ScanSupport.isList(actual())) {
+                if(WalkerUtils.isList(actual())) {
                     return actual().params().get(0);
                 }
                 return IClass.OBJECT;
@@ -357,35 +365,34 @@ public class ScanWalker {
         }
 
         @Override
-        public void accept(ObjectScanner.ObjectListener listener) {
+        public void accept(ObjectVisitor visitor) {
             try {
-                if (!listener.enterList(this)) {
+                if (!visitor.visitList(this)) {
                     return;
                 }
                 if (!isNull()) {
                     IClass<?> cmp = component();
                     for (int index = 0, n = size(); index < n; index++) {
-                        context(this, "[" + index + "]", cmp, at(index)).accept(listener);
+                        context(this, "[" + index + "]", cmp, at(index)).accept(visitor);
                     }
                 }
             } finally {
                 mark(this);
-                listener.leaveList(this);
             }
         }
     }
 
-    private class CCollection extends CAbstract implements ObjectScanner.CollectionContext {
+    private class CCollection extends CAbstract implements CollectionContext {
 
         private final Collection<?> collection;
         private final LazyFunction<IClass<?>> component = LazyReference.concurrent();
 
-        public CCollection(ObjectScanner.ObjectContext parent, IField field, Object value) {
+        public CCollection(ObjectContext parent, IField field, Object value) {
             super(parent, field, value);
             this.collection = (Collection<?>)value();
         }
 
-        public CCollection(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
+        public CCollection(ObjectContext parent, String name, IClass<?> type, Object value) {
             super(parent, name, type, value);
             this.collection = (Collection<?>)value();
         }
@@ -398,10 +405,10 @@ public class ScanWalker {
         @Override
         public IClass<?> component() {
             return component.apply(() -> {
-                if(ScanSupport.isCollection(type())) {
+                if(WalkerUtils.isCollection(type())) {
                     return type().params().get(0);
                 }
-                if(ScanSupport.isCollection(actual())) {
+                if(WalkerUtils.isCollection(actual())) {
                     return actual().params().get(0);
                 }
                 return IClass.OBJECT;
@@ -419,9 +426,9 @@ public class ScanWalker {
         }
 
         @Override
-        public void accept(ObjectScanner.ObjectListener listener) {
+        public void accept(ObjectVisitor listener) {
             try {
-                if (!listener.enterCollection(this)) {
+                if (!listener.visitCollection(this)) {
                     return;
                 }
                 if (!isNull()) {
@@ -432,23 +439,22 @@ public class ScanWalker {
                 }
             } finally {
                 mark(this);
-                listener.leaveCollection(this);
             }
         }
     }
 
-    private class CMap extends CAbstract implements ObjectScanner.MapContext {
+    private class CMap extends CAbstract implements MapContext {
 
         private final Map<?,?> map;
         private final LazyFunction<IClass<?>> keyComponent = LazyReference.concurrent();
         private final LazyFunction<IClass<?>> valueComponent = LazyReference.concurrent();
 
-        public CMap(ObjectScanner.ObjectContext parent, IField field, Object value) {
+        public CMap(ObjectContext parent, IField field, Object value) {
             super(parent, field, value);
             this.map = (Map<?,?>)value();
         }
 
-        public CMap(ObjectScanner.ObjectContext parent, String name, IClass<?> type, Object value) {
+        public CMap(ObjectContext parent, String name, IClass<?> type, Object value) {
             super(parent, name, type, value);
             this.map = (Map<?,?>)value();
         }
@@ -466,10 +472,10 @@ public class ScanWalker {
         @Override
         public IClass<?> keyComponent() {
             return keyComponent.apply(() -> {
-                if(ScanSupport.isMap(type())) {
+                if(WalkerUtils.isMap(type())) {
                     return type().params().get(0);
                 }
-                if(ScanSupport.isMap(actual())) {
+                if(WalkerUtils.isMap(actual())) {
                     return actual().params().get(0);
                 }
                 return IClass.OBJECT;
@@ -479,10 +485,10 @@ public class ScanWalker {
         @Override
         public IClass<?> valueComponent() {
             return valueComponent.apply(() -> {
-                if(ScanSupport.isMap(type())) {
+                if(WalkerUtils.isMap(type())) {
                     return type().params().get(1);
                 }
-                if(ScanSupport.isMap(actual())) {
+                if(WalkerUtils.isMap(actual())) {
                     return actual().params().get(1);
                 }
                 return IClass.OBJECT;
@@ -495,20 +501,38 @@ public class ScanWalker {
         }
 
         @Override
-        public void accept(ObjectScanner.ObjectListener listener) {
+        public void accept(ObjectVisitor listener) {
             try {
-                if (!listener.enterMap(this)) {
+                if (!listener.visitMap(this)) {
                     return;
                 }
                 if (!isNull()) {
-                    IClass<?> cmp = valueComponent();
-                    for (Map.Entry<?, ?> entry : map.entrySet()) {
-                        context(this, "[" + entry.getKey() + "]", cmp, entry.getValue()).accept(listener);
+                    if(map instanceof SortedMap || map instanceof LinkedHashMap) {
+                        exactIterate(listener);
+                    } else {
+                        lexicalIterate(listener);
+
                     }
                 }
             } finally {
                 mark(this);
-                listener.leaveMap(this);
+            }
+        }
+
+        private void exactIterate(ObjectVisitor listener) {
+            IClass<?> cmp = valueComponent();
+            for (Map.Entry<?, ?> entry : map.entrySet()) {
+                context(this, "[" + entry.getKey() + "]", cmp, entry.getValue()).accept(listener);
+            }
+        }
+
+        private void lexicalIterate(ObjectVisitor listener) {
+            IClass<?> cmp = valueComponent();
+            ArrayList<?> keys = new ArrayList<>(map.keySet());
+
+            keys.sort(Comparator.comparing(String::valueOf));
+            for(Object key : keys) {
+                context(this, "[" + key + "]", cmp, map.get(key)).accept(listener);
             }
         }
     }

+ 101 - 0
assira/src/main/java/net/ranides/assira/reflection/visit/WalkerContexts.java

@@ -0,0 +1,101 @@
+package net.ranides.assira.reflection.visit;
+
+import net.ranides.assira.reflection.IClass;
+
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+public class WalkerContexts {
+
+    public interface ObjectContext {
+
+        void accept(ObjectVisitor visitor);
+
+        ObjectContext parent();
+
+        String typeName();
+
+        String name();
+
+        IClass<?> type();
+
+        IClass<?> actual();
+
+        Object value();
+
+        long id();
+
+        long ref();
+
+        String typePath();
+
+        String namePath();
+
+        String idPath();
+
+        boolean isRoot();
+
+        boolean isField();
+
+        boolean isVisited();
+
+        boolean isDefined();
+
+        boolean isPrimitive();
+
+        boolean isNull();
+
+        boolean isSubclass();
+
+        default boolean isSubclass(IClass<?> type) {
+            return type().isSubclass(type) || actual().isSubclass(type);
+        }
+
+        default String contextName() {
+            return getClass().getSimpleName();
+        }
+    }
+
+    public interface ArrayContext extends ObjectContext {
+
+        IClass<?> component();
+
+        int size();
+
+        Object at(int index);
+
+    }
+
+    public interface ListContext extends ObjectContext {
+
+        IClass<?> component();
+
+        int size();
+
+        Object at(int index);
+
+    }
+
+    public interface CollectionContext extends ObjectContext {
+
+        IClass<?> component();
+
+        int size();
+
+        Iterator<?> iterator();
+
+    }
+
+    public interface MapContext extends ObjectContext {
+
+        IClass<?> keyComponent();
+
+        IClass<?> valueComponent();
+
+        int size();
+
+        Set<? extends Map.Entry<?, ?>> entries();
+
+    }
+}

+ 80 - 0
assira/src/main/java/net/ranides/assira/reflection/visit/WalkerUtils.java

@@ -0,0 +1,80 @@
+package net.ranides.assira.reflection.visit;
+
+import net.ranides.assira.generic.TypeToken;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.visit.WalkerContexts.ObjectContext;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+class WalkerUtils {
+
+    public static final Undefined UNDEFINED = new Undefined();
+
+    public static final IClass<?> LIST = new TypeToken<List<?>>() {};
+    public static final IClass<?> COLLECTION = new TypeToken<Collection<?>>() {};
+    public static final IClass<?> MAP = new TypeToken<Map<?,?>>() {};
+
+    public static final String PATH_TYPE = "/";
+    public static final String PATH_NAME = "/";
+    public static final String PATH_ID = ".";
+
+    public static boolean isList(IClass<?> actual) {
+        return actual != IClass.NULL && actual.isSubclass(LIST);
+    }
+
+    public static boolean isList(IClass<?> declared, IClass<?> actual) {
+        return isList(declared) || isList(actual);
+    }
+
+    public static boolean isCollection(IClass<?> actual) {
+        return actual != IClass.NULL && actual.isSubclass(COLLECTION);
+    }
+
+    public static boolean isCollection(IClass<?> declared, IClass<?> actual) {
+        return isCollection(declared) || isCollection(actual);
+    }
+
+    public static boolean isMap(IClass<?> actual) {
+        return actual != IClass.NULL && actual.isSubclass(MAP);
+    }
+
+    public static boolean isMap(IClass<?> declared, IClass<?> actual) {
+        return isMap(declared) || isMap(actual);
+    }
+
+    public static boolean isArray(IClass<?> actual) {
+        return actual != IClass.NULL && actual.isArray();
+    }
+
+    public static boolean isArray(IClass<?> declared, IClass<?> actual) {
+        return isArray(declared) || isArray(actual);
+    }
+
+    public static String typePath(ObjectContext ctx) {
+        return ctx.isRoot() ? ctx.typeName() : ctx.parent().typePath() + PATH_TYPE + ctx.typeName();
+    }
+
+    public static String namePath(ObjectContext ctx) {
+        if(ctx.parent() != null) {
+            String head = ctx.parent().namePath();
+            if(head != null) {
+                return head + PATH_NAME + ctx.name();
+            }
+        }
+        return ctx.name();
+    }
+
+    public static String idPath(ObjectContext ctx) {
+        return ctx.isRoot() ? String.valueOf(ctx.id()) : ctx.parent().idPath() + PATH_ID + ctx.id();
+    }
+
+    public static class Undefined {
+        @Override
+        public String toString() {
+            return "<undefined>";
+        }
+    }
+
+}

+ 1 - 1
assira/src/main/java/net/ranides/assira/text/ResolveLocale.java

@@ -725,7 +725,7 @@ public final class ResolveLocale {
         public FText(ResolvePattern resolver, String options, Locale locale) {
             this.resolver = resolver;
             this.locale = locale;
-            Matcher hit = Pattern.compile("(?<padd>[-+])?(?<min>[0-9]+)(?:.(?<max>[0-9]+))?(?<upper>[uU])?(?<lower>[lLsS])?").matcher(options);
+            Matcher hit = Pattern.compile("(?<padd>[-+])?(?<min>[0-9]+)?(?:.(?<max>[0-9]+))?(?<upper>[uU])?(?<lower>[lLsS])?").matcher(options);
             if(!hit.matches()) {
                 throw new IllegalArgumentException("Invalid text format: " + options);
             }

+ 44 - 9
assira/src/test/java/net/ranides/assira/reflection/ObjectScannerTest.java

@@ -3,16 +3,19 @@ package net.ranides.assira.reflection;
 import lombok.Builder;
 import lombok.Data;
 import lombok.Singular;
-import net.ranides.assira.functional.Branch;
+import net.ranides.assira.reflection.visit.ObjectVisitor.FormatRules;
+import net.ranides.assira.reflection.visit.ObjectVisitor.FormatVisitor;
+import net.ranides.assira.reflection.visit.ObjectWalker;
+import net.ranides.assira.reflection.visit.ObjectVisitor;
+import net.ranides.assira.reflection.visit.WalkerContexts.ObjectContext;
 import org.junit.Test;
 
+import java.io.StringWriter;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
 
-import static org.junit.Assert.*;
-
-public class ObjectScannerTest {
+public class ObjectWalkerTest {
 
     @Test
     public void basic() {
@@ -23,12 +26,15 @@ public class ObjectScannerTest {
             .name("Adam")
             .point(17, new Point(21,6,4))
             .point(18, new Point(21,6,4))
+            .point(3, new Point(9,9,9))
+            .point(8, new Point(9,9,9))
+            .point(21, new Point(9,9,9))
             .point(88, new Point(21,6,4))
             .build();
 
-        ObjectScanner.walk(foo, new ObjectScanner.AbstractObjectVisitor() {
+        ObjectWalker.walk(foo, new ObjectVisitor.AbstractVisitor() {
             @Override
-            public boolean visitObject(ObjectScanner.ObjectContext ctx) {
+            public boolean visitObject(ObjectContext ctx) {
                 System.out.printf(" - [%-12s] %-40s | %s = %s%n",
                     ctx.getClass().getSimpleName(),
                     ctx.typePath(),
@@ -45,6 +51,34 @@ public class ObjectScannerTest {
         });
     }
 
+    @Test
+    public void format() {
+        Foo foo = Foo.builder()
+            .bar(new Bar("456", "0xFAD"))
+            .name("John")
+            .name("Jenny")
+            .name("Adam")
+            .point(17, new Point(21,6,4))
+            .point(18, new Point(21,6,4))
+            .point(3, new Point(9,9,9))
+            .point(8, new Point(9,9,9))
+            .point(21, new Point(9,9,9))
+            .point(88, new Point(21,6,4))
+            .build();
+
+        FormatVisitor visitor = new FormatRules()
+            .hint(Object.class, "{namePath,text} = {value}")
+            .term(ObjectContext::isNull, "{namePath,text} = <null>")
+            .term(String.class, "{namePath,text} = '{value}'")
+            .visitor(new StringWriter());
+
+        ObjectWalker.walk(foo, "foo1", visitor);
+        ObjectWalker.walk(foo, "foo2", visitor);
+
+        System.out.println("===");
+        System.out.println(visitor.toString());
+    }
+
     @Test
     public void visitors() {
         Foo foo = Foo.builder()
@@ -57,9 +91,9 @@ public class ObjectScannerTest {
             .point(88, new Point(21,6,4))
             .build();
 
-        ObjectScanner.walk(foo, new ObjectScanner.AbstractObjectVisitor() {
+        ObjectWalker.walk(foo, new ObjectVisitor.AbstractVisitor() {
             @Override
-            public boolean visitObject(ObjectScanner.ObjectContext ctx) {
+            public boolean visitObject(ObjectContext ctx) {
                 System.out.printf(" - [%-12s] %-40s | %s = %s%n",
                     ctx.getClass().getSimpleName(),
                     ctx.typePath(),
@@ -102,7 +136,7 @@ public class ObjectScannerTest {
 
         List<Foo> list = Arrays.asList(foo1, foo2, foo1, foo3);
 
-        ObjectScanner.walk(list, ctx -> {
+        ObjectWalker.walk(list, ctx -> {
             System.out.printf(" - [%-12s] %8s %-40s | %s = %s%n",
                 ctx.getClass().getSimpleName(),
                 ctx.isVisited(),
@@ -126,6 +160,7 @@ public class ObjectScannerTest {
         private final Bar bar;
         @Singular
         private final List<String> names;
+        private final Bar empty;
         @Singular
         private final Map<Integer, Point> points;
     }