| /* |
| * Copyright 2014 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| * use this file except in compliance with the License. You may obtain a copy of |
| * the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package java.util; |
| |
| import static java.util.ConcurrentModificationDetector.structureChanged; |
| |
| import java.util.Map.Entry; |
| |
| import javaemul.internal.JsUtils; |
| |
| /** |
| * A simple wrapper around JavaScript Map for key type is string. |
| */ |
| class InternalStringMap<K, V> implements Iterable<Entry<K, V>> { |
| |
| private final InternalJsMap<V> backingMap = InternalJsMapFactory.newJsMap(); |
| private AbstractHashMap<K, V> host; |
| private int size; |
| |
| /** |
| * A mod count to track 'value' replacements in map to ensure that the 'value' that we have in the |
| * iterator entry is guaranteed to be still correct. |
| * This is to optimize for the common scenario where the values are not modified during |
| * iterations where the entries are never stale. |
| */ |
| private int valueMod; |
| |
| public InternalStringMap(AbstractHashMap<K, V> host) { |
| this.host = host; |
| } |
| |
| public boolean contains(String key) { |
| return !JsUtils.isUndefined(backingMap.get(key)); |
| } |
| |
| public V get(String key) { |
| return backingMap.get(key); |
| } |
| |
| public V put(String key, V value) { |
| V oldValue = backingMap.get(key); |
| backingMap.set(key, toNullIfUndefined(value)); |
| |
| if (JsUtils.isUndefined(oldValue)) { |
| size++; |
| structureChanged(host); |
| } else { |
| valueMod++; |
| } |
| return oldValue; |
| } |
| |
| public V remove(String key) { |
| V value = backingMap.get(key); |
| if (!JsUtils.isUndefined(value)) { |
| backingMap.delete(key); |
| size--; |
| structureChanged(host); |
| } else { |
| valueMod++; |
| } |
| |
| return value; |
| } |
| |
| public int size() { |
| return size; |
| } |
| |
| @Override |
| public Iterator<Entry<K, V>> iterator() { |
| return new Iterator<Map.Entry<K, V>>() { |
| InternalJsMap.Iterator<V> entries = backingMap.entries(); |
| InternalJsMap.IteratorEntry<V> current = entries.next(); |
| InternalJsMap.IteratorEntry<V> last; |
| |
| @Override |
| public boolean hasNext() { |
| return !current.isDone(); |
| } |
| |
| @Override |
| public Entry<K, V> next() { |
| last = current; |
| current = entries.next(); |
| return newMapEntry(last, valueMod); |
| } |
| |
| @Override |
| public void remove() { |
| InternalStringMap.this.remove(last.getKey()); |
| } |
| }; |
| } |
| |
| private Entry<K, V> newMapEntry( |
| final InternalJsMap.IteratorEntry<V> entry, final int lastValueMod) { |
| return new AbstractMapEntry<K, V>() { |
| @SuppressWarnings("unchecked") |
| @Override |
| public K getKey() { |
| return (K) entry.getKey(); |
| } |
| |
| @Override |
| public V getValue() { |
| if (valueMod != lastValueMod) { |
| // Let's get a fresh copy as the value may have changed. |
| return get(entry.getKey()); |
| } |
| return entry.getValue(); |
| } |
| |
| @Override |
| public V setValue(V object) { |
| return put(entry.getKey(), object); |
| } |
| }; |
| } |
| |
| private static <T> T toNullIfUndefined(T value) { |
| return JsUtils.isUndefined(value) ? null : value; |
| } |
| } |