Prechádzať zdrojové kódy

new: IteratorUtils#remove
new: MapBuilder
change: MultiMap#replace
draft: URIBuilder
draft: CJarHandler

Ranides Atterwim 9 rokov pred
rodič
commit
a36e3e859c

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 613 - 604
assira/src/main/java/net/ranides/assira/collection/iterators/IntIteratorUtils.java


+ 9 - 0
assira/src/main/java/net/ranides/assira/collection/iterators/IteratorUtils.java

@@ -180,6 +180,15 @@ public final class IteratorUtils {
         }
         return a.hasNext() ? +1 : b.hasNext() ? -1 : 0;
     }
+    
+    public static <T> void remove(Iterator<T> iterator, Predicate<? super T> predicate) {
+        while(iterator.hasNext()) {
+            T v = iterator.next();
+            if(predicate.test(v)) {
+                iterator.remove();
+            }
+        }
+    }
 
     private static final class Adapter<S, T> implements Iterator<T> {
     

+ 108 - 0
assira/src/main/java/net/ranides/assira/collection/maps/MapBuilder.java

@@ -0,0 +1,108 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.collection.maps;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class MapBuilder<K,V> {
+    
+    private final Map<K,V> map;
+
+    public MapBuilder(Map<K, V> target) {
+        this.map = target;
+    }
+    
+    public Map<K,V> map() {
+        return map;
+    }
+    
+    public MultiMap<K,V> multimap() {
+        return (MultiMap<K,V>)map;
+    }
+    
+    public MapBuilder<K,V> put(K key, V value) {
+        map.put(key, value);
+        return this;
+    }
+    
+    public MapBuilder<K,V> putAll(Map<? extends K, ? extends V> values) {
+        map.putAll(values);
+        return this;
+    }
+
+    public MapBuilder<K,V> putIfAbsent(K key, V value) {
+        map.putIfAbsent(key, value);
+        return this;
+    }
+    
+    public MapBuilder<K,V> replace(K key, V value) {
+        map.replace(key, value);
+        return this;
+    }
+    
+    public MapBuilder<K,V> replace(K key, V oldValue, V newValue) {
+        map.replace(key, oldValue, newValue);
+        return this;
+    }
+    
+    public MapBuilder<K,V> replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
+        map.replaceAll(function);
+        return this;
+    }
+      
+    public MapBuilder<K,V> merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
+        map.merge(key, value, remappingFunction);
+        return this;
+    }
+    
+    public MapBuilder<K,V> remove(Object key) {
+        map.remove(key);
+        return this;
+    }
+    
+    public MapBuilder<K,V> remove(Object key, Object value) {
+        map.remove(key, value);
+        return this;
+    }
+    
+    public MapBuilder<K,V> clear() {
+        map.clear();
+        return this;
+    }
+    
+    public MapBuilder<K,V> forEach(BiConsumer<? super K, ? super V> action) {
+        map.forEach(action);
+        return this;
+    }
+        
+    public MapBuilder<K,V> putAll(K key, Iterable<? extends V> values) {
+        ((MultiMap<K,V>)map).putAll(key, values);
+        return this;
+    }
+    
+    public MapBuilder<K,V> replaceAll(K key, Collection<V> values) {
+        ((MultiMap<K,V>)map).replaceAll(key, values);
+        return this;
+    }
+
+    public MapBuilder<K,V> removeAll(Object key) {
+        if(map instanceof MultiMap) {
+            ((MultiMap<K,V>)map).removeAll(key);
+        } else {
+            map.remove(key);
+        }
+        return this;
+    }
+    
+}

+ 80 - 59
assira/src/main/java/net/ranides/assira/collection/maps/MultiMap.java

