Browse Source

new: URIBuilder - expand List in query arguments
new: ResolverFormat - support for {{ and {{{ tags

Mariusz Sieroń 6 years ago
parent
commit
40337b3d26

+ 68 - 35
assira/src/main/java/net/ranides/assira/io/uri/URIBuilder.java

@@ -8,33 +8,28 @@ package net.ranides.assira.io.uri;
 
 import java.net.URI;
 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.*;
 import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 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.ResolveFormatUtils;
-import net.ranides.assira.text.StrBuilder;
-import net.ranides.assira.text.StringTraits;
-import net.ranides.assira.text.StringUtils;
+import net.ranides.assira.reflection.util.ResolveUtils;
+import net.ranides.assira.text.*;
 
 /**
  *
  * @author Ranides Atterwim <ranides@gmail.com>
  */
 public class URIBuilder {
-    
+
+    //                                                     scheme       login       password    host           port       path            params    fragment
+    //                                                     1            2           3           4              5          6               7         8
+    private static final Pattern RE_URL = Pattern.compile("([^:]+)://(?:([^:@]+)(?::([^@]+))?@)?([^/:?#]+)?(?::([^/?#]+))?(/[^?#]+)?(?:\\?([^?#]*))?(?:#(.+))?");
+
     private String scheme;
     private String host;
     private String port;
@@ -51,7 +46,18 @@ public class URIBuilder {
     }
     
     public URIBuilder(String uri) {
-        this(parse(uri));
+        Matcher matcher = RE_URL.matcher(uri);
+        if(!matcher.matches()) {
+            throw new IllegalArgumentException("Invalid URI: " + uri);
+        }
+        this.scheme = matcher.group(1);
+        this.login = matcher.group(2);
+        this.password = matcher.group(3);
+        this.host = matcher.group(4);
+        this.port = matcher.group(5);
+        this.path = matcher.group(6);
+        this.params = new ParamMap(matcher.group(7));
+        this.fragment = matcher.group(8);
     }
     
     public URIBuilder(URI uri) {
@@ -85,8 +91,8 @@ public class URIBuilder {
         return this;
     }
     
-    public String host() {
-        return host;
+    public Optional<String> host() {
+        return Optional.ofNullable(host);
     }
     
     public URIBuilder host(String value) {
@@ -174,17 +180,15 @@ public class URIBuilder {
     }
     
     public URIBuilder resolve(Object context) {
-        Function<String,String> r = resolver(context);
-
         URIBuilder out = new URIBuilder();
-        out.scheme = r.apply(scheme);
-        out.host = r.apply(host);
-        out.port = r.apply(port);
-        out.path = r.apply(path);
-        out.fragment = r.apply(fragment);
-        out.params = resolve(r, params);
-        out.login = r.apply(login);
-        out.password = r.apply(password);
+        out.scheme = resolveFormat(scheme, context);
+        out.host = resolveFormat(host, context);
+        out.port = resolveFormat(port, context);
+        out.path = resolveFormat(path, context);
+        out.fragment = resolveFormat(fragment, context);
+        out.params = resolveParams(params, context);
+        out.login = resolveFormat(login, context);
+        out.password = resolveFormat(password, context);
         
         return out;
     }
@@ -250,21 +254,50 @@ public class URIBuilder {
     private static String paramValue(String[] array) {
         return array.length > 1 ? array[1] : "";
     }
-    
-    private static Function<String,String> resolver(Object context) {
-        return value -> null == value ? null : ResolveFormatUtils.format(value, context);
+
+
+    private static String resolveFormat(String value, Object context) {
+        return null == value ? null : ResolveFormatUtils.format(value, context);
     }
-    
-    private static ParamMap resolve(Function<String,String> resolver, ParamMap map) {
+
+    private static ParamMap resolveParams(ParamMap map, Object context) {
         ParamMap out = new ParamMap();
         for(Entry<String,String> entry : map.set) {
-            out.set.add( newEntry(entry, resolver.apply(entry.getKey()), resolver.apply(entry.getValue())) );
+            String key = resolveFormat(entry.getKey(), context);
+            if(entry.getValue() == null) {
+                out.set.add( newEntry(entry, key, null) );
+            }
+            else {
+                List<String> list = resolveList(entry.getValue(), context);
+                if(list != null) {
+                    for(String item : list) {
+                        out.set.add(new ParamEntry(key, item));
+                    }
+                } else {
+                    out.set.add(newEntry(entry, entry.getKey(), ResolveFormatUtils.format(entry.getValue(), context)));
+                }
+            }
         }
         return out;
     }
 
+    private static List<String> resolveList(String value, Object context) {
+        if(isExpression(value)) {
+            String expression = value.substring(2, value.length() - 2);
+            Object object = ResolveUtils.get(context, expression);
+            if(object instanceof List) {
+                return ((List<?>)object).stream().map(String::valueOf).collect(Collectors.toList());
+            }
+        }
+        return null;
+    }
+
+    private static boolean isExpression(String value) {
+        return value!=null && value.startsWith("{{") && value.endsWith("}}");
+    }
+
     private static Entry<String, String> newEntry(Entry<String, String> e, String key, String value) {
-        return value.equals(e.getValue()) && key.equals(e.getKey()) ? e :new ParamEntry(key, value);
+        return Objects.equals(value, e.getValue()) && Objects.equals(key, e.getKey()) ? e : new ParamEntry(key, value);
     }
     
     

+ 7 - 4
assira/src/main/java/net/ranides/assira/text/ResolveFormat.java

@@ -56,10 +56,10 @@ 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 static final Pattern REGEX_SPECIAL = Pattern.compile("([\\\\*+\\[\\]()\\$.?\\^|])");
     
+    private static final Pattern RE_COMPILE = Pattern.compile("(\\{{1,3})(.+?)(?:,(.+?))?(?:,(.+?))?(\\}{1,3})");
+
     private final ResolveDialect dialect;
     
     private final String pattern;
@@ -79,11 +79,14 @@ public class ResolveFormat implements Serializable {
         Matcher hit = RE_COMPILE.matcher(pattern);
         int last = 0;
         while(hit.find()) {
+            if(hit.group(1).length() != hit.group(5).length()) {
+                throw new IllegalArgumentException("Unbalanced expression delimiters: \"" + hit.group(0) + "\"");
+            }
             if(hit.start()-last > 0) {
                 String text = pattern.substring(last,hit.start());
                 itokens.add(new FString(text));
             }
-            Token token = dialect.get(hit.group(1), hit.group(2), hit.group(3));
+            Token token = dialect.get(hit.group(2), hit.group(3), hit.group(4));
             itokens.add(token);
             iparsers.add(token);
             

+ 39 - 8
assira/src/test/java/net/ranides/assira/io/uri/URIBuilderTest.java

@@ -8,6 +8,7 @@ package net.ranides.assira.io.uri;
 
 import java.net.URI;
 import java.util.Arrays;
+import java.util.List;
 import java.util.TreeMap;
 import org.junit.Test;
 import static net.ranides.assira.junit.NewAssert.*;
@@ -24,9 +25,17 @@ public class URIBuilderTest {
         
         URIBuilder u2 = new URIBuilder("file:///home/ranides/.git");
         assertEquals("file", u2.scheme());
-        
-        URIBuilder u3 = new URIBuilder("code://path");
+        assertEquals("/home/ranides/.git", u2.path().get());
+
+        URIBuilder u3 = new URIBuilder("code://host");
         assertEquals("code", u3.scheme());
+        assertEquals("host", u3.host().get());
+        assertFalse(u3.path().isPresent());
+
+        URIBuilder u4 = new URIBuilder("code:///path");
+        assertEquals("code", u4.scheme());
+        assertFalse(u4.host().isPresent());
+        assertEquals("/path", u4.path().get());
     }
 
     @Test
@@ -34,7 +43,7 @@ public class URIBuilderTest {
         URIBuilder u1 = new URIBuilder("http://ranides.net/dir/main.html?action=open&art=55&now");
         assertEmpty(u1.login());
         assertEmpty(u1.password());
-        assertEquals("ranides.net", u1.host());
+        assertEquals("ranides.net", u1.host().get());
         assertEmpty(u1.port());
         assertEquals("/dir/main.html", u1.path().get());
         assertEmpty(u1.fragment());
@@ -44,7 +53,7 @@ public class URIBuilderTest {
         assertEquals("http://ranides.net/dir/main.html?action=open&art=55&now", u1.build().toString());
         
         URIBuilder u2 = new URIBuilder("http://www.ranides.net/dir/page.html?");
-        assertEquals("www.ranides.net", u2.host());
+        assertEquals("www.ranides.net", u2.host().get());
         assertEquals("/dir/page.html", u2.path().get());
         assertEmpty(u2.params().map());
         assertEquals("http://www.ranides.net/dir/page.html", u2.build().toString());
@@ -52,7 +61,7 @@ public class URIBuilderTest {
         URIBuilder u3 = new URIBuilder("http://login:pass@ranides.net:33/rest?action=open&action=write&action=save#top");
         assertEquals("login", u3.login().get());
         assertEquals("pass",u3.password().get());
-        assertEquals("ranides.net", u3.host());
+        assertEquals("ranides.net", u3.host().get());
         assertEquals("33", u3.port().get());
         assertEquals("/rest", u3.path().get());
         assertEquals("top", u3.fragment().get());
@@ -64,12 +73,22 @@ public class URIBuilderTest {
         URIBuilder u4 = new URIBuilder("http://login:pass@ranides.net:33");
         assertEquals("login", u4.login().get());
         assertEquals("pass",u4.password().get());
-        assertEquals("ranides.net", u4.host());
+        assertEquals("ranides.net", u4.host().get());
         assertEquals("33", u4.port().get());
         assertEmpty(u4.path());
         assertEmpty(u4.fragment());
         assertEmpty(u4.params().map());
         assertEquals("http://login:pass@ranides.net:33", u4.build().toString());
+
+        URIBuilder u5 = new URIBuilder("http://login@ranides.net:33");
+        assertEquals("login", u5.login().get());
+        assertEmpty(u5.password());
+        assertEquals("ranides.net", u5.host().get());
+        assertEquals("33", u5.port().get());
+        assertEmpty(u5.path());
+        assertEmpty(u5.fragment());
+        assertEmpty(u5.params().map());
+        assertEquals("http://login@ranides.net:33", u5.build().toString());
     }
     
     @Test
@@ -137,7 +156,7 @@ public class URIBuilderTest {
     @Test
     public void testParseError() {
         assertThrows(IllegalArgumentException.class, ()->{
-            new URIBuilder("http://{host}.net");
+            new URIBuilder("http://{host}.net").build();
         });
         assertThrows(IllegalArgumentException.class, ()->{
             new URIBuilder().scheme("http").host("{host}.net").build();
@@ -173,7 +192,19 @@ public class URIBuilderTest {
         
         u1.params().removeAll("c");
         assertEquals("http://host?a=1&a&b=4&d=7&e", u1.build().toString());
-        
+    }
+
+    @Test
+    public void testParamsReconstruct() {
+
+        URIBuilder u1 = new URIBuilder("http://host?a={{x}}&b=login&c={{y}}&d=terminate");
+
+        URIBuilder u2 = u1.resolve(new Object(){
+            String x = "EN";
+            List<Object> y = Arrays.asList(1,2,3);
+        });
+
+        assertEquals("http://host?a=EN&b=login&c=1&c=2&c=3&d=terminate", u2.build().toString());
     }
     
 }

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

@@ -73,7 +73,7 @@ public class ResolveFormatTest {
         assertEquals(ResolveDialect.getDefault(), a.dialect());
         assertEquals(ResolveDialect.getInstance("en-US"), b.dialect());
     }
-    
+
     @Test
     public void testEquals() {
         ResolveFormat a = ResolveFormat.compile("PL", "{x} {y} {z}");
@@ -148,6 +148,27 @@ public class ResolveFormatTest {
         assertArrayEquals(new Double[]{14.3, 0.35, 55.0}, out3);
     }
 
+    @Test
+    public void testHandlebars() {
+        Integer[] output= new Integer[3];
+        ResolveFormat fmt = ResolveFormat.compile("{0,number} / {{1,number,java}} / {{{2,number,java}}}!");
+        fmt.parse("12 / 33 / 55_100!", output);
+        assertArrayEquals(new Integer[]{12,33,55100}, output);
+
+        Integer[] input= new Integer[]{17, 42, 81};
+        assertEquals("17 / 42 / 81!", fmt.format(input));
+
+        assertThrows(c -> c.getMessage().equals("Unbalanced expression delimiters: \"{{1,number,java}}}\""), () -> {
+            ResolveFormat.compile("{0,number} / {{1,number,java}}} / {{{2,number,java}}}!");
+        });
+        assertThrows(c -> c.getMessage().equals("Unbalanced expression delimiters: \"{1,number,java}}\""), () -> {
+            ResolveFormat.compile("{0,number} / {1,number,java}} / {{{2,number,java}}}!");
+        });
+        assertThrows(c -> c.getMessage().equals("Unbalanced expression delimiters: \"{{1,number,java}\""), () -> {
+            ResolveFormat.compile("{0,number} / {{1,number,java} / {{{2,number,java}}}!");
+        });
+    }
+
     
     @Test
     public void testFormat_Date() {