/* * Copyright 2003-2010 the original author or authors. * * Licensed 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.codehaus.groovy.runtime.memoize; import groovy.lang.Closure; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.Collections; import static java.util.Arrays.asList; /** * Implements memoize for Closures. * It is supposed to be used by the Closure class itself to implement the memoize() family of methods. * * @author Vaclav Pech */ public abstract class Memoize { /** * A place-holder for null values in cache */ final static private MemoizeNullValue MEMOIZE_NULL = new MemoizeNullValue(); /** * Creates a new closure delegating to the supplied one and memoizing all return values by the arguments. * * The supplied cache is used to store the memoized values and it is the cache's responsibility to put limits * on the cache size or implement cache eviction strategy. * The LRUCache, for example, allows to set the maximum cache size constraint and implements * the LRU (Last Recently Used) eviction strategy. * * @param cache A map to hold memoized return values * @param closure The closure to memoize * @param <V> The closure's return type * @return A new memoized closure */ public static <V> Closure<V> buildMemoizeFunction(final MemoizeCache<Object, Object> cache, final Closure<V> closure) { return new MemoizeFunction<V>(cache, closure); } /** * Creates a new closure delegating to the supplied one and memoizing all return values by the arguments. * The memoizing closure will use SoftReferences to remember the return values allowing the garbage collector * to reclaim the memory, if needed. * * The supplied cache is used to store the memoized values and it is the cache's responsibility to put limits * on the cache size or implement cache eviction strategy. * The LRUCache, for example, allows to set the maximum cache size constraint and implements * the LRU (Last Recently Used) eviction strategy. * * If the protectedCacheSize argument is greater than 0 an optional LRU (Last Recently Used) cache of hard references * is maintained to protect recently touched memoized values against eviction by the garbage collector. * * @param protectedCacheSize The number of hard references to keep in order to prevent some (LRU) memoized return values from eviction * @param cache A map to hold memoized return values * @param closure The closure to memoize * @param <V> The closure's return type * @return A new memoized closure */ public static <V> Closure<V> buildSoftReferenceMemoizeFunction(final int protectedCacheSize, final MemoizeCache<Object, Object> cache, final Closure<V> closure) { final ProtectionStorage lruProtectionStorage = protectedCacheSize > 0 ? new LRUProtectionStorage(protectedCacheSize) : new NullProtectionStorage(); // Nothing should be done when no elements need protection against eviction final ReferenceQueue queue = new ReferenceQueue(); return new SoftReferenceMemoizeFunction<V>(cache, closure, lruProtectionStorage, queue); } /** * Creates a key to use in the memoize cache * * @param args The arguments supplied to the closure invocation * * @return The key - a list holding all arguments */ private static Object generateKey(final Object[] args) { if (args == null) return Collections.emptyList(); return asList(args); } /** * A place-holder for cached null values */ private static class MemoizeNullValue { @Override public boolean equals(final Object obj) { return obj instanceof MemoizeNullValue; } @Override public int hashCode() { return "MemoizeNullValue".hashCode(); } } private static class MemoizeFunction<V> extends Closure<V> { final MemoizeCache<Object, Object> cache; final Closure<V> closure; MemoizeFunction(final MemoizeCache<Object, Object> cache, Closure<V> closure) { super(closure.getOwner()); this.cache = cache; this.closure = closure; } @Override public V call(final Object... args) { final Object key = generateKey(args); Object result = cache.get(key); if (result == null) { result = closure.call(args); //noinspection GroovyConditionalCanBeElvis cache.put(key, result != null ? result : MEMOIZE_NULL); } return result == MEMOIZE_NULL ? null : (V) result; } } private static class SoftReferenceMemoizeFunction<V> extends MemoizeFunction<V> { final ProtectionStorage lruProtectionStorage; final ReferenceQueue queue; SoftReferenceMemoizeFunction(final MemoizeCache<Object, Object> cache, Closure<V> closure, ProtectionStorage lruProtectionStorage, ReferenceQueue queue) { super(cache, closure); this.lruProtectionStorage = lruProtectionStorage; this.queue = queue; } @Override public V call(final Object... args) { if (queue.poll() != null) cleanUpNullReferences(cache, queue); // if something has been evicted, do a clean-up final Object key = generateKey(args); final SoftReference reference = (SoftReference) cache.get(key); Object result = reference != null ? reference.get() : null; if (result == null) { result = closure.call(args); if (result == null) { result = MEMOIZE_NULL; } cache.put(key, new SoftReference(result)); } lruProtectionStorage.touch(key, result); return result == MEMOIZE_NULL ? null : (V) result; } /** * After the garbage collector has done its job, we need to clean the cache from references to all the evicted memoized values. * @param cache The cache to prune * @param queue A reference queue holding references to gc-evicted memoized values */ private static void cleanUpNullReferences(final MemoizeCache<Object, Object> cache, final ReferenceQueue queue) { while(queue.poll() != null) {} //empty the reference queue cache.cleanUpNullReferences(); } } }