Pārlūkot izejas kodu

new: ResolvePattern

Ranides Atterwim 10 gadi atpakaļ
vecāks
revīzija
88dcde5102

+ 169 - 0
assira/src/main/java/net/ranides/assira/text/ResolvePattern.java

@@ -0,0 +1,169 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.text;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.ranides.assira.generic.CompareUtils;
+import net.ranides.assira.generic.SerializationUtils;
+import net.ranides.assira.generic.ValueUtils;
+import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.reflection.Resolver;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class ResolvePattern implements Serializable {
+    
+    private static final long serialVersionUID = 1L;
+    
+    private static final Pattern RE_COMPILE = Pattern.compile("\\{(.+?)\\}");
+    
+    private final String pattern;
+    
+    private transient final Pattern regex;
+    
+    private transient final Resolver[] resolvers;
+    
+    private transient final Token[] tokens;
+    
+    private ResolvePattern(String pattern) {
+        
+        final List<Resolver> iresolvers = new ArrayList<>();
+        String cpattern = StringUtils.replace(pattern, RE_COMPILE, m -> {
+            iresolvers.add( Resolver.compile(m.group(1)) );
+            return "(.+)";
+        });
+        this.pattern = pattern;
+        this.regex = Pattern.compile(cpattern);
+        this.resolvers = iresolvers.toArray(new Resolver[iresolvers.size()]);
+
+        List<Token> itokens = new ArrayList<>();
+        Matcher hit = RE_COMPILE.matcher(pattern);
+        int last = 0;
+        while(hit.find()) {
+            if(hit.start()-last > 0) {
+                itokens.add(new SToken(pattern.substring(last,hit.start())));
+            }
+            itokens.add(new RToken(hit.group(1)));
+            last = hit.end();
+        }
+        if(last<pattern.length()) {
+            itokens.add(new SToken(pattern.substring(last)));
+        }
+        this.tokens = itokens.toArray(new Token[itokens.size()]);
+    }
+    
+    public static ResolvePattern compile(String pattern) {
+        return new ResolvePattern(pattern);
+    }
+    
+    public String pattern() {
+        return pattern;
+    }
+    
+    private Object writeReplace() {
+        return SerializationUtils.proxy(this, pattern);
+    }
+
+    @Override
+    public int hashCode() {
+        return pattern.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if(object instanceof ResolvePattern) {
+            ResolvePattern that = (ResolvePattern)object;
+            return CompareUtils.equals(this.pattern, that.pattern);
+        } else {
+            return false;
+        }
+    }
+    
+    public String format(Object context) {
+        try {
+            return format(new StrBuilder(), context).toString();
+        } catch(IOException cause) {
+            throw new AssertionError("unreachable code", cause);
+        }
+    }
+    
+    public static String format(String pattern, Object context) {
+        return compile(pattern).format(context);
+    }
+    
+    public <T extends Appendable> T format(T output, Object context) throws IOException {
+        for(Token token : tokens) {
+            token.append(output, context);
+        }
+        return output;
+    }
+    
+    public static <T extends Appendable> T format(String pattern, T output, Object context) throws IOException {
+        return compile(pattern).format(output, context);
+    }
+    
+    public boolean scan(CharSequence input, Object context) {
+        Matcher hit = regex.matcher(input);
+        if(!hit.matches()) {
+            return false;
+        }
+        for(int i=0; i<resolvers.length; i++) {
+            IClass type = resolvers[i].type(context);
+            Object value = LexicalCast.cast(hit.group(i+1), type);
+            resolvers[i].set(context, value);
+        }
+        return true;
+    }
+    
+    public static boolean scan(String pattern, CharSequence input, Object context) {
+        return compile(pattern).scan(input, context);
+    }
+    
+    private interface Token {
+        
+        void append(Appendable target, Object context) throws IOException;
+        
+    }
+    
+    private static final class SToken implements Token {
+        
+        private final String value;
+
+        public SToken(String value) {
+            this.value = value;
+        }
+
+        @Override
+        public void append(Appendable target, Object context) throws IOException {
+            target.append(value);
+        }
+        
+    }
+    
+    private static final class RToken implements Token {
+        
+        private final Resolver resolver;
+
+        public RToken(String expression) {
+            this.resolver = Resolver.compile(expression);
+        }
+
+        @Override
+        public void append(Appendable target, Object context) throws IOException {
+            target.append(String.valueOf( resolver.get(context)));
+        }
+        
+    }
+    
+}

