From e342593d2ed67a9aa6b2c51000c26591c58acf13 Mon Sep 17 00:00:00 2001 From: Ilya Gorbunov Date: Fri, 26 Aug 2016 21:51:37 +0300 Subject: [PATCH] Flatten HashMap hierarhy, provide string-keyed map and set specializations. --- .../src/core/collections/AbstractHashMap.kt | 264 ------------------ .../core/collections/EqualityComparator.kt | 38 +++ .../src/core/collections/HashMap.kt | 258 ++++++++++++++++- .../src/core/collections/HashSet.kt | 6 +- .../core/collections/InternalHashCodeMap.kt | 31 +- .../src/core/collections/InternalMap.kt | 27 ++ .../src/core/collections/InternalStringMap.kt | 127 ++++----- libraries/stdlib/test/js/MapJsTest.kt | 2 +- libraries/stdlib/test/js/SetJsTest.kt | 2 +- libraries/stdlib/test/js/StubsJVM.kt | 22 ++ libraries/tools/kotlin-js-tests/pom.xml | 1 + 11 files changed, 426 insertions(+), 352 deletions(-) delete mode 100644 js/js.libraries/src/core/collections/AbstractHashMap.kt create mode 100644 js/js.libraries/src/core/collections/EqualityComparator.kt create mode 100644 js/js.libraries/src/core/collections/InternalMap.kt create mode 100644 libraries/stdlib/test/js/StubsJVM.kt diff --git a/js/js.libraries/src/core/collections/AbstractHashMap.kt b/js/js.libraries/src/core/collections/AbstractHashMap.kt deleted file mode 100644 index 2856f8410a7..00000000000 --- a/js/js.libraries/src/core/collections/AbstractHashMap.kt +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2010-2016 JetBrains s.r.o. - * - * 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. - */ -/* - * Based on GWT AbstractHashMap - * Copyright 2008 Google Inc. - */ - -package kotlin.collections - -import kotlin.collections.Map.Entry -import kotlin.collections.MutableMap.MutableEntry - -abstract class AbstractHashMap : AbstractMap { - - private inner class EntrySet : AbstractSet>() { - - override fun clear() { - this@AbstractHashMap.clear() - } - - override operator fun contains(element: MutableEntry): Boolean = containsEntry(element) - - override operator fun iterator(): MutableIterator> = hashCodeMap.iterator() - - override fun remove(entry: MutableEntry): Boolean { - if (contains(entry)) { - this@AbstractHashMap.remove(entry.key) - return true - } - return false - } - - override val size: Int get() = this@AbstractHashMap.size - } - - /* - private inner class EntrySetIterator : Iterator> { - private val stringMapEntries = stringMap!!.iterator() - private var current: MutableIterator> = stringMapEntries - private var last: MutableIterator>? = null - private var hasNext = computeHasNext() - - init { - recordLastKnownStructure(this@AbstractHashMap, this) - } - - override fun hasNext(): Boolean { - return hasNext - } - - private fun computeHasNext(): Boolean { - if (current.hasNext()) { - return true - } - if (current !== stringMapEntries) { - return false - } - current = hashCodeMap!!.iterator() - return current.hasNext() - } - - override fun next(): Entry { - checkStructuralChange(this@AbstractHashMap, this) - checkElement(hasNext()) - - last = current - val rv = current.next() - hasNext = computeHasNext() - - return rv - } - - override fun remove() { - checkState(last != null) - checkStructuralChange(this@AbstractHashMap, this) - - last!!.remove() - last = null - hasNext = computeHasNext() - - recordLastKnownStructure(this@AbstractHashMap, this) - } - }*/ - - /** - * A map of integral hashCodes onto entries. - */ - private var hashCodeMap = InternalHashCodeMap(this) - -// /** -// * A map of Strings onto values. -// */ -// @Transient private var stringMap: InternalStringMap? = null - - constructor() : super() -// init { -// reset() -// } - - constructor(capacity: Int, loadFactor: Float = 0f) : this() { - // This implementation of HashMap has no need of load factors or capacities. - require(capacity >= 0) { "Negative initial capacity" } - require(loadFactor >= 0) { "Non-positive load factor" } - } - - constructor(original: Map) : this() { - this.putAll(original) - } - - override fun clear() { -// reset() -// } -// -// private fun reset() { - hashCodeMap = InternalHashCodeMap(this) -// stringMap = InternalStringMap(this) -// structureChanged(this) - } - -// @SpecializeMethod(params = { String.class }, target = "hasStringValue") - override fun containsKey(key: K): Boolean { - return hasHashValue(key) -// return if (key is String) -// hasStringValue(JsUtils.unsafeCastToString(key)) -// else -// hasHashValue(key) - } - - override fun containsValue(value: V): Boolean { - return /*containsValue(value, stringMap) || */ containsValue(value, hashCodeMap) - } - - private fun containsValue(value: V, entries: Iterable>): Boolean = entries.any { equals(it.value, value) } - - override val entries: MutableSet> - get() = EntrySet() - -// @SpecializeMethod(params = { String.class }, target = "getStringValue") - override operator fun get(key: K): V? { - return getHashValue(key) -// return if (key is String) -// getStringValue(JsUtils.unsafeCastToString(key)) -// else -// getHashValue(key) - } - -// @SpecializeMethod(params = { String.class, Object .class }, target = "putStringValue") - override fun put(key: K, value: V): V? { - return putHashValue(key, value) -// return if (key is String) -// putStringValue(JsUtils.unsafeCastToString(key), value) -// else -// putHashValue(key, value) - } - -// @SpecializeMethod(params = { String.class }, target = "removeStringValue") - override fun remove(key: K): V? { - return removeHashValue(key) -// return if (key is String) -// removeStringValue(JsUtils.unsafeCastToString(key)) -// else -// removeHashValue(key) - } - - override val size: Int get() { - return hashCodeMap.size /*+ stringMap!!.size()*/ - } - - /** - * Subclasses must override to return a whether or not two keys or values are - * equal. - */ - internal abstract fun equals(value1: Any?, value2: Any?): Boolean - - /** - * Subclasses must override to return a hash code for a given key. The key is - * guaranteed to be non-null and not a String. - */ - internal abstract fun getHashCode(key: K): Int - - /** - * Returns the Map.Entry whose key is Object equal to `key`, - * provided that `key`'s hash code is `hashCode`; - * or `null` if no such Map.Entry exists at the specified - * hashCode. - */ - private fun getHashValue(key: K): V? { - return hashCodeMap.getEntry(key)?.value - } - -// /** -// * Returns the value for the given key in the stringMap. Returns -// * `null` if the specified key does not exist. -// */ -// private fun getStringValue(key: String?): V { -// return if (key == null) getHashValue(null) else stringMap!!.get(key) -// } - - /** - * Returns true if the a key exists in the hashCodeMap that is Object equal to - * `key`, provided that `key`'s hash code is - * `hashCode`. - */ - private fun hasHashValue(key: K): Boolean { - return hashCodeMap.getEntry(key) != null - } -// -// /** -// * Returns true if the given key exists in the stringMap. -// */ -// private fun hasStringValue(key: String?): Boolean { -// return if (key == null) hasHashValue(null) else stringMap!!.contains(key) -// } - - /** - * Sets the specified key to the specified value in the hashCodeMap. Returns - * the value previously at that key. Returns `null` if the - * specified key did not exist. - */ - private fun putHashValue(key: K, value: V): V? { - return hashCodeMap.put(key, value) - } - -// /** -// * Sets the specified key to the specified value in the stringMap. Returns the -// * value previously at that key. Returns `null` if the specified -// * key did not exist. -// */ -// private fun putStringValue(key: String?, value: V): V { -// return if (key == null) putHashValue(null, value) else stringMap!!.put(key, value) -// } - - /** - * Removes the pair whose key is Object equal to `key` from - * `hashCodeMap`, provided that `key`'s hash code - * is `hashCode`. Returns the value that was associated with the - * removed key, or null if no such key existed. - */ - private fun removeHashValue(key: K): V? { - return hashCodeMap.remove(key) - } - -// /** -// * Removes the specified key from the stringMap and returns the value that was -// * previously there. Returns `null` if the specified key does not -// * exist. -// */ -// private fun removeStringValue(key: String?): V { -// return if (key == null) removeHashValue(null) else stringMap!!.remove(key) -// } -} \ No newline at end of file diff --git a/js/js.libraries/src/core/collections/EqualityComparator.kt b/js/js.libraries/src/core/collections/EqualityComparator.kt new file mode 100644 index 00000000000..442db33d0f0 --- /dev/null +++ b/js/js.libraries/src/core/collections/EqualityComparator.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 kotlin.collections + +internal interface EqualityComparator { + /** + * Subclasses must override to return a whether or not two keys or values are + * equal. + */ + abstract fun equals(value1: Any?, value2: Any?): Boolean + + /** + * Subclasses must override to return a hash code for a given key. The key is + * guaranteed to be non-null and not a String. + */ + abstract fun getHashCode(value: Any?): Int + + + object HashCode : EqualityComparator { + override fun equals(value1: Any?, value2: Any?): Boolean = value1 == value2 + + override fun getHashCode(value: Any?): Int = value?.hashCode() ?: 0 + } +} \ No newline at end of file diff --git a/js/js.libraries/src/core/collections/HashMap.kt b/js/js.libraries/src/core/collections/HashMap.kt index ab6aeeb22f7..fb06e6a530b 100644 --- a/js/js.libraries/src/core/collections/HashMap.kt +++ b/js/js.libraries/src/core/collections/HashMap.kt @@ -14,24 +14,264 @@ * limitations under the License. */ /* - * Based on GWT HashMap + * Based on GWT AbstractHashMap * Copyright 2008 Google Inc. */ package kotlin.collections +import kotlin.collections.Map.Entry +import kotlin.collections.MutableMap.MutableEntry -open class HashMap : AbstractHashMap { +open class HashMap : AbstractMap { - constructor() : super() - constructor(capacity: Int, loadFactor: Float = 0f) : super(capacity, loadFactor) - constructor(original: Map) : super(original) + private inner class EntrySet : AbstractSet>() { -// public override fun clone(): Any { -// return HashMap(this) + override fun clear() { + this@HashMap.clear() + } + + override operator fun contains(element: MutableEntry): Boolean = containsEntry(element) + + override operator fun iterator(): MutableIterator> = internalMap.iterator() + + override fun remove(entry: MutableEntry): Boolean { + if (contains(entry)) { + this@HashMap.remove(entry.key) + return true + } + return false + } + + override val size: Int get() = this@HashMap.size + } + + /* + private inner class EntrySetIterator : Iterator> { + private val stringMapEntries = stringMap!!.iterator() + private var current: MutableIterator> = stringMapEntries + private var last: MutableIterator>? = null + private var hasNext = computeHasNext() + + init { + recordLastKnownStructure(this@AbstractHashMap, this) + } + + override fun hasNext(): Boolean { + return hasNext + } + + private fun computeHasNext(): Boolean { + if (current.hasNext()) { + return true + } + if (current !== stringMapEntries) { + return false + } + current = hashCodeMap!!.iterator() + return current.hasNext() + } + + override fun next(): Entry { + checkStructuralChange(this@AbstractHashMap, this) + checkElement(hasNext()) + + last = current + val rv = current.next() + hasNext = computeHasNext() + + return rv + } + + override fun remove() { + checkState(last != null) + checkStructuralChange(this@AbstractHashMap, this) + + last!!.remove() + last = null + hasNext = computeHasNext() + + recordLastKnownStructure(this@AbstractHashMap, this) + } + }*/ + + /** + * A map of integral hashCodes onto entries. + */ + private val internalMap: InternalMap + + internal val equality: EqualityComparator + +// /** +// * A map of Strings onto values. +// */ +// @Transient private var stringMap: InternalStringMap? = null +// + internal constructor(internalMapProvider: (HashMap) -> InternalMap, equality: EqualityComparator = EqualityComparator.HashCode) : super() { + this.equality = equality + this.internalMap = internalMapProvider(this) + } + + constructor() : this( { m -> InternalHashCodeMap(m) }) +// init { +// reset() // } - override fun equals(value1: Any?, value2: Any?): Boolean = value1 == value2 + constructor(capacity: Int, loadFactor: Float = 0f) : this() { + // This implementation of HashMap has no need of load factors or capacities. + require(capacity >= 0) { "Negative initial capacity" } + require(loadFactor >= 0) { "Non-positive load factor" } + } - override fun getHashCode(key: K): Int = key?.hashCode() ?: 0 + constructor(original: Map) : this() { + this.putAll(original) + } + + override fun clear() { + internalMap.clear() +// reset() +// } +// +// private fun reset() { +// internalMap = internalMapProvider(this) +// stringMap = InternalStringMap(this) +// structureChanged(this) + } + +// @SpecializeMethod(params = { String.class }, target = "hasStringValue") + override fun containsKey(key: K): Boolean { + return internalMap.contains(key) +// return if (key is String) +// hasStringValue(JsUtils.unsafeCastToString(key)) +// else +// hasHashValue(key) + } + + override fun containsValue(value: V): Boolean { + return /*containsValue(value, stringMap) || */ containsValue(value, internalMap) + } + + private fun containsValue(value: V, entries: Iterable>): Boolean = entries.any { equality.equals(it.value, value) } + + override val entries: MutableSet> + get() = EntrySet() + +// @SpecializeMethod(params = { String.class }, target = "getStringValue") + override operator fun get(key: K): V? { + return internalMap.get(key) +// return if (key is String) +// getStringValue(JsUtils.unsafeCastToString(key)) +// else +// getHashValue(key) + } + +// @SpecializeMethod(params = { String.class, Object .class }, target = "putStringValue") + override fun put(key: K, value: V): V? { + return internalMap.put(key, value) +// return if (key is String) +// putStringValue(JsUtils.unsafeCastToString(key), value) +// else +// putHashValue(key, value) + } + +// @SpecializeMethod(params = { String.class }, target = "removeStringValue") + override fun remove(key: K): V? { + return internalMap.remove(key) +// return if (key is String) +// removeStringValue(JsUtils.unsafeCastToString(key)) +// else +// removeHashValue(key) + } + + override val size: Int get() { + return internalMap.size /*+ stringMap!!.size()*/ + } + +// /** +// * Subclasses must override to return a whether or not two keys or values are +// * equal. +// */ +// internal abstract fun equals(value1: Any?, value2: Any?): Boolean +// +// /** +// * Subclasses must override to return a hash code for a given key. The key is +// * guaranteed to be non-null and not a String. +// */ +// internal abstract fun getHashCode(key: K): Int +// +// /** +// * Returns the Map.Entry whose key is Object equal to `key`, +// * provided that `key`'s hash code is `hashCode`; +// * or `null` if no such Map.Entry exists at the specified +// * hashCode. +// */ +// private fun getHashValue(key: K): V? { +// return hashCodeMap.getEntry(key)?.value +// } + +// /** +// * Returns the value for the given key in the stringMap. Returns +// * `null` if the specified key does not exist. +// */ +// private fun getStringValue(key: String?): V { +// return if (key == null) getHashValue(null) else stringMap!!.get(key) +// } + +// /** +// * Returns true if the a key exists in the hashCodeMap that is Object equal to +// * `key`, provided that `key`'s hash code is +// * `hashCode`. +// */ +// private fun hasHashValue(key: K): Boolean { +// return hashCodeMap.getEntry(key) != null +// } +// +// /** +// * Returns true if the given key exists in the stringMap. +// */ +// private fun hasStringValue(key: String?): Boolean { +// return if (key == null) hasHashValue(null) else stringMap!!.contains(key) +// } + +// /** +// * Sets the specified key to the specified value in the hashCodeMap. Returns +// * the value previously at that key. Returns `null` if the +// * specified key did not exist. +// */ +// private fun putHashValue(key: K, value: V): V? { +// return hashCodeMap.put(key, value) +// } + +// /** +// * Sets the specified key to the specified value in the stringMap. Returns the +// * value previously at that key. Returns `null` if the specified +// * key did not exist. +// */ +// private fun putStringValue(key: String?, value: V): V { +// return if (key == null) putHashValue(null, value) else stringMap!!.put(key, value) +// } + +// /** +// * Removes the pair whose key is Object equal to `key` from +// * `hashCodeMap`, provided that `key`'s hash code +// * is `hashCode`. Returns the value that was associated with the +// * removed key, or null if no such key existed. +// */ +// private fun removeHashValue(key: K): V? { +// return hashCodeMap.remove(key) +// } + +// /** +// * Removes the specified key from the stringMap and returns the value that was +// * previously there. Returns `null` if the specified key does not +// * exist. +// */ +// private fun removeStringValue(key: String?): V { +// return if (key == null) removeHashValue(null) else stringMap!!.remove(key) +// } } + + +public fun stringMapOf(vararg pairs: Pair): HashMap { + return HashMap({ m -> InternalStringMap(m) }).apply { putAll(pairs) } +} \ No newline at end of file diff --git a/js/js.libraries/src/core/collections/HashSet.kt b/js/js.libraries/src/core/collections/HashSet.kt index 4a10d5e51b0..0055624a3bd 100644 --- a/js/js.libraries/src/core/collections/HashSet.kt +++ b/js/js.libraries/src/core/collections/HashSet.kt @@ -44,7 +44,7 @@ open class HashSet : AbstractSet { * @param map underlying map to use. */ - protected constructor(map: HashMap) { + internal constructor(map: HashMap) { this.map = map } @@ -72,3 +72,7 @@ open class HashSet : AbstractSet { override val size: Int get() = map.size } + +public fun stringSetOf(vararg elements: String): HashSet { + return HashSet(stringMapOf()).apply { addAll(elements) } +} diff --git a/js/js.libraries/src/core/collections/InternalHashCodeMap.kt b/js/js.libraries/src/core/collections/InternalHashCodeMap.kt index ca05c7954a8..29b2a198972 100644 --- a/js/js.libraries/src/core/collections/InternalHashCodeMap.kt +++ b/js/js.libraries/src/core/collections/InternalHashCodeMap.kt @@ -35,14 +35,14 @@ import kotlin.collections.AbstractMap.SimpleEntry * have the same hash, each value in hashCodeMap is actually an array containing all entries whose * keys share the same hash. */ -internal class InternalHashCodeMap(private val host: AbstractHashMap) : MutableIterable> { +internal class InternalHashCodeMap(private val host: HashMap) : InternalMap { - private val backingMap: dynamic = js("new Object()") - var size: Int = 0 + private var backingMap: dynamic = js("Object.create(null)") + override var size: Int = 0 private set - fun put(key: K, value: V): V? { - val hashCode = host.getHashCode(key) + override fun put(key: K, value: V): V? { + val hashCode = host.equality.getHashCode(key) val chain = getChainOrNull(hashCode) if (chain == null) { // This is a new chain, put it to the map. @@ -61,12 +61,12 @@ internal class InternalHashCodeMap(private val host: AbstractHashMap return null } - fun remove(key: K): V? { - val hashCode = host.getHashCode(key) + override fun remove(key: K): V? { + val hashCode = host.equality.getHashCode(key) val chain = getChainOrNull(hashCode) ?: return null for (index in 0..chain.size-1) { val entry = chain[index] - if (host.equals(key, entry.key)) { + if (host.equality.equals(key, entry.key)) { if (chain.size == 1) { chain.asDynamic().length = 0 // remove the whole array @@ -84,11 +84,20 @@ internal class InternalHashCodeMap(private val host: AbstractHashMap return null } - fun getEntry(key: K): MutableEntry? = - getChainOrNull(host.getHashCode(key))?.findEntryInChain(key) + override fun clear() { + backingMap = js("Object.create(null)") + size = 0 + } + + override fun contains(key: K): Boolean = getEntry(key) != null + + override fun get(key: K): V? = getEntry(key)?.value + + private fun getEntry(key: K): MutableEntry? = + getChainOrNull(host.equality.getHashCode(key))?.findEntryInChain(key) private fun Array>.findEntryInChain(key: K): MutableEntry? = - firstOrNull { entry -> host.equals(entry.key, key) } + firstOrNull { entry -> host.equality.equals(entry.key, key) } override fun iterator(): MutableIterator> { diff --git a/js/js.libraries/src/core/collections/InternalMap.kt b/js/js.libraries/src/core/collections/InternalMap.kt new file mode 100644 index 00000000000..2dfa38c5da3 --- /dev/null +++ b/js/js.libraries/src/core/collections/InternalMap.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 kotlin.collections + +internal interface InternalMap : MutableIterable> { + val size: Int + operator fun contains(key: K): Boolean + operator fun get(key: K): V? + + fun put(key: K, value: V): V? + fun remove(key: K): V? + fun clear(): Unit +} \ No newline at end of file diff --git a/js/js.libraries/src/core/collections/InternalStringMap.kt b/js/js.libraries/src/core/collections/InternalStringMap.kt index ce3b9cddbb3..b3422659ec0 100644 --- a/js/js.libraries/src/core/collections/InternalStringMap.kt +++ b/js/js.libraries/src/core/collections/InternalStringMap.kt @@ -19,106 +19,103 @@ */ package kotlin.collections +import kotlin.collections.MutableMap.MutableEntry + /** * A simple wrapper around JavaScript Map for key type is string. */ -internal class InternalStringMap(private val host: AbstractHashMap) : Iterable> { +internal class InternalStringMap(private val host: HashMap) : InternalMap { - private val backingMap = InternalJsMapFactory.newJsMap() - private var size: Int = 0 + private var backingMap: dynamic = js("Object.create(null)") + override var size: Int = 0 + private set - /** - * 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 var valueMod: Int = 0 +// /** +// * 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 var valueMod: Int = 0 - operator fun contains(key: String): Boolean { - return !JsUtils.isUndefined(backingMap.get(key)) + override operator fun contains(key: K): Boolean { + if (key !is String) return false + return backingMap[key] !== undefined } - operator fun get(key: String): V { - return backingMap.get(key) + override operator fun get(key: K): V? { + if (key !is String) return null + val value = backingMap[key] + return if (value !== undefined) value as V else null } - fun put(key: String, value: V): V { - val oldValue = backingMap.get(key) - backingMap.set(key, toNullIfUndefined(value)) - if (JsUtils.isUndefined(oldValue)) { + override fun put(key: K, value: V): V? { + require(key is String) + val oldValue = backingMap[key] + backingMap[key] = value + + if (oldValue == undefined) { size++ - structureChanged(host) +// structureChanged(host) + return null } else { - valueMod++ +// valueMod++ + return oldValue as V } - return oldValue } - fun remove(key: String): V { - val value = backingMap.get(key) - if (!JsUtils.isUndefined(value)) { - backingMap.delete(key) + override fun remove(key: K): V? { + if (key !is String) return null + val value = backingMap[key] + if (value !== undefined) { + deleteProperty(backingMap, key!!) size-- - structureChanged(host) +// structureChanged(host) + return value as V } else { - valueMod++ +// valueMod++ + return null } - - return value } - fun size(): Int { - return size + + override fun clear() { + backingMap = js("Object.create(null)") + size = 0 } - override fun iterator(): Iterator> { - return object : Iterator> { - internal var entries = backingMap.entries() - internal var current = entries.next() - internal var last: InternalJsMap.IteratorEntry - override fun hasNext(): Boolean { - return !current.done - } + override fun iterator(): MutableIterator> { + return object : MutableIterator> { + private val keys: Array = js("Object").keys(backingMap) + private val iterator = keys.iterator() + private var lastKey: String? = null - override fun next(): Entry { - last = current - current = entries.next() - return newMapEntry(last, valueMod) + override fun hasNext(): Boolean = iterator.hasNext() + + override fun next(): MutableEntry { + val key = iterator.next() + lastKey = key + return newMapEntry(key as K) } override fun remove() { - this@InternalStringMap.remove(last.getKey()) + this@InternalStringMap.remove(checkNotNull(lastKey) as K) } } } - private fun newMapEntry(entry: InternalJsMap.IteratorEntry, - lastValueMod: Int): Entry { - return object : AbstractMapEntry() { - val key: K - @SuppressWarnings("unchecked") - get() = entry.getKey() - // Let's get a fresh copy as the value may have changed. - val value: V - get() { - if (valueMod != lastValueMod) { - return get(entry.getKey()) - } - return entry.getValue() - } + private fun newMapEntry(key: K): MutableEntry = object : MutableEntry { + override val key: K get() = key + override val value: V get() = this@InternalStringMap[key] as V - fun setValue(`object`: V): V { - return put(entry.getKey(), `object`) - } - } - } + override fun setValue(value: V): V = this@InternalStringMap.put(key, value) as V - private fun toNullIfUndefined(value: T): T? { - return if (JsUtils.isUndefined(value)) null else value + override fun hashCode(): Int = AbstractMap.entryHashCode(this) + override fun toString(): String = AbstractMap.entryToString(this) + override fun equals(other: Any?): Boolean = AbstractMap.entryEquals(this, other) } } diff --git a/libraries/stdlib/test/js/MapJsTest.kt b/libraries/stdlib/test/js/MapJsTest.kt index 7d516a561f7..ceaac411287 100644 --- a/libraries/stdlib/test/js/MapJsTest.kt +++ b/libraries/stdlib/test/js/MapJsTest.kt @@ -41,7 +41,7 @@ class PrimitiveMapJsTest : MapJsTest() { } override fun > Collection.toNormalizedList(): List = this.sorted() - override fun emptyMutableMap(): MutableMap = HashMap() + override fun emptyMutableMap(): MutableMap = stringMapOf() override fun emptyMutableMapWithNullableKeyValue(): MutableMap = HashMap() @test fun compareBehavior() { diff --git a/libraries/stdlib/test/js/SetJsTest.kt b/libraries/stdlib/test/js/SetJsTest.kt index 21fd4340cb2..8ce3319dbb4 100644 --- a/libraries/stdlib/test/js/SetJsTest.kt +++ b/libraries/stdlib/test/js/SetJsTest.kt @@ -31,7 +31,7 @@ class ComplexSetJsTest : SetJsTest() { } class PrimitiveSetJsTest : SetJsTest() { - override fun createEmptyMutableSet(): MutableSet = HashSet() + override fun createEmptyMutableSet(): MutableSet = stringSetOf() override fun createEmptyMutableSetWithNullableValues(): MutableSet = HashSet() @Test override fun constructors() { diff --git a/libraries/stdlib/test/js/StubsJVM.kt b/libraries/stdlib/test/js/StubsJVM.kt new file mode 100644 index 00000000000..b86bbf8ad71 --- /dev/null +++ b/libraries/stdlib/test/js/StubsJVM.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2010-2016 JetBrains s.r.o. + * + * 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 test.collections.js + +import java.util.* + +public fun stringMapOf(vararg pairs: Pair): HashMap = hashMapOf(*pairs) +public fun stringSetOf(vararg elements: String): HashSet = hashSetOf(*elements) diff --git a/libraries/tools/kotlin-js-tests/pom.xml b/libraries/tools/kotlin-js-tests/pom.xml index 9493d790da7..ee98da60c53 100644 --- a/libraries/tools/kotlin-js-tests/pom.xml +++ b/libraries/tools/kotlin-js-tests/pom.xml @@ -43,6 +43,7 @@ +