    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.lang3.concurrent;
    018
    019import java.util.concurrent.Callable;
    020import java.util.concurrent.CancellationException;
    021import java.util.concurrent.ConcurrentHashMap;
    022import java.util.concurrent.ConcurrentMap;
    023import java.util.concurrent.ExecutionException;
    024import java.util.concurrent.Future;
    025import java.util.concurrent.FutureTask;
    026
    027/**
    028 * <p>
    029 * Definition of an interface for a wrapper around a calculation that takes a
    030 * single parameter and returns a result. The results for the calculation will
    031 * be cached for future requests.
    032 * </p>
    033 * <p>
    034 * This is not a fully functional cache, there is no way of limiting or removing
    035 * results once they have been generated. However, it is possible to get the
    036 * implementation to regenerate the result for a given parameter, if an error
    037 * was thrown during the previous calculation, by setting the option during the
    038 * construction of the class. If this is not set the class will return the
    039 * cached exception.
    040 * </p>
    041 * <p>
    042 * Thanks should go to Brian Goetz, Tim Peierls and the members of JCP JSR-166
    043 * Expert Group for coming up with the original implementation of the class. It
    044 * was also published within Java Concurrency in Practice as a sample.
    045 * </p>
    046 *
    047 * @param <I>
    048 *            the type of the input to the calculation
    049 * @param <O>
    050 *            the type of the output of the calculation
    051 *
    052 * @since 3.6
    053 */
    054public class Memoizer<I, O> implements Computable<I, O> {
    055
    056    private final ConcurrentMap<I, Future<O>> cache = new ConcurrentHashMap<>();
    057    private final Computable<I, O> computable;
    058    private final boolean recalculate;
    059
    060    /**
    061     * <p>
    062     * Constructs a Memoizer for the provided Computable calculation.
    063     * </p>
    064     * <p>
    065     * If a calculation is thrown an exception for any reason, this exception
    066     * will be cached and returned for all future calls with the provided
    067     * parameter.
    068     * </p>
    069     *
    070     * @param computable
    071     *            the computation whose results should be memorized
    072     */
    073    public Memoizer(final Computable<I, O> computable) {
    074        this(computable, false);
    075    }
    076
    077    /**
    078     * <p>
    079     * Constructs a Memoizer for the provided Computable calculation, with the
    080     * option of whether a Computation that experiences an error should
    081     * recalculate on subsequent calls or return the same cached exception.
    082     * </p>
    083     *
    084     * @param computable
    085     *            the computation whose results should be memorized
    086     * @param recalculate
    087     *            determines whether the computation should be recalculated on
    088     *            subsequent calls if the previous call failed
    089     */
    090    public Memoizer(final Computable<I, O> computable, final boolean recalculate) {
    091        this.computable = computable;
    092        this.recalculate = recalculate;
    093    }
    094
    095    /**
    096     * <p>
    097     * This method will return the result of the calculation and cache it, if it
    098     * has not previously been calculated.
    099     * </p>
    100     * <p>
    101     * This cache will also cache exceptions that occur during the computation
    102     * if the {@code recalculate} parameter is the constructor was set to
    103     * {@code false}, or not set. Otherwise, if an exception happened on the
    104     * previous calculation, the method will attempt again to generate a value.
    105     * </p>
    106     *
    107     * @param arg
    108     *            the argument for the calculation
    109     * @return the result of the calculation
    110     * @throws InterruptedException
    111     *             thrown if the calculation is interrupted
    112     */
    113    @Override
    114    public O compute(final I arg) throws InterruptedException {
    115        while (true) {
    116            Future<O> future = cache.get(arg);
    117            if (future == null) {
    118                final Callable<O> eval = new Callable<O>() {
    119
    120                    @Override
    121                    public O call() throws InterruptedException {
    122                        return computable.compute(arg);
    123                    }
    124                };
    125                final FutureTask<O> futureTask = new FutureTask<>(eval);
    126                future = cache.putIfAbsent(arg, futureTask);
    127                if (future == null) {
    128                    future = futureTask;
    129                    futureTask.run();
    130                }
    131            }
    132            try {
    133                return future.get();
    134            } catch (final CancellationException e) {
    135                cache.remove(arg, future);
    136            } catch (final ExecutionException e) {
    137                if (recalculate) {
    138                    cache.remove(arg, future);
    139                }
    140
    141                throw launderException(e.getCause());
    142            }
    143        }
    144    }
    145
    146    /**
    147     * <p>
    148     * This method launders a Throwable to either a RuntimeException, Error or
    149     * any other Exception wrapped in an IllegalStateException.
    150     * </p>
    151     *
    152     * @param throwable
    153     *            the throwable to laundered
    154     * @return a RuntimeException, Error or an IllegalStateException
    155     */
    156    private RuntimeException launderException(final Throwable throwable) {
    157        if (throwable instanceof RuntimeException) {
    158            return (RuntimeException) throwable;
    159        } else if (throwable instanceof Error) {
    160            throw (Error) throwable;
    161        } else {
    162            throw new IllegalStateException("Unchecked exception", throwable);
    163        }
    164    }
    165}