+ 143 - 0
assira/src/test/java/net/ranides/assira/text/ResolvePatternTest.java

@@ -0,0 +1,143 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.text;
+
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.ranides.assira.generic.HashUtils;
+import net.ranides.assira.generic.SerializationUtils;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class ResolvePatternTest {
+    
+    @Test
+    public void testSerialization() throws IOException {
+        ResolvePattern a = ResolvePattern.compile("{x} {y} {z}");
+        ResolvePattern b = SerializationUtils.copy(a);
+        
+        assertNotSame(a, b);
+        assertEquals(a, b);
+        
+        A target = new A();
+        assertTrue( ResolvePattern.scan("{x} {y} {z}", "23 45 56", target) );
+        assertEquals(new A(23, 45, 56), target);
+    }
+    
+    @Test
+    public void testScan() {
+        A target = new A();
+        
+        assertTrue( ResolvePattern.scan("{x}/{y} {z}", "12/37 45", target) );
+        assertEquals(new A(12, 37, 45), target);
+        
+        assertTrue( ResolvePattern.scan("{x} {y} {z}", "23 45 56", target) );
+        assertEquals(new A(23, 45, 56), target);
+        
+        assertTrue( ResolvePattern.scan("{x}-{y}-{z}", "32-54-65", target) );
+        assertEquals(new A(32, 54, 65), target);
+    }
+    
+    @Test
+    public void testCompileScan() {
+        A target = new A();
+        
+        assertTrue( ResolvePattern.compile("{x}/{y} {z}").scan("12/37 45", target) );
+        assertEquals(new A(12, 37, 45), target);
+        
+        assertTrue( ResolvePattern.compile("{x} {y} {z}").scan("23 45 56", target) );
+        assertEquals(new A(23, 45, 56), target);
+        
+        assertTrue( ResolvePattern.compile("{x}-{y}-{z}").scan("32-54-65", target) );
+        assertEquals(new A(32, 54, 65), target);
+    }
+    
+    @Test
+    public void testFormatArray() {
+        String ret = ResolvePattern.format("Hello {1} world {2}{3}!", new int[]{17, 19, 47, 33});
+        assertEquals("Hello 19 world 4733!", ret);
+    }
+    
+    @Test
+    public void testFormatMatcher() {
+        String source ="AH-ello";
+        Matcher hit = Pattern.compile("(.)(.)-(.+)").matcher(source);
+        assertTrue(hit.find());
+        
+        String ret = ResolvePattern.format("Hello {1} world {2}{3}!", hit);
+        assertEquals("Hello A world Hello!", ret);
+    }
+    
+    @Test
+    public void testFormatComplex() {
+        String source ="XY-point";
+        Matcher hit = Pattern.compile("(.)(.)-(.+)").matcher(source);
+        assertTrue(hit.find());
+        
+        Wrapper wrapper = new Wrapper("Hello", 77, hit, new Wrapper("world", '?'));
+        String r1 = ResolvePattern.format("{[0]} {[1]} {[2][3]} {[3].items[0]} {[3].items[1]}", wrapper.items);
+        assertEquals("Hello 77 point world ?", r1);
+        
+        String r2 = ResolvePattern.format("{0} {1} {2.3.4} {3.items.0} {3.items.1}", wrapper.items);
+        assertEquals("Hello 77 t world ?", r2);
+    }
+    
+    private static final class Wrapper {
+        public Object[] items;
+
+        public Wrapper(Object... items) {
+            this.items = items;
+        }
+        
+    }
+    
+    private static class A {
+        
+        public int x;
+        public int y;
+        public int z;
+        
+        public A() {
+            this(0,0,0);
+        }
+
+        public A(int x, int y, int z) {
+            this.x = x;
+            this.y = y;
+            this.z = z;
+        }
+
+        @Override
+        public int hashCode() {
+            return HashUtils.hashValues(5, 37, x, y, z);
+        }
+
+        @Override
+        public boolean equals(Object object) {
+            if(!(object instanceof A)) {
+                return false;
+            }
+            final A other = (A) object;
+            return 
+                (this.x == other.x) &&
+                (this.y == other.y) &&
+                (this.z == other.z);
+        }
+
+        @Override
+        public String toString() {
+            return "A{" + "x=" + x + ", y=" + y + ", z=" + z + '}';
+        }
+        
+    }
+    
+}