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.util.Collection;
21  import java.util.Collections;
22  import java.util.ConcurrentModificationException;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.NoSuchElementException;
26  import java.util.Objects;
27  import java.util.Set;
28  
29  import org.eclipse.jdt.annotation.NonNull;
30  
31  /**
32   * A {@link Set} that is a view on the keys of a {@link Map} that have a certain value.
33   * <p>
34   * The value is controlled by the concrete subclass ({@link #getValue()}). It can, for example, be a
35   * boolean or a counter, depending on the use case. If the value is equal to a "removed" sentinel
36   * value.
37   *
38   * @param <T> The element type.
39   * @author Christian Kohlschütter
40   */
41  final class MapValueSet<T, V> implements Set<T> {
42    private final Map<T, V> map;
43    private final ValueSupplier<@NonNull V> valueSupplier;
44    private final V removedSentinel;
45  
46    @SuppressWarnings("unchecked")
47    MapValueSet(Map<? extends T, V> map, ValueSupplier<@NonNull V> valueSupplier, V removedSentinel) {
48      this.valueSupplier = Objects.requireNonNull(valueSupplier);
49      this.removedSentinel = removedSentinel;
50      this.map = (Map<T, V>) map;
51    }
52  
53    @FunctionalInterface
54    interface ValueSupplier<V> {
55      V supplyValue();
56    }
57  
58    /**
59     * Marks the given element as "removed"; this may actually add an element to the underlying map.
60     * <p>
61     * Depending on the "removed" sentinel, the key may be added (if value is non-null but the map
62     * does not yet contain the key), modified (value is non-null, and the map has a different value
63     * for the key), or removed (if value is null).
64     *
65     * @param elem The element to remove.
66     */
67    public void markRemoved(T elem) {
68      if (removedSentinel == null) {
69        map.remove(elem);
70      } else {
71        map.put(elem, removedSentinel);
72      }
73    }
74  
75    /**
76     * Sets all entries in the backing map to the "removed" sentinel, or removes them all if that
77     * value is {@code null}.
78     */
79    public void markAllRemoved() {
80      if (removedSentinel == null) {
81        map.clear();
82      } else {
83        for (Map.Entry<T, V> en : map.entrySet()) {
84          en.setValue(removedSentinel);
85        }
86      }
87    }
88  
89    private @NonNull V getValue() {
90      return Objects.requireNonNull(valueSupplier.supplyValue());
91    }
92  
93    @Override
94    public int size() {
95      V val = getValue();
96      if (val.equals(removedSentinel)) {
97        return 0;
98      }
99  
100     int size = 0;
101     for (Map.Entry<T, V> en : map.entrySet()) {
102       if (val.equals(en.getValue())) {
103         size++;
104       }
105     }
106     return size;
107   }
108 
109   @Override
110   public boolean isEmpty() {
111     V val = getValue();
112     if (val.equals(removedSentinel)) {
113       return true;
114     }
115 
116     for (Map.Entry<T, V> en : map.entrySet()) {
117       if (val.equals(en.getValue())) {
118         return false;
119       }
120     }
121     return true;
122   }
123 
124   private boolean isDefinitelyEmpty() {
125     return getValue().equals(removedSentinel);
126   }
127 
128   @Override
129   public boolean contains(Object o) {
130     if (isDefinitelyEmpty()) {
131       return false;
132     }
133     return getValue().equals(map.get(o));
134   }
135 
136   @Override
137   public Iterator<T> iterator() {
138     if (isDefinitelyEmpty()) {
139       return Collections.emptyIterator();
140     }
141 
142     Iterator<Map.Entry<T, V>> mapit = map.entrySet().iterator();
143 
144     V val = getValue();
145 
146     return new Iterator<T>() {
147       Map.Entry<T, V> nextObj = null;
148       Map.Entry<T, V> currentObj = null;
149 
150       @Override
151       public boolean hasNext() {
152         if (nextObj != null) {
153           return true;
154         }
155         while (mapit.hasNext()) {
156           Map.Entry<T, V> en = mapit.next();
157           if (val.equals(en.getValue())) {
158             nextObj = en;
159             return true;
160           }
161         }
162         return false;
163       }
164 
165       @Override
166       public T next() {
167         currentObj = null;
168         if (nextObj == null) {
169           if (!hasNext()) {
170             throw new NoSuchElementException();
171           }
172         }
173         T next = nextObj.getKey();
174         if (val.equals(nextObj.getValue())) {
175           currentObj = nextObj;
176           nextObj = null;
177           return next;
178         } else {
179           throw new ConcurrentModificationException();
180         }
181       }
182 
183       @Override
184       public void remove() {
185         if (currentObj == null) {
186           throw new IllegalStateException();
187         }
188         markRemoved(currentObj.getKey());
189         currentObj = null;
190       }
191     };
192   }
193 
194   @Override
195   @SuppressWarnings("PMD.OptimizableToArrayCall")
196   public Object[] toArray() {
197     return toArray(new Object[size()]);
198   }
199 
200   @SuppressWarnings({"unchecked", "null"})
201   @Override
202   public <E> E[] toArray(E[] a) {
203     int size = size();
204 
205     if (a.length < size) {
206       return toArray((E[]) java.lang.reflect.Array.newInstance(a.getClass().getComponentType(),
207           size));
208     }
209 
210     int i = 0;
211     for (T elem : this) {
212       a[i++] = (E) elem;
213     }
214     if (i < a.length) {
215       a[i] = null;
216     }
217 
218     return a;
219   }
220 
221   /**
222    * Updates an already-existing entry in the backing map to the current value (obtained via
223    * {@link #getValue()}), thereby adding it to the set.
224    *
225    * @param e The entry to update.
226    */
227   public boolean update(T e) {
228     if (map.containsKey(e)) {
229       map.put(e, getValue());
230       return true;
231     } else {
232       return false;
233     }
234   }
235 
236   /**
237    * Adds an entry to the set, adding it to the backing map if necessary.
238    */
239   @Override
240   public boolean add(T e) {
241     if (contains(e)) {
242       return false;
243     } else if (update(e)) {
244       return true;
245     } else {
246       map.put(e, getValue());
247       return true;
248     }
249   }
250 
251   @SuppressWarnings("unchecked")
252   @Override
253   public boolean remove(Object o) {
254     if (isDefinitelyEmpty() || !map.containsKey(o)) {
255       return false;
256     }
257 
258     markRemoved((T) o);
259     return true;
260   }
261 
262   @Override
263   public boolean containsAll(Collection<?> c) {
264     if (isDefinitelyEmpty()) {
265       return c.isEmpty();
266     }
267     for (Object obj : c) {
268       if (!contains(obj)) {
269         return false;
270       }
271     }
272     return true;
273   }
274 
275   @Override
276   public boolean addAll(Collection<? extends T> c) {
277     boolean changed = false;
278     for (T elem : c) {
279       changed |= add(elem);
280     }
281     return changed;
282   }
283 
284   @Override
285   public boolean retainAll(Collection<?> c) {
286     boolean changed = false;
287     for (Iterator<T> it = iterator(); it.hasNext();) {
288       T elem = it.next();
289       if (!c.contains(elem)) {
290         it.remove();
291         changed = true;
292       }
293     }
294     return changed;
295   }
296 
297   @Override
298   public boolean removeAll(Collection<?> c) {
299     if (isDefinitelyEmpty()) {
300       return false;
301     }
302     boolean changed = false;
303     for (Object obj : c) {
304       changed |= remove(obj);
305     }
306     return changed;
307   }
308 
309   /**
310    * Marks all entries in the backing map that are currently considered contained in this set as
311    * removed; see {@link #markAllRemoved()} for an unoptimized version that affects all keys.
312    *
313    * @see #markAllRemoved()
314    */
315   @Override
316   public void clear() {
317     V val = getValue();
318     if (val.equals(removedSentinel)) {
319       return;
320     }
321 
322     for (Map.Entry<T, V> en : map.entrySet()) {
323       if (val.equals(en.getValue())) {
324         markRemoved(en.getKey());
325       }
326     }
327   }
328 }