@@ -1,59 +1,80 @@
-/*
- * @author Ranides Atterwim <ranides@gmail.com>
- * @copyright Ranides Atterwim
- * @license WTFPL
- * @url http://ranides.net/projects/assira
- */
-package net.ranides.assira.collection.maps;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Map;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.function.BinaryOperator;
-import net.ranides.assira.collection.sets.HashSet;
-
-/**
- *
- * @author Ranides Atterwim <ranides@gmail.com>
- */
-public interface MultiMap<K,V> extends Map<K,V> {
-    
-    @SuppressWarnings("rawtypes")
-    MultiMap EMPTY = new MapUtils.EmptyMM();
-    
-    Collection<V> getAll(Object key);
-    
-    default V get(Object key, BinaryOperator<V> fold) {
-        return getAll(key).stream().reduce(fold).orElseThrow(()->new NoSuchElementException());
-    }
-    
-    default boolean containsEntry(Object key, Object value) {
-        return getAll(key).contains(value);
-    }
-
-    default void putAll(K key, Iterable<? extends V> values) {
-        for(V value : values) {
-            put(key, value);
-        }
-    }
-
-    default Collection<V> removeAll(Object key) {
-        Collection<V> prev = new ArrayList<>();
-        V last;
-        while(null != (last=remove(key))) {
-            prev.add(last);
-        }
-        return prev;
-    }
-    
-    default Set<K> uniqueKeySet() {
-        return new HashSet<>(keySet());
-    }
-
-    @SuppressWarnings("unchecked")
-    static <K,V> MultiMap<K,V> empty() {
-        return EMPTY;
-    }
-}
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.collection.maps;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.BinaryOperator;
+import net.ranides.assira.collection.sets.HashSet;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public interface MultiMap<K,V> extends Map<K,V> {
+    
+    @SuppressWarnings("rawtypes")
+    MultiMap EMPTY = new MapUtils.EmptyMM();
+    
+    Collection<V> getAll(Object key);
+    
+    default V get(Object key, BinaryOperator<V> fold) {
+        return getAll(key).stream().reduce(fold).orElseThrow(()->new NoSuchElementException());
+    }
+    
+    default boolean containsEntry(Object key, Object value) {
+        return getAll(key).contains(value);
+    }
+
+    default void putAll(K key, Iterable<? extends V> values) {
+        for(V value : values) {
+            put(key, value);
+        }
+    }
+    
+    @Override
+    default V replace(K key, V value) {
+        V prev;
+        if (((prev = get(key)) != null) || containsKey(key)) {
+            removeAll(key);
+            put(key, value);
+        }
+        return prev;
+    }
+    
+    default Collection<V> replaceAll(K key, Collection<V> values) {
+        Collection<V> prev = Collections.emptyList();
+        if (containsKey(key)) {
+            prev = removeAll(key);
+            putAll(key, values);
+        }
+        return prev;
+    }
+
+    default Collection<V> removeAll(Object key) {
+        Collection<V> prev = new ArrayList<>();
+        V last;
+        while(null != (last=remove(key))) {
+            prev.add(last);
+        }
+        return prev;
+    }
+    
+    default Set<K> uniqueKeySet() {
+        return new HashSet<>(keySet());
+    }
+
+    @SuppressWarnings("unchecked")
+    static <K,V> MultiMap<K,V> empty() {
+        return EMPTY;
+    }
+    
+}

+ 182 - 18
assira/src/main/java/net/ranides/assira/io/uri/URIBuilder.java

@@ -7,11 +7,25 @@
 package net.ranides.assira.io.uri;
 
 import java.net.URI;
-import java.net.URL;
+import java.net.URISyntaxException;
+import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+import net.ranides.assira.collection.maps.AMap;
+import net.ranides.assira.collection.maps.MapBuilder;
 import net.ranides.assira.collection.maps.MultiMap;
