/*
 * Chionographis
 *
 * These codes are licensed under CC0.
 * https://creativecommons.org/publicdomain/zero/1.0/deed
 */

package net.furfurylic.chionographis;

import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.WeakHashMap;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * A class for caching objects identified by URIs.
 *
 * The cached objects are held by soft references.
 * Objects of this class are thread-safe.
 *
 * @param <T>
 *      the class of the cached objects.
 */
final class NetResourceCache<T> {

    private final Object LOCK = new Object();

    /** A synchronized canonicalization mapping for URIs. */
    private Map<URI, WeakReference<URI>> canonURIs_ = null;

    /** A possibly identity-based synchronized map. */
    private SoftReference<Map<URI, Optional<T>>> cache_ = null;

    /** Sole constructor. */
    public NetResourceCache() {
    }

    /**
     * Fetches an object from the cache. If no objects are bound to the specified URI,
     * an object for the URI is created and cached.
     *
     * @param uri
     *      a URI.
     * @param listenStored
     *      a listener invoked when an object is about to be cached.
     * @param listenHit
     *      a listener invoked when an object is fetched from the cache.
     * @param factory
     *      a factory function which make an object from a URI, which can return {@code null}
     *      in case of errors.
     *
     * @return
     *      a possibly-{@code null} resolved object.
     */
    public T get(URI uri,
            Consumer<URI> listenStored, Consumer<URI> listenHit,
            Function<URI, ? extends T> factory) {
        assert uri != null;
        assert uri.isAbsolute();

        Map<URI, Optional<T>> strongOne = null;
        synchronized (LOCK) {
            if (cache_ == null) {
                canonURIs_ = Collections.synchronizedMap(new WeakHashMap<>());
            } else {
                strongOne = cache_.get();
            }
            if (strongOne == null) {
                strongOne = Collections.synchronizedMap(new IdentityHashMap<>());
                cache_ = new SoftReference<>(strongOne);
            }
        }

        // Get the privately-canonicalized form.
        URI canonicalizedURI = canonicalizeURI(uri);

        // From here uri shall not be in the canonicalized form.
        if (canonicalizedURI == uri) {
            uri = URI.create(uri.toString());
        }

        // Lock with privately-canonicalized form
        // in order to minimize granularity of locks.
        synchronized (canonicalizedURI) {
            Optional<T> cached = strongOne.get(canonicalizedURI);
            if (cached != null) {
                if (!cached.isPresent()) {
                    // Means that an error occurred in the previous try.
                    return null;
                } else {
                    // Cache hit.
                    listenHit.accept(uri);
                }
            } else {
                cached = Optional.ofNullable(factory.apply(uri));
                if (cached.isPresent()) {
                    listenStored.accept(uri);
                }
                strongOne.put(canonicalizedURI, cached);
            }
            return cached.get();
        }
    }

    /**
     * Canonicalizes a URI so that URIs which have the same logical content are one same object.
     *
     * @param uri
     *      a URI to canonicalize.
     *
     * @return
     *      the canonicalized form of the URI,
     *      which is different object from the parameter <i>uri</i>
     *      if it did not come from this method.
     */
    private URI canonicalizeURI(URI uri) {
        assert uri != null;
        WeakReference<URI> ref = canonURIs_.putIfAbsent(uri, new WeakReference<>(uri));
        URI existing = (ref != null) ? ref.get() : null;
        if (existing == null) {
            return uri;
        } else {
            return existing;
        }
    }
}

@@ -2033,36 +2033,8 @@
 _ = 
-Collections.synchronizedMap(
 new 
@@ -2040,33 +2040,32 @@
  WeakHashMap%3C%3E()
-)
 ;%0A            %7D 
@@ -2462,19 +2462,19 @@
 i shall 
-not
+NOT
  be in t
@@ -3584,16 +3584,54 @@
 nicalize
+, which must not come from this method
 .%0A     *
@@ -3754,18 +3754,19 @@
 ter 
-%3Ci%3Euri%3C/i%3E
+%7B@code uri%7D
 %0A   
@@ -3778,43 +3778,68 @@
     
+only 
 if it 
-did not come from this method
+has been the first object for its logical content
 .%0A  
@@ -3927,60 +3927,146 @@
     
-WeakReference%3CURI%3E ref = canonURIs_.putIfAbsent(uri,
+synchronized (canonURIs_) %7B%0A            WeakReference%3CURI%3E ref = canonURIs_.get(uri);%0A            if (ref == null) %7B%0A                ref =
  new
@@ -4082,25 +4082,24 @@
 rence%3C%3E(uri)
-)
 ;%0A        UR
@@ -4100,89 +4100,46 @@
     
-URI existing = (ref != null) ? ref.get() : null;%0A        if (existing == null) %7B%0A
+        canonURIs_.put(uri, ref);%0A    
     
@@ -4158,32 +4158,36 @@
 rn uri;%0A        
+    
 %7D else %7B%0A       
@@ -4195,24 +4195,85 @@
     
+    
-return existing;
+assert ref.get() != null;%0A                return ref.get();%0A            %7D
 %0A   

Refactor NetResourceCache slightly