/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.lucene.util;


import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;

Implements a combination of WeakHashMap and IdentityHashMap. Useful for caches that need to key off of a == comparison instead of a .equals.

This class is not a general-purpose Map implementation! It intentionally violates Map's general contract, which mandates the use of the equals method when comparing objects. This class is designed for use only in the rare cases wherein reference-equality semantics are required.

This implementation was forked from Apache CXF but modified to not implement the Map interface and without any set views on it, as those are error-prone and inefficient, if not implemented carefully. The map only contains Iterator implementations on the values and not-GCed keys. Lucene's implementation also supports null keys, but those are never weak!

The map supports two modes of operation:

  • reapOnRead = true: This behaves identical to a WeakHashMap where it also cleans up the reference queue on every read operation (get(Object), containsKey(Object), size(), valueIterator()), freeing map entries of already GCed keys.
  • reapOnRead = false: This mode does not call reap() on every read operation. In this case, the reference queue is only cleaned up on write operations (like put(Object, Object)). This is ideal for maps with few entries where the keys are unlikely be garbage collected, but there are lots of get(Object) operations. The code can still call reap() to manually clean up the queue without doing a write operation.
@lucene.internal
/** * Implements a combination of {@link java.util.WeakHashMap} and * {@link java.util.IdentityHashMap}. * Useful for caches that need to key off of a {@code ==} comparison * instead of a {@code .equals}. * * <p>This class is not a general-purpose {@link java.util.Map} * implementation! It intentionally violates * Map's general contract, which mandates the use of the equals method * when comparing objects. This class is designed for use only in the * rare cases wherein reference-equality semantics are required. * * <p>This implementation was forked from <a href="http://cxf.apache.org/">Apache CXF</a> * but modified to <b>not</b> implement the {@link java.util.Map} interface and * without any set views on it, as those are error-prone and inefficient, * if not implemented carefully. The map only contains {@link Iterator} implementations * on the values and not-GCed keys. Lucene's implementation also supports {@code null} * keys, but those are never weak! * * <p><a name="reapInfo"></a>The map supports two modes of operation: * <ul> * <li>{@code reapOnRead = true}: This behaves identical to a {@link java.util.WeakHashMap} * where it also cleans up the reference queue on every read operation ({@link #get(Object)}, * {@link #containsKey(Object)}, {@link #size()}, {@link #valueIterator()}), freeing map entries * of already GCed keys.</li> * <li>{@code reapOnRead = false}: This mode does not call {@link #reap()} on every read * operation. In this case, the reference queue is only cleaned up on write operations * (like {@link #put(Object, Object)}). This is ideal for maps with few entries where * the keys are unlikely be garbage collected, but there are lots of {@link #get(Object)} * operations. The code can still call {@link #reap()} to manually clean up the queue without * doing a write operation.</li> * </ul> * * @lucene.internal */
public final class WeakIdentityMap<K,V> { private final ReferenceQueue<Object> queue = new ReferenceQueue<>(); private final Map<IdentityWeakReference, V> backingStore; private final boolean reapOnRead;
Creates a new WeakIdentityMap based on a non-synchronized HashMap. The map cleans up the reference queue on every read operation.
/** * Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}. * The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. */
public static <K,V> WeakIdentityMap<K,V> newHashMap() { return newHashMap(true); }
Creates a new WeakIdentityMap based on a non-synchronized HashMap.
Params:
/** * Creates a new {@code WeakIdentityMap} based on a non-synchronized {@link HashMap}. * @param reapOnRead controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. */
public static <K,V> WeakIdentityMap<K,V> newHashMap(boolean reapOnRead) { return new WeakIdentityMap<>(new HashMap<IdentityWeakReference,V>(), reapOnRead); }
Creates a new WeakIdentityMap based on a ConcurrentHashMap. The map cleans up the reference queue on every read operation.
/** * Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}. * The map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. */
public static <K,V> WeakIdentityMap<K,V> newConcurrentHashMap() { return newConcurrentHashMap(true); }
Creates a new WeakIdentityMap based on a ConcurrentHashMap.
Params:
/** * Creates a new {@code WeakIdentityMap} based on a {@link ConcurrentHashMap}. * @param reapOnRead controls if the map <a href="#reapInfo">cleans up the reference queue on every read operation</a>. */
public static <K,V> WeakIdentityMap<K,V> newConcurrentHashMap(boolean reapOnRead) { return new WeakIdentityMap<>(new ConcurrentHashMap<IdentityWeakReference,V>(), reapOnRead); }
Private only constructor, to create use the static factory methods.
/** Private only constructor, to create use the static factory methods. */
private WeakIdentityMap(Map<IdentityWeakReference, V> backingStore, boolean reapOnRead) { this.backingStore = backingStore; this.reapOnRead = reapOnRead; }
Removes all of the mappings from this map.
/** Removes all of the mappings from this map. */
public void clear() { backingStore.clear(); reap(); }
Returns true if this map contains a mapping for the specified key.
/** Returns {@code true} if this map contains a mapping for the specified key. */
public boolean containsKey(Object key) { if (reapOnRead) reap(); return backingStore.containsKey(new IdentityWeakReference(key, null)); }
Returns the value to which the specified key is mapped.
/** Returns the value to which the specified key is mapped. */
public V get(Object key) { if (reapOnRead) reap(); return backingStore.get(new IdentityWeakReference(key, null)); }
Associates the specified value with the specified key in this map. If the map previously contained a mapping for this key, the old value is replaced.
/** Associates the specified value with the specified key in this map. * If the map previously contained a mapping for this key, the old value * is replaced. */
public V put(K key, V value) { reap(); return backingStore.put(new IdentityWeakReference(key, queue), value); }
Returns true if this map contains no key-value mappings.
/** Returns {@code true} if this map contains no key-value mappings. */
public boolean isEmpty() { return size() == 0; }
Removes the mapping for a key from this weak hash map if it is present. Returns the value to which this map previously associated the key, or null if the map contained no mapping for the key. A return value of null does not necessarily indicate that the map contained.
/** Removes the mapping for a key from this weak hash map if it is present. * Returns the value to which this map previously associated the key, * or {@code null} if the map contained no mapping for the key. * A return value of {@code null} does not necessarily indicate that * the map contained.*/
public V remove(Object key) { reap(); return backingStore.remove(new IdentityWeakReference(key, null)); }
Returns the number of key-value mappings in this map. This result is a snapshot, and may not reflect unprocessed entries that will be removed before next attempted access because they are no longer referenced.
/** Returns the number of key-value mappings in this map. This result is a snapshot, * and may not reflect unprocessed entries that will be removed before next * attempted access because they are no longer referenced. */
public int size() { if (backingStore.isEmpty()) return 0; if (reapOnRead) reap(); return backingStore.size(); }
Returns an iterator over all weak keys of this map. Keys already garbage collected will not be returned. This Iterator does not support removals.
/** Returns an iterator over all weak keys of this map. * Keys already garbage collected will not be returned. * This Iterator does not support removals. */
public Iterator<K> keyIterator() { reap(); final Iterator<IdentityWeakReference> iterator = backingStore.keySet().iterator(); // IMPORTANT: Don't use oal.util.FilterIterator here: // We need *strong* reference to current key after setNext()!!! return new Iterator<K>() { // holds strong reference to next element in backing iterator: private Object next = null; // the backing iterator was already consumed: private boolean nextIsSet = false; @Override public boolean hasNext() { return nextIsSet || setNext(); } @Override @SuppressWarnings("unchecked") public K next() { if (!hasNext()) { throw new NoSuchElementException(); } assert nextIsSet; try { return (K) next; } finally { // release strong reference and invalidate current value: nextIsSet = false; next = null; } } @Override public void remove() { throw new UnsupportedOperationException(); } private boolean setNext() { assert !nextIsSet; while (iterator.hasNext()) { next = iterator.next().get(); if (next == null) { // the key was already GCed, we can remove it from backing map: iterator.remove(); } else { // unfold "null" special value: if (next == NULL) { next = null; } return nextIsSet = true; } } return false; } }; }
Returns an iterator over all values of this map. This iterator may return values whose key is already garbage collected while iterator is consumed, especially if reapOnRead is false.
/** Returns an iterator over all values of this map. * This iterator may return values whose key is already * garbage collected while iterator is consumed, * especially if {@code reapOnRead} is {@code false}. */
public Iterator<V> valueIterator() { if (reapOnRead) reap(); return backingStore.values().iterator(); }
This method manually cleans up the reference queue to remove all garbage collected key/value pairs from the map. Calling this method is not needed if reapOnRead = true. Otherwise it might be a good idea to call this method when there is spare time (e.g. from a background thread).
See Also:
/** * This method manually cleans up the reference queue to remove all garbage * collected key/value pairs from the map. Calling this method is not needed * if {@code reapOnRead = true}. Otherwise it might be a good idea * to call this method when there is spare time (e.g. from a background thread). * @see <a href="#reapInfo">Information about the <code>reapOnRead</code> setting</a> */
public void reap() { Reference<?> zombie; while ((zombie = queue.poll()) != null) { backingStore.remove(zombie); } } // we keep a hard reference to our NULL key, so map supports null keys that never get GCed: static final Object NULL = new Object(); private static final class IdentityWeakReference extends WeakReference<Object> { private final int hash; IdentityWeakReference(Object obj, ReferenceQueue<Object> queue) { super(obj == null ? NULL : obj, queue); hash = System.identityHashCode(obj); } @Override public int hashCode() { return hash; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof IdentityWeakReference) { final IdentityWeakReference ref = (IdentityWeakReference)o; if (this.get() == ref.get()) { return true; } } return false; } } }