sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1395581 - in /sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis: internal/util/ util/collection/ util/resources/
Date Mon, 08 Oct 2012 14:29:50 GMT
Author: desruisseaux
Date: Mon Oct  8 14:29:50 2012
New Revision: 1395581

URL: http://svn.apache.org/viewvc?rev=1395581&view=rev
Log:
Initial port of the Cache class.

Added:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Executors.java
      - copied, changed from r1395068, sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java   (with props)
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java   (with props)
Modified:
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/DaemonThread.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/ReferenceQueueConsumer.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
    sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/DaemonThread.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/DaemonThread.java?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/DaemonThread.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/DaemonThread.java Mon Oct  8 14:29:50 2012
@@ -133,7 +133,7 @@ abstract class DaemonThread extends Thre
         for (DaemonThread thread=first; thread!=null; thread=thread.previous) {
             final long delay = stopWaitingAt - System.nanoTime();
             if (delay <= 0) break;
-            thread.join(delay);
+            thread.join(delay / 1000000); // Convert nanoseconds to milliseconds.
         }
     }
 

Copied: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Executors.java (from r1395068, sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java)
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Executors.java?p2=sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Executors.java&p1=sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java&r1=1395068&r2=1395581&rev=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Executors.java Mon Oct  8 14:29:50 2012
@@ -16,81 +16,166 @@
  */
 package org.apache.sis.internal.util;
 
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.RejectedExecutionException;
 import org.apache.sis.util.Static;
-import org.apache.sis.util.logging.Logging;
 
 
 /**
- * Utilities methods for threads. This class declares in a single place every {@link ThreadGroup}
- * used in SIS. Their intend is to bring some order in debugger informations, by grouping the
- * threads created by SIS together under the same parent tree node.
+ * Library-wide executors for Apache SIS internal tasks only (not arbitrary user tasks).
+ * This class extends {@link ThreadFactory} and {@link RejectedExecutionHandler} for opportunist
+ * reasons. Developers shall ignore this implementation detail, which may change at any time.
+ * The methods for use in this class are:
+ * <p>
+ * <ul>
+ *   <li>{@link #executeDaemonTask(Runnable)}</li>
+ *   <li>{@link #createThreadFactory(String)}</li>
+ * </ul>
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.03)
  * @version 0.3
  * @module
  */
