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