All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.persistence.internal.cache.Memoizer Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0,
 * or the Eclipse Distribution License v. 1.0 which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
 */

// Contributors:
//      Marcel Valovy - initial API and implementation
package org.eclipse.persistence.internal.cache;

import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * Memoizer for computing resource expensive tasks asynchronously & concurrently.
 * 

* Inspired by D. Lea. JCiP, 2nd edition. Addison-Wesley, 2006. pp.109 * * @author Marcel Valovy - [email protected] * @since 2.6 */ public class Memoizer implements LowLevelProcessor { private final ConcurrentMap> cache = new ConcurrentHashMap<>(); private final KeyStorage keyStorage = new KeyStorage(); @Override public V compute(final ComputableTask c, final A taskArg) throws InterruptedException { final MemoizerKey key = keyStorage.get(c, taskArg); while (true) { Future f = cache.get(key); if (f == null) { Callable evaluation = new Callable<>() { @Override public V call() throws InterruptedException { return c.compute(taskArg); } }; FutureTask ft = new FutureTask<>(evaluation); f = cache.putIfAbsent(key, ft); if (f == null) { f = ft; ft.run(); } } try { return f.get(); } catch (CancellationException e) { /* * "Caching a Future instead of a value creates the possibility of cache pollution: if a computation * is cancelled or fails, future attempts to compute the results will also indicate cancellation or * failure. To avoid this, Memoizer removes the Future from the cache if it detects that the * computation was cancelled." */ cache.remove(key, f); } catch (ExecutionException e) { throw new RuntimeException(e); } } } /** * Forgets result of the specified task. * This allows to manually control size of internal cache. * * @param task computable task, forming part of result key * @param key argument of computation */ public void forget(ComputableTask task, A key) { cache.remove(this.new MemoizerKey(task, key)); } /** * Forgets all cached results. */ public void forgetAll() { cache.clear(); } /** * Composite-key map. */ private class KeyStorage { Map map = new ConcurrentHashMap<>(); public MemoizerKey get(final ComputableTask c, final A taskArg) { MemoizerKey certificate = new MemoizerKey(c, taskArg); MemoizerKey key = map.putIfAbsent(certificate, certificate); return (key == null) ? certificate : key; } } /** * Key for composite-key map. */ private class MemoizerKey { private final ComputableTask task; private final A key; private MemoizerKey(ComputableTask task, A key) { this.task = task; this.key = key; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; //noinspection unchecked MemoizerKey cacheKey = (MemoizerKey) o; //noinspection SimplifiableIfStatement if (!Objects.equals(key, cacheKey.key)) return false; return !(!Objects.equals(task, cacheKey.task)); } @Override public int hashCode() { int result = task != null ? task.hashCode() : 0; result = 31 * result + (key != null ? key.hashCode() : 0); return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy