Browse Source

resolve #24
resolve #55

Mariusz Sieroń 6 năm trước cách đây
mục cha
commit
1dfc13885a

+ 4 - 0
assira/src/main/java/net/ranides/assira/annotations/Meta.java

@@ -123,8 +123,12 @@ public final class Meta {
 
     }
 
+    /**
+     * Annotated method is invoked at runtime. Used by DynamicInvoker.
+     */
     @Target(METHOD)
     @Retention(RUNTIME)
+    @Documented
     public @interface DynamicDispatch {
     }
 

+ 50 - 0
assira/src/main/java/net/ranides/assira/generic/IdentTuple.java

@@ -0,0 +1,50 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.generic;
+
+public final class IdentTuple {
+
+    private final Object[] values;
+
+    private final int hash;
+
+    public IdentTuple(Object... values) {
+        this.values = values;
+        this.hash = hashCode0(values);
+    }
+
+    @Override
+    public boolean equals(Object value) {
+        return this == value || ((value!=null && IdentTuple.class.equals(value.getClass())) && equals0(values, ((IdentTuple)value).values));
+    }
+
+    @Override
+    public int hashCode() {
+        return hash;
+    }
+
+    private static int hashCode0(Object[] values) {
+        int hash = 0;
+        for(Object v : values) {
+            hash ^= HashUtils.murmurHash3(System.identityHashCode(v));
+        }
+        return hash;
+    }
+
+    private static boolean equals0(Object[] a, Object[] b) {
+        if(a.length != b.length) {
+            return false;
+        }
+        for(int i=0, n=a.length; i<n; i++) {
+            if(a[i] != b[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}

+ 2 - 2
assira/src/main/java/net/ranides/assira/generic/Tuple.java

@@ -9,7 +9,7 @@ package net.ranides.assira.generic;
 import java.util.Arrays;
 import net.ranides.assira.collection.arrays.ArrayUtils;
 
-public class Tuple implements Comparable<Tuple> {
+public final class Tuple implements Comparable<Tuple> {
 
     private final Object[] values;
 
@@ -19,7 +19,7 @@ public class Tuple implements Comparable<Tuple> {
 
     @Override
     public boolean equals(Object value) {
-        return (value instanceof Tuple) && Arrays.equals(values, ((Tuple)value).values);
+        return this == value || ((value!=null && Tuple.class.equals(value.getClass())) && Arrays.equals(values, ((Tuple)value).values));
     }
 
     @Override

+ 4 - 3
assira/src/main/java/net/ranides/assira/lexer/ByteStreamer.java

@@ -89,7 +89,7 @@ public class ByteStreamer<UserArgument> {
      * Odrzuca bieżący fragment i rozpoczyna przetwarzanie nowego od aktualnej
      * pozycji kursora.
      */
-    public void flush() {
+    public void dispose() {
         recent = position;
     }
 
@@ -105,7 +105,7 @@ public class ByteStreamer<UserArgument> {
         if(recent < position && position <= array.length) {
             send(extra, Arrays.copyOfRange(array, recent+lclip, position-rclip) );
         }
-        flush();
+        dispose();
     }
 
     /**
@@ -123,7 +123,7 @@ public class ByteStreamer<UserArgument> {
      * @throws IllegalStateException jeśli strumień jest w trakcie przetwarzania
      * danych.
      */
-    public void open(byte[] input) {
+    public ByteStreamer<UserArgument> open(byte[] input) {
         if( null != tail ) {
             array = new byte[tail.length + input.length];
             System.arraycopy(tail, 0, array, 0, tail.length);
@@ -134,6 +134,7 @@ public class ByteStreamer<UserArgument> {
         }
         position = 0;
         recent = 0;
+        return this;
     }
 
     /**

+ 4 - 3
assira/src/main/java/net/ranides/assira/lexer/CharStreamer.java

@@ -93,7 +93,7 @@ public class CharStreamer<UserArgument> {
      * Odrzuca bieżący fragment i rozpoczyna przetwarzanie nowego od aktualnej
      * pozycji kursora.
      */
-    public void flush() {
+    public void dispose() {
         recent = position;
     }
     
@@ -109,7 +109,7 @@ public class CharStreamer<UserArgument> {
         if(recent < position && position <= array.length) {
             send(extra, Arrays.copyOfRange(array, recent+left, position-right) );
         } 
-        flush();
+        dispose();
     }
 
     /**
@@ -127,7 +127,7 @@ public class CharStreamer<UserArgument> {
      * @throws IllegalStateException jeśli strumień jest w trakcie przetwarzania
      * danych.
      */
-    public void open(char[] input) {
+    public CharStreamer<UserArgument> open(char[] input) {
         if(array != EMPTY) {
             throw new IllegalStateException("already opened");
         }
@@ -141,6 +141,7 @@ public class CharStreamer<UserArgument> {
         }
         position = 0;
         recent = 0;
+        return this;
     }
     
     /**

+ 166 - 0
assira/src/main/java/net/ranides/assira/lexer/StringStreamer.java

@@ -0,0 +1,166 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+package net.ranides.assira.lexer;
+
+import java.util.Arrays;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+
+/**
+ * <p>
+ * Strumień przetwarzający kolejno elementy zbioru przydatny przy pisaniu
+ * lexerów <i>ad hoc</i>. Żadna funkcjonalność związana z obsługą gramatyki
+ * nie istnieje. Przydatny do przetwarzania sekwencji sterujących, kodów
+ * escape'ujących w stringach, wzorców podstawieniowych etc
+ * </p><p>
+ * Bardziej ściśle: do przetwarzania formatów, w których bezkontekstowe
+ * symbole nie są zagnieżdżane rekurencyjnie.
+ * </p>
+ * @author ranides
+ */
+public class StringStreamer {
+
+    private static final char[] EMPTY = new char[0];
+    private final Consumer<String> consumer;
+    private String tail;
+    private String array;
+    private int recent;
+    private int position;
+
+
+    /**
+     * Tworzy nowy strumień, który przekazuje wyodrębnione fragmenty do podanego
+     * konsumenta.
+     * @param consumer
+     */
+    public StringStreamer(Consumer<String> consumer) {
+        this.tail      = null;
+        this.array     = "";
+        this.recent    = 0;
+        this.position  = 0;
+        this.consumer  = consumer;
+    }
+    
+    /**
+     * Odczytuje element ze strumienia i przesuwa kursor do przodu.
+     * @return 
+     */
+    public char get() {
+        return array.charAt(position++);
+    }
+    
+    /**
+     * Odczytuje element ze strumienia nie przesuwając kursora
+     * @return 
+     */
+    public char peek() {
+        return array.charAt(position);
+    }
+    
+    /**
+     * Pomija element
+     */
+    public void skip() {
+        position++;
+    }
+    
+    public void skip(int offset) {
+        position += offset;
+    }
+    
+    /**
+     * Sprawdza, czy przetworzono wszystkie dostępne dane.
+     * @return 
+     */
+    public boolean end() {
+        return position >= array.length();
+    }
+    
+    /**
+     * Wysyła do odbiorcy fragment o wskazanej treści
+     * @param data
+     */
+    public void send(String data) {
+        consumer.accept(data);
+    }
+
+    /**
+     * Odrzuca bieżący fragment i rozpoczyna przetwarzanie nowego od aktualnej
+     * pozycji kursora.
+     */
+    public void dispose() {
+        recent = position;
+    }
+
+    /**
+     * Wysyła bieżący fragment do odbiorcy, ignorując {@code left} pierwszych
+     * znaków oraz {@code right} ostatnich. Następnie rozpoczyna przetwarzanie 
+     * nowego fragmentu rozpoczynającego się od aktualnej pozycji kursora.
+     * @param left
+     * @param right 
+     */
+    public void flush(int left, int right) {
+        if(recent < position && position <= array.length()) {
+            send(array.substring(recent+left, position-right));
+        }
+        dispose();
+    }
+
+    /**
+     * Wysyła bieżący fragment do odbiorcy. Następnie rozpoczyna przetwarzanie 
+     * nowego fragmentu od aktualnej pozycji kursora.
+     */
+    public void flush() {
+        flush(0, 0);
+    }
+    
+    /**
+     * Rozpoczyna przetwarzanie kolejnej porcji danych
+     * @param input 
+     * @throws IllegalStateException jeśli strumień jest w trakcie przetwarzania
+     * danych.
+     */
+    public StringStreamer open(String input) {
+        if(!array.isEmpty()) {
+            throw new IllegalStateException("already opened");
+        }
+        if( null != tail ) {
+            array = tail + input;
+            tail = null;
+        } else {
+            array = input;
+        }
+        position = 0;
+        recent = 0;
+        return this;
+    }
+    
+    /**
+     * Kończy przetwarzanie danych
+     */
+    public void close() {
+        if(tail != null) { 
+            if( !array.isEmpty() ) {
+                throw new IllegalStateException("tail already in use");
+            }
+        } else {
+            tail = array.substring(recent);
+            recent = 0;
+            position = 0;
+            array = "";
+        }
+    }
+    
+    /**
+     * Sprawdza,czy przetwarzanie wykorzystało wszystkie przekazane dane
+     * @return 
+     */
+    public boolean pending() {
+        return tail != null;
+    }
+
+}

+ 12 - 2
assira/src/main/java/net/ranides/assira/reflection/BeanModel.java

@@ -8,10 +8,13 @@
 package net.ranides.assira.reflection;
 
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 
+import net.ranides.assira.collection.maps.AMap;
 import net.ranides.assira.reflection.impl.bean.FBeanModel;
+import net.ranides.assira.reflection.impl.bean.RBeanModel;
 
 /**
  * 
@@ -53,12 +56,19 @@ public interface BeanModel {
 
     Set<String> fluent();
 
-    Map<String, Object> fluent(Object that);
+    FluentMap fluent(Object that);
 
     Object construct();
     
     Object construct(Map<String, Object> values);
     
     Object clone(Object that);
-    
+
+    interface FluentMap extends Map<String, Object> {
+
+        Object unwrap();
+
+        BeanModel model();
+
+    }
 }

+ 14 - 4
assira/src/main/java/net/ranides/assira/reflection/impl/bean/RBeanModel.java

@@ -115,8 +115,8 @@ public class RBeanModel implements BeanModel {
     }
 
     @Override
-    public Map<String, Object> fluent(Object that) {
-        return new FluentMap(that);
+    public FluentMap fluent(Object that) {
+        return new RFluentMap(that);
     }
 
     @Override
@@ -251,17 +251,27 @@ public class RBeanModel implements BeanModel {
         throw new IllegalArgumentException("Unknown property pattern: " + name);
     }
 
-    private class FluentMap extends AMap<String, Object> {
+    private class RFluentMap extends AMap<String, Object> implements FluentMap {
 
         private final Object that;
 
         private final Set<Entry<String, Object>> entries;
 
-        public FluentMap(Object that) {
+        public RFluentMap(Object that) {
             this.that = that;
             this.entries = new FluentEntries(that);
         }
 
+        @Override
+        public Object unwrap() {
+            return that;
+        }
+
+        @Override
+        public BeanModel model() {
+            return RBeanModel.this;
+        }
+
         @Override
         public Set<String> keySet() {
             return keys;

+ 9 - 9
assira/src/main/java/net/ranides/assira/text/StringUtils.java

@@ -279,39 +279,39 @@ public final class StringUtils {
         char code = stream.get();
         switch(code) {
             case 'n':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_NL);
                 break;
             case 'r':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_CR);
                 break;
             case 't':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_TAB);
                 break;
             case 'f':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_FORM);
                 break;
             case 'b':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_BKSPACE);
                 break;
             case '\\':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_SLASH);
                 break;
             case '"':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_QUOTE);
                 break;
             case 'e':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_ESC);
                 break;
             case '^':
-                stream.flush();
+                stream.dispose();
                 stream.send(CharsType.STR, C_ESC_SEQ);
                 break;
             default:

+ 53 - 0
assira/src/test/java/net/ranides/assira/lexer/StringStreamerTest.java

@@ -0,0 +1,53 @@
+package net.ranides.assira.lexer;
+
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.junit.Assert.*;
+
+public class StringStreamerTest {
+
+    @Test
+    public void testSplit() {
+        assertEquals(Arrays.asList("hello"), split("hello"));
+        assertEquals(Arrays.asList("hello", "world"), split("hello&world"));
+        assertEquals(Arrays.asList("hello", "world", "here"), split("hello&world&here"));
+
+
+        assertEquals(Arrays.asList("hello<component>"), split("hello<component>"));
+        assertEquals(Arrays.asList("hello<component<nest>>"), split("hello<component<nest>>"));
+        assertEquals(Arrays.asList("hello<n>", "world"), split("hello<n>&world"));
+
+
+        assertEquals(Arrays.asList("hello", "world<data>", "here"), split("hello&world<data>&here"));
+        assertEquals(Arrays.asList("hello", "world<data<list>>", "here"), split("hello&world<data<list>>&here"));
+        assertEquals(Arrays.asList("hello", "world<data<list>>", "here", "last"), split("hello&world<data<list>>&here&last"));
+        assertEquals(Arrays.asList("hello", "world<data<list>>", "here", "last<array>"), split("hello&world<data<list>>&here&last<array>"));
+    }
+
+
+    public List<String> split(String name) {
+        List<String> list = new ArrayList<>();
+
+        StringStreamer is = new StringStreamer(list::add);
+
+        is.open(name);
+        int level = 0;
+        while(!is.end()) {
+            char c = is.get();
+            if('<' == c) {
+                level++;
+            }
+            if('>' == c) {
+                level--;
+            }
+            if('&' == c && (level ==0)) {
+                is.flush(0,1);
+            }
+        }
+        is.flush();
+        return list;
+    }
+
+}