Переглянути джерело

Merge branch 'master' of http://repo1.mydevil.net/git/priv/ranides/assira

Ranides Atterwim 10 роки тому
батько
коміт
b4ba01bda2

+ 0 - 3
assira/pom.xml

@@ -64,9 +64,6 @@
                                 <include>net/ranides/assira/text/LexicalCast*</include>
                                 <include>net/ranides/assira/text/StringIO*</include>
                                 <include>net/ranides/assira/text/format/**</include>
-                                <include>net/ranides/assira/text/ResolveUtils**</include>
-                                <include>net/ranides/assira/text/ResolveFormat**</include>
-                                <include>net/ranides/assira/text/ResolveDialect**</include>
                                 
                                 <include>net/ranides/assira/xml/XMLQuery*</include>
                                 <include>net/ranides/assira/xml/XMLWriter*</include>

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

@@ -197,6 +197,10 @@ public final class FormatNumber {
             this.dv = BigDecimal.valueOf(v);
             this.u = u;
         }
+        
+        public long longValue() {
+            return (long)v;
+        }
 
         public double doubleValue() {
             return v;
@@ -211,5 +215,5 @@ public final class FormatNumber {
         }
         
     }
-
+    
 }

+ 15 - 0
assira/src/main/java/net/ranides/assira/text/LexicalCast.java

@@ -12,8 +12,11 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.SerializationFeature;
 import com.fasterxml.jackson.databind.type.TypeFactory;
 import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
 import java.time.temporal.TemporalAccessor;
 import java.util.Date;
+import net.ranides.assira.reflection.IArguments;
 import net.ranides.assira.reflection.IClass;
 
 /**
@@ -141,6 +144,15 @@ public final class LexicalCast {
                 return new Date(((Number)value).longValue());
             }
         }
+        if(value instanceof LocalDateTime) {
+            LocalDateTime dt = (LocalDateTime)value;
+            if( raw.equals(Date.class)) {
+                return Date.from(dt.atZone(ZoneOffset.systemDefault()).toInstant());
+            }
+            if( raw.equals(java.sql.Date.class)) {
+                return java.sql.Date.from(dt.atZone(ZoneOffset.systemDefault()).toInstant());
+            }
+        }
         if(value instanceof TemporalAccessor) {
             if( raw.equals(Date.class)) {
                 return Date.from(Instant.from((TemporalAccessor)value));
@@ -157,6 +169,9 @@ public final class LexicalCast {
                 return ((Date)value).getTime();
             }
         }
+        if(!type.constructors().accepts(value).isEmpty()) {
+            return type.construct(value);
+        }
         return LexicalCast.fromJSON(LexicalCast.toJSON(value), type);
     }
     

+ 216 - 91
assira/src/main/java/net/ranides/assira/text/ResolveDialect.java

@@ -9,23 +9,18 @@ package net.ranides.assira.text;
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
-import java.text.ChoiceFormat;
-import java.text.DateFormat;
-import java.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.text.ParsePosition;
-import java.text.SimpleDateFormat;
+import java.text.*;
 import java.time.Instant;
+import java.time.LocalDateTime;
 import java.time.ZoneOffset;
 import java.time.format.DateTimeFormatter;
 import java.util.Date;
-import java.util.IllegalFormatConversionException;
 import java.util.Locale;
 import java.util.Map;
-import java.util.Objects;
 import java.util.function.BiFunction;
 import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 import net.ranides.assira.collection.maps.Cache;
 import net.ranides.assira.collection.maps.HashMap;
 import net.ranides.assira.generic.CompareUtils;
@@ -48,9 +43,9 @@ public final class ResolveDialect {
     
     private static final class Li { // NOPMD - lazy init idiom
         
-        private static final Cache<String, ResolveDialect> DIALECTS = new Cache<>(32,4);
+        public static final Cache<String, ResolveDialect> DIALECTS = new Cache<>(32,4);
         
-        private static final ResolveDialect DEFAULT = new ResolveDialect(Locale.getDefault().getCountry());
+        public static final ResolveDialect DEFAULT = new ResolveDialect(Locale.getDefault().getCountry());
     
     }
     
@@ -68,7 +63,7 @@ public final class ResolveDialect {
         Locale locale = new Locale(name);
         DecimalFormatSymbols us = DecimalFormatSymbols.getInstance(Locale.US);
         
-        put("", "", r -> new FGeneric(r));
+        putFormat("", "", r -> new FGeneric(r));
         
         // number       : 2 + 3      5
         // date         : 2 + 8     10
@@ -77,58 +72,60 @@ public final class ResolveDialect {
         // metric       : 1 + 4      5
         // binary       : 1 + 6      7
         //              :           40 x language
-        
-        put("number",          (r,p) -> new FNumber(r, new DecimalFormat(p,us)));
-        put("number", "",          r -> new FNumber(r, DecimalFormat.getInstance(locale)));
-        put("number", "integer",   r -> new FNumber(r, DecimalFormat.getIntegerInstance(locale)));
-        put("number", "currency",  r -> new FNumber(r, getCurrencyFormat(locale)));
-        put("number", "percent",   r -> new FNumber(r, DecimalFormat.getPercentInstance(locale)));
-        
-        put("date",            (r,p) -> new FDateTime(r, new SimpleDateFormat(p,locale)));
-        put("date", "",            r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.DEFAULT,locale)));
-        put("date", "short",       r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.SHORT,locale)));
-        put("date", "medium",      r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.MEDIUM,locale)));
-        put("date", "long",        r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.LONG,locale)));
-        put("date", "full",        r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.FULL,locale)));
-        put("date", "iso",         r -> new FDateTime8(r, "yyyy-MM-dd'T'HH:mmX"));
-        put("date", "gnu",         r -> new FDateTime(r, new SimpleDateFormat("yyyyMMddHHmm",Locale.ROOT)));
-        put("date", "trace",       r -> new FDateTime(r, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS",Locale.ROOT)));
-        put("date", "local",       r -> new FDateTime(r, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.ROOT)));
-        
-        put("time",            (r,p) -> new FDateTime(r, new SimpleDateFormat(p,locale)));
-        put("time", "",            r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.DEFAULT,locale)));
-        put("time", "short",       r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.SHORT,locale)));
-        put("time", "medium",      r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.MEDIUM,locale)));
-        put("time", "long",        r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.LONG,locale)));
-        put("time", "full",        r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.FULL,locale)));
-        put("time", "iso",         r -> new FDateTime8(r, "HH:mm:ssX"));
-        put("time", "gnu",         r -> new FDateTime(r, new SimpleDateFormat("HHmmss",Locale.ROOT)));
-        put("time", "trace",       r -> new FDateTime(r, new SimpleDateFormat("HH:mm:ss.SSS",Locale.ROOT)));
-        put("time", "local",       r -> new FDateTime(r, new SimpleDateFormat("HH:mm:ss",Locale.ROOT)));
-        
-        put("hex", "",             r -> new FHexArray(r));
-        put("hex", "array",        r -> new FHexArray(r));
-        put("hex", "stream",       r -> new FHexStream(r));
-        
-        put("metric",          (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.MP));
-        put("metric/si",       (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.MP));
-        put("metric/e",        (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.ME));
-        put("metric", "",          r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.MP));
-        put("metric/si", "",       r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.MP));
-        put("metric/e", "",        r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.ME));
-        
-        put("binary",          (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BP));
-        put("binary/iec",      (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BP));
-        put("binary/e",        (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BE));
-        put("binary/knuth",    (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BK));
-        put("binary", "",          r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BP));
-        put("binary/iec", "",      r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BP));
-        put("binary/e", "",        r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BE));
-        put("binary/knuth", "",    r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BK));
-
-        put("choice",          (r,p) -> new FChoice(r, new ChoiceFormat(p)));
-    }
 
+        putFormat("number",          (r,p) -> new FNumber(r, new DecimalFormat(p,us)));
+        putFormat("number", "",          r -> new FNumber(r, DecimalFormat.getInstance(locale)));
+        putFormat("number", "integer",   r -> new FNumber(r, DecimalFormat.getIntegerInstance(locale)));
+        putFormat("number", "c++",       r -> new FNumber(r, getDecimalFormat(Locale.US, '.','\'')));
+        putFormat("number", "java",      r -> new FNumber(r, getDecimalFormat(Locale.US, '.','_')));
+        putFormat("number", "currency",  r -> new FNumber(r, getCurrencyFormat(locale)));
+        putFormat("number", "percent",   r -> new FNumber(r, DecimalFormat.getPercentInstance(locale)));
+        
+        putFormat("date",            (r,p) -> new FDateTime(r, new SimpleDateFormat(p,locale)));
+        putFormat("date", "",            r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.DEFAULT,locale)));
+        putFormat("date", "short",       r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.SHORT,locale)));
+        putFormat("date", "medium",      r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.MEDIUM,locale)));
+        putFormat("date", "long",        r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.LONG,locale)));
+        putFormat("date", "full",        r -> new FDateTime(r, DateFormat.getDateInstance(DateFormat.FULL,locale)));
+        putFormat("date", "iso",         r -> new FDateTime8(r, "yyyy-MM-dd'T'HH:mmX"));
+        putFormat("date", "gnu",         r -> new FDateTime(r, new SimpleDateFormat("yyyyMMddHHmm",Locale.ROOT)));
+        putFormat("date", "trace",       r -> new FDateTime(r, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS",Locale.ROOT)));
+        putFormat("date", "local",       r -> new FDateTime(r, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",Locale.ROOT)));
+        
+        putFormat("time",            (r,p) -> new FDateTime(r, new SimpleDateFormat(p,locale)));
+        putFormat("time", "",            r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.DEFAULT,locale)));
+        putFormat("time", "short",       r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.SHORT,locale)));
+        putFormat("time", "medium",      r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.MEDIUM,locale)));
+        putFormat("time", "long",        r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.LONG,locale)));
+        putFormat("time", "full",        r -> new FDateTime(r, DateFormat.getTimeInstance(DateFormat.FULL,locale)));
+        putFormat("time", "iso",         r -> new FDateTime8(r, "HH:mm:ssX"));
+        putFormat("time", "gnu",         r -> new FDateTime(r, new SimpleDateFormat("HHmmss",Locale.ROOT)));
+        putFormat("time", "trace",       r -> new FDateTime(r, new SimpleDateFormat("HH:mm:ss.SSS",Locale.ROOT)));
+        putFormat("time", "local",       r -> new FDateTime(r, new SimpleDateFormat("HH:mm:ss",Locale.ROOT)));
+        
+        putFormat("hex", "",             r -> new FHexArray(r));
+        putFormat("hex", "array",        r -> new FHexArray(r));
+        putFormat("hex", "stream",       r -> new FHexStream(r));
+        
+        putFormat("metric",          (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.MP));
+        putFormat("metric/si",       (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.MP));
+        putFormat("metric/e",        (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.ME));
+        putFormat("metric", "",          r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.MP));
+        putFormat("metric/si", "",       r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.MP));
+        putFormat("metric/e", "",        r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.ME));
+        
+        putFormat("binary",          (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BP));
+        putFormat("binary/iec",      (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BP));
+        putFormat("binary/e",        (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BE));
+        putFormat("binary/knuth",    (r,p) -> new FEngineer(r, new DecimalFormat(p,us), FormatNumber.BK));
+        putFormat("binary", "",          r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BP));
+        putFormat("binary/iec", "",      r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BP));
+        putFormat("binary/e", "",        r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BE));
+        putFormat("binary/knuth", "",    r -> new FEngineer(r, DecimalFormat.getInstance(Locale.US), FormatNumber.BK));
+
+        putFormat("choice",          (r,p) -> new FChoice(r, new ChoiceFormat(p)));
+    }
+    
     public static ResolveDialect getDefault() {
         return Li.DEFAULT;
     }
@@ -166,12 +163,11 @@ public final class ResolveDialect {
     }
     
     
-    
-    void put(String format, String pattern, Function<Resolver,ResolveFormat.Token> token) {
+    private void putFormat(String format, String pattern, Function<Resolver,ResolveFormat.Token> token) {
         mDefined.put(new TokenKey(format, pattern), token);
     }
     
-    void put(String format, BiFunction<Resolver,String,ResolveFormat.Token> token) {
+    private void putFormat(String format, BiFunction<Resolver,String,ResolveFormat.Token> token) {
         mPattern.put(format, token);
     }
     
@@ -198,6 +194,16 @@ public final class ResolveDialect {
         return nf;
     }
     
+    private DecimalFormat getDecimalFormat(Locale locale, char dseparator, char gseparator) {
+        DecimalFormat fmt = (DecimalFormat)DecimalFormat.getInstance(locale);
+        DecimalFormatSymbols dfs = fmt.getDecimalFormatSymbols();
+        dfs.setDecimalSeparator(dseparator);
+        dfs.setGroupingSeparator(gseparator);
+        fmt.setGroupingUsed(true);
+        fmt.setDecimalFormatSymbols(dfs);
+        return fmt;
+    }
+    
     private static StrBuffer asHex(StrBuffer target, byte value) {
         int ivalue = value & 0xFF;
         target.append( HC_TABLE[ivalue >>> 4] );
@@ -252,11 +258,12 @@ public final class ResolveDialect {
         }
 
         @Override
-        public final void parse(String source, ParsePosition position, Object context) {
-            set(context, parse(source, position));
+        public final void parse(String source, Object context) throws ParseException {
+//            iresolver.type(context)
+            set(context, parse(source));
         }
         
-        public abstract Object parse(String source, ParsePosition position);
+        public abstract Object parse(String source) throws ParseException;
     }
     
     private static final class FNumber extends RToken {
@@ -268,14 +275,46 @@ public final class ResolveDialect {
             this.format = nf;
         }
 
+        @Override
+        public String regex() {
+            if(format instanceof DecimalFormat) {
+                DecimalFormatSymbols dfs = ((DecimalFormat)format).getDecimalFormatSymbols();
+                StrBuilder s = new StrBuilder();
+                s.append("(");
+                s.append(dfs.getMinusSign()).append("?");
+                digits(s, dfs);
+                s.append("+");
+                s.append("(?:")
+                    .append(dfs.getExponentSeparator())
+                    .append("|")
+                    .append(dfs.getExponentSeparator().toLowerCase(Locale.ROOT))
+                    .append(")?");
+                digits(s, dfs);
+                s.append("*");
+                s.append("%?");
+                s.append(")");
+                return s.toString();
+            } else {
+                return "((?:0x)?[0-9.,'A-Fa-f]+)";
+            }
+        }
+
+        private StrBuilder digits(StrBuilder s, DecimalFormatSymbols dfs) {
+            s.append("[0-9");
+            s.append(dfs.getDecimalSeparator());
+            s.append(dfs.getGroupingSeparator());
+            s.append("]");
+            return s;
+        }
+
         @Override
         public void append(Appendable target, Object context) throws IOException {
             target.append(format.format(get(context)));
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            return format.parse(source, position);
+        public Object parse(String source) throws ParseException {
+            return format.parse(source);
         }
         
     }
@@ -289,14 +328,19 @@ public final class ResolveDialect {
             this.format = nf;
         }
 
+        @Override
+        public String regex() {
+            return "(.+)";
+        }
+
         @Override
         public void append(Appendable target, Object context) throws IOException {
             target.append(format.format(get(context,Date.class)));
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            return format.parse(source, position);
+        public Object parse(String source) throws ParseException {
+            return format.parse(source);
         }
         
     }
@@ -309,6 +353,11 @@ public final class ResolveDialect {
             super(resolver);
             this.format = DateTimeFormatter.ofPattern(pattern).withZone(ZoneOffset.UTC);
         }
+        
+        @Override
+        public String regex() {
+            return "(.+)";
+        }
 
         @Override
         public void append(Appendable target, Object context) throws IOException {
@@ -316,8 +365,8 @@ public final class ResolveDialect {
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            return format.parse(source, position);
+        public Object parse(String source) {
+            return LocalDateTime.parse(source, format);
         }
 
     }
@@ -332,6 +381,11 @@ public final class ResolveDialect {
             super(resolver);
             this.format = nf;
         }
+        
+        @Override
+        public String regex() {
+            return "(.+)";
+        }
 
         @Override
         public void append(Appendable target, Object context) throws IOException {
@@ -339,17 +393,24 @@ public final class ResolveDialect {
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            return format.parse(source, position);
+        public Object parse(String source) throws ParseException {
+            return format.parse(source);
         }
         
     }
     
     private static final class FHexArray extends RToken {
         
+        private static final Pattern RE_PARSE = Pattern.compile("\\[([0-9]+)\\]\\{(.*)\\}");
+        
         public FHexArray(Resolver resolver) {
             super(resolver);
         }
+        
+        @Override
+        public String regex() {
+            return "(\\[[0-9]+\\]\\{(?:0x[0-9a-fA-F]{2}?)?(?:,0x[0-9a-fA-F]{2})*\\})";
+        }
 
         @Override
         public void append(Appendable target, Object context) throws IOException {
@@ -375,9 +436,20 @@ public final class ResolveDialect {
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            throw new UnsupportedOperationException("Not supported yet.");
+        public Object parse(String source) throws ParseException {
+            Matcher hit = RE_PARSE.matcher(source);
+            if( !hit.matches() ) {
+                throw new ParseException("Value is not byte array: " + source, 0);
+            }
+            int size = Integer.parseInt(hit.group(1));
+            byte[] array = new byte[size];
+            int index = 0 ;
+            for(String v : hit.group(2).split(",")) {
+                array[index++] = Byte.parseByte(v.substring(2), 16);
+            }
+            return array;
         }
+        
 
     }
     
@@ -386,6 +458,11 @@ public final class ResolveDialect {
         public FHexStream(Resolver resolver) {
             super(resolver);
         }
+        
+        @Override
+        public String regex() {
+            return "((?:[0-9A-Fa-f]{2})?(?: [0-9A-Fa-f]{2})*)";
+        }
 
         @Override
         public void append(Appendable target, Object context) throws IOException {
@@ -404,26 +481,42 @@ public final class ResolveDialect {
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            throw new UnsupportedOperationException("Not supported yet.");
+        public Object parse(String source) {
+            String[] ss = source.split(" ");
+            int n = ss.length;
+            byte[] array = new byte[n];
+            for(int i=0; i<n; i++) {
+                array[i] = Byte.parseByte(ss[i], 16);
+            }
+            return array;
         }
         
     }
     
-    private static final class FEngineer extends RToken {
+    private static final class FEngineer implements ResolveFormat.Token {
         
+        private final Resolver iresolver;
         private final NumberFormat nf;
         private final UnitPrefix[] units;
+        private final String re;
+        private final Pattern regex;
         
         public FEngineer(Resolver resolver, NumberFormat nf, UnitPrefix[] units) {
-            super(resolver);
+            this.iresolver = resolver;
             this.nf = nf;
             this.units = units;
+            this.re = "([0-9]+(?:\\.[0-9]+)?(?:" + StringUtils.join(units, "|", b -> b.text()) + "))";
+            this.regex = Pattern.compile("([0-9]+(?:\\.[0-9]+)?)(" + StringUtils.join(units, "|", b -> b.text()) + ")");
         }
 
+        @Override
+        public String regex() {
+            return re;
+        }
+        
         @Override
         public void append(Appendable target, Object context) throws IOException {
-            Object object = get(context);
+            Object object = iresolver.get(context);
             if (object instanceof BigInteger) {
                 BigInteger value = (BigInteger) object;
                 if(value.bitLength()<64) {
@@ -450,10 +543,40 @@ public final class ResolveDialect {
         }
 
         @Override
-        public Object parse(String source, ParsePosition position) {
-            throw new UnsupportedOperationException("Not supported yet.");
+        public void parse(String source, Object context) throws ParseException {
+            Matcher hit = regex.matcher(source);
+            if(!hit.matches()) {
+                throw new IllegalArgumentException("Cannot parse Number: " + source);
+            }
+            UnitPrefix unit = unit(hit.group(2));
+            IClass type = iresolver.type(context);
+            
+            Object out;
+            if (type.isSubclass(IClass.typeinfo(BigInteger.class))) {
+                out = new BigDecimal(hit.group(1)).multiply(unit.decimalValue()).toBigInteger();
+            }
+            else if (type.isSubclass(IClass.typeinfo(BigDecimal.class))) {
+                out = new BigDecimal(hit.group(1)).multiply(unit.decimalValue());
+            }
+            else if(IAttribute.INTEGER.matches(type)) {
+                out = LexicalCast.cast(Long.parseLong(hit.group(1)) * unit.longValue(), type);
+            }
+            else {
+                out = LexicalCast.cast(Double.parseDouble(hit.group(1)) * unit.doubleValue(), type);
+            }
+            iresolver.set(context, out);
+        }
+        
+        private UnitPrefix unit(String name) {
+            for(UnitPrefix u : units) {
+                if(u.text().equals(name)) {
+                    return u;
+                }
+            }
+            throw new IllegalArgumentException("Unknown prefix: " + name);
         }
         
+
         private void vappend(Appendable target, long value) throws IOException {
             if(value < 0) {
                 target.append('-');
@@ -509,17 +632,19 @@ public final class ResolveDialect {
             this.resolver = resolver;
         }
 
+        @Override
+        public String regex() {
+            return "(.+)";
+        }
+        
         @Override
         public void append(Appendable target, Object context) throws IOException {
             target.append(String.valueOf(resolver.get(context)));
         }
         
         @Override
-        public void parse(String source, ParsePosition position, Object context) {
-            IClass type = resolver.type(context);
-            Object value = null;
-            resolver.set(context, value);
-            throw new UnsupportedOperationException("Not supported yet.");
+        public void parse(String source, Object context) {
+            resolver.set(context, LexicalCast.cast(source, resolver.type(context)));
         }
         
     }

+ 51 - 22
assira/src/main/java/net/ranides/assira/text/ResolveFormat.java

@@ -9,6 +9,7 @@ package net.ranides.assira.text;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import java.io.IOException;
 import java.io.Serializable;
+import java.text.ParseException;
 import java.text.ParsePosition;
 import java.util.ArrayList;
 import java.util.List;
@@ -20,7 +21,6 @@ import net.ranides.assira.generic.HashUtils;
 
 /**
  *
- * @todo (assira #0) ResolvePattern: formatting
  * Składnia wzorców jest identyczna jak składnia z MessageFormat.
  * Podajemy kolejno:
  *      { ArgumentIndex, Format, Pattern  }
@@ -57,6 +57,8 @@ public class ResolveFormat implements Serializable {
     
     private static final long serialVersionUID = 1L;
     
+    private static final Pattern  REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\]()\\$.?\\^|])");
+    
     private static final Pattern RE_COMPILE = Pattern.compile("\\{(.+?)(?:,(.+?))?(?:,(.+?))?\\}");
     
     private final ResolveDialect dialect;
@@ -65,11 +67,16 @@ public class ResolveFormat implements Serializable {
     
     private final Token[] tokens;
     
+    private final Token[] parsers;
+    
+    private final Pattern scanre;
+    
     ResolveFormat(ResolveDialect dialect, String pattern) {
         this.dialect = dialect;
         this.pattern = pattern;
 
         List<Token> itokens = new ArrayList<>();
+        List<Token> iparsers = new ArrayList<>();
         Matcher hit = RE_COMPILE.matcher(pattern);
         int last = 0;
         while(hit.find()) {
@@ -77,7 +84,9 @@ public class ResolveFormat implements Serializable {
                 String text = pattern.substring(last,hit.start());
                 itokens.add(new FString(text));
             }
-            itokens.add(dialect.get(hit.group(1), hit.group(2), hit.group(3)));
+            Token token = dialect.get(hit.group(1), hit.group(2), hit.group(3));
+            itokens.add(token);
+            iparsers.add(token);
             
             last = hit.end();
         }
@@ -86,6 +95,14 @@ public class ResolveFormat implements Serializable {
             itokens.add(new FString(text));
         }
         this.tokens = itokens.toArray(new Token[itokens.size()]);
+        this.parsers = iparsers.toArray(new Token[iparsers.size()]);
+        
+        int[] index = new int[]{0};
+        
+        String p = REGEX_SPECIAL.matcher(pattern).replaceAll("\\\\$1");
+        this.scanre = Pattern.compile(StringUtils.replace(p, RE_COMPILE, h -> {
+            return parsers[index[0]++].regex();
+        }));
     }
     
     public static ResolveFormat compile(String pattern) {
@@ -112,7 +129,7 @@ public class ResolveFormat implements Serializable {
         }
     }
     
-    public String format(Object... arguments) {
+    public String vformat(Object... arguments) {
         return format((Object)arguments);
     }
     
@@ -123,23 +140,23 @@ public class ResolveFormat implements Serializable {
         return output;
     }
     
-    public <T extends Appendable> T format(T output, Object... arguments) throws IOException {
+    public <T extends Appendable> T vformat(T output, Object... arguments) throws IOException {
         return format(output, (Object)arguments);
     }
     
     public void parse(String input, Object context) {
-        ParsePosition position = new ParsePosition(0);
-        for(Token token : tokens) {
-            token.parse(input, position, context);
+        Matcher hit = scanre.matcher(input);
+        if(!hit.matches()) {
+            throw new IllegalArgumentException("Expression does not match pattern: \"" + pattern + "\" expression: \"" + input + "\"nregex: " + scanre.pattern());
+        }
+        try {
+            int i=0;
+            for(Token p : parsers) {
+                p.parse(hit.group(++i), context);
+            }
+        } catch(ParseException cause) {
+            throw new ResolveException(cause);
         }
-        // @todo (assira #0) ResolvePattern#parse
-        //      jeśli next token jest FString'iem to robimy:
-        //      int end = next.indexOf(position)
-        //      token.parse(StringUtils.limit(input, end), position)
-    }
-    
-    public void parse(String input, Object... arguments) {
-        parse(input, (Object)arguments);
     }
     
     private Object writeReplace() {
@@ -172,9 +189,11 @@ public class ResolveFormat implements Serializable {
     
     interface Token {
 
+        String regex();
+        
         void append(Appendable target, Object context) throws IOException;
 
-        void parse(String source, ParsePosition position, Object context);
+        void parse(String source, Object context) throws ParseException;
 
     }
     
@@ -190,13 +209,15 @@ public class ResolveFormat implements Serializable {
         public void append(Appendable target, Object context) throws IOException {
             target.append(value);
         }
-
+        
         @Override
-        public void parse(String source, ParsePosition position, Object context) {
-            if( !source.substring(position.getIndex()).startsWith(value) ) {
-                throw new RuntimeException("");
-            }
-            position.setIndex(position.getIndex()+value.length());
+        public String regex() {
+            throw new UnsupportedOperationException("Unreachable code.");
+        }
+            
+        @Override
+        public void parse(String source, Object context) {
+            throw new UnsupportedOperationException("Unreachable code.");
         }
                 
     }
@@ -220,4 +241,12 @@ public class ResolveFormat implements Serializable {
 
     }
     
+    public static class ResolveException extends IllegalArgumentException {
+
+        public ResolveException(Throwable cause) {
+            super(cause);
+        }
+
+    }
+    
 }

+ 0 - 4
assira/src/main/java/net/ranides/assira/text/ResolveUtils.java

@@ -38,8 +38,4 @@ public final class ResolveUtils {
         ResolveFormat.compile(pattern).parse(input, context);
     }
     
-    public static void vparse(String pattern, String input, Object... arguments) {
-        ResolveFormat.compile(pattern).parse(input, arguments);
-    }
-    
 }

+ 19 - 0
assira/src/test/java/net/ranides/assira/text/LexicalCastTest.java

@@ -56,6 +56,11 @@ public class LexicalCastTest {
         assertEquals(3.14, LexicalCast.cast(3.14f, Double.class), 0.001);
     }
     
+    @Test
+    public void testStringConstructor() {
+        assertEquals("[ranides]", LexicalCast.cast("ranides", Name.class).name());
+    }
+    
     private static Short asShort(int value) {
         return (short)value;
     }
@@ -63,4 +68,18 @@ public class LexicalCastTest {
     private static Byte asByte(int value) {
         return (byte)value;
     }
+    
+    private static final class Name {
+        
+        private final String mName;
+
+        public Name(String iname) {
+            this.mName = "[" + iname + "]";
+        }
+
+        public String name() {
+            return mName;
+        }
+        
+    }
 }

+ 186 - 9
assira/src/test/java/net/ranides/assira/text/ResolveFormatTest.java

@@ -7,11 +7,14 @@
 package net.ranides.assira.text;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.time.Instant;
+import java.util.Arrays;
 import java.util.Date;
 import java.util.GregorianCalendar;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import net.ranides.assira.generic.HashUtils;
@@ -69,7 +72,7 @@ public class ResolveFormatTest {
     }
 
     @Test
-    public void testFormat_Number() {
+    public void testFormat_Number() throws UnsupportedEncodingException {
         assertEquals("3.1415",      ResolveFormat.compile("{value1}").format(ITEM) );
         
         assertEquals("3,141",       ResolveFormat.compile("{value1,number}").format(ITEM) );
@@ -83,8 +86,41 @@ public class ResolveFormatTest {
         assertEquals(" 3.14",       ResolveFormat.compile("US","{value1,number,currency}").format(ITEM) );
         assertEquals("- 150.50",    ResolveFormat.compile("US","{value2,number,currency}").format(ITEM) );
         assertEquals("314%",        ResolveFormat.compile("US","{value1,number,percent}").format(ITEM) );
+        
+        assertEquals("-150_456.5",    ResolveFormat.compile("{fvalue,number,java}").format(ITEM) );
+        assertEquals("-150'456.5",    ResolveFormat.compile("{fvalue,number,c++}").format(ITEM) );
+    }
+        
+    @Test
+    public void testParse_Errors() {
+        Integer[] out= new Integer[3];
+        ResolveFormat rf = ResolveFormat.compile("{0,number} / {1,number,java} / {2,number,java}");
+        assertThrows(IllegalArgumentException.class, ()->{
+            rf.parse("12z / 33 / 55_100", out);
+        }); 
+        assertThrows(IllegalArgumentException.class, ()->{
+            rf.parse("12 33 / 55_100", out);
+        }); 
+        rf.parse("12 / 33 / 55_100", out);
+        rf.parse("12,4 / 33 / 55_100", out);
     }
     
+    @Test
+    public void testParse_Number() {
+        Integer[] out= new Integer[3];
+        ResolveFormat.compile("{0,number} / {1,number,java} / {2,number,java}").parse("12 / 33 / 55_100", out);
+        assertArrayEquals(new Integer[]{12,33,55100}, out);
+        
+        Double[] out2= new Double[3];
+        ResolveFormat.compile("{0,number} / {1,number,java} / {2,number,java}").parse("14,3 / 35 / 55", out2);
+        assertArrayEquals(new Double[]{14.3,35.0,55.0}, out2);
+        
+        Double[] out3= new Double[3];
+        ResolveFormat.compile("US","{0,number} / {1,number,percent} / {2,number,java}").parse("14.3 / 35% / 55", out3);
+        assertArrayEquals(new Double[]{14.3, 0.35, 55.0}, out3);
+    }
+
+    
     @Test
     public void testFormat_Date() {
         assertEquals("Sun Jun 15 13:25:10 CEST 2014", ResolveFormat.compile("{date1}").format(ITEM) );
@@ -111,6 +147,37 @@ public class ResolveFormatTest {
         
     }
     
+    @Test
+    public void testParse_Date() {
+        Date[] out1 = new Date[5];
+        ResolveFormat
+                .compile("PL","{0,date} / {1,date,short} / {2,date,medium} / {3,date,long} / {4,date,full}")
+                .parse("2014-06-11 / 14-06-12 / 2014-06-13 / 14 czerwca 2014 / niedziela, 15 czerwca 2014", out1);
+        assertEquals(
+            "[Wed Jun 11 00:00:00 CEST 2014, Thu Jun 12 00:00:00 CEST 2014, Fri Jun 13 00:00:00 CEST 2014, Sat Jun 14 00:00:00 CEST 2014, Sun Jun 15 00:00:00 CEST 2014]",
+            Arrays.toString(out1)
+        );
+        
+        Date[] out2 = new Date[4];
+        ResolveFormat
+                .compile("PL","{0,date,iso} / {1,date,gnu} / {2,date,trace} / {3,date,local}")
+                .parse("2014-06-11T11:25Z / 201406121325 / 2014-06-13 14:25:10.000 / 2014-06-14 18:25:10", out2);
+        assertEquals(
+            "[Wed Jun 11 11:25:00 CEST 2014, Thu Jun 12 13:25:00 CEST 2014, Fri Jun 13 14:25:10 CEST 2014, Sat Jun 14 18:25:10 CEST 2014]",
+            Arrays.toString(out2)
+        );
+        
+        
+        Date[] out3 = new Date[5];
+        ResolveFormat
+            .compile("US","{0,date} | {1,date,short} | {2,date,medium} | {3,date,long} | {4,date,full}")
+            .parse("Jun 11, 2014 | 6/12/14 | Jun 13, 2014 | June 14, 2014 | Sunday, June 15, 2014", out3);
+        assertEquals(
+            "[Wed Jun 11 00:00:00 CEST 2014, Thu Jun 12 00:00:00 CEST 2014, Fri Jun 13 00:00:00 CEST 2014, Sat Jun 14 00:00:00 CEST 2014, Sun Jun 15 00:00:00 CEST 2014]",
+            Arrays.toString(out3)
+        );
+    }
+    
     @Test
     public void testFormat_Instant() {
         assertEquals("2014-06-15T11:25:10.777Z", ResolveFormat.compile("{date2}").format(ITEM) );
@@ -162,12 +229,30 @@ public class ResolveFormatTest {
     }
     
     @Test
-    public void testHexArray() {
+    public void testFormat_HexArray() {
         assertEquals("hex = [4]{7A,0x31,0x45,0xAC}", ResolveFormat.compile("hex = {data1,hex}").format(ITEM) );
         assertEquals("hex = [4]{7A,0x31,0x45,0xAC}", ResolveFormat.compile("hex = {data1,hex,array}").format(ITEM) );
         assertEquals("hex = 7A 31 45 AC", ResolveFormat.compile("hex = {data1,hex,stream}").format(ITEM) );
     }
     
+    @Test
+    public void testParse_HexArray() {
+        BData out1 = new BData();
+        ResolveFormat.compile("array = {value,hex,array}x").parse("array = [3]{0x10,0x3C,0x3A}x", out1);
+        assertArrayEquals(new byte[]{0x10,0x3C,0x3A}, out1.value);
+        
+        BData out2 = new BData();
+        ResolveFormat.compile("array = {value,hex,stream}B").parse("array = 10 3C 3AB", out2);
+        assertArrayEquals(new byte[]{0x10,0x3C,0x3A}, out2.value);
+    }
+    
+    @Test
+    public void testParse_Generic() {
+        String out[] = new String[3];
+        ResolveFormat.compile("{0} / {1} / last = {2}.").parse("hello / world / last = 23.", out);
+        assertArrayEquals(new String[]{"hello","world","23"}, out);
+    }
+    
     @Test
     public void testFormat_Metric_Int() {
         assertEquals("-154.27k", ResolveFormat.compile("{ivalue,metric}").format(ITEM));
@@ -223,6 +308,61 @@ public class ResolveFormatTest {
         assertEquals("-154.200E3", ResolveFormat.compile("{bivalue,metric/e,#.000}").format(ITEM));
     }
     
+    @Test
+    public void testParse_MetricSI() {
+        NumData out = new NumData();
+        ResolveFormat.compile("{iv,metric/si}!").parse("12k!", out);
+        assertEquals(12000, out.iv);
+        
+        ResolveFormat.compile("{iv,metric/si}!").parse("12!", out);
+        assertEquals(12, out.iv);
+        
+        ResolveFormat.compile("{dv,metric/si}!").parse("12.5M!", out);
+        assertEquals(12500000.0, out.dv, 0.1);
+        
+        ResolveFormat.compile("{dv,metric/si}!").parse("12.5372!", out);
+        assertEquals(12.5372, out.dv, 0.00001);
+        
+        ResolveFormat.compile("{bd,metric/si}!").parse("125k!", out);
+        assertTrue(0 == new BigDecimal("125000").compareTo(out.bd));
+        
+        ResolveFormat.compile("{bd,metric/si}!").parse("12.5372!", out);
+        assertTrue(0 == new BigDecimal("12.5372").compareTo(out.bd));
+        
+        ResolveFormat.compile("{bi,metric/si}!").parse("127k!", out);
+        assertTrue(0 == new BigInteger("127000").compareTo(out.bi));
+        
+        ResolveFormat.compile("{bi,metric/si}!").parse("19!", out);
+        assertTrue(0 == new BigInteger("19").compareTo(out.bi));
+    }
+    
+    @Test
+    public void testParse_MetricBinaryIEC() {
+        NumData out = new NumData();
+        ResolveFormat.compile("{iv,binary/iec}!").parse("12Ki!", out);
+        assertEquals(12*1024, out.iv);
+        
+        ResolveFormat.compile("{iv,binary/iec}!").parse("12!", out);
+        assertEquals(12, out.iv);
+        
+        ResolveFormat.compile("{dv,binary/iec}!").parse("12.5Mi!", out);
+        assertEquals(12.5*1024.0*1024.0, out.dv, 0.1);
+        
+        ResolveFormat.compile("{dv,binary/iec}!").parse("12.5372!", out);
+        assertEquals(12.5372, out.dv, 0.00001);
+        
+        ResolveFormat.compile("{bd,binary/iec}!").parse("125Ki!", out);
+        assertTrue(0 == new BigDecimal("128000").compareTo(out.bd));
+        
+        ResolveFormat.compile("{bd,binary/iec}!").parse("12.5372!", out);
+        assertTrue(0 == new BigDecimal("12.5372").compareTo(out.bd));
+        
+        ResolveFormat.compile("{bi,binary/iec}!").parse("127Ki!", out);
+        assertTrue(0 == new BigInteger("130048").compareTo(out.bi));
+        
+        ResolveFormat.compile("{bi,binary/iec}!").parse("19!", out);
+        assertTrue(0 == new BigInteger("19").compareTo(out.bi));
+    }
     
     @Test
     public void testFormat_Binary_Int() {
@@ -300,6 +440,30 @@ public class ResolveFormatTest {
         assertEquals("Hello 77 t world ?", r2);
     }
     
+    @Test
+    public void testFormat_Choice() {
+        ResolveFormat rf = ResolveFormat.compile("value = {0,number} {1,choice,-1#negative|0#zero|1#one|2#two|2<positive}");
+
+        assertEquals("value = -3 negative", rf.vformat(-3,-3));
+        assertEquals("value = -1 negative", rf.vformat(-1,-1));
+        assertEquals("value = 0 zero", rf.vformat(0,0));
+        assertEquals("value = 1,5 one", rf.vformat(1.5,1.5));
+        assertEquals("value = 3 positive", rf.vformat(3.0,3.0));
+        
+        Double[] out1 = new Double[2];
+        rf.parse("value = 3 negative", out1);
+        assertEquals("[3.0, -1.0]", Arrays.toString(out1));
+        
+        Double[] out2 = new Double[2];
+        rf.parse("value = 77 one", out2);
+        assertEquals("[77.0, 1.0]", Arrays.toString(out2));
+        
+        assertThrows(ResolveFormat.ResolveException.class, ()->{
+            rf.parse("value = 77 four", out2);
+        });
+        
+    }
+    
     private static final class Wrapper {
         public Object[] items;
 
@@ -349,13 +513,6 @@ public class ResolveFormatTest {
         
     }
     
-    
-    // @todo (assira #2) test: ResolveFormat# parse
-    
-    // @todo (assira #3) test: ResolveFormat# choice pattern
-    // @todo (assira #3) test: ResolveFormat# metric BigDecimal - big values
-    // @todo (assira #3) test: ResolveFormat# binary BigDecimal - big values
-    
     private static final Object ITEM = new Object(){
     
         public float value1 = 3.1415f;
@@ -379,5 +536,25 @@ public class ResolveFormatTest {
         public BigInteger value128 = new BigInteger("-1542000000000000000000000");
         
     };
+    
+    private static final class BData {
+        
+        public byte[] value;
+        
+    }
+    
+    private static final class NumData {
+        
+        public BigDecimal bd;
+        
+        public BigInteger bi;
+        
+        public AtomicInteger ai;
+        
+        public double dv;
+        
+        public long iv;
+        
+    }
 
 }

+ 28 - 2
assira/src/test/java/net/ranides/assira/text/ResolveUtilsTest.java

@@ -17,7 +17,7 @@ import static org.junit.Assert.*;
 public class ResolveUtilsTest {
     
     @Test
-    public void testFormat_Pattern_Array() {
+    public void testFormat_Array() {
         String r1 = ResolveUtils.format("Hello {1} world {2}{3}!", new int[]{17, 19, 47, 33});
         String r2 = ResolveUtils.vformat("Hello {1} world {2}{3}!", 17, 19, 47, 33);
         String r3 = ResolveUtils.vformat("Hello {0} world!", 17);
@@ -28,7 +28,7 @@ public class ResolveUtilsTest {
     }
     
     @Test
-    public void testFormat_Pattern_Array_Append() throws IOException {
+    public void testFormat_Append() throws IOException {
         StringBuilder r1 = new StringBuilder().append("r1 = ");
         StringBuilder r2 = new StringBuilder().append("r2 = ");
         StringBuilder r3 = new StringBuilder().append("r3 = ");
@@ -41,4 +41,30 @@ public class ResolveUtilsTest {
         assertEquals("r3 = Hello 17 world!", r3.toString());
     }
     
+    @Test
+    public void testCompileFormat_Array() {
+        String r2 = ResolveFormat.compile("Hello {1} world {2}{3}!").vformat(17, 19, 47, 33);
+        String r3 = ResolveFormat.compile("Hello {0} world!").vformat(17);
+        
+        assertEquals("Hello 19 world 4733!", r2);
+        assertEquals("Hello 17 world!", r3);
+    }
+    
+    @Test
+    public void testCompileFormat_Append() throws IOException {
+        StringBuilder r2 = new StringBuilder().append("r2 = ");
+        StringBuilder r3 = new StringBuilder().append("r3 = ");
+        ResolveFormat.compile("Hello {1} world {2}{3}!").vformat(r2, 17, 19, 47, 33);
+        ResolveFormat.compile("Hello {0} world!").vformat(r3, 17);
+        
+        assertEquals("r2 = Hello 19 world 4733!", r2.toString());
+        assertEquals("r3 = Hello 17 world!", r3.toString());
+    }
+    
+    @Test
+    public void testParse() {
+        int[] out = new int[2];
+        ResolveUtils.parse("{0,number} / {1,number}", "7 / 9", out);
+        assertArrayEquals(new int[]{7,9}, out);
+    }
 }