
com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency.SingletonMap Maven / Gradle / Ivy
The newest version!
/*
* This file is part of ClassGraph.
*
* Author: Luke Hutchison
*
* Hosted at: https://github.com/classgraph/classgraph
*
* --
*
* The MIT License (MIT)
*
* Copyright (c) 2019 Luke Hutchison
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
* EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
* OR OTHER DEALINGS IN THE SOFTWARE.
*/
package com.hazelcast.shaded.nonapi.io.github.classgraph.concurrency;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import com.hazelcast.shaded.nonapi.io.github.classgraph.utils.LogNode;
/**
* A map from keys to singleton instances. Allows you to create object instance singletons and add them to a
* {@link ConcurrentMap} on demand, based on a key value. Works the same as
* {@code concurrentMap.computeIfAbsent(key, key -> newInstance(key))}, except that it also works on JDK 7.
*
* @param
* The key type.
* @param
* The value type.
* @param
* the element type
*/
public abstract class SingletonMap {
/** The map. */
private final ConcurrentMap> map = new ConcurrentHashMap<>();
// -------------------------------------------------------------------------------------------------------------
/** Thrown when {@link SingletonMap#newInstance(Object, LogNode)} returns null. */
public static class NullSingletonException extends Exception {
/** serialVersionUID. */
static final long serialVersionUID = 1L;
/**
* Constructor.
*
* @param
* the key type
* @param key
* the key
*/
public NullSingletonException(final K key) {
super("newInstance returned null for key " + key);
}
}
/** Thrown when {@link SingletonMap#newInstance(Object, LogNode)} throws an exception. */
public static class NewInstanceException extends Exception {
/** serialVersionUID. */
static final long serialVersionUID = 1L;
/**
* Constructor.
*
* @param
* the key type
* @param key
* the key
* @param t
* the Throwable that was thrown
*/
public NewInstanceException(final K key, final Throwable t) {
super("newInstance threw an exception for key " + key + " : " + t, t);
}
}
// -------------------------------------------------------------------------------------------------------------
/**
* Wrapper to allow an object instance to be put into a ConcurrentHashMap using putIfAbsent() without requiring
* the instance to be initialized first, so that putIfAbsent can be performed without wrapping it with a
* synchronized lock, and so that initialization work is not wasted if an object is already in the map for the
* key.
*
* @param
* the singleton type
*/
private static class SingletonHolder {
/** The singleton. */
@SuppressWarnings("null")
private volatile V singleton;
/** Whether or not the singleton has been initialized (the count will have reached 0 if so). */
private final CountDownLatch initialized = new CountDownLatch(1);
/**
* Set the singleton value, and decreases the countdown latch to 0.
*
* @param singleton
* the singleton
* @throws IllegalArgumentException
* if this method is called more than once (indicating an internal inconsistency).
*/
void set(final V singleton) throws IllegalArgumentException {
if (initialized.getCount() < 1) {
// Should not happen
throw new IllegalArgumentException("Singleton already initialized");
}
this.singleton = singleton;
initialized.countDown();
if (initialized.getCount() != 0) {
// Should not happen
throw new IllegalArgumentException("Singleton initialized more than once");
}
}
/**
* Get the singleton value.
*
* @return the singleton value.
* @throws InterruptedException
* if the thread was interrupted while waiting for the value to be set.
*/
V get() throws InterruptedException {
initialized.await();
return singleton;
}
}
/**
* Construct a new singleton instance.
*
* @param key
* The key for the singleton.
* @param log
* The log.
* @return The singleton instance. This method must either return a non-null value, or throw an exception of
* type E.
* @throws E
* If something goes wrong while instantiating the new object instance.
* @throws InterruptedException
* if the thread was interrupted while instantiating the singleton.
*/
public abstract V newInstance(K key, LogNode log) throws E, InterruptedException;
/**
* Create a new instance.
*
* @param
* The instance type.
*/
@FunctionalInterface
public interface NewInstanceFactory {
/**
* Create a new instance.
*
* @return The new instance.
*/
public V newInstance() throws E, InterruptedException;
}
/**
* Check if the given key is in the map, and if so, return the value of {@link #newInstance(Object, LogNode)}
* for that key, or block on the result of {@link #newInstance(Object, LogNode)} if another thread is currently
* creating the new instance.
*
* If the given key is not currently in the map, store a placeholder in the map for this key, then run
* {@link #newInstance(Object, LogNode)} for the key, store the result in the placeholder (which unblocks any
* other threads waiting for the value), and then return the new instance.
*
* @param key
* The key for the singleton.
* @param newInstanceFactory
* if non-null, a factory for creating new instances, otherwise if null, then
* {@link #newInstance(Object, LogNode)} is called instead (this allows new instance creation to be
* overridden on a per-instance basis).
* @param log
* The log.
* @return The non-null singleton instance, if {@link #newInstance(Object, LogNode)} returned a non-null
* instance on this call or a previous call, otherwise throws {@link NullPointerException} if this call
* or a previous call to {@link #newInstance(Object, LogNode)} returned null.
* @throws E
* If {@link #newInstance(Object, LogNode)} threw an exception.
* @throws InterruptedException
* if the thread was interrupted while waiting for the singleton to be instantiated by another
* thread.
* @throws NullSingletonException
* if {@link #newInstance(Object, LogNode)} returned null.
* @throws NewInstanceException
* if {@link #newInstance(Object, LogNode)} threw an exception.
*/
public V get(final K key, final LogNode log, final NewInstanceFactory newInstanceFactory)
throws E, InterruptedException, NullSingletonException, NewInstanceException {
final SingletonHolder singletonHolder = map.get(key);
@SuppressWarnings("null")
V instance = null;
if (singletonHolder != null) {
// There is already a SingletonHolder in the map for this key -- get the value
instance = singletonHolder.get();
} else {
// There is no SingletonHolder in the map for this key, need to create one
// (need to handle race condition, hence the putIfAbsent call)
final SingletonHolder newSingletonHolder = new SingletonHolder<>();
final SingletonHolder oldSingletonHolder = map.putIfAbsent(key, newSingletonHolder);
if (oldSingletonHolder != null) {
// There was already a singleton in the map for this key, due to a race condition --
// return the existing singleton
instance = oldSingletonHolder.get();
} else {
try {
// Create a new instance
if (newInstanceFactory != null) {
// Call NewInstanceFactory
instance = newInstanceFactory.newInstance();
} else {
// Call overridden newInstance method
instance = newInstance(key, log);
}
} catch (final Throwable t) {
// Initialize newSingletonHolder with the new instance.
// Always need to call .set() even if an exception is thrown by newInstance()
// or newInstance() returns null, since .set() calls initialized.countDown().
// Otherwise threads that call .get() may end up waiting forever.
newSingletonHolder.set(instance);
throw new NewInstanceException(key, t);
}
newSingletonHolder.set(instance);
}
}
if (instance == null) {
throw new NullSingletonException(key);
} else {
return instance;
}
}
/**
* Check if the given key is in the map, and if so, return the value of {@link #newInstance(Object, LogNode)}
* for that key, or block on the result of {@link #newInstance(Object, LogNode)} if another thread is currently
* creating the new instance.
*
* If the given key is not currently in the map, store a placeholder in the map for this key, then run
* {@link #newInstance(Object, LogNode)} for the key, store the result in the placeholder (which unblocks any
* other threads waiting for the value), and then return the new instance.
*
* @param key
* The key for the singleton.
* @param log
* The log.
* @return The non-null singleton instance, if {@link #newInstance(Object, LogNode)} returned a non-null
* instance on this call or a previous call, otherwise throws {@link NullPointerException} if this call
* or a previous call to {@link #newInstance(Object, LogNode)} returned null.
* @throws E
* If {@link #newInstance(Object, LogNode)} threw an exception.
* @throws InterruptedException
* if the thread was interrupted while waiting for the singleton to be instantiated by another
* thread.
* @throws NullSingletonException
* if {@link #newInstance(Object, LogNode)} returned null.
* @throws NewInstanceException
* if {@link #newInstance(Object, LogNode)} threw an exception.
*/
public V get(final K key, final LogNode log)
throws E, InterruptedException, NullSingletonException, NewInstanceException {
return get(key, log, null);
}
/**
* Get all valid singleton values in the map.
*
* @return the singleton values in the map, skipping over any value for which newInstance() threw an exception
* or returned null.
* @throws InterruptedException
* If getting the values was interrupted.
*/
public List values() throws InterruptedException {
final List entries = new ArrayList<>(map.size());
for (final Entry> ent : map.entrySet()) {
final V entryValue = ent.getValue().get();
if (entryValue != null) {
entries.add(entryValue);
}
}
return entries;
}
/**
* Returns true if the map is empty.
*
* @return true, if the map is empty
*/
public boolean isEmpty() {
return map.isEmpty();
}
/**
* Get the map entries.
*
* @return the map entries.
* @throws InterruptedException
* if interrupted.
*/
public List> entries() throws InterruptedException {
final List> entries = new ArrayList<>(map.size());
for (final Entry> ent : map.entrySet()) {
entries.add(new SimpleEntry<>(ent.getKey(), ent.getValue().get()));
}
return entries;
}
/**
* Remove the singleton for a given key.
*
* @param key
* the key
* @return the old singleton from the map, if one was present, otherwise null.
* @throws InterruptedException
* if interrupted.
*/
@SuppressWarnings("null")
public V remove(final K key) throws InterruptedException {
final SingletonHolder val = map.remove(key);
return val == null ? null : val.get();
}
/** Clear the map. */
public void clear() {
map.clear();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy