瀏覽代碼

new: IntArrayMap
new: BeanUtils - serialization to/from xml
new: W3Element#xpath() - implemented

Ranides Atterwim 10 年之前
父節點
當前提交
3cc8e89838

+ 139 - 138
assira/pom.xml

@@ -1,139 +1,140 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
-	<modelVersion>4.0.0</modelVersion>
-	<parent>
-		<groupId>net.ranides</groupId>
-		<artifactId>assira.common</artifactId>
-		<version>2.0.1</version>
-	</parent>
-	<name>assira</name>
-	<groupId>net.ranides</groupId>
-	<artifactId>assira</artifactId>
-	<version>2.0.1</version>
-	<packaging>jar</packaging>
-	
-	<properties>
-		<assira.junit.debug>false</assira.junit.debug>
-	</properties>
-
-	<profiles>
-		<profile>
-			<id>recent-coverage</id>
-			<build>
-				<plugins>
-					<plugin>
-						<groupId>org.jacoco</groupId>
-						<artifactId>jacoco-maven-plugin</artifactId>
-						<version>0.7.5.201505241946</version>
-						<configuration>
-                            <includes>
-                                <include>net/ranides/assira/annotations/Meta*</include>
-                                <include>net/ranides/assira/annotations/javac/**</include>
-                                
-                                <include>net/ranides/assira/awt/ImageLoader*</include>
-                                <include>net/ranides/assira/awt/AWTInvoker*</include>
-                                
-                                <include>net/ranides/assira/collection/arrays/NativeArrayAccess</include>
-                                <include>net/ranides/assira/collection/arrays/NativeArrayUtils</include>
-                                <include>net/ranides/assira/collection/arrays/ArrayUtils</include>
-                                <include>net/ranides/assira/collection/lists/ListUtils</include>
-                                <include>net/ranides/assira/collection/sets/Bitmask*</include>
-                                <include>net/ranides/assira/collection/maps/Cache*</include>
-                                <include>net/ranides/assira/collection/maps/MapUtils*</include>
-                                <include>net/ranides/assira/collection/maps/IntMapUtils*</include>
-                                <include>net/ranides/assira/collection/maps/IntMapCollectors*</include>
-                                <include>net/ranides/assira/collection/maps/MapCollectors*</include>
-                                <include>net/ranides/assira/collection/maps/MultiMap*</include>
-                                <include>net/ranides/assira/collection/maps/ArrayMap*</include>
-                                <include>net/ranides/assira/collection/query/**</include>
-                                
-<!--                                <include>net/ranides/assira/io/PathUtils*</include>-->
-                                
-                                <include>net/ranides/assira/lexer/**</include>
-                                
-                                <include>net/ranides/assira/text/LexicalCast*</include>
-                                <include>net/ranides/assira/text/StringIO*</include>
-                                
-                                <include>net/ranides/assira/xml/XMLQuery*</include>
-                                <include>net/ranides/assira/xml/XMLWriter*</include>
-                            </includes>
-						</configuration>
-					</plugin>
-					<plugin>
-						<groupId>org.apache.maven.plugins</groupId>
-						<artifactId>maven-surefire-plugin</artifactId>
-						<configuration>
-							<excludes>
-								<exclude>**/EventLockTest.java</exclude>
-								<exclude>**/EventJoinerTest.java</exclude>
-								<exclude>**/EventDispatcherTest.java</exclude>
-								<exclude>**/EventProactorTest.java</exclude>
-								<exclude>**/EventReactorTest.java</exclude>
-								<exclude>**/LazyReferenceTest.java</exclude>
-							</excludes>
-						</configuration>
-					</plugin>
-				</plugins>
-			</build>
-		</profile>
-	</profiles>
-	
-	<build>
-		<plugins>
-			<plugin>
-				<groupId>org.jacoco</groupId>
-				<artifactId>jacoco-maven-plugin</artifactId>
-				<version>0.7.5.201505241946</version>
-				<configuration>
-					<excludes>
-						<exclude>net/ranides/assira/test/**</exclude>
-					</excludes>
-				</configuration>
-			</plugin>
-		</plugins>
-	</build>
-    
-	<repositories>
-		<repository>
-			<id>ranides.net</id>
-			<name>ranides.net</name>
-			<url>http://maven.ranides.net</url>
-		</repository>
-	</repositories>
-    
-	<dependencies>
-		<dependency>
-			<groupId>net.ranides</groupId>
-			<artifactId>assira.asm</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>junit</groupId>
-			<artifactId>junit</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>net.ranides</groupId>
-			<artifactId>assira.junit</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>org.slf4j</groupId>
-			<artifactId>slf4j-api</artifactId>
-		</dependency>
-		<dependency>
-			<groupId>com.google.code.findbugs</groupId>
-			<artifactId>annotations</artifactId>
-		</dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.core</groupId>
-            <artifactId>jackson-core</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>com.fasterxml.jackson.datatype</groupId>
-            <artifactId>jackson-datatype-json-org</artifactId>
-        </dependency>
-	 <dependency>
-	  <groupId>se.fishtank</groupId>
-	  <artifactId>css-selectors</artifactId>
-	  <version>2.0</version>
-	 </dependency>
-	</dependencies>
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+	<parent>
+		<groupId>net.ranides</groupId>
+		<artifactId>assira.common</artifactId>
+		<version>2.0.1</version>
+	</parent>
+	<name>assira</name>
+	<groupId>net.ranides</groupId>
+	<artifactId>assira</artifactId>
+	<version>2.0.1</version>
+	<packaging>jar</packaging>
+	
+	<properties>
+		<assira.junit.debug>false</assira.junit.debug>
+	</properties>
+
+	<profiles>
+		<profile>
+			<id>recent-coverage</id>
+			<build>
+				<plugins>
+					<plugin>
+						<groupId>org.jacoco</groupId>
+						<artifactId>jacoco-maven-plugin</artifactId>
+						<version>0.7.5.201505241946</version>
+						<configuration>
+                            <includes>
+                                <include>net/ranides/assira/annotations/Meta*</include>
+                                <include>net/ranides/assira/annotations/javac/**</include>
+                                
+                                <include>net/ranides/assira/awt/ImageLoader*</include>
+                                <include>net/ranides/assira/awt/AWTInvoker*</include>
+                                
+                                <include>net/ranides/assira/collection/arrays/NativeArrayAccess</include>
+                                <include>net/ranides/assira/collection/arrays/NativeArrayUtils</include>
+                                <include>net/ranides/assira/collection/arrays/ArrayUtils</include>
+                                <include>net/ranides/assira/collection/lists/ListUtils</include>
+                                <include>net/ranides/assira/collection/sets/Bitmask*</include>
+                                <include>net/ranides/assira/collection/maps/Cache*</include>
+                                <include>net/ranides/assira/collection/maps/MapUtils*</include>
+                                <include>net/ranides/assira/collection/maps/IntMapUtils*</include>
+                                <include>net/ranides/assira/collection/maps/IntMapCollectors*</include>
+                                <include>net/ranides/assira/collection/maps/MapCollectors*</include>
+                                <include>net/ranides/assira/collection/maps/MultiMap*</include>
+                                <include>net/ranides/assira/collection/maps/ArrayMap*</include>
+                                <include>net/ranides/assira/collection/maps/IntArrayMap*</include>
+                                <include>net/ranides/assira/collection/query/**</include>
+                                
+<!--                                <include>net/ranides/assira/io/PathUtils*</include>-->
+                                
+                                <include>net/ranides/assira/lexer/**</include>
+                                
+                                <include>net/ranides/assira/text/LexicalCast*</include>
+                                <include>net/ranides/assira/text/StringIO*</include>
+                                
+                                <include>net/ranides/assira/xml/XMLQuery*</include>
+                                <include>net/ranides/assira/xml/XMLWriter*</include>
+                            </includes>
+						</configuration>
+					</plugin>
+					<plugin>
+						<groupId>org.apache.maven.plugins</groupId>
+						<artifactId>maven-surefire-plugin</artifactId>
+						<configuration>
+							<excludes>
+								<exclude>**/EventLockTest.java</exclude>
+								<exclude>**/EventJoinerTest.java</exclude>
+								<exclude>**/EventDispatcherTest.java</exclude>
+								<exclude>**/EventProactorTest.java</exclude>
+								<exclude>**/EventReactorTest.java</exclude>
+								<exclude>**/LazyReferenceTest.java</exclude>
+							</excludes>
+						</configuration>
+					</plugin>
+				</plugins>
+			</build>
+		</profile>
+	</profiles>
+	
+	<build>
+		<plugins>
+			<plugin>
+				<groupId>org.jacoco</groupId>
+				<artifactId>jacoco-maven-plugin</artifactId>
+				<version>0.7.5.201505241946</version>
+				<configuration>
+					<excludes>
+						<exclude>net/ranides/assira/test/**</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+    
+	<repositories>
+		<repository>
+			<id>ranides.net</id>
+			<name>ranides.net</name>
+			<url>http://maven.ranides.net</url>
+		</repository>
+	</repositories>
+    
+	<dependencies>
+		<dependency>
+			<groupId>net.ranides</groupId>
+			<artifactId>assira.asm</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>junit</groupId>
+			<artifactId>junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>net.ranides</groupId>
+			<artifactId>assira.junit</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.slf4j</groupId>
+			<artifactId>slf4j-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>com.google.code.findbugs</groupId>
+			<artifactId>annotations</artifactId>
+		</dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-json-org</artifactId>
+        </dependency>
+	 <dependency>
+	  <groupId>se.fishtank</groupId>
+	  <artifactId>css-selectors</artifactId>
+	  <version>2.0</version>
+	 </dependency>
+	</dependencies>
 </project>

+ 422 - 424
assira/src/main/java/net/ranides/assira/collection/maps/Cache.java

@@ -1,424 +1,422 @@
-/*
- * @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.lang.ref.Reference;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.SoftReference;
-import java.time.Duration;
-import java.time.Instant;
-import java.util.AbstractMap;
-import java.util.ArrayDeque;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Supplier;
-
-/**
- * <p>
- * Kolekcja zaprojektowana jako podręczna pamięć na obiekty, których koszt 
- * utworzenia jest relatywnie wysoki. Kolekcja przechowuje {@code holdsize} 
- * ostatnio używanych elementów, bez względu na to, czy istnieją inne referencje 
- * do nich. Przez "użycie" rozumiemy dowolną operację zapis lub odczytu. 
- * W szczególności taka strategia pozwala polegać na metodzie "containsKey", 
- * która uchroni wartość przed natychmiastowym usunięciem - jeśli tylko stwierdzono, 
- * że klucz istnieje. Dodatkowo kolekcja przechowuje {@code cachesize} elementów 
- * za pomocą WeakReference - obiekty te mogą zostać zwolnione przez GC, jeśli 
- * zajdzie taka potrzeba.
- * </p><p>
- * Uwaga: wszystkie widoki zwracane przez "cache" są niebezpieczne w tym sensie, 
- * że nie można wywoływać <b>żadnej</b> metody na obiekcie "cache" w czasie 
- * iterowania po widoku. Jest to spowodowane tym, że cache uwzględnia czas dostępu 
- * do obiektów: nawet odczyt zmienia wewnętrzną reprezentację. W efekcie, jeśli 
- * podczas iteracji zostanie wywołana jakakolwiek inna metoda, to zostanie rzucone 
- * ConcurentModificationException. 
- * </p><p> 
- * Known issue: holdlist contains duplicate entries. If you invoke #get(key) x times, 
- * then "key" will be inserted to holdlist x times too. It's important observation, because
- * it means that other old keys will be discarded. In effect, we will protect last 
- * {@code holdsize} "just keys" but not "distinct keys".
- * </p>
- * 
- * @param <K> 
- * @param <V> 
- * @author Ranides Atterwim <ranides@gmail.com>
- */
-@SuppressWarnings("element-type-mismatch")
-public class Cache<K,V> extends AbstractMap<K,V> {
-    
-    // @todo (assira #4) CacheTTL
-
-    private final CacheMap<K,V> map;
-
-    private final CacheList<V> list;
-    
-    private final Map<K,V> view;
-    
-    /**
-     * Tworzy cache, który przechowuje maksymalnie 1024 elementy, w tym 32 
-     * ostatnio użyte są chronione przez usunięciem przez GC.
-     */
-    public Cache() {
-        this(1024, 32);
-        
-    }
-    
-    /**
-     * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych 
-     * elementów, bez względu na to, czy istnieją do nich inne referencje.
-     * @param cachesize ilość przechowywanych elementów
-     */
-    public Cache(int cachesize) {
-        this(cachesize, cachesize);
-    }
-
-    /**
-     * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych 
-     * elementów, w tym chroni przez usunięciem przez GC ostatnie {@code holdsize}
-     * elementów.
-     * @param cachesize ilość przechowywanych elementów
-     * @param holdsize ilość elementów chronionych przed GC
-     */
-    public Cache(int cachesize, int holdsize) {
-        if( cachesize < holdsize ) {
-            throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
-        }
-        this.list   = new CacheList<>(Math.max(1,holdsize));
-        this.map    = new CRMap<>(cachesize);
-        this.view   = this.map.view();
-    }
-    
-    public Cache(int cachesize, Duration ttl) {
-        this(cachesize, cachesize, ttl);
-    }
-    
-    public Cache(int cachesize, int holdsize, Duration ttl) {
-        if( cachesize < holdsize ) {
-            throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
-        }
-        this.list   = new CacheList<>(Math.max(1,holdsize));
-        this.map    = new TRMap<>(cachesize, ttl);
-        this.view   = this.map.view();
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-        synchronized(this) {
-            return null != get(key);
-        }
-    }
-
-    @Override
-    public V get(Object key) {
-        synchronized(this) {
-            V result = map.getValue(key);
-            if (null != result) {
-                list.hold(result);
-            }
-            return result;
-        }
-    }
-    
-    public V get(K key, Supplier<V> supplier) {
-        synchronized(this) {
-            V result = map.getValue(key);
-            if (null == result) {
-                map.putValue(key, result = supplier.get());
-            }
-            list.hold(result);
-            return result;
-        }
-    }
-    
-    /**
-     * Wstawia wartość do cache'u i zwraca ją jako wynik. Jeśli w mapie
-     * istniała już jakaś wartość, to ją zastępuje.
-     * @param key
-     * @param value
-     * @return 
-     */
-    public V pass(K key, V value) {
-        synchronized(this) {
-            map.release();
-            map.putValue(key, value);
-            return value;
-        }
-    }
-
-    @Override
-    public V put(K key, V value) {
-        synchronized(this) {
-            map.release();
-            V prev = map.getValue(key);
-            map.putValue(key, value);
-            return prev;
-        }
-    }
-
-    @Override
-    public V remove(Object key) {
-        synchronized(this) {
-            map.release();
-            return map.removeValue(key);
-        }
-    }
-
-    @Override
-    public void clear() {
-        synchronized(this) {
-            list.clear();
-            map.clear();
-        }
-    }
-
-    @Override
-    public int size() {
-        synchronized(this) {
-            map.release();
-            return map.size();
-        }
-    }
-    
-    /**
-     * Returns number of holded entries
-     * Please note, that holdlist could contain duplicated entries, 
-     * so returned number could be deceptively greater than size of map. 
-     * @return
-     */
-    public int holdsize() {
-        synchronized(this) {
-            map.release();
-            return list.size();
-        }
-    }
-    
-    /**
-     * Holds value specified by key
-     * @param key
-     * @return 
-     */
-    public boolean hold(K key) {
-        synchronized(this) {
-            return null != get(key);
-        }
-    }
-    
-    /**
-     * Holds as many entries as possible
-     * @return number of holded values
-     */
-    public int hold() {
-        synchronized(this) {
-            for(V value : values() ) {
-                if(null != value) {
-                    list.hold(value);
-                }
-            }
-            return list.size();
-        }
-    }
-    
-    @Override
-    public Set<Entry<K,V>> entrySet() {
-        map.release();
-        return view.entrySet();
-    }
-
-    @Override
-    public Set<K> keySet() {
-        map.release();
-        return view.keySet();
-    }
-
-    @Override
-    public Collection<V> values() {
-        map.release();
-        return view.values();
-    }
-    
-    @SuppressWarnings("unchecked")
-    private static <TK> TK asKey(Reference<?> ref) {
-        return (ref != null) ? ((KRef<TK,?>)ref).key : null;
-    }
-    
-    private static class KRef<K,V> extends SoftReference<V> {
-        
-        public final K key;
-
-        public KRef(K key, V value, ReferenceQueue<V> queue) {
-            super(value, queue);
-            this.key = key;
-        }
-    }
-    
-    private static class TRef<K,V> extends KRef<K,V> {
-        
-        public Instant time;
-
-        public TRef(K key, V value, ReferenceQueue<V> queue, Duration ttl) {
-            super(key, value, queue);
-            this.time = Instant.now().plus(ttl);
-        }
-
-        @Override
-        public V get() {
-            return time.isAfter(Instant.now()) ? null : super.get();
-        }
-        
-    }
-    
-    private static class TRMap<K,V> extends LinkedHashMap<K, TRef<K,V>> implements CacheMap<K,V> {
-        
-        protected final int maxsize;
-        
-        protected final Duration ttl;
-        
-        protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
-    
-        public TRMap(int maxsize, Duration ttl) {
-            super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, false);
-            this.maxsize = maxsize;
-            this.ttl = ttl;
-        }
-        
-        @Override
-        protected boolean removeEldestEntry(Entry<K, TRef<K,V>> eldest) {
-            return this.size() > maxsize || eldest.getValue().time.isAfter(Instant.now());
-        }
-        
-        @Override
-        public void release() {
-            synchronized(this) {
-                Reference<? extends V> ref;
-                while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
-                
-                Instant now = Instant.now();
-                Iterator<Entry<K, TRef<K, V>>> iterator = entrySet().iterator();
-                while(iterator.hasNext()) {
-                    if(iterator.next().getValue().time.isBefore(now)) {
-                        break;
-                    }
-                    iterator.remove();
-                }
-            }
-        }
-        
-        @Override
-        public void putValue(K key, V value) {
-            put(key, new TRef<>(key, value, queue, ttl));
-        }
-        
-        @Override
-        public V removeValue(Object key) {
-            Reference<V> ref = remove(key);
-            return (ref != null) ? ref.get() : null;
-        }
-        
-        @Override
-        public Map<K, V> view() {
-            return MapUtils.map(this, v -> v.get());
-        }
-        
-    };
-    
-    private static class CRMap<K,V> extends LinkedHashMap<K, KRef<K,V>> implements CacheMap<K,V> {
-        
-        protected final int maxsize;
-        
-        protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
-    
-        public CRMap(int maxsize) {
-            super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, true);
-            this.maxsize = maxsize;
-        }
-        
-        @Override
-        protected boolean removeEldestEntry(Entry<K, KRef<K,V>> eldest) {
-            return this.size() > maxsize;
-        }
-        
-        @Override
-        public void release() {
-            synchronized(this) {
-                Reference<? extends V> ref;
-                while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
-            }
-        }
-        
-        @Override
-        public void putValue(K key, V value) {
-            put(key, new KRef<>(key, value, queue));
-        }
-        
-        @Override
-        public V removeValue(Object key) {
-            Reference<V> ref = remove(key);
-            return (ref != null) ? ref.get() : null;
-        }
-        
-        @Override
-        public Map<K, V> view() {
-            return MapUtils.map(this, v -> v.get());
-        }
-        
-    };
-    
-    private interface CacheMap<K,V> {
-        
-        Map<K,V> view();
-        
-        void release();
-        
-        void putValue(K key, V value);
-        
-        default V getValue(Object key) {
-            Reference<V> ref = get(key);
-            if(null == ref) {
-                return null;
-            }
-            V var = ref.get();
-            if(null == var) {
-                remove(key);
-            }
-            return var;
-        }
-        
-        default V removeValue(Object key) {
-            Reference<V> ref = remove(key);
-            return (ref != null) ? ref.get() : null;
-        }
-        
-        void clear();
-        
-        int size();
-        
-        Reference<V> get(Object key);
-        
-        Reference<V> remove(Object key);
-        
-        
-        
-    }
-    
-    private static class CacheList<TV> extends ArrayDeque<TV> {
-        
-        private final int maxsize;
-
-        public CacheList(int maxsize) {
-            this.maxsize = maxsize;
-        }
-        
-        public void hold(TV value) {
-            addFirst(value);
-            if (size() > maxsize) { removeLast(); }
-        }
-        
-    }
-    
-}
+/*
+ * @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.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.AbstractMap;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Supplier;
+
+/**
+ * <p>
+ * Kolekcja zaprojektowana jako podręczna pamięć na obiekty, których koszt 
+ * utworzenia jest relatywnie wysoki. Kolekcja przechowuje {@code holdsize} 
+ * ostatnio używanych elementów, bez względu na to, czy istnieją inne referencje 
+ * do nich. Przez "użycie" rozumiemy dowolną operację zapis lub odczytu. 
+ * W szczególności taka strategia pozwala polegać na metodzie "containsKey", 
+ * która uchroni wartość przed natychmiastowym usunięciem - jeśli tylko stwierdzono, 
+ * że klucz istnieje. Dodatkowo kolekcja przechowuje {@code cachesize} elementów 
+ * za pomocą WeakReference - obiekty te mogą zostać zwolnione przez GC, jeśli 
+ * zajdzie taka potrzeba.
+ * </p><p>
+ * Uwaga: wszystkie widoki zwracane przez "cache" są niebezpieczne w tym sensie, 
+ * że nie można wywoływać <b>żadnej</b> metody na obiekcie "cache" w czasie 
+ * iterowania po widoku. Jest to spowodowane tym, że cache uwzględnia czas dostępu 
+ * do obiektów: nawet odczyt zmienia wewnętrzną reprezentację. W efekcie, jeśli 
+ * podczas iteracji zostanie wywołana jakakolwiek inna metoda, to zostanie rzucone 
+ * ConcurentModificationException. 
+ * </p><p> 
+ * Known issue: holdlist contains duplicate entries. If you invoke #get(key) x times, 
+ * then "key" will be inserted to holdlist x times too. It's important observation, because
+ * it means that other old keys will be discarded. In effect, we will protect last 
+ * {@code holdsize} "just keys" but not "distinct keys".
+ * </p>
+ * 
+ * @param <K> 
+ * @param <V> 
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+@SuppressWarnings("element-type-mismatch")
+public class Cache<K,V> extends AbstractMap<K,V> {
+    
+    private final CacheMap<K,V> map;
+
+    private final CacheList<V> list;
+    
+    private final Map<K,V> view;
+    
+    /**
+     * Tworzy cache, który przechowuje maksymalnie 1024 elementy, w tym 32 
+     * ostatnio użyte są chronione przez usunięciem przez GC.
+     */
+    public Cache() {
+        this(1024, 32);
+        
+    }
+    
+    /**
+     * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych 
+     * elementów, bez względu na to, czy istnieją do nich inne referencje.
+     * @param cachesize ilość przechowywanych elementów
+     */
+    public Cache(int cachesize) {
+        this(cachesize, cachesize);
+    }
+
+    /**
+     * Tworzy cache, którzy przechowuje {@code cachesize} ostatnio używanych 
+     * elementów, w tym chroni przez usunięciem przez GC ostatnie {@code holdsize}
+     * elementów.
+     * @param cachesize ilość przechowywanych elementów
+     * @param holdsize ilość elementów chronionych przed GC
+     */
+    public Cache(int cachesize, int holdsize) {
+        if( cachesize < holdsize ) {
+            throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
+        }
+        this.list   = new CacheList<>(Math.max(1,holdsize));
+        this.map    = new CRMap<>(cachesize);
+        this.view   = this.map.view();
+    }
+    
+    public Cache(int cachesize, Duration ttl) {
+        this(cachesize, cachesize, ttl);
+    }
+    
+    public Cache(int cachesize, int holdsize, Duration ttl) {
+        if( cachesize < holdsize ) {
+            throw new IllegalArgumentException("cachesize must be greater or equal to holdsize");
+        }
+        this.list   = new CacheList<>(Math.max(1,holdsize));
+        this.map    = new TRMap<>(cachesize, ttl);
+        this.view   = this.map.view();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        synchronized(this) {
+            return null != get(key);
+        }
+    }
+
+    @Override
+    public V get(Object key) {
+        synchronized(this) {
+            V result = map.getValue(key);
+            if (null != result) {
+                list.hold(result);
+            }
+            return result;
+        }
+    }
+    
+    public V get(K key, Supplier<V> supplier) {
+        synchronized(this) {
+            V result = map.getValue(key);
+            if (null == result) {
+                map.putValue(key, result = supplier.get());
+            }
+            list.hold(result);
+            return result;
+        }
+    }
+    
+    /**
+     * Wstawia wartość do cache'u i zwraca ją jako wynik. Jeśli w mapie
+     * istniała już jakaś wartość, to ją zastępuje.
+     * @param key
+     * @param value
+     * @return 
+     */
+    public V pass(K key, V value) {
+        synchronized(this) {
+            map.release();
+            map.putValue(key, value);
+            return value;
+        }
+    }
+
+    @Override
+    public V put(K key, V value) {
+        synchronized(this) {
+            map.release();
+            V prev = map.getValue(key);
+            map.putValue(key, value);
+            return prev;
+        }
+    }
+
+    @Override
+    public V remove(Object key) {
+        synchronized(this) {
+            map.release();
+            return map.removeValue(key);
+        }
+    }
+
+    @Override
+    public void clear() {
+        synchronized(this) {
+            list.clear();
+            map.clear();
+        }
+    }
+
+    @Override
+    public int size() {
+        synchronized(this) {
+            map.release();
+            return map.size();
+        }
+    }
+    
+    /**
+     * Returns number of holded entries
+     * Please note, that holdlist could contain duplicated entries, 
+     * so returned number could be deceptively greater than size of map. 
+     * @return
+     */
+    public int holdsize() {
+        synchronized(this) {
+            map.release();
+            return list.size();
+        }
+    }
+    
+    /**
+     * Holds value specified by key
+     * @param key
+     * @return 
+     */
+    public boolean hold(K key) {
+        synchronized(this) {
+            return null != get(key);
+        }
+    }
+    
+    /**
+     * Holds as many entries as possible
+     * @return number of holded values
+     */
+    public int hold() {
+        synchronized(this) {
+            for(V value : values() ) {
+                if(null != value) {
+                    list.hold(value);
+                }
+            }
+            return list.size();
+        }
+    }
+    
+    @Override
+    public Set<Entry<K,V>> entrySet() {
+        map.release();
+        return view.entrySet();
+    }
+
+    @Override
+    public Set<K> keySet() {
+        map.release();
+        return view.keySet();
+    }
+
+    @Override
+    public Collection<V> values() {
+        map.release();
+        return view.values();
+    }
+    
+    @SuppressWarnings("unchecked")
+    private static <TK> TK asKey(Reference<?> ref) {
+        return (ref != null) ? ((KRef<TK,?>)ref).key : null;
+    }
+    
+    private static class KRef<K,V> extends SoftReference<V> {
+        
+        public final K key;
+
+        public KRef(K key, V value, ReferenceQueue<V> queue) {
+            super(value, queue);
+            this.key = key;
+        }
+    }
+    
+    private static class TRef<K,V> extends KRef<K,V> {
+        
+        public Instant time;
+
+        public TRef(K key, V value, ReferenceQueue<V> queue, Duration ttl) {
+            super(key, value, queue);
+            this.time = Instant.now().plus(ttl);
+        }
+
+        @Override
+        public V get() {
+            return time.isAfter(Instant.now()) ? null : super.get();
+        }
+        
+    }
+    
+    private static class TRMap<K,V> extends LinkedHashMap<K, TRef<K,V>> implements CacheMap<K,V> {
+        
+        protected final int maxsize;
+        
+        protected final Duration ttl;
+        
+        protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
+    
+        public TRMap(int maxsize, Duration ttl) {
+            super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, false);
+            this.maxsize = maxsize;
+            this.ttl = ttl;
+        }
+        
+        @Override
+        protected boolean removeEldestEntry(Entry<K, TRef<K,V>> eldest) {
+            return this.size() > maxsize || eldest.getValue().time.isAfter(Instant.now());
+        }
+        
+        @Override
+        public void release() {
+            synchronized(this) {
+                Reference<? extends V> ref;
+                while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
+                
+                Instant now = Instant.now();
+                Iterator<Entry<K, TRef<K, V>>> iterator = entrySet().iterator();
+                while(iterator.hasNext()) {
+                    if(iterator.next().getValue().time.isBefore(now)) {
+                        break;
+                    }
+                    iterator.remove();
+                }
+            }
+        }
+        
+        @Override
+        public void putValue(K key, V value) {
+            put(key, new TRef<>(key, value, queue, ttl));
+        }
+        
+        @Override
+        public V removeValue(Object key) {
+            Reference<V> ref = remove(key);
+            return (ref != null) ? ref.get() : null;
+        }
+        
+        @Override
+        public Map<K, V> view() {
+            return MapUtils.map(this, v -> v.get());
+        }
+        
+    };
+    
+    private static class CRMap<K,V> extends LinkedHashMap<K, KRef<K,V>> implements CacheMap<K,V> {
+        
+        protected final int maxsize;
+        
+        protected transient final ReferenceQueue<V> queue = new ReferenceQueue<>();
+    
+        public CRMap(int maxsize) {
+            super( Math.min(Math.round(maxsize / 0.75f)+2, 2048), 0.75f, true);
+            this.maxsize = maxsize;
+        }
+        
+        @Override
+        protected boolean removeEldestEntry(Entry<K, KRef<K,V>> eldest) {
+            return this.size() > maxsize;
+        }
+        
+        @Override
+        public void release() {
+            synchronized(this) {
+                Reference<? extends V> ref;
+                while ( null != (ref = queue.poll()) ) { remove( asKey(ref) ); }
+            }
+        }
+        
+        @Override
+        public void putValue(K key, V value) {
+            put(key, new KRef<>(key, value, queue));
+        }
+        
+        @Override
+        public V removeValue(Object key) {
+            Reference<V> ref = remove(key);
+            return (ref != null) ? ref.get() : null;
+        }
+        
+        @Override
+        public Map<K, V> view() {
+            return MapUtils.map(this, v -> v.get());
+        }
+        
+    };
+    
+    private interface CacheMap<K,V> {
+        
+        Map<K,V> view();
+        
+        void release();
+        
+        void putValue(K key, V value);
+        
+        default V getValue(Object key) {
+            Reference<V> ref = get(key);
+            if(null == ref) {
+                return null;
+            }
+            V var = ref.get();
+            if(null == var) {
+                remove(key);
+            }
+            return var;
+        }
+        
+        default V removeValue(Object key) {
+            Reference<V> ref = remove(key);
+            return (ref != null) ? ref.get() : null;
+        }
+        
+        void clear();
+        
+        int size();
+        
+        Reference<V> get(Object key);
+        
+        Reference<V> remove(Object key);
+        
+        
+        
+    }
+    
+    private static class CacheList<TV> extends ArrayDeque<TV> {
+        
+        private final int maxsize;
+
+        public CacheList(int maxsize) {
+            this.maxsize = maxsize;
+        }
+        
+        public void hold(TV value) {
+            addFirst(value);
+            if (size() > maxsize) { removeLast(); }
+        }
+        
+    }
+    
+}

