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.util.LinkedHashSet;
017import java.util.Set;
018
019import java.util.concurrent.locks.Lock;
020import java.util.concurrent.locks.ReentrantLock;
021
022/**
023 * A straightforward {@link AutoCloseableRegistry} implementation.
024 *
025 * @author <a href="https://about.me/lairdnelson" target="_top">Laird Nelson</a>
026 *
027 * @see AutoCloseableRegistry
028 */
029public class DefaultAutoCloseableRegistry implements AutoCloseableRegistry {
030
031
032  /*
033   * Instance fields.
034   */
035
036
037  private final Lock lock;
038
039  // @GuardedBy("lock")
040  private Set<AutoCloseable> closeables;
041
042
043  /*
044   * Constructors.
045   */
046
047
048  /**
049   * Creates a new {@link DefaultAutoCloseableRegistry}.
050   */
051  public DefaultAutoCloseableRegistry() {
052    super();
053    this.lock = new ReentrantLock();
054  }
055
056
057  /*
058   * Instance methods.
059   */
060
061
062  /**
063   * Returns a new {@link DefaultAutoCloseableRegistry} instance that is not {@linkplain #closed() closed}, has no
064   * {@linkplain #register(AutoCloseable) registrations} yet, and is {@linkplain #register(AutoCloseable) registered}
065   * with this {@link DefaultAutoCloseableRegistry}.
066   *
067   * @return a new, {@linkplain #closed() unclosed} {@link DefaultAutoCloseableRegistry} {@linkplain
068   * #register(AutoCloseable) registered} with this {@link DefaultAutoCloseableRegistry}
069   *
070   * @exception IllegalStateException if this {@link DefaultAutoCloseableRegistry} is {@linkplain #closed() closed}
071   *
072   * @nullability This method does not, and its overrides must not, return {@code null}.
073   *
074   * @idempotency Overrides of this method must return new, distinct {@link DefaultAutoCloseableRegistry} instances.
075   *
076   * @threadsafety This method is, and its overrides must be, safe for concurrent use by multiple threads.
077   *
078   * @see #register(AutoCloseable)
079   */
080  @Override // AutoCloseableRegistry
081  public DefaultAutoCloseableRegistry newChild() {
082    final DefaultAutoCloseableRegistry child = new DefaultAutoCloseableRegistry();
083    if (!this.register(child)) { // CRITICAL
084      throw new IllegalStateException();
085    }
086    return child;
087  }
088
089  /**
090   * Closes this {@link DefaultAutoCloseableRegistry} and {@linkplain AutoCloseable#close() closes} its {@linkplain
091   * #register(AutoCloseable) registrants}.
092   *
093   * <p>{@link AutoCloseable#close()} is called on all {@linkplain #register(AutoCloseable) registrants}, even in the
094   * presence of exceptions. {@link RuntimeException}s consequently thrown may therefore {@linkplain
095   * Throwable#getSuppressed() contain suppressed exceptions}.</p>
096   *
097   * <p>Overrides of this method wishing to add semantics to this behavior should perform that work before calling
098   * {@link #close() super.close()}.</p>
099   *
100   * <p>Overrides of this method must call {@link #close() super.close()} or undefined behavior may result.</p>
101   *
102   * <p>After any successful invocation of this method, an invocation of the {@link #closed()} method will forever after
103   * return {@code true}.</p>
104   *
105   * @idempotency This method is, and its overrides must be, idempotent.
106   *
107   * @threadsafety This method is, and its overrides must be, safe for concurrent use by multiple threads.
108   *
109   * @see #closed()
110   */
111  @Override // AutoCloseableRegistry (AutoCloseable)
112  public void close() {
113    final Set<? extends AutoCloseable> closeables;
114    lock.lock();
115    try {
116      closeables = this.closeables;
117      if (closeables == Set.<AutoCloseable>of()) {
118        // Already closed
119        return;
120      }
121      this.closeables = Set.of();
122    } finally {
123      lock.unlock();
124    }
125    if (closeables == null) {
126      // nothing to close
127      return;
128    }
129    RuntimeException re = null;
130    for (final AutoCloseable c : closeables) {
131      try {
132        c.close();
133      } catch (final RuntimeException e) {
134        if (re == null) {
135          re = e;
136        } else {
137          re.addSuppressed(e);
138        }
139      } catch (final InterruptedException e) {
140        Thread.currentThread().interrupt();
141        if (re == null) {
142          re = new BeanException(e.getMessage(), e);
143        } else {
144          re.addSuppressed(e);
145        }
146      } catch (final Exception e) {
147        if (re == null) {
148          re = new BeanException(e.getMessage(), e);
149        } else {
150          re.addSuppressed(e);
151        }
152      }
153    }
154    if (re != null) {
155      throw re;
156    }
157  }
158
159  /**
160   * Returns {@code true} if and only if this {@link DefaultAutoCloseableRegistry} is {@linkplain #close() closed}.
161   *
162   * <p>Once an invocation of this method has returned {@code true}, on any thread, subsequent invocations must also
163   * return {@code true}, on any thread.</p>
164   *
165   * <p>This method will return {@code false} until an invocation of the {@link #close()} method has successfully
166   * completed, and will return {@code true} thereafter.</p>
167   *
168   * @return {@code true} if and only if this {@link DefaultAutoCloseableRegistry} is {@linkplain #close() closed}
169   *
170   * @idempotency This method is idempotent.
171   *
172   * @threadsafety This method is safe for concurrent use by multiple threads.
173   *
174   * @see #close()
175   */
176  @Override // AutoCloseableRegistry
177  public final boolean closed() {
178    lock.lock();
179    try {
180      return this.closeables == Set.<AutoCloseable>of();
181    } finally {
182      lock.unlock();
183    }
184  }
185
186  /**
187   * If this {@link DefaultAutoCloseableRegistry} is not {@linkplain #closed() closed}, and if the supplied {@link
188   * AutoCloseable} has not yet been registered, registers it such that it will be {@linkplain AutoCloseable#close()
189   * closed} when this {@link DefaultAutoCloseableRegistry} is {@linkplain #close() closed}, and returns {@code true}.
190   *
191   * <p>This method takes no action and returns {@code false} in all other cases.</p>
192   *
193   * @param closeable the {@link AutoCloseable} to register; must not be {@code null}
194   *
195   * @return {@code true} if and only if this {@link DefaultAutoCloseableRegistry} is not {@linkplain #closed() closed}
196   * and the supplied {@link AutoCloseable} is not already registered and registration completed successfully; {@code
197   * false} in all other cases
198   *
199   * @exception NullPointerException if {@code closeable} is {@code null}
200   *
201   * @idempotency This method is idempotent.
202   *
203   * @threadsafety This method is safe for concurrent use by multiple threads.
204   */
205  @Override // AutoCloseableRegistry
206  public final boolean register(final AutoCloseable closeable) {
207    if (closeable == null || closeable == this) {
208      return false;
209    }
210    lock.lock();
211    try {
212      if (this.closeables == Set.<AutoCloseable>of()) {
213        // Already closed
214        return false;
215      } else if (this.closeables == null) {
216        this.closeables = new LinkedHashSet<>();
217      }
218      return this.closeables.add(closeable);
219    } finally {
220      lock.unlock();
221    }
222  }
223
224}