View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.newsclub.net.unix;
19  
20  import java.io.Closeable;
21  import java.io.IOException;
22  import java.lang.ref.WeakReference;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  /**
28   * A set of {@link Closeables} that can be closed at once.
29   *
30   * @author Christian Kohlschütter
31   */
32  public final class Closeables implements Closeable {
33    private boolean closed = false;
34    private List<WeakReference<Closeable>> list;
35  
36    /**
37     * Creates a new {@link Closeables} instance.
38     */
39    public Closeables() {
40    }
41  
42    /**
43     * Creates a new {@link Closeables} instance, populating it with the given {@link Closeable}
44     * objects.
45     *
46     * @param closeable The {@link Closeable}s to add.
47     */
48    public Closeables(Closeable... closeable) {
49      this.list = new ArrayList<>();
50      for (Closeable cl : closeable) {
51        this.list.add(new HardReference<>(cl));
52      }
53    }
54  
55    @Override
56    public void close() throws IOException {
57      close(null);
58    }
59  
60    /**
61     * Checks if this instance has been closed already.
62     *
63     * @return {@code true} if closed.
64     */
65    public synchronized boolean isClosed() {
66      return closed;
67    }
68  
69    /**
70     * Closes all registered closeables.
71     *
72     * @param superException If set, any exceptions thrown in here will be chained to the given
73     *          exception via addSuppressed, and then thrown.
74     * @throws IOException if an exception occurs.
75     */
76    public void close(IOException superException) throws IOException {
77      IOException exc = superException;
78  
79      List<WeakReference<Closeable>> l;
80      synchronized (this) {
81        closed = true;
82  
83        l = this.list;
84        if (l == null) {
85          return;
86        }
87  
88        l = new ArrayList<>(l);
89        this.list = null;
90      }
91  
92      for (WeakReference<Closeable> ref : l) {
93        @SuppressWarnings("resource")
94        Closeable cl = ref.get();
95        if (cl == null) {
96          continue;
97        }
98        try {
99          cl.close();
100       } catch (IOException e) {
101         if (exc == null) {
102           exc = e;
103         } else {
104           exc.addSuppressed(e);
105         }
106       }
107     }
108 
109     if (exc != null) {
110       throw exc;
111     }
112   }
113 
114   private static final class HardReference<V> extends WeakReference<V> {
115     private final V strongRef;
116 
117     @SuppressWarnings("null")
118     HardReference(final V referent) {
119       super(null);
120       this.strongRef = referent;
121     }
122 
123     @Override
124     public V get() {
125       return strongRef;
126     }
127   }
128 
129   /**
130    * Adds the given closeable, but only using a weak reference.
131    *
132    * @param closeable The closeable.
133    * @return {@code true} iff the closeable was added, {@code false} if it was {@code null}, already
134    *         added before, or if the {@link Closeables} instance has been closed already.
135    */
136   public synchronized boolean add(WeakReference<Closeable> closeable) {
137     if (closed) {
138       return false;
139     }
140     Closeable cl = closeable.get();
141     if (cl == null) {
142       // ignore
143       return false;
144     }
145     if (list == null) {
146       list = new ArrayList<>();
147     } else {
148       for (WeakReference<Closeable> ref : list) {
149         if (cl.equals(ref.get())) {
150           return false;
151         }
152       }
153     }
154     list.add(closeable);
155 
156     return true;
157   }
158 
159   /**
160    * Adds the given closeable.
161    *
162    * @param closeable The closeable.
163    * @return {@code true} iff the closeable was added, {@code false} if it was {@code null} or
164    *         already added before.
165    */
166   public synchronized boolean add(Closeable closeable) {
167     return add(new HardReference<>(closeable));
168   }
169 
170   /**
171    * Removes the given closeable.
172    *
173    * @param closeable The closeable.
174    * @return {@code true} iff the closeable was removed, {@code fale} if it was {@code null} or not
175    *         previously added.
176    */
177   public synchronized boolean remove(Closeable closeable) {
178     if (list == null || closeable == null || closed) {
179       return false;
180     }
181     for (Iterator<WeakReference<Closeable>> it = list.iterator(); it.hasNext();) {
182       if (closeable.equals(it.next().get())) {
183         it.remove();
184         return true;
185       }
186     }
187     return false;
188   }
189 }