1 /*
2  * Copyright 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.arch.core.internal;
18 
19 import androidx.annotation.RestrictTo;
20 
21 import org.jspecify.annotations.NonNull;
22 import org.jspecify.annotations.Nullable;
23 
24 import java.util.Iterator;
25 import java.util.Map;
26 import java.util.WeakHashMap;
27 
28 /**
29  * LinkedList, which pretends to be a map and supports modifications during iterations.
30  * It is NOT thread safe.
31  *
32  * @param <K> Key type
33  * @param <V> Value type
34  */
35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
36 public class SafeIterableMap<K, V> implements Iterable<Map.Entry<K, V>> {
37 
38     @SuppressWarnings("WeakerAccess") /* synthetic access */
39     Entry<K, V> mStart;
40     private Entry<K, V> mEnd;
41     // using WeakHashMap over List<WeakReference>, so we don't have to manually remove
42     // WeakReferences that have null in them.
43     private final WeakHashMap<SupportRemove<K, V>, Boolean> mIterators = new WeakHashMap<>();
44     private int mSize = 0;
45 
46     @SuppressWarnings("HiddenTypeParameter")
get(K k)47     protected @Nullable Entry<K, V> get(K k) {
48         Entry<K, V> currentNode = mStart;
49         while (currentNode != null) {
50             if (currentNode.mKey.equals(k)) {
51                 break;
52             }
53             currentNode = currentNode.mNext;
54         }
55         return currentNode;
56     }
57 
58     /**
59      * If the specified key is not already associated
60      * with a value, associates it with the given value.
61      *
62      * @param key key with which the specified value is to be associated
63      * @param v   value to be associated with the specified key
64      * @return the previous value associated with the specified key,
65      * or {@code null} if there was no mapping for the key
66      */
putIfAbsent(@onNull K key, @NonNull V v)67     public V putIfAbsent(@NonNull K key, @NonNull V v) {
68         Entry<K, V> entry = get(key);
69         if (entry != null) {
70             return entry.mValue;
71         }
72         put(key, v);
73         return null;
74     }
75 
put(@onNull K key, @NonNull V v)76     Entry<K, V> put(@NonNull K key, @NonNull V v) {
77         Entry<K, V> newEntry = new Entry<>(key, v);
78         mSize++;
79         if (mEnd == null) {
80             mStart = newEntry;
81             mEnd = mStart;
82             return newEntry;
83         }
84 
85         mEnd.mNext = newEntry;
86         newEntry.mPrevious = mEnd;
87         mEnd = newEntry;
88         return newEntry;
89 
90     }
91 
92     /**
93      * Removes the mapping for a key from this map if it is present.
94      *
95      * @param key key whose mapping is to be removed from the map
96      * @return the previous value associated with the specified key,
97      * or {@code null} if there was no mapping for the key
98      */
remove(@onNull K key)99     public V remove(@NonNull K key) {
100         Entry<K, V> toRemove = get(key);
101         if (toRemove == null) {
102             return null;
103         }
104         mSize--;
105         if (!mIterators.isEmpty()) {
106             for (SupportRemove<K, V> iter : mIterators.keySet()) {
107                 iter.supportRemove(toRemove);
108             }
109         }
110 
111         if (toRemove.mPrevious != null) {
112             toRemove.mPrevious.mNext = toRemove.mNext;
113         } else {
114             mStart = toRemove.mNext;
115         }
116 
117         if (toRemove.mNext != null) {
118             toRemove.mNext.mPrevious = toRemove.mPrevious;
119         } else {
120             mEnd = toRemove.mPrevious;
121         }
122 
123         toRemove.mNext = null;
124         toRemove.mPrevious = null;
125         return toRemove.mValue;
126     }
127 
128     /**
129      * @return the number of elements in this map
130      */
size()131     public int size() {
132         return mSize;
133     }
134 
135     /**
136      * @return an ascending iterator, which doesn't include new elements added during an
137      * iteration.
138      */
139     @Override
iterator()140     public @NonNull Iterator<Map.Entry<K, V>> iterator() {
141         ListIterator<K, V> iterator = new AscendingIterator<>(mStart, mEnd);
142         mIterators.put(iterator, false);
143         return iterator;
144     }
145 
146     /**
147      * @return an descending iterator, which doesn't include new elements added during an
148      * iteration.
149      */
descendingIterator()150     public @NonNull Iterator<Map.Entry<K, V>> descendingIterator() {
151         DescendingIterator<K, V> iterator = new DescendingIterator<>(mEnd, mStart);
152         mIterators.put(iterator, false);
153         return iterator;
154     }
155 
156     /**
157      * return an iterator with additions.
158      */
iteratorWithAdditions()159     public @NonNull IteratorWithAdditions iteratorWithAdditions() {
160         IteratorWithAdditions iterator = new IteratorWithAdditions();
161         mIterators.put(iterator, false);
162         return iterator;
163     }
164 
165     /**
166      * @return eldest added entry or null
167      */
eldest()168     public Map.@Nullable Entry<K, V> eldest() {
169         return mStart;
170     }
171 
172     /**
173      * @return newest added entry or null
174      */
newest()175     public Map.@Nullable Entry<K, V> newest() {
176         return mEnd;
177     }
178 
179     @SuppressWarnings("rawtypes")
180     @Override
equals(Object obj)181     public boolean equals(Object obj) {
182         if (obj == this) {
183             return true;
184         }
185         if (!(obj instanceof SafeIterableMap)) {
186             return false;
187         }
188         SafeIterableMap map = (SafeIterableMap) obj;
189         if (this.size() != map.size()) {
190             return false;
191         }
192         Iterator<Map.Entry<K, V>> iterator1 = iterator();
193         Iterator iterator2 = map.iterator();
194         while (iterator1.hasNext() && iterator2.hasNext()) {
195             Map.Entry<K, V> next1 = iterator1.next();
196             Object next2 = iterator2.next();
197             if ((next1 == null && next2 != null)
198                     || (next1 != null && !next1.equals(next2))) {
199                 return false;
200             }
201         }
202         return !iterator1.hasNext() && !iterator2.hasNext();
203     }
204 
205     @Override
hashCode()206     public int hashCode() {
207         int h = 0;
208         for (Map.Entry<K, V> kvEntry : this) {
209             h += kvEntry.hashCode();
210         }
211         return h;
212     }
213 
214     @Override
toString()215     public String toString() {
216         StringBuilder builder = new StringBuilder();
217         builder.append("[");
218         Iterator<Map.Entry<K, V>> iterator = iterator();
219         while (iterator.hasNext()) {
220             builder.append(iterator.next().toString());
221             if (iterator.hasNext()) {
222                 builder.append(", ");
223             }
224         }
225         builder.append("]");
226         return builder.toString();
227     }
228 
229     private abstract static class ListIterator<K, V> extends SupportRemove<K, V>
230             implements Iterator<Map.Entry<K, V>> {
231         Entry<K, V> mExpectedEnd;
232         Entry<K, V> mNext;
233 
ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd)234         ListIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
235             this.mExpectedEnd = expectedEnd;
236             this.mNext = start;
237         }
238 
239         @Override
hasNext()240         public boolean hasNext() {
241             return mNext != null;
242         }
243 
244         @SuppressWarnings("ReferenceEquality")
245         @Override
supportRemove(@onNull Entry<K, V> entry)246         public void supportRemove(@NonNull Entry<K, V> entry) {
247             if (mExpectedEnd == entry && entry == mNext) {
248                 mNext = null;
249                 mExpectedEnd = null;
250             }
251 
252             if (mExpectedEnd == entry) {
253                 mExpectedEnd = backward(mExpectedEnd);
254             }
255 
256             if (mNext == entry) {
257                 mNext = nextNode();
258             }
259         }
260 
261         @SuppressWarnings("ReferenceEquality")
nextNode()262         private Entry<K, V> nextNode() {
263             if (mNext == mExpectedEnd || mExpectedEnd == null) {
264                 return null;
265             }
266             return forward(mNext);
267         }
268 
269         @Override
next()270         public Map.Entry<K, V> next() {
271             Map.Entry<K, V> result = mNext;
272             mNext = nextNode();
273             return result;
274         }
275 
forward(Entry<K, V> entry)276         abstract Entry<K, V> forward(Entry<K, V> entry);
277 
backward(Entry<K, V> entry)278         abstract Entry<K, V> backward(Entry<K, V> entry);
279     }
280 
281     static class AscendingIterator<K, V> extends ListIterator<K, V> {
AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd)282         AscendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
283             super(start, expectedEnd);
284         }
285 
286         @Override
forward(Entry<K, V> entry)287         Entry<K, V> forward(Entry<K, V> entry) {
288             return entry.mNext;
289         }
290 
291         @Override
backward(Entry<K, V> entry)292         Entry<K, V> backward(Entry<K, V> entry) {
293             return entry.mPrevious;
294         }
295     }
296 
297     private static class DescendingIterator<K, V> extends ListIterator<K, V> {
298 
DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd)299         DescendingIterator(Entry<K, V> start, Entry<K, V> expectedEnd) {
300             super(start, expectedEnd);
301         }
302 
303         @Override
forward(Entry<K, V> entry)304         Entry<K, V> forward(Entry<K, V> entry) {
305             return entry.mPrevious;
306         }
307 
308         @Override
backward(Entry<K, V> entry)309         Entry<K, V> backward(Entry<K, V> entry) {
310             return entry.mNext;
311         }
312     }
313 
314     /**
315      */
316     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
317     public class IteratorWithAdditions extends SupportRemove<K, V>
318             implements Iterator<Map.Entry<K, V>> {
319         private Entry<K, V> mCurrent;
320         private boolean mBeforeStart = true;
321 
IteratorWithAdditions()322         IteratorWithAdditions() {
323         }
324 
325         @SuppressWarnings("ReferenceEquality")
326         @Override
supportRemove(@onNull Entry<K, V> entry)327         void supportRemove(@NonNull Entry<K, V> entry) {
328             if (entry == mCurrent) {
329                 mCurrent = mCurrent.mPrevious;
330                 mBeforeStart = mCurrent == null;
331             }
332         }
333 
334         @Override
hasNext()335         public boolean hasNext() {
336             if (mBeforeStart) {
337                 return mStart != null;
338             }
339             return mCurrent != null && mCurrent.mNext != null;
340         }
341 
342         @Override
next()343         public Map.Entry<K, V> next() {
344             if (mBeforeStart) {
345                 mBeforeStart = false;
346                 mCurrent = mStart;
347             } else {
348                 mCurrent = mCurrent != null ? mCurrent.mNext : null;
349             }
350             return mCurrent;
351         }
352     }
353 
354     /**
355      *
356      * @param <K>
357      * @param <V>
358      */
359     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
360     public abstract static class SupportRemove<K, V> {
361         @SuppressWarnings("HiddenAbstractMethod")
supportRemove(@onNull Entry<K, V> entry)362         abstract void supportRemove(@NonNull Entry<K, V> entry);
363     }
364 
365     static class Entry<K, V> implements Map.Entry<K, V> {
366         final @NonNull K mKey;
367         final @NonNull V mValue;
368         Entry<K, V> mNext;
369         Entry<K, V> mPrevious;
370 
Entry(@onNull K key, @NonNull V value)371         Entry(@NonNull K key, @NonNull V value) {
372             mKey = key;
373             this.mValue = value;
374         }
375 
376         @Override
getKey()377         public @NonNull K getKey() {
378             return mKey;
379         }
380 
381         @Override
getValue()382         public @NonNull V getValue() {
383             return mValue;
384         }
385 
386         @Override
setValue(V value)387         public V setValue(V value) {
388             throw new UnsupportedOperationException("An entry modification is not supported");
389         }
390 
391         @Override
toString()392         public String toString() {
393             return mKey + "=" + mValue;
394         }
395 
396         @SuppressWarnings({"ReferenceEquality", "rawtypes"})
397         @Override
equals(Object obj)398         public boolean equals(Object obj) {
399             if (obj == this) {
400                 return true;
401             }
402             if (!(obj instanceof Entry)) {
403                 return false;
404             }
405             Entry entry = (Entry) obj;
406             return mKey.equals(entry.mKey) && mValue.equals(entry.mValue);
407         }
408 
409         @Override
hashCode()410         public int hashCode() {
411             return mKey.hashCode() ^ mValue.hashCode();
412         }
413     }
414 }
415