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