+import net.ranides.assira.generic.CompareUtils;
+import net.ranides.assira.text.ResolveUtils;
+import net.ranides.assira.text.StringTraits;
+import net.ranides.assira.text.StringUtils;
 
 /**
  *
@@ -26,100 +40,134 @@ public class URIBuilder {
     private List<String> path;
     
     private String fragment;
-    private MultiMap<String,String> params;
+    private ParamMap params;
     
     private String login;
     private String password;
     
-    public static URIBuilder from(String value) {
-        return new URIBuilder();
+    public URIBuilder() {
+        
     }
     
-    public static URIBuilder from(URI value) {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public URIBuilder(String uri) {
+        this(parse(uri));
     }
     
-    public static URIBuilder from(URL value) {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public URIBuilder(URI uri) {
+        scheme = uri.getScheme();
+        host = uri.getHost();
+        port = URIUtils.getPort(uri).orElse(null);
+        path = new ArrayList<>(Arrays.asList(uri.getPath().split("/")));
+        
+        fragment = uri.getFragment();
+        params = new ParamMap(uri.getQuery());
+        
+        login = URIUtils.getLogin(uri).orElse(null);
+        password = URIUtils.getPassword(uri).orElse(null);
     }
     
     public String login() {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return login;
     }
     
     public URIBuilder login(String value) {
+        this.login = value;
         return this;
     }
     
     public String password() {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return password;
     }
     
     public URIBuilder password(String value) {
+        this.password = value;
         return this;
     }
     
     public String host() {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return host;
     }
     
     public URIBuilder host(String value) {
+        this.host = value;
         return this;
     }
     
-    public Optional<String> fragment() {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public String fragment() {
+        return fragment;
     }
     
     public URIBuilder fragment(String value) {
+        this.fragment = value;
         return this;
     }
     
     public String path() {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return StringUtils.join(path, "/");
     }
     
     public URIBuilder path(String value) {
+        path.add(value);
         return this;
     }
     
     public URIBuilder path(String... values) {
+        path.addAll(Arrays.asList(values));
         return this;
     }
     
     public Optional<Integer> port() {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return Optional.ofNullable(port);
     }
     
     public URIBuilder port(String value) {
+        this.port = Integer.parseInt(value);
         return this;
     }
     
     public URIBuilder port(int value) {
+        this.port = value;
         return this;
     }
     
     public List<String> param(String name) {
-        throw new UnsupportedOperationException("Not supported yet.");
+        return params.getAll(name);
     }
     
     public URIBuilder param(String name, String value) {
+        params.put(name, value);
         return this;
     }
     
-    public Map<String,String> params() {
-        throw new UnsupportedOperationException("Not supported yet.");
+    public URIBuilder param(String name, List<String> value) {
+        params.putAll(name, value);
+        return this;
+    }
+    
+    public MapBuilder<String,String> params() {
+        return new MapBuilder<>(params);
     }
     
     public URIBuilder params(Map<String,String> value) {
+        params.putAll(value);
         return this;
     }
     
+    public String scheme() {
+        return scheme;
+    }
+    
     public URIBuilder scheme(String value) {
+        this.scheme = value;
         return this;
     }
     
     public URIBuilder resolve(Object context) {
+        // @todo (assira #2) URIBuilder#resolve
+        // tutaj rozwijamy wszystkie zmienne w stringach w formie #{variable}
+        host = ResolveUtils.format(host, context);
+        fragment = ResolveUtils.format(fragment, context);
+        // ...
         return this;
     }
     
@@ -127,4 +175,120 @@ public class URIBuilder {
         throw new UnsupportedOperationException("Not supported yet.");
     }
     
+    private static URI parse(String uri) {
+        try {
+            return new URI(uri);
+        } catch (URISyntaxException cause) {
+            throw new IllegalArgumentException(cause);
+        }
+    }
+    private static String paramName(String[] array) {
+        return array.length > 0 ? array[0] : "";
+    }
+    
+    private static String paramValue(String[] array) {
+        return array.length > 1 ? array[1] : "";
+    }
+    
+    private static final class ParamEntry extends AbstractMap.SimpleEntry<String,String> {
+        
+        
+        public ParamEntry(String[] p) {
+            super(paramName(p), paramValue(p));
+        }
+        
+        public ParamEntry(String key, String value) {
+            super(key, value);
+        }
+        
+    }
+    
+    private static final class ParamSet extends ArrayList<Entry<String,String>> implements Set<Entry<String,String>> {
+        
+    }
+    
+    private static final class ParamMap extends AMap<String,String> implements MultiMap<String,String> {
+        
+        private final ParamSet set = new ParamSet();
+        
+        public ParamMap(String query) {
+            if(StringTraits.isEmpty(query)) {
+                return;
+            }
+            Arrays
+                .stream(query.split("&"))
+                .map(v -> v.split("="))
+                .forEachOrdered(p -> set.add(new ParamEntry(p)) );
+        }
+
+        @Override
+        public Set<Entry<String, String>> entrySet() {
+            return set;
+        }
+
+        @Override
+        public int size() {
+            return set.size();
+        }
+
+        @Override
+        public boolean containsKey(Object key) {
+            return set.stream().anyMatch(e -> e.getKey().equals(key));
+        }
+
+        @Override
+        public boolean containsValue(Object value) {
+            return set.stream().anyMatch(e -> e.getValue().equals(value));
+        }
+
+        @Override
+        public String get(Object key) {
+            return set.stream().filter(e -> e.getKey().equals(key)).findAny().map(e -> e.getValue()).orElse(null);
+        }
+
+        @Override
+        public List<String> getAll(Object key) {
+            return set.stream().filter(e -> e.getKey().equals(key)).map(e -> e.getValue()).collect(Collectors.toList());
+        }
+
+        @Override
+        public String remove(Object key) {
+            Iterator<Entry<String, String>> i = set.iterator();
+            while(i.hasNext()) {
+                Entry<String, String> e = i.next();
+                if(e.getKey().equals(key)) {
+                    i.remove();
+                    return e.getValue();
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public Collection<String> removeAll(Object key) {
+            List<String> out = new ArrayList<>();
+            Iterator<Entry<String, String>> i = set.iterator();
+            while(i.hasNext()) {
+                Entry<String, String> e = i.next();
+                if(e.getKey().equals(key)) {
+                    i.remove();
+                    out.add(e.getValue());
+                }
+            }
+            return out;
+        }
+        
+        @Override
+        public String put(String key, String value) {
+            return super.put(key, value); //To change body of generated methods, choose Tools | Templates.
+        }
+
+        @Override
+        public boolean containsEntry(Object key, Object value) {
+            return set.stream().anyMatch(e -> e.getKey().equals(key) && CompareUtils.equals(e.getValue(),value));
+        }
+        
+        
+    }
+    
 }

+ 7 - 7
assira/src/main/java/net/ranides/assira/io/uri/impl/CJarHandler.java

@@ -11,7 +11,6 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.nio.charset.Charset;
 import java.time.Instant;
 import java.util.Arrays;
@@ -24,13 +23,13 @@ import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.collection.query.CQueryBuilder;
 import net.ranides.assira.io.IOHandle;
 import net.ranides.assira.io.InputStreamWrapper;
+import net.ranides.assira.io.uri.URIBuilder;
 import net.ranides.assira.io.uri.URIFlags;
 import net.ranides.assira.io.uri.URIHandle;
 import net.ranides.assira.io.uri.URIHandler;
 import net.ranides.assira.io.uri.URIResolver;
 import net.ranides.assira.io.uri.URITime;
 import net.ranides.assira.io.uri.URIUtils;
-import net.ranides.assira.trace.ExceptionUtils;
 
 
 // @todo (assira #1) uri: JAR Handler
@@ -59,11 +58,11 @@ public class CJarHandler implements URIHandler {
     }
     
     private static URI concat(File jar, String file) {
-        try {
-            return new URI("jar", jar+"?file="+file, null);
-        } catch (URISyntaxException cause) {
-            throw ExceptionUtils.rethrow(cause);
-        }
+        return new URIBuilder()
+            .scheme("jar")
+            .path(jar.toString())
+            .param("file", file)
+            .build();
     }
     
     private class CJarHandle extends CHandle {
@@ -191,6 +190,7 @@ public class CJarHandler implements URIHandler {
 
         @Override
         public InputStream istream() throws IOException {
+            man.open(jar);
             InputStream is = man.shared(jar).getInputStream(entry.get());
             return new InputStreamWrapper(is){
                 @Override

+ 170 - 53
assira/src/main/java/net/ranides/assira/reflection/Resolver.java

@@ -9,11 +9,13 @@ package net.ranides.assira.reflection;
 
 import java.io.Serializable;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import net.ranides.assira.collection.arrays.NativeArray;
+import net.ranides.assira.generic.SerializationUtils;
 import net.ranides.assira.generic.Wrapper;
 import net.ranides.assira.reflection.impl.RCompareUtils;
 import net.ranides.assira.text.StringTraits;
@@ -26,14 +28,17 @@ import net.ranides.assira.text.StringUtils;
 public class Resolver implements Serializable {
     
     private static final long serialVersionUID = 1L;
-
+    
     private static final Pattern RE_INDEX = Pattern.compile("\\[([^\\]]+)\\]");
     
+    private final String expression;
+    
     private final String[] tokens;
     
     private Resolver(String expression) {
         assert expression != null;
         String[] array = StringUtils.replace(expression, RE_INDEX, m -> "."+m.group(1)+".").split("\\.");
+        this.expression = expression;
         this.tokens = Arrays.stream(array).filter(p->!p.isEmpty()).toArray(String[]::new);
     }
 
@@ -83,78 +88,129 @@ public class Resolver implements Serializable {
         return compile(expression).bind(that);
     }
     
+    public String expression() {
+        return expression;
+    }
+    
     public Object get(Object that) {
-        for(String token :tokens) {
-            that = iget(that, token);
+        ErrorHandler eh = new ErrorHandler();
+        for(int i=0,n=tokens.length-1; i<n; i++) {
+            that = iget(eh, that, tokens[i]);
+            if(eh.isError()) {
+                return eh.handleNoValuePath();
+            }
+        }
+        that = iget(eh, that, tokens[tokens.length-1]);
+        if(eh.isError()) {
+            eh.handleNoValue();
         }
         return that;
     }
     
     public void set(Object that, Object value) {
+        ErrorHandler eh = new ErrorHandler();
         for(int i=0,n=tokens.length-1; i<n; i++) {
-            that = iget(that, tokens[i]);
+            that = iget(eh, that, tokens[i]);
+            if(eh.isError()) {
+                eh.handleNoValuePath();
+            }
+        }
+        iset(eh, that, tokens[tokens.length-1], value);
+        if(eh.isError()) {
+            eh.handleNoSet();
         }
-        iset(that, tokens[tokens.length-1], value);
     }
     
     public Object replace(Object that, Object value) {
+        ErrorHandler eh = new ErrorHandler();
         for(int i=0,n=tokens.length-1; i<n; i++) {
-            that = iget(that, tokens[i]);
+            that = iget(eh, that, tokens[i]);
+            if(eh.isError()) {
+                eh.handleNoValuePath();
+            }
+        }
+        Object out = ireplace(eh, that, tokens[tokens.length-1], value);
+        if(eh.isError()) {
+            return eh.handleNoReplace();
         }
-        return ireplace(that, tokens[tokens.length-1], value);
+        return out;
     }
     
     public IClass<?> type(Object that) {
+        ErrorHandler eh = new ErrorHandler();
         IClass<?> type = IClass.typefor(that);
         for(int i=0,n=tokens.length-1; i<n; i++) {
-            type = itype(type, tokens[i]);
+            type = itype(eh, type, tokens[i]);
+            if(eh.isError()) {
+                return eh.handleNoTypePath();
+            }
         }
-        return itype(type, tokens[tokens.length-1]);
+        IClass<?> out = itype(eh, type, tokens[tokens.length-1]);
+        if(eh.isError()) {
+            return eh.handleNoType();
+        }
+        return out;
     }
     
     public Wrapper<Object> bind(Object that) {
         return new RWrapper(that);
     }
     
-    private Object iget(Object that, String name) {
+    private Object iget(ErrorHandler handler, Object that, String name) {
         for(Model m : MODELS) {
             if(m.matches(that)) {
-                return m.get(that, name);
+                return m.get(handler, that, name);
             }
         }
         throw new IllegalArgumentException("Illegal value: " + that);
     }
     
-    private void iset(Object that, String name, Object value) {
+    private void iset(ErrorHandler handler, Object that, String name, Object value) {
         for(Model m : MODELS) {
             if(m.matches(that)) {
-                m.set(that, name, value);
+                m.set(handler, that, name, value);
                 return;
             }
         }
         throw new IllegalArgumentException("Illegal value: " + that);
     }
     
-    private Object ireplace(Object that, String name, Object value) {
+    private Object ireplace(ErrorHandler handler, Object that, String name, Object value) {
         for(Model m : MODELS) {
             if(m.matches(that)) {
-                return m.replace(that, name, value);
+                return m.replace(handler, that, name, value);
             }
         }
         throw new IllegalArgumentException("Illegal value: " + that);
     }
     
-    private IClass<?> itype(IClass<?> that, String name) {
+    private IClass<?> itype(ErrorHandler handler, IClass<?> that, String name) {
         for(Model m : MODELS) {
             if(m.matchesType(that)) {
-                return m.type(that, name);
+                return m.type(handler, that, name);
             }
         }
         throw new IllegalArgumentException("Illegal value: " + that);
     }
     
+    private Object writeReplace() {
+        return SerializationUtils.proxy(this, expression);
+    }
+    
     private static final Model[] MODELS = Model.values();
     
+    public static class ResolveException extends IllegalArgumentException {
+        
+        public ResolveException(String message) {
+            super(message);
+        }
+
+        public ResolveException(Throwable cause) {
+            super(cause);
+        }
+
+    }
+    
     private enum Model {
         ARRAY(){
             @Override
@@ -166,19 +222,19 @@ public class Resolver implements Serializable {
                 return IAttribute.ARRAY.matches(that);
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler _h, Object that, String name) {
                 return NativeArray.wrap(that).get(Integer.parseInt(name));
             }
             @Override
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler _h, Object that, String name, Object value) {
                 NativeArray.wrap(that).set(Integer.parseInt(name), value);
             }
             @Override
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler _h, Object that, String name, Object value) {
                 return NativeArray.wrap(that).replace(Integer.parseInt(name), value);
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler _h, IClass<?> that, String name) {
                 return that.component();
             }
         },
@@ -192,17 +248,17 @@ public class Resolver implements Serializable {
                 return that!=IClass.NULL && RCompareUtils.isRawSuper(IClass.typeinfo(List.class), that);
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler _h, Object that, String name) {
                 return ((List<?>)that).get(Integer.parseInt(name));
             }
             @Override
             @SuppressWarnings("unchecked")
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler _h, Object that, String name, Object value) {
                 ((List<Object>)that).set(Integer.parseInt(name), value);
             }
             @Override
             @SuppressWarnings("unchecked")
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler _h, Object that, String name, Object value) {
                 List<Object> list = (List<Object>)that;
                 int index = Integer.parseInt(name);
                 Object prv = list.get(index);
@@ -210,7 +266,7 @@ public class Resolver implements Serializable {
                 return prv;
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler _h, IClass<?> that, String name) {
                 return that.params().get(0);
             }
         },
@@ -224,21 +280,21 @@ public class Resolver implements Serializable {
                 return that!=IClass.NULL && RCompareUtils.isRawSuper(IClass.typeinfo(Map.class), that);
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler _h, Object that, String name) {
                 return ((Map<?,?>)that).get(name);
             }
             @Override
             @SuppressWarnings("unchecked")
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler _h, Object that, String name, Object value) {
                 ((Map<String,Object>)that).put(name, value);
             }
             @Override
             @SuppressWarnings("unchecked")
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler _h, Object that, String name, Object value) {
                 return ((Map<String,Object>)that).put(name, value);
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler _h, IClass<?> that, String name) {
                 return that.params().get(1);
             }
         },
@@ -252,7 +308,7 @@ public class Resolver implements Serializable {
                 return that!=IClass.NULL && IClass.typeinfo(Matcher.class).isSuper(that);
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler _h, Object that, String name) {
                 if(StringTraits.isNumber(name)) {
                     return ((Matcher)that).group(Integer.parseInt(name));
                 } else {
@@ -260,15 +316,15 @@ public class Resolver implements Serializable {
                 }
             }
             @Override
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler _h, Object that, String name, Object value) {
                 throw new UnsupportedOperationException("Matcher is immutable.");
             }
             @Override
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler _h, Object that, String name, Object value) {
                 throw new UnsupportedOperationException("Matcher is immutable.");
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler _h, IClass<?> that, String name) {
                 return IClass.typeinfo(String.class);
             }
         },
@@ -282,19 +338,19 @@ public class Resolver implements Serializable {
                 return that!=IClass.NULL && IClass.typeinfo(CharSequence.class).isSuper(that);
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler _h, Object that, String name) {
                 return ((CharSequence)that).charAt(Integer.parseInt(name));
             }
             @Override
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler _h, Object that, String name, Object value) {
                 throw new UnsupportedOperationException("CharSequence is immutable.");
             }
             @Override
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler _h, Object that, String name, Object value) {
                 throw new UnsupportedOperationException("CharSequence is immutable.");
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler _h, IClass<?> that, String name) {
                 return IClass.typeinfo(char.class);
             }
         },
@@ -308,40 +364,62 @@ public class Resolver implements Serializable {
                 return that!=IClass.NULL;
             }
             @Override
-            public Object get(Object that, String name) {
+            public Object get(ErrorHandler handler, Object that, String name) {
                 BeanModel model = BeanModel.typefor(that);
                 if(model.properties().containsKey(name)) {
                     return model.property(name).get(that);
-                } else {
-                    return model.type().field(name).get(that);
+                } 
+                Iterator<IField> f = field(model, name);
+                if(f.hasNext()) {
+                    return f.next().get(that);
                 }
+                handler.failure();
+                return null;
+            }
+
+            private Iterator<IField> field(BeanModel model, String name) {
+                return model.type().fields().require(IAttribute.ANY).require(name).iterator();
             }
+            
             @Override
-            public void set(Object that, String name, Object value) {
+            public void set(ErrorHandler handler, Object that, String name, Object value) {
                 BeanModel model = BeanModel.typefor(that);
                 if(model.properties().containsKey(name)) {
                     model.property(name).set(that, value);
-                } else {
-                    model.type().field(name).set(that, value);
+                    return;
+                }
+                Iterator<IField> f = field(model, name);
+                if(f.hasNext()) {
+                    f.next().set(that, value);
+                    return;
                 }
+                handler.failure();
             }
             @Override
-            public Object replace(Object that, String name, Object value) {
+            public Object replace(ErrorHandler handler, Object that, String name, Object value) {
                 BeanModel model = BeanModel.typefor(that);
                 if(model.properties().containsKey(name)) {
                     return model.property(name).replace(that, value);
-                } else {
-                    return model.type().field(name).replace(that, value);
                 }
+                Iterator<IField> f = field(model, name);
+                if(f.hasNext()) {
+                    return f.next().replace(that, value);
+                }
+                handler.failure();
+                return null;
             }
             @Override
-            public IClass<?> type(IClass<?> that, String name) {
+            public IClass<?> type(ErrorHandler handler, IClass<?> that, String name) {
                 BeanModel model = BeanModel.typeinfo(that);
                 if(model.properties().containsKey(name)) {
                     return model.property(name).type();
-                } else {
-                    return model.type().field(name).type();
                 }
+                Iterator<IField> f = field(model, name);
+                if(f.hasNext()) {
+                    return f.next().type();
+                }
+                handler.failure();
+                return null;
             }
         };
         
@@ -349,13 +427,13 @@ public class Resolver implements Serializable {
         
         public abstract boolean matches(Object that);
         
-        public abstract Object get(Object that, String name);
+        public abstract Object get(ErrorHandler handler, Object that, String name);
         
-        public abstract void set(Object that, String name, Object value);
+        public abstract void set(ErrorHandler handler, Object that, String name, Object value);
         
-        public abstract Object replace(Object that, String name, Object value);
+        public abstract Object replace(ErrorHandler handler, Object that, String name, Object value);
         
-        public abstract IClass<?> type(IClass<?> that, String name);
+        public abstract IClass<?> type(ErrorHandler handler, IClass<?> that, String name);
         
     }
     
@@ -389,4 +467,43 @@ public class Resolver implements Serializable {
     
     }
     
+    private static class ErrorHandler {
+        
+        // @todo (assira #1) Resolver.ErrorHandler
+        
+        public boolean error = false;
+        
+        public final void failure() {
+            error = true;
+        }
+        
+        public final boolean isError() {
+            return error;
+        }
+        
+        public Object handleNoValue() {
+            return null;
+        }
+        
+        public Object handleNoValuePath() {
+            return null;
+        }
+        
+        public IClass<?> handleNoType() {
+            return null;
+        }
+        
+        public IClass<?> handleNoTypePath() {
+            return null;
+        }
+        
+        public Object handleNoReplace() {
+            return null;
+        }
+        
+        public void handleNoSet() {
+            // do something
+        }
+        
+    }
 }

+ 3 - 8
assira/src/main/java/net/ranides/assira/text/ResolveFormat.java

@@ -16,6 +16,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import net.ranides.assira.generic.CompareUtils;
 import net.ranides.assira.generic.HashUtils;
+import net.ranides.assira.reflection.Resolver.ResolveException;
 
 /**
  *
@@ -53,6 +54,8 @@ import net.ranides.assira.generic.HashUtils;
 @SuppressFBWarnings("SE_BAD_FIELD")
 public class ResolveFormat implements Serializable {
     
+    // @todo (assira #3) ResolveFormat: error handling
+    
     private static final long serialVersionUID = 1L;
     
     private static final Pattern  REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\]()\\$.?\\^|])");
@@ -239,12 +242,4 @@ public class ResolveFormat implements Serializable {
 
     }
     
-    public static class ResolveException extends IllegalArgumentException {
-
-        public ResolveException(Throwable cause) {
-            super(cause);
-        }
-
-    }
-    
 }

+ 6 - 0
assira/src/test/java/net/ranides/assira/reflection/ResolverTest.java

@@ -287,5 +287,11 @@ public class ResolverTest {
         assertEquals(ForIClass.INT, item.type());
     }
     
+    @Test
+    public void testResolveUnknown() {
+        A context = new A();
+//        System.out.printf("%s%n", Resolver.compile("list[1].name").get(context));
+        System.out.printf("%s%n", Resolver.compile("list[1].unknown").get(context));
+    }
         
 }

+ 8 - 1
assira/src/test/java/net/ranides/assira/text/ResolveFormatTest.java

@@ -23,6 +23,7 @@ import net.ranides.assira.generic.HashUtils;
 import net.ranides.assira.generic.SerializationUtils;
 import org.junit.Test;
 import static net.ranides.assira.junit.NewAssert.*;
+import net.ranides.assira.reflection.Resolver.ResolveException;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 
@@ -91,6 +92,12 @@ public class ResolveFormatTest {
         assertSymEquals(a, b);
     }
 
+    @Test
+    public void testFormat_Unknown() throws UnsupportedEncodingException {
+//        System.out.printf("%s%n", ResolveFormat.compile("{value1} {tag}").format(ITEM));
+        System.out.printf("%s%n", ResolveFormat.compile("{value1} {bivalue}").format(ITEM));
+    }
+    
     @Test
     public void testFormat_Number() throws UnsupportedEncodingException {
         assertEquals("2.1415",      ResolveFormat.compile("{value1}").format(ITEM) );
@@ -480,7 +487,7 @@ public class ResolveFormatTest {
         rf.parse("value = 77 one", out2);
         assertEquals("[77.0, 1.0]", Arrays.toString(out2));
         
-        assertThrows(ResolveFormat.ResolveException.class, ()->{
+        assertThrows(ResolveException.class, ()->{
             rf.parse("value = 77 four", out2);
         });