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 = () -> computable.compute(arg); 119 final FutureTask<O> futureTask = new FutureTask<>(eval); 120 future = cache.putIfAbsent(arg, futureTask); 121 if (future == null) { 122 future = futureTask; 123 futureTask.run(); 124 } 125 } 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 135 throw launderException(e.getCause()); 136 } 137 } 138 } 139 140 /** 141 * <p> 142 * This method launders a Throwable to either a RuntimeException, Error or 143 * any other Exception wrapped in an IllegalStateException. 144 * </p> 145 * 146 * @param throwable 147 * the throwable to laundered 148 * @return a RuntimeException, Error or an IllegalStateException 149 */ 150 private RuntimeException launderException(final Throwable throwable) { 151 if (throwable instanceof RuntimeException) { 152 return (RuntimeException) throwable; 153 } else if (throwable instanceof Error) { 154 throw (Error) throwable; 155 } else { 156 throw new IllegalStateException("Unchecked exception", throwable); 157 } 158 } 159}