+ 199 - 0
assira/src/main/java/net/ranides/assira/collection/maps/IntArrayMap.java

@@ -0,0 +1,199 @@
+/*
+ * @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.AbstractSet;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.Set;
+import net.ranides.assira.collection.IntComparator;
+import net.ranides.assira.collection.arrays.ArraySearch;
+import net.ranides.assira.collection.arrays.IntArrays;
+import net.ranides.assira.collection.arrays.NativeArraySort;
+import net.ranides.assira.collection.iterators.IntIterator;
+import net.ranides.assira.collection.iterators.RandomAccessIterator;
+import net.ranides.assira.collection.sets.AIntSet;
+import net.ranides.assira.collection.sets.IntSet;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public class IntArrayMap<V> extends AIntMap<V> {
+
+    private static final long serialVersionUID = 1L;
+    
+    private final Class<?> type;
+    private final int[] keys;
+    private final V[] vars;
+    
+    private final IntComparator cmp;
+    
+    public IntArrayMap(int[] keys, V[] vars) {
+        this(keys, vars, IntComparator.ASC);
+    }
+
+    public IntArrayMap(int[] keys, V[] vars, IntComparator comparator) {
+        this.type = keys.getClass().getComponentType();
+        this.keys = keys.clone();
+        this.vars = vars.clone();
+        this.cmp = comparator;
+        sort();
+    }
+    
+    private void sort() {
+        NativeArraySort.quickSort(0, keys.length, (a,b) -> cmp.compare(keys[a],keys[b]), (a,b) -> {
+            swap(keys, a, b);
+            swap(vars, a, b);
+        });
+    }
+    
+    private static <T> void swap(int[] array, int a, int b) {
+        int t = array[a];
+        array[a] = array[b];
+        array[b] = t;
+    }
+    
+    private static <T> void swap(T[] array, int a, int b) {
+        T t = array[a];
+        array[a] = array[b];
+        array[b] = t;
+    }
+
+    @Override
+    public Set<IntEntry<V>> fastEntrySet() {
+        return new AbstractSet<IntEntry<V>>() {
+            @Override
+            public Iterator<IntEntry<V>> iterator() {
+                return new AIterator(0);
+            }
+
+            @Override
+            public int size() {
+                return keys.length;
+            }
+
+        };
+    }
+    
+    
+    
+    @Override
+    public Collection<V> values() {
+        return Arrays.asList(vars);
+    }
+
+    @Override
+    public IntSet keySet() {
+        return new AKeys();
+    }
+
+    @Override
+    public V get(int key) {
+        if(!type.isInstance(key)) {
+            return null;
+        }
+        int i = ArraySearch.binarySearch(keys, key, cmp);
+        return i<0 ? null : vars[i];
+    }
+
+    @Override
+    public boolean containsKey(int key) {
+        if(!type.isInstance(key)) {
+            return false;
+        }
+        int i = ArraySearch.binarySearch(keys, key, cmp);
+        return i >= 0;
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+        if (value==null) {
+            for (int i=0; i<vars.length; i++) {
+                if (vars[i]==null) { return true; }
+            }
+        } else {
+            for(int i=0; i<vars.length; i++) {
+                if( value.equals(vars[i]) ) { return true; }
+            }
+        }
+        return false;
+    }
+    
+    @Override
+    public int size() {
+        return keys.length;
+    }
+    
+    private class AEntry implements IntEntry<V> {
+    
+        private final int index;
+
+        public AEntry(int index) {
+            this.index = index;
+        }
+
+        @Override
+        public int getIntKey() {
+            return keys[index];
+        }
+
+        @Override
+        public V getValue() {
+            return vars[index];
+        }
+
+        @Override
+        public V setValue(V value) {
+            V prev = vars[index];
+            vars[index] = value;
+            return prev;
+        }
+        
+    }
+    
+    private class AIterator extends RandomAccessIterator<IntEntry<V>> {
+
+        public AIterator(int index) {
+            super(index);
+        }
+
+        @Override
+        protected int size() {
+            return keys.length;
+        }
+
+        @Override
+        protected IntEntry<V> get(int index) {
+            return new AEntry(index);
+        }
+
+    }
+
+    private class AKeys extends AIntSet {
+        
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public IntIterator iterator() {
+            return IntArrays.iterator(keys);
+        }
+
+        @Override
+        public int size() {
+            return keys.length;
+        }
+
+        @Override
+        public boolean contains(Object o) {
+            return containsKey(o);
+        }
+    }
+
+}

+ 45 - 0
assira/src/main/java/net/ranides/assira/reflection/util/BeanUtils.java

@@ -0,0 +1,45 @@
+/*
+ * @author Ranides Atterwim <ranides@gmail.com>
+ * @copyright Ranides Atterwim
+ * @license WTFPL
+ * @url http://ranides.net/projects/assira
+ */
+
+package net.ranides.assira.reflection.util;
+
+import java.beans.XMLDecoder;
+import java.beans.XMLEncoder;
+import net.ranides.assira.io.StringInput;
+import net.ranides.assira.io.StringOutput;
+import org.xml.sax.InputSource;
+
+/**
+ *
+ * @author Ranides Atterwim <ranides@gmail.com>
+ */
+public final class BeanUtils {
+    
+    private BeanUtils() {
+        /* utility class */
+    }
+
+    public static Object fromXML(String xml) {
+        XMLDecoder dec = new XMLDecoder(new StringInput(xml));
+        Object object = dec.readObject();
+        dec.close();
+        return object;
+    }
+    
+    public static String toXML(Object object) {
+        StringOutput output = new StringOutput();
+        XMLEncoder encoder = new XMLEncoder(output);
+        encoder.writeObject(object);
+        encoder.close();
+        return output.toString();
+    }
+    
+    public static Object copy(Object object) {
+        return fromXML( toXML(object) );
+    }
+    
+}

