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

com.tectonica.collections.AutoEvictMap Maven / Gradle / Ivy

Go to download

Set of Java utility classes, all completely independent, to provide lightweight solutions for common situations

There is a newer version: 0.6.1
Show newest version
/*
 * Copyright (C) 2014 Zach Melamed
 * 
 * Latest version available online at https://github.com/zach-m/tectonica-commons
 *
 * 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 com.tectonica.collections;

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Concurrent, thread-safe container, retaining a key-value map with a reference count mechanism. Instead of the standard {@code get},
 * {@code put} and {@code remove} methods, if offers the following:
 * 
    *
  • {@code acquire} - gets a value given a key, creating it if necessary. If the key already exists, its reference count is increased *
  • {@code release} - decreases a reference count of a value, removing it if the number of acquires matches the number of releases *
* * of course, both actions are performed atomically. *

* When the class is constructed a default {@link Factory} may be provided, which will generate the values when {@link #acquire(Object)} * needs them. Alternatively, on each invocation of {@link #acquire(Object, Factory)}, a custom ad-hoc factory may be provided for the * particular key acquired. When keys are released, no additional action is taken. * * @author Zach Melamed */ public class AutoEvictMap { public static interface Factory { V valueOf(K key); } private final ConcurrentMap> map = new ConcurrentHashMap>(); private final Factory defaultFactory; public AutoEvictMap() { this.defaultFactory = null; } public AutoEvictMap(Factory defaultFactory) { this.defaultFactory = defaultFactory; } public V acquire(final K key) throws InterruptedException { if (defaultFactory == null) throw new NullPointerException("defaultFactory"); return acquire(key, defaultFactory); } public V acquire(final K key, final Factory customFactory) throws InterruptedException { if (key == null) throw new NullPointerException("key"); if (customFactory == null) throw new NullPointerException("customFactory"); Holder holder; while (true) { holder = map.get(key); if (holder == null) { if (map.putIfAbsent(key, holder = new Holder(key, customFactory)) == null) { // initial creation of the value holder.run(); break; } } else { if (map.replace(key, holder, holder = holder.inc())) break; // ref-count increased } } return holder.get(); // NOTE: think whether to remove from map in case of exception/cancellation } public boolean release(K key) { if (key == null) throw new NullPointerException("key"); while (true) { Holder holder = map.get(key); if (holder == null) return true; // was already removed if (holder.isInitial()) { if (map.remove(key, holder.initial())) return true; // removed now } else { if (map.replace(key, holder, holder.dec())) return false; // not removed, just decreased ref-count } } } public int size() { return map.size(); } public void clear() { map.clear(); } // ///////////////////////////////////////////////////////////////////////////////////////// private static class Holder { private final FutureTask ft; private final int refCount; public Holder(final K key, final Factory generator) { ft = new FutureTask(new Callable() { public V call() throws InterruptedException { return generator.valueOf(key); } }); refCount = 1; } private Holder(FutureTask ft, int refCount) { this.ft = ft; this.refCount = refCount; } public V get() throws InterruptedException { try { return ft.get(); } catch (ExecutionException e) { Throwable t = e.getCause(); if (t instanceof RuntimeException) throw (RuntimeException) t; else if (t instanceof Error) throw (Error) t; else throw new IllegalStateException("Not unchecked", t); } } public void run() { ft.run(); } public Holder inc() { return new Holder(ft, refCount + 1); } public Holder dec() { return new Holder(ft, refCount - 1); } public Holder initial() { return new Holder(ft, 1); } public boolean isInitial() { return refCount == 1; } @Override @SuppressWarnings("unchecked") public boolean equals(Object obj) { return (refCount == ((Holder) obj).refCount); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy