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}