+ 24 - 5
assira/src/main/java/net/ranides/assira/xml/impl/W3Element.java

@@ -7,7 +7,9 @@
 package net.ranides.assira.xml.impl;
 
 import java.util.AbstractList;
+import java.util.ArrayDeque;
 import java.util.Collection;
+import java.util.Deque;
 import java.util.function.Function;
 import java.util.function.Predicate;
 
@@ -33,6 +35,7 @@ import net.ranides.assira.collection.query.CQuery;
 import net.ranides.assira.collection.query.CQueryBuilder;
 import net.ranides.assira.generic.ValueUtils;
 import net.ranides.assira.reflection.IClass;
+import net.ranides.assira.text.StringUtils;
 import net.ranides.assira.xml.*;
 import org.w3c.dom.DOMException;
 
@@ -100,11 +103,27 @@ public class W3Element extends CElement implements XMLElement {
 
     @Override
     public String xpath() {
-        // @todo (assira #3.2) xml: W3Element.xpath
-        // https://github.com/jOOQ/jOOX/blob/master/jOOX/src/main/java/org/joox/Util.java
-        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
+        Deque<String> list = new ArrayDeque<>();
+        W3Element item;
+        if(node instanceof Attr) {
+            list.addFirst("@" + name());
+            item = $element(((Attr)node).getOwnerElement());
+        } else {
+            list.addFirst(iname());
+            item = getParentNode();
+        }
+        Document root = document().node();
+        for(; item.node!=root; item=item.getParentNode()) {
+            list.addFirst(item.iname());
+        }
+        return "/"+StringUtils.join(list,"/");
     }
-
+    
+    private String iname() {
+        int index = prev(name()).size();
+        return 1 == index ? name() : name() + "[" + index + "]";
+    }
+    
     @Override
     public String content() {
         return node.getNodeValue();
@@ -419,7 +438,7 @@ public class W3Element extends CElement implements XMLElement {
         }
     }
     
-    static XMLElement $element(Node node) {
+    static W3Element $element(Node node) {
         return null == node ? null : new W3Element(node);
     }
     

+ 21 - 5
assira/src/test/java/net/ranides/assira/xml/impl/W3ElementTest.java

@@ -13,7 +13,6 @@ import javax.xml.transform.TransformerConfigurationException;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.dom.DOMResult;
 import net.ranides.assira.TestFiles;
-import net.ranides.assira.junit.NewAssert;
 import net.ranides.assira.reflection.IClass;
 import net.ranides.assira.text.Charsets;
 import net.ranides.assira.text.IOStrings;
@@ -62,7 +61,7 @@ public class W3ElementTest {
         assertEquals("password", doc.find("user login").first().next().name());
         assertEquals(exp, doc.find("user login").first().next("mail").texts());
     }
-    
+ 
     @Test
     public void testSiblinngs() {
         XMLElement doc = $(TestFiles.XML_INPUT).normalize();
@@ -72,7 +71,7 @@ public class W3ElementTest {
         assertEquals(exp1, doc.find("user password").first().siblings().names());
         assertEquals(exp2, doc.find("user password").first().siblings("mail").texts());
     }
-    
+ 
     @Test
     public void testPrev() {
         XMLElement doc = $(TestFiles.XML_INPUT).normalize();
@@ -81,7 +80,7 @@ public class W3ElementTest {
         assertEquals("password", doc.find("user name").first().prev().name());
         assertEquals(exp, doc.find("user name").first().prev("login").texts());
     }
-    
+
     @Test
     public void testParents() {
         XMLElement doc = $(TestFiles.XML_INPUT).normalize();
@@ -117,7 +116,7 @@ public class W3ElementTest {
         assertEquals(exp1, sout);
         assertEquals(exp1, sdom);
         
-        NewAssert.assertThrows(XMLException.class, ()->{
+        assertThrows(XMLException.class, ()->{
             doc.apply($(TestFiles.XML_1));
         });
         assertThrows(XMLException.class, ()->{
@@ -125,4 +124,21 @@ public class W3ElementTest {
         });
     }
     
+    @Test
+    public void testXPath() {
+        XMLElement doc = $(TestFiles.XML_INPUT).normalize();
+        
+        assertEquals("/ccms/users/user", doc.find("user[id=\"101\"]").first().xpath() );
+        assertEquals("/ccms/users/user[2]", doc.find("user[id=\"102\"]").first().xpath() );
+        assertEquals("/ccms/users/user[3]", doc.find("user[id=\"103\"]").first().xpath() );
+        
+        assertEquals("/ccms/users/user[4]/mail[2]", doc.find("user[id=\"104\"] mail[type=\"private\"]").first().xpath() );
+        assertEquals("/ccms/users/user[4]/mail[2]/@type", doc.find("user[id=\"104\"] mail[type=\"private\"]").first().attr("type").xpath() );
+    }
+    
+    protected int position(XMLElement node) {
+        return node.prev(node.name()).size();
+    }
+    
+    
 }