001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.tools;
003
004import java.lang.ref.Reference;
005import java.lang.ref.ReferenceQueue;
006import java.lang.ref.WeakReference;
007
008import org.openstreetmap.josm.tools.bugreport.BugReport;
009
010/**
011 * This is a special weak reference that notifies a listener when it is no longer available.
012 *
013 * A special dereferenced-thread is used for this, so make sure your code is thread-safe.
014 * @author Michael Zangl
015 * @param <T> The weak reference
016 * @since 12181
017 */
018public class ListenableWeakReference<T> extends WeakReference<T> {
019    private static final ReferenceQueue<Object> GLOBAL_QUEUE = new ReferenceQueue<>();
020    private static Thread thread;
021    private Runnable runOnDereference;
022
023    /**
024     * Create a new {@link ListenableWeakReference}
025     * @param referent The object that is referenced
026     */
027    public ListenableWeakReference(T referent) {
028        this(referent, () -> { });
029    }
030
031    /**
032     * Create a new {@link ListenableWeakReference}
033     * @param referent The object that is referenced
034     * @param runOnDereference The runnable to run when the object is no longer referenced.
035     */
036    public ListenableWeakReference(T referent, Runnable runOnDereference) {
037        super(referent, GLOBAL_QUEUE);
038        this.runOnDereference = runOnDereference;
039        ensureQueueStarted();
040    }
041
042    /**
043     * This method is called after the object is dereferenced.
044     */
045    protected void onDereference() {
046        this.runOnDereference.run();
047    }
048
049    private static synchronized void ensureQueueStarted() {
050        if (thread == null) {
051            thread = new Thread(ListenableWeakReference::clean, "Weak reference cleaner");
052            thread.start();
053        }
054    }
055
056    private static void clean() {
057        boolean running = true;
058        try {
059            while (running) {
060                Reference<? extends Object> ref = GLOBAL_QUEUE.remove();
061                if (ref instanceof ListenableWeakReference) {
062                    ((ListenableWeakReference<?>) ref).onDereference();
063                }
064            }
065        } catch (InterruptedException e) {
066            running = false;
067            BugReport.intercept(e).warn();
068            Thread.currentThread().interrupt();
069        }
070    }
071}