SelfPopulatingMap.java

package org.pojomatic.internal;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * A thread-safe "map" which generates values on demand, with the guarantee that no more than one
 * value will be auto-created for a given key.
 * Classes extending this class should override {@link #create(Object)}.
 * @param <K> the key type
 * @param <V> the value type
 */
public abstract class SelfPopulatingMap<K, V> {

  public V get(final K key) {
    V value = valueMap.get(key);
    if (value == null) {
      final Object mutex = new Object();
      synchronized (mutex) {
        Object existingMutex = mutexMap.putIfAbsent(key, mutex);
        if (existingMutex == null) {
          return tryCreatingValue(key);
        }
        else {
          synchronized (existingMutex) {
            V oldValue = valueMap.get(key);
            // if the previous holder of this mutex failed to create a value, we'll give it a shot.
            return oldValue != null ? oldValue : tryCreatingValue(key);
          }
        }
      }
    }
    return value;
  }

  /**
   * Create a value for a key.  This will be called by {@link #get(Object)} when there is not
   * already an existing value, and no other thread is already creating a value for that key.
   * The value returned must not be null.
   * @param key the key to create the value for
   * @return the value
   */
  protected abstract V create(K key);

  private V tryCreatingValue(K key) {
    V value = create(key);
    valueMap.put(key, value);
    return value;
  }

  /**
   * The values held by this map.
   */
  private final ConcurrentMap<K, V> valueMap = new ConcurrentHashMap<>();

  /**
   * Mutexes created on demand to ensure that only a single value is created for each key.
   */
  private final ConcurrentMap<K, Object> mutexMap = new ConcurrentHashMap<>();
}