Closeables.java
/*
* junixsocket
*
* Copyright 2009-2024 Christian Kohlschütter
*
* Licensed 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
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.newsclub.net.unix;
import java.io.Closeable;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* A set of {@link Closeables} that can be closed at once.
*
* @author Christian Kohlschütter
*/
public final class Closeables implements Closeable {
private boolean closed = false;
private List<WeakReference<Closeable>> list;
/**
* Creates a new {@link Closeables} instance.
*/
public Closeables() {
}
/**
* Creates a new {@link Closeables} instance, populating it with the given {@link Closeable}
* objects.
*
* @param closeable The {@link Closeable}s to add.
*/
public Closeables(Closeable... closeable) {
this.list = new ArrayList<>();
for (Closeable cl : closeable) {
this.list.add(new HardReference<>(cl));
}
}
@Override
public void close() throws IOException {
close(null);
}
/**
* Checks if this instance has been closed already.
*
* @return {@code true} if closed.
*/
public synchronized boolean isClosed() {
return closed;
}
/**
* Closes all registered closeables.
*
* @param superException If set, any exceptions thrown in here will be chained to the given
* exception via addSuppressed, and then thrown.
* @throws IOException if an exception occurs.
*/
public void close(IOException superException) throws IOException {
IOException exc = superException;
List<WeakReference<Closeable>> l;
synchronized (this) {
closed = true;
l = this.list;
if (l == null) {
return;
}
l = new ArrayList<>(l);
this.list = null;
}
for (WeakReference<Closeable> ref : l) {
@SuppressWarnings("resource")
Closeable cl = ref.get();
if (cl == null) {
continue;
}
try {
cl.close();
} catch (IOException e) {
if (exc == null) {
exc = e;
} else {
exc.addSuppressed(e);
}
}
}
if (exc != null) {
throw exc;
}
}
private static final class HardReference<V> extends WeakReference<V> {
private final V strongRef;
@SuppressWarnings("null")
HardReference(final V referent) {
super(null);
this.strongRef = referent;
}
@Override
public V get() {
return strongRef;
}
}
/**
* Adds the given closeable, but only using a weak reference.
*
* @param closeable The closeable.
* @return {@code true} iff the closeable was added, {@code false} if it was {@code null}, already
* added before, or if the {@link Closeables} instance has been closed already.
*/
public synchronized boolean add(WeakReference<Closeable> closeable) {
if (closed) {
return false;
}
Closeable cl = closeable.get();
if (cl == null) {
// ignore
return false;
}
if (list == null) {
list = new ArrayList<>();
} else {
for (WeakReference<Closeable> ref : list) {
if (cl.equals(ref.get())) {
return false;
}
}
}
list.add(closeable);
return true;
}
/**
* Adds the given closeable.
*
* @param closeable The closeable.
* @return {@code true} iff the closeable was added, {@code false} if it was {@code null} or
* already added before.
*/
public synchronized boolean add(Closeable closeable) {
return add(new HardReference<>(closeable));
}
/**
* Removes the given closeable.
*
* @param closeable The closeable.
* @return {@code true} iff the closeable was removed, {@code fale} if it was {@code null} or not
* previously added.
*/
public synchronized boolean remove(Closeable closeable) {
if (list == null || closeable == null || closed) {
return false;
}
for (Iterator<WeakReference<Closeable>> it = list.iterator(); it.hasNext();) {
if (closeable.equals(it.next().get())) {
it.remove();
return true;
}
}
return false;
}
}