001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2023–2024 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
006 * the License.  You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
011 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
012 * specific language governing permissions and limitations under the License.
013 */
014package org.microbean.bean;
015
016import java.lang.invoke.MethodHandles;
017import java.lang.invoke.VarHandle;
018
019import java.lang.System.Logger;
020
021import java.lang.ref.WeakReference;
022
023import java.util.Objects;
024
025import java.util.function.Consumer;
026
027import static java.lang.System.Logger.Level.DEBUG;
028
029/**
030 * An {@link AutoCloseable} {@link WeakReference} that formally disposes of referents after they have been {@linkplain
031 * #clear() cleared} by the Java Virtual Machine during garbage collection.
032 *
033 * <p>Note that garbage collection may never happen.</p>
034 *
035 * @param <R> the type of the reference
036 *
037 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
038 *
039 * @see #DisposableReference(Object, Consumer)
040 */
041public final class DisposableReference<R> extends WeakReference<R> implements AutoCloseable {
042
043
044  /*
045   * Static fields.
046   */
047
048
049  /**
050   * A {@link Logger} for instances of this class.
051   *
052   * <p>The {@link Logger}'s name is equal to this class' {@linkplain Class#getName() name}.</p>
053   */
054  private static final Logger LOGGER = System.getLogger(DisposableReference.class.getName());
055
056  /**
057   * A {@link VarHandle} providing access to the {@link #disposed} field.
058   *
059   * @nullability This field is never {@code null}.
060   *
061   * @see #disposed
062   *
063   * @see #dispose()
064   *
065   * @see #disposed()
066   */
067  private static final VarHandle DISPOSED;
068
069  static {
070    try {
071      DISPOSED = MethodHandles.lookup().findVarHandle(DisposableReference.class, "disposed", boolean.class);
072    } catch (final NoSuchFieldException | IllegalAccessException e) {
073      throw (Error)new ExceptionInInitializerError(e.getMessage()).initCause(e);
074    }
075  }
076
077
078  /*
079   * Instance fields.
080   */
081
082
083  /**
084   * The referent initially retrievable via the {@link #get()} method, but in a way that does not prevent weak
085   * reachability.
086   *
087   * @nullability This field may be {@code null}.
088   *
089   * @see #DisposableReference(Object, Consumer)
090   */
091  private final R referent;
092
093  /**
094   * A {@link Consumer} used to dispose of the {@linkplain #referent referent} at the appropriate time.
095   *
096   * @nullability This field may be {@code null}.
097   */
098  private final Consumer<? super R> disposer;
099
100  /**
101   * Whether or not the {@link #dispose()} method has been called successfully.
102   *
103   * @see #dispose()
104   *
105   * @see #disposed()
106   *
107   * @see #DISPOSED
108   */
109  private volatile boolean disposed;
110
111
112  /*
113   * Constructors.
114   */
115
116
117  /**
118   * Creates a new {@link DisposableReference}.
119   *
120   * @param referent the referent; may be {@code null}
121   *
122   * @param disposer a thread-safe {@link Consumer} whose {@link Consumer#accept(Object) accept(Object)} method, which
123   * must be idempotent, will be invoked from a separate thread to dispose of the referent after it has been {@linkplain
124   * #clear() cleared} by the Java Virtual Machine during garbage collection; may be {@code null} in which case no
125   * destruction will take place
126   */
127  public DisposableReference(final R referent, final Consumer<? super R> disposer) {
128    super(Objects.requireNonNull(referent, "referent"), ReferenceQueue.INSTANCE);
129    this.referent = referent;
130    this.disposer = disposer == null ? DisposableReference::noopDispose : disposer;
131  }
132
133
134  /*
135   * Instance methods.
136   */
137
138
139  /**
140   * Calls {@link #enqueue()}.
141   *
142   * @see #enqueue()
143   */
144  @Override // AutoCloseable
145  public final void close() {
146    this.enqueue(); // idempotent
147  }
148
149  /**
150   * Calls {@link #refersTo(Object) refersTo(null)} and returns the result.
151   *
152   * @return the result of an invocation of {@link #refersTo(Object) refersTo(null)}
153   *
154   * @see #refersTo(Object)
155   */
156  public final boolean closed() {
157    return this.refersTo(null);
158  }
159
160  /**
161   * If there has been no prior successful invocation of this method, calls the {@link Consumer#accept(Object)
162   * accept(Object)} method on the {@link Consumer} representing the disposer {@linkplain #DisposableReference(Object,
163   * Consumer) supplied at construction time}, thus notionally disposeing the {@linkplain #DisposableReference(Object,
164   * Consumer) referent supplied at construction time}, and returns {@code true}.
165   *
166   * <p>Destruction does not imply {@linkplain #close() closing}, and closing does not imply destruction (though it
167   * normally will eventually lead to it).</p>
168   *
169   * <p>If destruction does not result in any {@link RuntimeException} or {@link Error} being thrown, then calling this
170   * method again, from any thread, will have no effect and it will return {@code false}.</p>
171   *
172   * <p>If the first invocation of this method from any thread succeeds, then it will return {@code true}, and all other
173   * invocations of this method from any thread will return {@code false}, and will have no effect.</p>
174   *
175   * <p>This method is often called from a thread dedicated to disposeing {@linkplain #enqueue() enqueued} {@link
176   * DisposableReference}s, so the {@link Consumer} {@linkplain #DisposableReference(Object, Consumer) supplied at
177   * construction time} must be thread-safe.</p>
178   *
179   * @return {@code true} if this invocation of this method caused destruction to happen successfully; {@code false} in
180   * all other cases
181   *
182   * @exception RuntimeException if an invocation of the {@link Consumer#accept(Object) accept(Object)} method on the
183   * {@link Consumer} {@linkplain #DisposableReference(Object, Consumer) supplied at construction time} fails
184   *
185   * @idempotency This method is idempotent.
186   *
187   * @threadsafety This method is safe for concurrent use by multiple threads.
188   *
189   * @see #DisposableReference(Object, Consumer)
190   */
191  public final boolean dispose() {
192    if (DISPOSED.compareAndSet(this, false, true)) { // volatile write; assume disposer success
193      try {
194        this.disposer.accept(this.referent);
195      } catch (RuntimeException | Error e) {
196        this.disposed = false; // volatile write; oops; our optimism was misplaced
197        throw e;
198      }
199      return true;
200    } else {
201      return false;
202    }
203  }
204
205  /**
206   * Returns {@code true} if and only if there has been a prior successful invocation of {@link #dispose()} that
207   * returned {@code true}.
208   *
209   * @return {@code true} if and only if there has been a prior successful invocation of {@link #dispose()} that
210   * returned {@code true}
211   *
212   * @see #dispose()
213   */
214  public final boolean disposed() {
215    return this.disposed; // volatile read
216  }
217
218
219  /*
220   * Static methods.
221   */
222
223
224  private static final <R> void noopDispose(final R r) {
225    if (LOGGER.isLoggable(DEBUG)) {
226      LOGGER.log(DEBUG, "DisposableReference referent " + r + " has been cleared");
227    }
228  }
229
230
231  /*
232   * Inner and nested classes.
233   */
234
235
236  private static final class ReferenceQueue extends java.lang.ref.ReferenceQueue<Object> implements Runnable {
237
238
239    /*
240     * Static fields.
241     */
242
243
244    private static final ReferenceQueue INSTANCE = new ReferenceQueue();
245
246    static {
247      final Thread t = new Thread(ReferenceQueue.INSTANCE, "DisposableReference disposer");
248      t.setDaemon(true);
249      t.setPriority(3); // a little less important than the default
250      t.start();
251    }
252
253
254    /*
255     * Constructors.
256     */
257
258
259    private ReferenceQueue() {
260      super();
261    }
262
263
264    /*
265     * Instance methods.
266     */
267
268
269    @Override // Runnable
270    public final void run() {
271      while (!Thread.currentThread().isInterrupted()) {
272        try {
273          ((DisposableReference<?>)this.remove()).dispose();
274        } catch (final InterruptedException e) {
275          Thread.currentThread().interrupt();
276        }
277      }
278    }
279
280  }
281
282}