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