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 * https://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.CancellationException; 020import java.util.concurrent.ConcurrentHashMap; 021import java.util.concurrent.ConcurrentMap; 022import java.util.concurrent.ExecutionException; 023import java.util.concurrent.Future; 024import java.util.function.Function; 025 026import org.apache.commons.lang3.exception.ExceptionUtils; 027 028/** 029 * Definition of an interface for a wrapper around a calculation that takes a single parameter and returns a result. The 030 * results for the calculation will be cached for future requests. 031 * 032 * <p> 033 * This is not a fully functional cache, there is no way of limiting or removing results once they have been generated. 034 * However, it is possible to get the implementation to regenerate the result for a given parameter, if an error was 035 * thrown during the previous calculation, by setting the option during the construction of the class. If this is not 036 * set the class will return the cached exception. 037 * </p> 038 * <p> 039 * Thanks go to Brian Goetz, Tim Peierls and the members of JCP JSR-166 Expert Group for coming up with the 040 * original implementation of the class. It was also published within Java Concurrency in Practice as a sample. 041 * </p> 042 * 043 * @param <I> the type of the input to the calculation 044 * @param <O> the type of the output of the calculation 045 * @since 3.6 046 */ 047public class Memoizer<I, O> implements Computable<I, O> { 048 049 private final ConcurrentMap<I, Future<O>> cache = new ConcurrentHashMap<>(); 050 private final Function<? super I, ? extends Future<O>> mappingFunction; 051 private final boolean recalculate; 052 053 /** 054 * Constructs a Memoizer for the provided Computable calculation. 055 * 056 * <p> 057 * If a calculation throws an exception for any reason, this exception will be cached and returned for all future 058 * calls with the provided parameter. 059 * </p> 060 * 061 * @param computable the computation whose results should be memorized 062 */ 063 public Memoizer(final Computable<I, O> computable) { 064 this(computable, false); 065 } 066 067 /** 068 * Constructs a Memoizer for the provided Computable calculation, with the option of whether a Computation that 069 * experiences an error should recalculate on subsequent calls or return the same cached exception. 070 * 071 * @param computable the computation whose results should be memorized 072 * @param recalculate determines whether the computation should be recalculated on subsequent calls if the previous call 073 * failed 074 */ 075 public Memoizer(final Computable<I, O> computable, final boolean recalculate) { 076 this.recalculate = recalculate; 077 this.mappingFunction = k -> FutureTasks.run(() -> computable.compute(k)); 078 } 079 080 /** 081 * Constructs a Memoizer for the provided Function calculation. 082 * 083 * <p> 084 * If a calculation throws an exception for any reason, this exception will be cached and returned for all future 085 * calls with the provided parameter. 086 * </p> 087 * 088 * @param function the function whose results should be memorized 089 * @since 2.13.0 090 */ 091 public Memoizer(final Function<I, O> function) { 092 this(function, false); 093 } 094 095 /** 096 * Constructs a Memoizer for the provided Function calculation, with the option of whether a Function that 097 * experiences an error should recalculate on subsequent calls or return the same cached exception. 098 * 099 * @param function the computation whose results should be memorized 100 * @param recalculate determines whether the computation should be recalculated on subsequent calls if the previous call 101 * failed 102 * @since 2.13.0 103 */ 104 public Memoizer(final Function<I, O> function, final boolean recalculate) { 105 this.recalculate = recalculate; 106 this.mappingFunction = k -> FutureTasks.run(() -> function.apply(k)); 107 } 108 109 /** 110 * This method will return the result of the calculation and cache it, if it has not previously been calculated. 111 * 112 * <p> 113 * This cache will also cache exceptions that occur during the computation if the {@code recalculate} parameter in the 114 * constructor was set to {@code false}, or not set. Otherwise, if an exception happened on the previous calculation, 115 * the method will attempt again to generate a value. 116 * </p> 117 * 118 * @param arg the argument for the calculation 119 * @return the result of the calculation 120 * @throws InterruptedException thrown if the calculation is interrupted 121 */ 122 @Override 123 public O compute(final I arg) throws InterruptedException { 124 while (true) { 125 final Future<O> future = cache.computeIfAbsent(arg, mappingFunction); 126 try { 127 return future.get(); 128 } catch (final CancellationException e) { 129 cache.remove(arg, future); 130 } catch (final ExecutionException e) { 131 if (recalculate) { 132 cache.remove(arg, future); 133 } 134 throw launderException(e.getCause()); 135 } 136 } 137 } 138 139 /** 140 * This method launders a Throwable to either a RuntimeException, Error or any other Exception wrapped in an 141 * IllegalStateException. 142 * 143 * @param throwable the throwable to laundered 144 * @return a RuntimeException, Error or an IllegalStateException 145 */ 146 private RuntimeException launderException(final Throwable throwable) { 147 throw new IllegalStateException("Unchecked exception", ExceptionUtils.throwUnchecked(throwable)); 148 } 149}