/* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * 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 . */ package com.sun.star.lib.util; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * A hash map that holds values of type WeakReference. * *

Like HashMap, this implementation provides all of the * optional map operations, and permits the null key.

* *

Also like HashMap, this implementation is not synchronized. * If multiple threads share an instance, and at least one of them executes any * modifying operations on the WeakMap, they have to use external * synchronization.

* *

Unlike other map implementations, WeakMap is asymmetric in * that put expects the given value to be a plain object that is * then wrapped in a WeakReference, while the occurrences of values * in all other methods (containsValue, entrySet, * equals, get, hashCode, * remove, values, and also the return value of * put) expect already wrapped instances of * WeakReference. That is, after weakMap.put("key", * o), weakMap.get("key").equals(o) does not work as * naïvely expected; neither does * weakMap1.putAll(weakMap2).

* *

At an arbitrary time after the WeakReference value of an * entry has been cleared by the garbage collector, the entry is automatically * removed from the map.

* *

Values placed into a WeakMap may optionally support the * DisposeNotifier interface. For those that do, the associated * WeakReference wrappers are automatically cleared as soon as the * values are disposed.

* * Note that this class does not actually implement the Map interface properly, * the type of the return value of the entrySet and values methods is wrong, * but the "implements Map" is retained for backward compatibility. */ public final class WeakMap implements Map { /** * Declare the map as WeakReference instead of Entry because it makes the return * type signatures of values() and keySet() cleaner. */ private final HashMap> map = new HashMap>(); private final ReferenceQueue queue = new ReferenceQueue(); /** * Constructs an empty WeakMap. */ public WeakMap() {} /** * Constructs a new WeakMap with the same mappings as the * specified Map. * * @param m the map whose mappings are to be placed in this map */ public WeakMap(Map m) { putAll(m); } /** * Returns the number of key–value mappings in this map. * *

This is a non-modifying operation.

* * @return the number of key–value mappings in this map */ public int size() { return map.size(); } /** * Returns true if this map contains no key–value * mappings. * *

This is a non-modifying operation.

* * @return true if this map contains no key–value * mappings */ public boolean isEmpty() { return map.isEmpty(); } /** * Returns true if this map contains a mapping for the * specified key. * *

This is a non-modifying operation.

* * @param key the key whose presence in this map is to be tested * @return true if this map contains a mapping for the * specified key */ public boolean containsKey(/*K*/ Object key) { return map.containsKey(key); } /** * Returns true if this map maps one or more keys to the * specified value. * *

This is a non-modifying operation.

* * @param value the value whose presence in this map is to be tested * @return true if this map maps one or more keys to the * specified value */ public boolean containsValue(Object /*WeakReference*/ value) { return map.containsValue(value); } /** * Returns the value to which the specified key is mapped in this map, or * null if the map contains no mapping for this key. * *

This is a non-modifying operation.

* * @param key the key whose associated value is to be returned * * @return the value to which this map maps the specified key, or * null if the map contains no mapping for this key */ public WeakReference get(/*K*/ Object key) { return map.get(key); } /** * Associates the specified value with the specified key in this map. * *

This is a modifying operation.

* * @param key the key with which the specified value is to be associated * @param value the value to be associated with the specified key. This * must be a plain object, which is then wrapped in a * WeakReference. * @return previous value associated with the specified key, or * null if there was no mapping for the key */ public Object /*WeakReference*/ put(/*K*/ Object key, /*V*/ Object value) { cleanUp(); return map.put((K) key, new Entry((K) key, (V) value, queue)); } /** * Removes the mapping for this key from this map if present. * *

This is a modifying operation.

* * @param key the key whose mapping is to be removed from the map * @return previous value associated with the specified key, or * null if there was no mapping for the key */ public Object /*WeakReference*/ remove(/*K*/ Object key) { cleanUp(); return map.remove(key); } /** * Copies all of the mappings from the specified map to this map. * *

This is a modifying operation.

* * @param m mappings to be stored in this map. The values of those mappings * must be plain objects, which are then wrapped in instances of * WeakReference. */ public void putAll(Map/**/ m) { cleanUp(); for (Iterator> i = m.entrySet().iterator(); i.hasNext();) { Map.Entry e = i.next(); K k = e.getKey(); map.put(k, new Entry(k, e.getValue(), queue)); } } /** * Removes all mappings from this map. * *

This is a modifying operation.

*/ public void clear() { cleanUp(); map.clear(); } /** * Returns a view of the keys contained in this map. * *

This is a non-modifying operation.

* * @return a set view of the keys contained in this map */ public Set keySet() { return map.keySet(); } /** * Returns a collection view of the values contained in this map. * *

This is a non-modifying operation.

* * @return a collection view of the values contained in this map */ public Collection> values() { return map.values(); } /** * Returns a collection view of the mappings contained in this map. * *

This is a non-modifying operation.

* * @return a collection view of the mappings contained in this map */ public Set/*>>*/ entrySet() { return map.entrySet(); } @Override public boolean equals(Object o) { return map.equals(o); } @Override public int hashCode() { return map.hashCode(); } /** * Returns the referent of a WeakReference, silently handling a * null argument. * *

This static method is useful to wrap around the return values of * methods like get.

* * @param ref must be either an instance of WeakReference or * null * @return the referent of the specified WeakReference, or * null if ref is null */ public static T getValue(Object /*WeakReference*/ ref) { return ref == null ? null : ((WeakReference) ref).get(); } /** * cleanUp() must only be called from within modifying methods. Otherwise, * the implementations of entrySet, keySet and values would break * (Specifically, iterating over the collections returned by those * methods), as non-modifying methods might modify the underlying map. **/ private void cleanUp() { for (;;) { Entry e = (Entry) queue.poll(); if (e == null) { break; } // It is possible that an Entry e1 becomes weakly reachable, then // another Entry e2 is added to the map for the same key, and only // then e1 is enqueued. To not erroneously remove the new e2 in // that case, check whether the map still contains e1: Object k = e.key; if (e == map.get(k)) { map.remove(k); } } } private static final class Entry extends WeakReference implements DisposeListener { private final K key; private Entry(K key, V value, ReferenceQueue queue) { super(value, queue); this.key = key; if (value instanceof DisposeNotifier) { ((DisposeNotifier) value).addDisposeListener(this); } } /** * @see DisposeListener#notifyDispose(DisposeNotifier) */ public void notifyDispose(DisposeNotifier source) { clear(); enqueue(); } } }