package tc.oc.commons.core.collection; import java.util.function.UnaryOperator; import com.google.common.collect.BiMap; /** * A {@link BiMap} that transforms keys on insertion to make them unique. It does this by * repeatedly applying the given {@link UnaryOperator} to any newly added key until it is * distinct from any key already in the map. A hard limit must be specified on the number * of iterations before giving up and throwing an {@link IllegalArgumentException}. * * This class extends {@link BiMap} since reverse lookups are likely needed in many use cases. * * The conflict resolution can be bypassed by calling {@link #forcePut}, which will silently * replace any existing entry with the same key (or with the same value, as per the * specification in {@link BiMap}). */ public abstract class ConflictResolvingMap<K, V> extends FilteredBiMap<K, V> { private final UnaryOperator<K> uniquifier; private final int limit; public ConflictResolvingMap(int limit, UnaryOperator<K> uniquifier) { this.uniquifier = uniquifier; this.limit = limit; } /** * Resolve the actual key that would be used if the given key and value * were inserted into the map in its current state, but do not actually * make any changes to the map. */ public K resolveKey(K key, V value) { final BiMap<K, V> delegate = delegate(); for(int tries = 0; tries < limit; tries++) { final V existing = delegate.get(key); if(existing == null || existing.equals(value)) { return key; } key = uniquifier.apply(key); } throw new IllegalArgumentException("Failed to generate a unique key after " + limit + " attempts"); } /** * Equivalent to {@link #put}, but the resolved key is returned instead * of the old value (which is always null anyway). */ public K putReturningKey(K key, V value) { key = resolveKey(key, value); delegate().put(key, value); return key; } @Override protected V putInternal(K key, V value) { key = resolveKey(key, value); return delegate().put(key, value); } @Override protected V forcePutInternal(K key, V value) { return delegate().forcePut(key, value); } }