alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Groovy example source code file (Memoize.java)

This example Groovy source code file (Memoize.java) is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Java - Groovy tags/keywords

closure, closure, memoize_null, memoizecache, memoizecache, memoizefunction, memoizenullvalue, object, object, override, protectionstorage, referencequeue, util, v, v

The Groovy Memoize.java source code

/*
 * 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 buildMemoizeFunction(final MemoizeCache cache, final Closure 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 buildSoftReferenceMemoizeFunction(final int protectedCacheSize, final MemoizeCache cache, final Closure 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 {
        final MemoizeCache<Object, Object> cache;
        final Closure<V> closure;
        
        MemoizeFunction(final MemoizeCache<Object, Object> cache, Closure 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 {
        final ProtectionStorage lruProtectionStorage;
        final ReferenceQueue queue;
        
        SoftReferenceMemoizeFunction(final MemoizeCache<Object, Object> cache, Closure 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();
        }
    }
}

Other Groovy examples (source code examples)

Here is a short list of links related to this Groovy Memoize.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.