-final class Threads extends Static {
+public final class Executors extends Static implements ThreadFactory, RejectedExecutionHandler {
     /**
-     * The parent of every threads declared in this class. This parent will be declared as close
-     * as possible to the root of all thread groups (i.e. not as an application thread subgroup).
-     * The intend is to separate the library thread groups from the user application thread groups.
+     * The library-wide executor for short tasks. This executor is not for heavy calculations.
+     * A single thread should be sufficient for those tasks. However we use a thread pool for
+     * scalability in case a unusually large amount of tasks are submitted simultaneously.
+     *
+     * <p>The threads created by this class are daemon threads in order to let user application
+     * terminate even if the user do not invoke {@link Threads#shutdown(long)}. Consequently,
+     * tasks given to this constructor shall not be critical tasks, unless the caller thread
+     * has some way to wait for its submitted tasks to complete.</p>
+     *
+     * <p>Queue and thread creation politic:</p>
+     * <ul>
+     *   <li>Initialized with no thread.</li>
+     *   <li>Create one thread when a task if first submitted.</li>
+     *   <li>All other tasks are enqueued, up to 10 enqueued tasks.</li>
+     *   <li>If there is more than 10 enqueued tasks, create more threads up to the number of processors.</li>
+     *   <li>All other tasks submission will block until it can be enqueued.</li>
+     * </ul>
      */
-    private static final ThreadGroup SIS;
+    private static final ExecutorService DAEMON_TASKS;
     static {
-        ThreadGroup parent = Thread.currentThread().getThreadGroup();
-        try {
-            ThreadGroup candidate;
-            while ((candidate = parent.getParent()) != null) {
-                parent = candidate;
-            }
-        } catch (SecurityException e) {
-            // If we are not allowed to get the parent, stop there.
-            // We went up in the tree as much as we were allowed to.
+        final Executors handlers = new Executors(true, "Pooled thread #");
+        final ThreadPoolExecutor ex = new ThreadPoolExecutor(1,
+                Runtime.getRuntime().availableProcessors(),
+                5L, TimeUnit.MINUTES,
+                new LinkedBlockingQueue<Runnable>(10),
+                handlers, handlers);
+        ex.allowCoreThreadTimeOut(true);
+        DAEMON_TASKS = ex;
+        synchronized (Threads.class) {
+            Threads.executor = ex; // Used for shutdown only.
         }
-        SIS = new ThreadGroup(parent, "Apache SIS");
     }
 
     /**
-     * The group of threads for resources disposal. We give them a priority slightly higher
-     * than the normal one since this group shall contain only tasks to be completed very
-     * quickly, and the benefit of executing those tasks soon is more resources made available.
+     * {@code true} if the threads to be created should be daemon threads.
+     * This will also determine the thread group and the thread priority.
      */
-    static final ThreadGroup RESOURCE_DISPOSERS = new ThreadGroup(SIS, "ResourceDisposers") {
-        /* Constructor */ {
-            setMaxPriority(Thread.NORM_PRIORITY + 3);
-        }
-        @Override public void uncaughtException(final Thread thread, final Throwable exception) {
-            Logging.severeException(Logging.getLogger("org.apache.sis"), thread.getClass(), "run", exception);
-        }
-    };
+    private final boolean isDaemon;
 
     /**
-     * The tail of a chain of {@code DaemonThread}s created by the {@code sis-utility} module.
-     * Other modules need to maintain their own chain, if any. See the {@link DaemonThread}
-     * javadoc for more information.
+     * The prefix to put at the beginning of thread names, before thread number.
      */
-    static DaemonThread lastCreatedDaemon;
+    private final String namePrefix;
 
     /**
-     * Do not allows instantiation of this class.
+     * Number of threads created up to date by this thread factory.
+     * This will be used together with {@link #namePrefix} for creating the thread name.
      */
-    private Threads() {
+    private int threadCount;
+
+    /**
+     * Creates a new thread factory, for internal use only.
+     *
+     * @param isDaemon   {@code true} if the threads to be created should be daemon threads.
+     * @param namePrefix The prefix to put at the beginning of thread names, before thread number.
+     */
+    private Executors(final boolean isDaemon, final String prefix) {
+        this.isDaemon = isDaemon;
+        this.namePrefix = prefix;
     }
 
     /**
-     * Sends a kill signal to all daemon threads created by the {@code sis-utility} module,
-     * and waits for the threads to die before to return.
-     * <p>
-     * <strong>This method is for internal use by Apache SIS shutdown hooks only.</strong>
-     * Users should never invoke this method explicitely.
+     * Executes the given short task in a daemon thread. This method shall be invoked for
+     * Apache SIS tasks only, <strong>not</strong> for arbitrary user task. The task must
+     * completes quickly, because we will typically use only one thread for all submitted
+     * tasks (while the number of thread will automatically increase in case of heavy load).
+     * Completion of the task shall not be critical, since the JVM is allowed to shutdown
+     * before task completion.
      *
-     * @param  stopWaitingAt A {@link System#nanoTime()} value telling when to stop waiting.
-     *         This is used for preventing shutdown process to block an indefinite amount of time.
-     * @throws InterruptedException If an other thread invoked {@link #interrupt()} while
-     *         we were waiting for the daemon threads to die.
+     * @param task The quick task to execute in a daemon thread.
      */
-    static synchronized void shutdown(final long stopWaitingAt) throws InterruptedException {
-        DaemonThread.killAll(lastCreatedDaemon, stopWaitingAt);
+    public static void executeDaemonTask(final Runnable task) {
+        DAEMON_TASKS.execute(task);
+    }
+
+    /**
+     * Creates a factory for worker threads created by the SIS library. This factory will
+     * creates ordinary (non-daemon) threads in the "Apache SIS" thread group, using the
+     * given prefix for the thread names.
+     *
+     * @param  prefix The prefix to put in front of thread names, before the thread number.
+     * @return The thread factory.
+     */
+    public static ThreadFactory createThreadFactory(final String prefix) {
+        return new Executors(false, prefix);
+    }
+
+    /**
+     * Invoked when a new thread needs to be created. This method is public as an
+     * implementation side-effect and should not be invoked directly.
+     *
+     * @param  task The task to execute.
+     * @return A new thread running the given task.
+     *
+     * @see #createThreadFactory(String)
+     */
+    @Override
+    public Thread newThread(final Runnable task) {
+        final int n;
+        synchronized (this) {
+            n = ++threadCount;
+        }
+        final String name = namePrefix + n;
+        final Thread thread = new Thread(isDaemon ? Threads.DAEMONS : Threads.SIS, task, name);
+        thread.setDaemon(isDaemon);
+        if (isDaemon) {
+            /*
+             * Daemon threads created by the Apache SIS library are expected to complete quickly.
+             * They are often (but not always) used for cleaning work, in which case a quick run
+             * may be benificial to the rest of the application by releasing resources.  This is
+             * a similar policy to the ReferenceQueueConsumer one.  Note that no user task shall
+             * be submitted in daemon thread.
+             */
+            thread.setPriority(Thread.NORM_PRIORITY + 1);
+        }
+        return thread;
+    }
+
+    /**
+     * Invoked when a task can not be accepted because the queue is full and the maximal number
+     * of threads has been reached. This method blocks until a slot is made available.
+     *
+     * @param  task The task to execute.
+     * @param  executor The executor that invoked this method.
+     * @throws RejectedExecutionException If the caller thread has been interrupted while waiting.
+     */
+    @Override
+    public void rejectedExecution(final Runnable task, final ThreadPoolExecutor executor) {
+        try {
+            executor.getQueue().put(task);
+        } catch (InterruptedException e) {
+            throw new RejectedExecutionException(e);
+        }
     }
 }

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/ReferenceQueueConsumer.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/ReferenceQueueConsumer.java?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/ReferenceQueueConsumer.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/ReferenceQueueConsumer.java Mon Oct  8 14:29:50 2012
@@ -77,13 +77,15 @@ public final class ReferenceQueueConsume
 
     /**
      * Constructs a new thread as a daemon thread. This thread will be sleeping most of the time.
-     * It will run only only a few nanoseconds every time a new {@link Reference} is enqueded.
+     * It will run only only a few nanoseconds every time a new {@link Reference} is enqueued.
+     *
+     * {@note We give to this thread a priority higher than the normal one since this thread shall
+     * execute only tasks to be completed very shortly. Quick execution of those tasks is at the
+     * benefit of the rest of the system, since they make more resources available sooner.}
      */
     private ReferenceQueueConsumer(final DaemonThread lastCreatedDaemon) {
-        super(Threads.RESOURCE_DISPOSERS, "ReferenceQueueConsumer", lastCreatedDaemon);
+        super(Threads.DAEMONS, "ReferenceQueueConsumer", lastCreatedDaemon);
         setPriority(Thread.MAX_PRIORITY - 2);
-        // The above line sets the priority to the maximal value allowed by the
-        // RESOURCE_DISPOSERS group, which is actually lower than MAX_PRIORITY.
     }
 
     /**

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Threads.java Mon Oct  8 14:29:50 2012
@@ -16,6 +16,8 @@
  */
 package org.apache.sis.internal.util;
 
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.ExecutorService;
 import org.apache.sis.util.Static;
 import org.apache.sis.util.logging.Logging;
 
@@ -25,6 +27,11 @@ import org.apache.sis.util.logging.Loggi
  * used in SIS. Their intend is to bring some order in debugger informations, by grouping the
  * threads created by SIS together under the same parent tree node.
  *
+ * {@section Note on dependencies}
+ * This class shall not depend on {@link Executors} or {@link ReferenceQueueConsumer}, because
+ * initialization of those classes create new threads or threaded executor. But it is okay to
+ * have dependencies the other way around.
+ *
  * @author  Martin Desruisseaux (Geomatys)
  * @since   0.3 (derived from geotk-3.03)
  * @version 0.3
@@ -36,7 +43,7 @@ final class Threads extends Static {
      * as possible to the root of all thread groups (i.e. not as an application thread subgroup).
      * The intend is to separate the library thread groups from the user application thread groups.
      */
-    private static final ThreadGroup SIS;
+    static final ThreadGroup SIS;
     static {
         ThreadGroup parent = Thread.currentThread().getThreadGroup();
         try {
@@ -52,14 +59,9 @@ final class Threads extends Static {
     }
 
     /**
-     * The group of threads for resources disposal. We give them a priority slightly higher
-     * than the normal one since this group shall contain only tasks to be completed very
-     * quickly, and the benefit of executing those tasks soon is more resources made available.
-     */
-    static final ThreadGroup RESOURCE_DISPOSERS = new ThreadGroup(SIS, "ResourceDisposers") {
-        /* Constructor */ {
-            setMaxPriority(Thread.NORM_PRIORITY + 3);
-        }
+     * The sub-group for daemon threads, usually for resources disposal.
+     */
+    static final ThreadGroup DAEMONS = new ThreadGroup(SIS, "Daemons") {
         @Override public void uncaughtException(final Thread thread, final Throwable exception) {
             Logging.severeException(Logging.getLogger("org.apache.sis"), thread.getClass(), "run", exception);
         }
@@ -73,6 +75,13 @@ final class Threads extends Static {
     static DaemonThread lastCreatedDaemon;
 
     /**
+     * Executor to shutdown. This is a copy of the {@link Executors#DAEMON_TASKS} field,
+     * copied here only when the {@link Executors} class is loaded and initialized. We
+     * do that way for avoiding dependency from {@code Threads} to {@code Executors}.
+     */
+    static ExecutorService executor;
+
+    /**
      * Do not allows instantiation of this class.
      */
     private Threads() {
@@ -91,6 +100,20 @@ final class Threads extends Static {
      *         we were waiting for the daemon threads to die.
      */
     static synchronized void shutdown(final long stopWaitingAt) throws InterruptedException {
+        if (executor != null) {
+            executor.shutdown();
+            /*
+             * Wait for work completion. In theory this is not necessary since the daemon
+             * tasks are only house-cleaning work. We nevertheless wait for their completion
+             * as a safety. There tasks are supposed to be short.
+             */
+            final long delay = stopWaitingAt - System.nanoTime();
+            if (delay > 0) {
+                executor.awaitTermination(delay, TimeUnit.NANOSECONDS);
+                // Even if the tasks didn't completed, continue without waiting for them.
+                // We can not log at this point, since the logging framework may be shutdown.
+            }
+        }
         DaemonThread.killAll(lastCreatedDaemon, stopWaitingAt);
     }
 }

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java?rev=1395581&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java Mon Oct  8 14:29:50 2012
@@ -0,0 +1,925 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.collection;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.AbstractMap;
+import java.util.LinkedHashMap;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+
+import org.apache.sis.util.Disposable;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.internal.util.ReferenceQueueConsumer;
+
+import static org.apache.sis.internal.util.Executors.executeDaemonTask;
+
+
+/**
+ * A concurrent cache mechanism. This implementation is thread-safe and supports concurrency.
+ * A cache entry can be locked when an object is in process of being created. The steps
+ * are as below:
+ * <p>
+ * <ol>
+ *   <li>Check if the value is already available in the map.
+ *       If it is, return it immediately and we are done.</li>
+ *   <li>Otherwise, get a lock and check again if the value is already available in the map
+ *       (because the value could have been computed by an other thread between step 1 and
+ *       the obtention of the lock). If it is, release the lock and we are done.</li>
+ *   <li>Otherwise compute the value, store the result and release the lock.</li>
+ * </ol>
+ * <p>
+ * The easiest way (except for exception handling) to use this class is to prepare a
+ * {@link Callable} statement to be executed only if the object is not in the cache,
+ * and to invoke the {@link #getOrCreate getOrCreate} method. Example:
+ *
+ * {@preformat java
+ *     private final Cache<String,MyObject> cache = new Cache<String,MyObject>();
+ *
+ *     public MyObject getMyObject(final String key) throws MyCheckedException {
+ *         try {
+ *             return cache.getOrCreate(key, new Callable<MyObject>() {
+ *                 MyObject call() throws FactoryException {
+ *                     return createMyObject(key);
+ *                 }
+ *             });
+ *         } catch (MyCheckedException e) {
+ *             throw e;
+ *         } catch (RuntimeException e) {
+ *             throw e;
+ *         } catch (Exception e) {
+ *             throw new UndeclaredThrowableException(e);
+ *         }
+ *     }
+ * }
+ *
+ * An alternative is to perform explicitly all the steps enumerated above. This alternative
+ * avoid the creation of a temporary {@code Callable} statement which may never be executed,
+ * and avoid the exception handling due to the {@code throws Exception} clause. Note that the
+ * call to {@link Handler#putAndUnlock putAndUnlock} <strong>must</strong> be in the {@code finally}
+ * block of a {@code try} block beginning immediately after the call to {@link #lock lock},
+ * no matter what the result of the computation is (including {@code null}).
+ *
+ * {@preformat java
+ *     private final Cache<String,MyObject> cache = new Cache<String,MyObject>();
+ *
+ *     public MyObject getMyObject(final String key) throws MyCheckedException {
+ *         MyObject value = cache.peek(key);
+ *         if (value == null) {
+ *             final Cache.Handler<MyObject> handler = cache.lock(key);
+ *             try {
+ *                 value = handler.peek();
+ *                 if (value == null) {
+ *                     value = createMyObject(key);
+ *                 }
+ *             } finally {
+ *                 handler.putAndUnlock(value);
+ *             }
+ *         }
+ *         return value;
+ *     }
+ * }
+ *
+ *
+ * {@section Eviction of eldest values}
+ *
+ * <ul>
+ *   <li><p>The <cite>cost</cite> of a value is the value returned by {@link #cost}. The default
+ *       implementation returns 1 in all cases, but subclasses can override this method for
+ *       more elaborated cost computation.</p></li>
+ *   <li><p>The <cite>total cost</cite> is the sum of the cost of all values held by strong
+ *       reference in this cache. The total cost does not include the cost of values held
+ *       by {@linkplain Reference weak or soft reference}.</p></li>
+ *   <li><p>The <cite>cost limit</cite> is the maximal value allowed for the total cost. If
+ *       the total cost exceed this value, then strong references to the eldest values are
+ *       replaced by {@linkplain Reference weak or soft references} until the total cost
+ *       become equals or lower than the cost limit.</p></li>
+ * </ul>
+ *
+ * The total cost is given at construction time. If the {@link #cost} method has not been
+ * overridden, then the total cost is the maximal amount of values to keep by strong references.
+ *
+ *
+ * {@section Circular dependencies}
+ *
+ * This implementation assumes that there is no circular dependencies (or cyclic graph) between
+ * the values in the cache. For example if creating <var>A</var> implies creating <var>B</var>,
+ * then creating <var>B</var> is not allowed to implies (directly or indirectly) the creation of
+ * <var>A</var>. If this rule is not meet, deadlock may occur randomly.
+ *
+ * @param <K> The type of key objects.
+ * @param <V> The type of value objects.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+@ThreadSafe
+public class Cache<K,V> extends AbstractMap<K,V> {
+    /**
+     * The map that contains the cached values. If a value is under the process of being
+     * calculated, then the value will be a temporary instance of {@link Handler}. The
+     * value may also be weak of soft {@link Reference} objects.
+     */
+    private final ConcurrentMap<K,Object> map;
+
+    /**
+     * The keys of values that are retained in the {@linkplain #map} by strong references,
+     * together with an estimation of their cost. This map is <strong>not</strong> thread
+     * safe. For this reason, it must be used by a single thread at a given time, even
+     * for read-only operations.
+     * <p>
+     * Entries in this map are ordered from least-recently accessed to most-recently accessed.
+     */
+    @GuardedBy("costs")
+    private final Map<K,Integer> costs;
+
+    /**
+     * The sum of all values in the {@link #costs} map. This field must be used in the
+     * same thread than {@link #costs}.
+     */
+    @GuardedBy("costs")
+    private long totalCost;
+
+    /**
+     * The maximal cost allowed. If the {@link #totalCost} is above that limit, then the eldest
+     * strong references will be replaced by {@linkplain Reference weak or soft references}.
+     */
+    private final long costLimit;
+
+    /**
+     * If {@code true}, use {@link SoftReference) instead of {@link WeakReference}.
+     */
+    private final boolean soft;
+
+    /**
+     * {@code true} if different values may be assigned to the same key. This is usually
+     * an error, so the default {@code Cache} behavior is to thrown an exception in such
+     * case.
+     */
+    private volatile boolean keyCollisionAllowed;
+
+    /**
+     * A view over the entries in the cache.
+     */
+    private transient Set<Entry<K,V>> entries;
+
+    /**
+     * Creates a new cache with a default initial capacity and cost limit of 100.
+     * The oldest objects will be hold by {@linkplain WeakReference weak references}.
+     */
+    public Cache() {
+        this(12, 100, false);
+    }
+
+    /**
+     * Creates a new cache using the given initial capacity and cost limit. The initial capacity
+     * is the expected number of values to be stored in this cache. More values are allowed, but
+     * a little bit of CPU time may be saved if the expected capacity is known before the cache
+     * is created.
+     * <p>
+     * The <cite>cost limit</cite> is the maximal value of the <cite>total cost</cite> (the sum
+     * of the {@linkplain #cost cost} of all values) before to replace eldest strong references
+     * by {@linkplain Reference weak or soft references}.
+     *
+     * @param initialCapacity the initial capacity.
+     * @param costLimit The maximum number of objects to keep by strong reference.
+     * @param soft If {@code true}, use {@link SoftReference} instead of {@link WeakReference}.
+     */
+    public Cache(int initialCapacity, final long costLimit, final boolean soft) {
+        if (initialCapacity < 1) {
+            throw new IllegalArgumentException(Errors.format(
+                    Errors.Keys.IllegalArgumentValue_2, "initialCapacity", initialCapacity));
+        }
+        if (costLimit < 0) {
+            throw new IllegalArgumentException(Errors.format(
+                    Errors.Keys.IllegalArgumentValue_2, "costLimit", costLimit));
+        }
+        initialCapacity = Collections.hashMapCapacity(initialCapacity);
+        this.map        = new ConcurrentHashMap<>(initialCapacity);
+        this.costs      = new LinkedHashMap<>((int) Math.min(initialCapacity, costLimit), 0.75f, true);
+        this.costLimit  = costLimit;
+        this.soft       = soft;
+    }
+
+    /**
+     * Clears the content of this cache.
+     */
+    @Override
+    public void clear() {
+        map.clear();
+        // Do not update "costs" and "totalCost". Instead let adjustReferences(...)
+        // do its job, which needs to be done in a different thread.
+    }
+
+    /**
+     * Returns {@code true} if this cache is empty.
+     *
+     * @return {@code true} if this cache do not contains any element.
+     */
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    /**
+     * Returns the number of elements in this cache. The count includes values keep by strong,
+     * {@linkplain SoftReference soft} or {@linkplain WeakReference weak} references, and the
+     * values under computation at the time this method is invoked.
+     *
+     * @return The number of elements currently cached.
+     */
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    /**
+     * Returns {@code true} if this map contains the specified key.
+     */
+    @Override
+    public boolean containsKey(final Object key) {
+        return map.containsKey(key);
+    }
+
+    /**
+     * Returns {@code true} if the given value is an instance of one of the reserved types
+     * used internally by this class.
+     */
+    static boolean isReservedType(final Object value) {
+        return (value instanceof Handler<?> || value instanceof Reference<?>);
+    }
+
+    /**
+     * Returns the value of the given object, unwrapping it if possible.
+     */
+    @SuppressWarnings("unchecked")
+    private static <V> V valueOf(final Object value) {
+        if (value instanceof Reference<?>) {
+            return ((Reference<V>) value).get();
+        }
+        if (value instanceof Handler<?>) {
+            final Handler<V> handler = (Handler<V>) value;
+            final ReentrantLock lock = (ReentrantLock) value;
+            /*
+             * A ClassCastException on the above line would be a bug in this class.
+             * See the comment in Cache.lock(K) method for more explanations.  The
+             * remainder of this block is an adaptation of Cache.Work.get().
+             */
+            if (lock.isHeldByCurrentThread()) {
+                return null;
+            }
+            lock.lock();
+            try {
+                return handler.peek();
+            } finally {
+                lock.unlock();
+            }
+        }
+        return (V) value;
+    }
+
+    /**
+     * Puts the given value in cache.
+     *
+     * @param  key   The key for which to set a value.
+     * @param  value The value to store.
+     * @return The value previously stored at the given key, or {@code null} if none.
+     */
+    @Override
+    public V put(final K key, final V value) {
+        if (isReservedType(value)) {
+            throw new IllegalArgumentException(Errors.format(
+                    Errors.Keys.IllegalArgumentClass_2, "value", value.getClass()));
+        }
+        final Object previous;
+        if (value != null) {
+            previous = map.put(key, value);
+            executeDaemonTask(new Strong(key, value));
+        } else {
+            previous = map.remove(key);
+        }
+        return Cache.<V>valueOf(previous);
+    }
+
+    /**
+     * Removes the value associated to the given key in the cache.
+     *
+     * @param  key The key of the value to removed.
+     * @return The value that were associated to the given key, or {@code null} if none.
+     */
+    @Override
+    public V remove(final Object key) {
+        return Cache.<V>valueOf(map.remove(key));
+    }
+
+    /**
+     * Returns the value associated to the given key in the cache. This method is similar to
+     * {@link #peek} except that it blocks if the value is currently under computation in an
+     * other thread.
+     *
+     * @param  key The key of the value to get.
+     * @return The value associated to the given key, or {@code null} if none.
+     */
+    @Override
+    public V get(final Object key) {
+        return Cache.<V>valueOf(map.get(key));
+    }
+
+    /**
+     * Returns the value for the given key. If a value already exists in the cache, then it
+     * is returned immediately. Otherwise the {@code creator.call()} method is invoked and
+     * its result is saved in this cache for future reuse.
+     *
+     * @param  key The key for which to get the cached or created value.
+     * @param  creator A method for creating a value, to be invoked only if no value are
+     *         cached for the given key.
+     * @return The value for the given key, which may have been created as a result of this
+     *         method call.
+     * @throws Exception If an exception occurred during the execution of {@code creator.call()}.
+     */
+    public V getOrCreate(final K key, final Callable<? extends V> creator) throws Exception {
+        V value = peek(key);
+        if (value == null) {
+            final Handler<V> handler = lock(key);
+            try {
+                value = handler.peek();
+                if (value == null) {
+                    value = creator.call();
+                }
+            } finally {
+                handler.putAndUnlock(value);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * If a value is already cached for the given key, returns it. Otherwise returns {@code null}.
+     * This method is similar to {@link #get(Object)} except that it doesn't block if the value is
+     * in process of being computed in an other thread; it returns {@code null} in such case.
+     *
+     * @param  key The key for which to get the cached value.
+     * @return The cached value for the given key, or {@code null} if there is none.
+     */
+    public V peek(final K key) {
+        final Object value = map.get(key);
+        if (value instanceof Handler<?>) {
+            // The value is under computation. We will not wait for it since it is
+            // not the purpose of this method (we should use lock(key) for that).
+            return null;
+        }
+        if (value instanceof Reference<?>) {
+            @SuppressWarnings("unchecked")
+            final Reference<V> ref = (Reference<V>) value;
+            final V result = ref.get();
+            if (result != null && map.replace(key, ref, result)) {
+                ref.clear(); // Prevents the reference from being enqueued.
+                executeDaemonTask(new Strong(key, result));
+            }
+            return result;
+        }
+        @SuppressWarnings("unchecked")
+        final V result = (V) value;
+        return result;
+    }
+
+    /**
+     * Invoked from the a background thread after a {@linkplain WeakReference weak}
+     * or {@linkplain SoftReference soft} reference has been replaced by a strong one. It will
+     * looks for older strong references to replace by weak references so that the total cost
+     * stay below the cost limit.
+     */
+    private final class Strong implements Runnable {
+        private final K key;
+        private final V value;
+
+        Strong(final K key, final V value) {
+            this.key = key;
+            this.value = value;
+        }
+
+        /**
+         * Process to the replacement of eldest strong references by weak references.
+         * This method should be invoked from the background thread only.
+         */
+        @Override public void run() {
+            Cache.this.adjustReferences(key, value);
+        }
+    }
+
+    /**
+     * Gets a lock for the entry at the given key and returns a handler to be used by the caller
+     * for unlocking and storing the result. This method <strong>must</strong> be used together
+     * with a {@link Handler#putAndUnlock(Object) putAndUnlock} call in {@code try} … {@code catch}
+     * blocks as in the example below:
+     *
+     * {@preformat java
+     *     Cache.Handler handler = cache.lock();
+     *     try {
+     *         // Compute the result...
+     *     } finally {
+     *         handler.putAndUnlock(result);
+     *     }
+     * }
+     *
+     * @param  key The key for the entry to lock.
+     * @return A handler to use for unlocking and storing the result.
+     */
+    public Handler<V> lock(final K key) {
+        final Work handler = new Work(key);
+        handler.lock();
+        Object value;
+        try {
+            do {
+                /*
+                 * Put the handler in the map, providing that the entry is not already occupied.
+                 * Note that the handler must be locked BEFORE we attempt to add it to the map.
+                 */
+                value = map.putIfAbsent(key, handler);
+                if (value == null) {
+                    /*
+                     * We succeed in adding the handler in the map (we know that because all our
+                     * map.put(...) or map.replace(...) operations are guaranteed to put non-null
+                     * values). We are done. But before to leave, lock again for canceling the
+                     * effect of unlock in the finally clause (we want the lock to still active).
+                     */
+                    handler.lock();
+                    return handler;
+                }
+                /*
+                 * If the value is a valid reference (strong, soft or weak), stop the loop and
+                 * release the lock. We will process that value after the finally block.
+                 */
+                if (!(value instanceof Reference<?>)) {
+                    break;
+                }
+                @SuppressWarnings("unchecked")
+                final Reference<V> ref = (Reference<V>) value;
+                final V result = ref.get();
+                if (result != null) {
+                    /*
+                     * The value is a valid weak reference. Replaces it by a strong reference
+                     * before to return it in a wrapper. Note: the call to ref.clear() is for
+                     * preventing the reference from being enqueued for ReferenceQueueConsumer
+                     * processing. It would not hurt if we let the processing happen, but it
+                     * would be useless.
+                     */
+                    if (map.replace(key, ref, result)) {
+                        ref.clear(); // Prevents the reference from being enqueued.
+                        executeDaemonTask(new Strong(key, result));
+                    }
+                    return new Simple<>(result);
+                }
+                /*
+                 * The weak reference is invalid but not yet discarded (it looks like that this
+                 * thread is going faster than ReferenceQueueConsumer). Try to replace it by our
+                 * handler.
+                 */
+                if (map.replace(key, ref, handler)) {
+                    handler.lock();
+                    return handler;
+                }
+                // The map content changed. Try again.
+            } while (true);
+        } finally {
+            handler.unlock();
+        }
+        /*
+         * From this point, we abandon our handler.
+         */
+        if (value instanceof Handler<?>) {
+            /*
+             * A value is already under computation. Returns a handler which will wait for the
+             * completion of the worker thread and returns its result. Note that the handler
+             * should never be any kind other than Work because this is the only kind we put
+             * in the map (see a few lines above in this method), so if we get a ClassCastException
+             * here this is a bug in this class.
+             */
+            @SuppressWarnings("unchecked")
+            final Work work = (Work) value;
+            if (work.isHeldByCurrentThread()) {
+                if (isKeyCollisionAllowed()) {
+                    /*
+                     * Example of key collision: the EPSG database defines the CoordinateOperation
+                     * 8653 ("ED50 to WGS84" using polynomial equations).  The EPSG factory sets a
+                     * lock for this code, then searches for OperationParameters associated to this
+                     * operation. One of those parameters ("Bu0v4") has the same key (EPSG:8653).
+                     * So we get a key collision. If we ignore the second occurrence, its value will
+                     * not be cached. This is okay since the value that we really want to cache is
+                     * CoordinateOperation, which is associated to the first occurrence of that key.
+                     */
+                    return new Simple<>(null);
+                }
+                throw new IllegalStateException(Errors.format(Errors.Keys.RecursiveCreateCallForKey_1, key));
+            }
+            return work.new Wait();
+        }
+        /*
+         * A calculation has already been completed. Returns a wrapper
+         * which will just return the result without any processing.
+         */
+        assert (value != null) && !isReservedType(value) : value;
+        @SuppressWarnings("unchecked")
+        final V result = (V) value;
+        return new Simple<>(result);
+    }
+
+    /**
+     * The handler returned by {@link Cache#lock}, to be used for unlocking and storing the
+     * result. This handler should be used as below (note the {@code try} … {@code catch}
+     * blocks, which are <strong>mandatory</strong>):
+     *
+     * {@preformat java
+     *     Value V = null;
+     *     Cache.Handler<V> handler = cache.lock(key);
+     *     try {
+     *         value = handler.peek();
+     *         if (value == null) {
+     *             value = createMyObject(key);
+     *         }
+     *     } finally {
+     *         handler.putAndUnlock(value);
+     *     }
+     * }
+     *
+     * See the {@link Cache} javadoc for a more complete example.
+     *
+     * @param <V> The type of value objects.
+     *
+     * @author  Martin Desruisseaux (Geomatys)
+     * @since   0.3 (derived from geotk-3.00)
+     * @version 0.3
+     * @module
+     */
+    public interface Handler<V> {
+        /**
+         * If the value is already in the cache, returns it. Otherwise returns {@code null}.
+         * This method should be invoked after the {@code Handler} creation in case a value
+         * has been computed in an other thread.
+         *
+         * @return The value from the cache, or {@code null} if none.
+         */
+        V peek();
+
+        /**
+         * Stores the given value in the cache and release the lock. This method
+         * <strong>must</strong> be invoked in a {@code finally} block, no matter
+         * what the result is.
+         *
+         * @param result The result to store in the cache, or {@code null} for removing
+         *        the entry from the cache. If an entry is removed, a new computation
+         *        will be attempted the next time a handler is created for the same key.
+         *
+         * @throws IllegalStateException May be thrown if this method is not invoked in
+         *         the pattern described in class javadoc, or if a key collision occurs.
+         */
+        void putAndUnlock(V result) throws IllegalStateException;
+    }
+
+    /**
+     * A simple handler implementation wrapping an existing value. This implementation
+     * is used when the value has been fully calculated in an other thread before this
+     * thread could start its work.
+     */
+    private final class Simple<V> implements Handler<V> {
+        /**
+         * The result calculated in an other thread.
+         */
+        private final V value;
+
+        /**
+         * Creates a new handler wrapping the given value.
+         */
+        Simple(final V value) {
+            this.value = value;
+        }
+
+        /**
+         * Returns the calculated value.
+         */
+        @Override
+        public V peek() {
+            return value;
+        }
+
+        /**
+         * Do nothing (except checking for programming error), since we don't hold any lock.
+         * We do not localize the exception message since it is targeted to the developer
+         * rather than the end user.
+         *
+         * {@note An alternative would have been to store the result in the map anyway.
+         *        But doing so is unsafe because we have no lock; we have no guarantee that
+         *        nothing has happened in an other thread between peek and putAndUnlock.}
+         */
+        @Override
+        public void putAndUnlock(final V result) throws IllegalStateException {
+            if (result != value && !isKeyCollisionAllowed()) {
+                throw new IllegalStateException("Key collision: the cache already has a value.");
+            }
+        }
+    }
+
+    /**
+     * A handler implementation used for telling to other threads that the current thread is
+     * computing a value.
+     */
+    @SuppressWarnings("serial") // Actually not intended to be serialized.
+    final class Work extends ReentrantLock implements Handler<V>, Runnable {
+        /**
+         * The key to use for storing the result in the map.
+         */
+        private final K key;
+
+        /**
+         * The result. This is initially null, as we expect since the value has not yet
+         * been created. When it will get a value, this value should not change anymore.
+         */
+        private V value;
+
+        /**
+         * Creates a new handler which will store the result in the given map at the given key.
+         */
+        Work(final K key) {
+            this.key = key;
+        }
+
+        /**
+         * Waits for the completion of the value computation and returns this result. This
+         * method should be invoked only from an other thread than the one doing the calculation.
+         */
+        final V get() {
+            if (isHeldByCurrentThread()) {
+                throw new IllegalStateException();
+            }
+            final V v;
+            lock();
+            v = value;
+            unlock();
+            return v;
+        }
+
+        /**
+         * Usually returns {@code null} since the value is not yet computed. May returns the
+         * result if this method is invoked again after the computation, but this is not the
+         * typical use case.
+         */
+        @Override
+        public V peek() {
+            return value;
+        }
+
+        /**
+         * Stores the result and release the lock.
+         *
+         * @throws IllegalStateException If the current thread does not hold the lock.
+         */
+        @Override
+        public void putAndUnlock(final V result) throws IllegalStateException {
+            final boolean done;
+            try {
+                if (isReservedType(result)) {
+                    throw new IllegalArgumentException(Errors.format(
+                            Errors.Keys.IllegalArgumentClass_2, "result", result.getClass()));
+                }
+                // Assignation of 'value' must happen before we release the lock.
+                value = result;
+                if (result != null) {
+                    done = map.replace(key, this, result);
+                } else {
+                    done = map.remove(key, this);
+                }
+            } finally {
+                unlock();
+            }
+            if (done) {
+                executeDaemonTask(this);
+            }
+        }
+
+        /**
+         * A handler implementation used when the value is in process of being computed in an
+         * other thread. At the difference of the {@code Simple} handler, the computation is
+         * not yet completed, so this handler has to wait.
+         */
+        final class Wait implements Handler<V> {
+            /**
+             * Waits that the worker finish its work and returns its value.
+             */
+            @Override
+            public V peek() {
+                return get();
+            }
+
+            /**
+             * Do nothing (except checking for programming error), since we don't hold any lock.
+             * We do not localize the exception message since it is targeted to the developer
+             * rather than the end user.
+             *
+             * {@note An alternative would have been to store the result in the map anyway.
+             *        But doing so is unsafe because we have no lock; we have no guarantee and
+             *        nothing has happened in an other thread between peek and putAndUnlock.}
+             */
+            @Override
+            public void putAndUnlock(final V result) throws IllegalStateException {
+                if (result != get() && !isKeyCollisionAllowed()) {
+                    throw new IllegalStateException("Key collision: the cache already has a value.");
+                }
+            }
+        }
+
+        /**
+         * Invoked in a background thread after a value has been set in the map.
+         * This method computes a cost estimation of the new value. If the total cost is greater
+         * than the cost limit, then oldest strong references are replaced by weak references.
+         */
+        @Override
+        public void run() {
+            final V value = this.value;
+            if (value != null) {
+                Cache.this.adjustReferences(key, value);
+            }
+        }
+    }
+
+    /**
+     * Invoked in a background thread after a value has been set in the map.
+     * This method computes a cost estimation of the new value. If the total cost is greater
+     * than the cost limit, then oldest strong references are replaced by weak references.
+     */
+    final void adjustReferences(final K key, final V value) {
+        int cost = cost(value);
+        synchronized (costs) {
+            final Integer old = costs.put(key, cost);
+            if (old != null) {
+                cost -= old;
+            }
+            if ((totalCost += cost) > costLimit) {
+                final Iterator<Map.Entry<K,Integer>> it = costs.entrySet().iterator();
+                while (it.hasNext()) {
+                    /*
+                     * Converts the current entry from strong reference to weak/soft reference.
+                     * We perform this conversion even if the entry is for the value just added
+                     * to the cache, if it happen that the cost is higher than the maximal one.
+                     * That entry should not be garbage collected to early anyway because the
+                     * caller should still have a strong reference to the value he just created.
+                     */
+                    final Map.Entry<K,Integer> entry = it.next();
+                    final K oldKey = entry.getKey();
+                    final Object oldValue = map.get(oldKey);
+                    if (oldValue != null && !isReservedType(oldValue)) {
+                        @SuppressWarnings("unchecked")
+                        final Reference<V> ref = soft ? new Soft<>(map, oldKey, (V) oldValue)
+                                                      : new Weak<>(map, oldKey, (V) oldValue);
+                        if (!map.replace(oldKey, oldValue, ref)) {
+                            ref.clear(); // Prevents the reference to be enqueued.
+                        }
+                    }
+                    it.remove();
+                    if ((totalCost -= entry.getValue()) <= costLimit) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * A soft reference which remove itself from the concurrent map when the reference
+     * is garbage-collected.
+     */
+    private static final class Soft<K,V> extends SoftReference<V> implements Disposable {
+        /** The key of the referenced value.      */ private final K key;
+        /** The map which contains the reference. */ private final ConcurrentMap<K,Object> map;
+
+        /** Creates a references to be stored in the given map under the given key. */
+        Soft(final ConcurrentMap<K,Object> map, final K key, final V value) {
+            super(value, ReferenceQueueConsumer.DEFAULT.queue);
+            this.map = map;
+            this.key = key;
+        }
+
+        /** Removes the reference from the map. */
+        @Override public void dispose() {
+            map.remove(key, this);
+            // There is nothing to remove from the cost map, since the later
+            // contains only the keys of objects hold by strong reference.
+        }
+    }
+
+    /**
+     * A weak reference which remove itself from the concurrent map when the reference
+     * is garbage-collected.
+     */
+    private static final class Weak<K,V> extends WeakReference<V> implements Disposable {
+        /** The key of the referenced value.      */ private final K key;
+        /** The map which contains the reference. */ private final ConcurrentMap<K,Object> map;
+
+        /** Creates a references to be stored in the given map under the given key. */
+        Weak(final ConcurrentMap<K,Object> map, final K key, final V value) {
+            super(value, ReferenceQueueConsumer.DEFAULT.queue);
+            this.map = map;
+            this.key = key;
+        }
+
+        /** Removes the reference from the map. */
+        @Override public void dispose() {
+            map.remove(key, this);
+            // There is nothing to remove from the cost map, since the later
+            // contains only the keys of objects hold by strong reference.
+        }
+    }
+
+    /**
+     * Returns the set of keys in this cache. The returned set is subjects to the same caution
+     * than the ones documented in the {@link ConcurrentHashMap#keySet()} method.
+     */
+    @Override
+    public Set<K> keySet() {
+        return map.keySet();
+    }
+
+    /**
+     * Returns the set of entries in this cache. The returned set is subjects to the same caution
+     * than the ones documented in the {@link ConcurrentHashMap#entrySet()} method, except that
+     * it doesn't support removal of elements (including through the {@link Iterator#remove}
+     * method call).
+     */
+    @Override
+    public Set<Entry<K,V>> entrySet() {
+        final Set<Entry<K,V>> es = entries;
+        return (es != null) ? es : (entries = new CacheEntries<>(map.entrySet()));
+    }
+
+    /**
+     * Returns {@code true} if different values may be assigned to the same key.
+     * The default value is {@code false}.
+     *
+     * @return {@code true} if key collisions are allowed.
+     */
+    public boolean isKeyCollisionAllowed() {
+        return keyCollisionAllowed;
+    }
+
+    /**
+     * If set to {@code true}, different values may be assigned to the same key. This is usually an
+     * error, so the default {@code Cache} behavior is to thrown an {@link IllegalStateException}
+     * in such cases, typically when {@link Handler#putAndUnlock(Object)} is invoked. However in
+     * some cases we may want to relax this check. For example the EPSG database sometime assigns
+     * the same key to different kind of objects.
+     * <p>
+     * If key collisions are allowed, then if two threads invoke {@link #lock(Object)} concurrently
+     * for the same key, then the value to be stored in the map will be the one computed by the first
+     * thread to get the lock. The value computed by any other concurrent thread will be ignored by
+     * this {@code Cache} class (however that thread would still return its computed value to its
+     * user).
+     * <p>
+     * This property can also be set in order to allow some recursivity. If during the creation of
+     * an object, the program asks to this {@code Cache} for the same object (using the same key),
+     * then the default {@code Cache} implementation will consider this situation as a key collision
+     * unless this property has been set to {@code true}.
+     *
+     * @param allowed {@code true} if key collisions should be allowed.
+     */
+    public void setKeyCollisionAllowed(final boolean allowed) {
+        keyCollisionAllowed = allowed;
+    }
+
+    /**
+     * Computes an estimation of the cost of the given value. The default implementation returns 1
+     * in all cases. Subclasses should override this method if they have some easy way to measure
+     * the relative cost of value objects.
+     *
+     * @param  value The object for which to get an estimation of its cost.
+     * @return The estimated cost of the given object.
+     *
+     * @see java.lang.instrument.Instrumentation#getObjectSize(Object)
+     */
+    protected int cost(final V value) {
+        return 1;
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/Cache.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java?rev=1395581&view=auto
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java (added)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java Mon Oct  8 14:29:50 2012
@@ -0,0 +1,160 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.util.collection;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.AbstractSet;
+import java.util.AbstractMap.SimpleEntry;
+import java.util.NoSuchElementException;
+import java.lang.ref.Reference;
+
+import net.jcip.annotations.ThreadSafe;
+
+
+/**
+ * The set of entries in the {@link Cache#map}. On iteration, handlers will be skipped
+ * and the values of weak references are returned instead of the {@link Reference} object.
+ * <p>
+ * This class is not needed for the normal working of {@link Cache}. it is used only if
+ * the user wants to see the cache entries through the standard Java collection API.
+ *
+ * @param <K> The type of key objects.
+ * @param <V> The type of value objects.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.3 (derived from geotk-3.00)
+ * @version 0.3
+ * @module
+ */
+@ThreadSafe // Assuming that the set given to the constructor is concurrent.
+final class CacheEntries<K,V> extends AbstractSet<Map.Entry<K,V>> {
+    /**
+     * The set of entries in the {@link Cache#map}.
+     */
+    private final Set<Map.Entry<K,Object>> entries;
+
+    /**
+     * Wraps the given set of entries of a {@link Cache#map}.
+     */
+    CacheEntries(final Set<Map.Entry<K,Object>> entries) {
+        this.entries = entries;
+    }
+
+    /**
+     * Returns {@code true} if the set is empty. Overloaded because {@code ConcurrentHashMap}
+     * has a more efficient implementation of this method than testing {@code size() == 0}.
+     */
+    @Override
+    public boolean isEmpty() {
+        return entries.isEmpty();
+    }
+
+    /**
+     * Returns the number of entries.
+     */
+    @Override
+    public int size() {
+        return entries.size();
+    }
+
+    /**
+     * Returns an iterator over the entries.
+     */
+    @Override
+    public Iterator<Map.Entry<K,V>> iterator() {
+        return new Iter<>(entries.iterator());
+    }
+
+    /**
+     * An iterator over the entries in the {@link Cache#map}. Handlers will be skipped and the
+     * values of weak references are returned instead of the {@link Reference} object.
+     */
+    private static final class Iter<K,V> implements Iterator<Map.Entry<K,V>> {
+        /**
+         * The iterator over the entries wrapped by {@link CacheEntries}.
+         */
+        private final Iterator<Map.Entry<K,Object>> it;
+
+        /**
+         * The next entry to returns, or {@code null} if we reached the end of iteration.
+         */
+        private Map.Entry<K,V> next;
+
+        /**
+         * Creates a new iterator wrapping the given iterator from {@link CacheEntries#entries}.
+         */
+        Iter(final Iterator<Map.Entry<K,Object>> it) {
+            this.it = it;
+            advance();
+        }
+
+        /**
+         * Advances the iterator to the next entry to be returned.
+         */
+        @SuppressWarnings("unchecked")
+        private void advance() {
+            while (it.hasNext()) {
+                final Map.Entry<K,Object> entry = it.next();
+                Object value = entry.getValue();
+                if (value == null || value instanceof Cache.Handler<?>) {
+                    continue;
+                }
+                if (value instanceof Reference<?>) {
+                    value = ((Reference<?>) value).get();
+                    if (value == null) {
+                        continue;
+                    }
+                }
+                next = new SimpleEntry<>(entry.getKey(), (V) value);
+                return;
+            }
+            next = null;
+        }
+
+        /**
+         * Returns {@code true} if there is more element to returns.
+         */
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        /**
+         * Returns the next element.
+         */
+        @Override
+        public Map.Entry<K, V> next() {
+            final Map.Entry<K,V> n = next;
+            if (n != null) {
+                advance();
+                return n;
+            }
+            throw new NoSuchElementException();
+        }
+
+        /**
+         * Unsupported operation, because the wrapped iterator is not after the proper element
+         * (it is after the next one).
+         */
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+}

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/collection/CacheEntries.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.java Mon Oct  8 14:29:50 2012
@@ -51,6 +51,11 @@ public final class Errors extends Indexe
         public static final int EmptyArgument_1 = 1;
 
         /**
+         * Argument ‘{0}’ can not be an instance of ‘{1}’.
+         */
+        public static final int IllegalArgumentClass_2 = 17;
+
+        /**
          * Argument ‘{0}’ can not be an instance of ‘{1}’. Expected an instance of ‘{2}’ or derived
          * type.
          */
@@ -112,6 +117,11 @@ public final class Errors extends Indexe
         public static final int NullArgument_1 = 0;
 
         /**
+         * Recursive call while creating an object for the “{0}” key.
+         */
+        public static final int RecursiveCreateCallForKey_1 = 18;
+
+        /**
          * Argument ‘{0}’ has {1} dimensions, while {2} was expected.
          */
         public static final int UnexpectedArgumentDimension_3 = 5;

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors.properties Mon Oct  8 14:29:50 2012
@@ -16,6 +16,7 @@
 #
 EmptyArgument_1                 = Argument \u2018{0}\u2019 shall not be empty.
 IllegalArgument_1               = Illegal value for argument \u2018{0}\u2019.
+IllegalArgumentClass_2          = Argument \u2018{0}\u2019 can not be an instance of \u2018{1}\u2019.
 IllegalArgumentClass_3          = Argument \u2018{0}\u2019 can not be an instance of \u2018{1}\u2019. Expected an instance of \u2018{2}\u2019 or derived type.
 IllegalArgumentValue_2          = Argument \u2018{0}\u2019 can not take the \u201c{1}\u201d value.
 IllegalBitsPattern_1            = Illegal bits pattern: {0}.
@@ -27,6 +28,7 @@ NegativeArgument_2              = Argume
 NotANumber_1                    = Argument \u2018{0}\u2019 shall not be NaN (Not-a-Number).
 NotAPrimitiveWrapper_1          = Class \u2018{0}\u2019 is not a primitive type wrapper.
 NullArgument_1                  = Argument \u2018{0}\u2019 shall not be null.
+RecursiveCreateCallForKey_1     = Recursive call while creating an object for the \u201c{0}\u201d key.
 UnexpectedArgumentDimension_3   = Argument \u2018{0}\u2019 has {1} dimensions, while {2} was expected.
 ValueAlreadyDefined_1           = A value is already defined for \u201c{0}\u201d.
 ValueNotGreaterThanZero_2       = Value \u2018{0}\u2019={1} is invalid. Expected a number greater than 0.

Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties
URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties?rev=1395581&r1=1395580&r2=1395581&view=diff
==============================================================================
--- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties (original)
+++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Errors_fr.properties Mon Oct  8 14:29:50 2012
@@ -16,6 +16,7 @@
 #
 EmptyArgument_1                 = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre vide.
 IllegalArgument_1               = Valeur ill\u00e9gale pour l\u2019argument \u2018{0}\u2019.
+IllegalArgumentClass_2          = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{1}\u2019.
 IllegalArgumentClass_3          = L\u2019argument \u2018{0}\u2019 ne peut pas \u00eatre de type \u2018{1}\u2019. Une instance de \u2018{2}\u2019 ou d\u2019un type d\u00e9riv\u00e9 \u00e9tait attendue.
 IllegalArgumentValue_2          = L\u2019argument \u2018{0}\u2019 n\u2019accepte pas la valeur \u201c{1}\u201d.
 IllegalBitsPattern_1            = Pattern de bits invalide: {0}.
@@ -27,6 +28,7 @@ NegativeArgument_2              = L\u201
 NotANumber_1                    = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre NaN (Not-a-Number).
 NotAPrimitiveWrapper_1          = La classe \u2018{0}\u2019 n\u2019est pas un adaptateur d\u2019un type primitif.
 NullArgument_1                  = L\u2019argument \u2018{0}\u2019 ne doit pas \u00eatre nul.
+RecursiveCreateCallForKey_1     = Appel r\u00e9cursif lors de la cr\u00e9ation d\u2019un objet pour la cl\u00e9 \u201c{0}\u201d.
 UnexpectedArgumentDimension_3   = L\u2019argument \u2018{0}\u2019 a {1} dimensions, alors qu\u2019on en attendait {2}.
 ValueAlreadyDefined_1           = Une valeur est d\u00e9j\u00e0 d\u00e9finie pour \u201c{0}\u201d.
 ValueNotGreaterThanZero_2       = La valeur \u2018{0}\u2019={1} n\u2019est pas valide. On attendait un nombre positif non-nul.



Mime
View raw message