Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing overrides in java.util.concurrent.ConcurrentMap #3527

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
40 changes: 35 additions & 5 deletions javalib/src/main/scala/java/util/Map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
package java.util

import java.util.function.{BiConsumer, BiFunction, Function}

import scala.scalanative.annotation.alwaysinline
import ScalaOps._

trait Map[K, V] {
Expand All @@ -27,13 +27,22 @@ trait Map[K, V] {
else defaultValue

def forEach(action: BiConsumer[_ >: K, _ >: V]): Unit = {
for (entry <- entrySet().scalaOps)
action.accept(entry.getKey(), entry.getValue())
Objects.requireNonNull(action)
entrySet().forEach(usingEntry(_)(action.accept))
}

def replaceAll(function: BiFunction[_ >: K, _ >: V, _ <: V]): Unit = {
for (entry <- entrySet().scalaOps)
entry.setValue(function.apply(entry.getKey(), entry.getValue()))
Objects.requireNonNull(function)
entrySet().forEach(entry =>
usingEntry(entry) { (k, v) =>
val newValue = function.apply(k, v)
try entry.setValue(newValue)
catch {
case ex: IllegalStateException =>
throw new ConcurrentModificationException(ex)
}
}
)
}

def putIfAbsent(key: K, value: V): V = {
Expand Down Expand Up @@ -136,6 +145,27 @@ trait Map[K, V] {
else
remove(key)
}

/** Helper method used to detect concurrent modification exception when
* accessing map entires. IllegalStateException means the entry is no longer
* available (remove)
*/
@alwaysinline
protected[util] def usingEntry[T](
entry: Map.Entry[K, V]
)(apply: (K, V) => T) = {
var key: K = null.asInstanceOf[K]
var value: V = null.asInstanceOf[V]

try {
key = entry.getKey()
value = entry.getValue()
} catch {
case ex: IllegalStateException =>
throw new ConcurrentModificationException(ex)
}
apply(key, value)
}
}

object Map {
Expand Down
126 changes: 124 additions & 2 deletions javalib/src/main/scala/java/util/concurrent/ConcurrentMap.scala
Original file line number Diff line number Diff line change
@@ -1,12 +1,134 @@
// Ported from Scala.js commit: bbf0314 dated: Mon, 13 Jun 2022

/*
* Ported from JSR-166
* Written by Doug Lea with assistance from members of JCP JSR-166
* Expert Group and released to the public domain, as explained at
* http://creativecommons.org/publicdomain/zero/1.0/
*/
package java.util.concurrent

import java.util._
import java.util.function.{BiFunction, Function, BiConsumer}
import java.{util => ju}

trait ConcurrentMap[K, V] extends Map[K, V] {
def putIfAbsent(key: K, value: V): V
def remove(key: Any, value: Any): Boolean
def replace(key: K, oldValue: V, newValue: V): Boolean
def replace(key: K, value: V): V

// Concurrency aware overrides
// JDK assumes ConcurrentMap cannot contain null values
override def getOrDefault(key: Any, defaultValue: V): V = get(key) match {
case null => defaultValue
case v => v
}

override def forEach(action: BiConsumer[? >: K, ? >: V]): Unit = {
Objects.requireNonNull(action)
entrySet().forEach(usingEntry(_)(action.accept))
}

override def replaceAll(
function: BiFunction[? >: K, ? >: V, ? <: V]
): Unit = {
Objects.requireNonNull(function)
forEach { (k, _v) =>
var break = false
var v = _v
while (!break && !replace(k, v, function.apply(k, v))) {
v = get(k)
if (v == null) break = true
}
}
}

override def computeIfAbsent(
key: K,
mappingFunction: Function[? >: K, ? <: V]
): V = {
Objects.requireNonNull(mappingFunction)

val oldValue = get(key)
if (oldValue != null) oldValue
else {
val newValue = mappingFunction.apply(key)
if (newValue == null) oldValue
else {
putIfAbsent(key, newValue) match {
case null => newValue
case oldValue => oldValue
}
}
}
}

override def computeIfPresent(
key: K,
remappingFunction: BiFunction[? >: K, ? >: V, ? <: V]
): V = {
Objects.requireNonNull(remappingFunction)
while ({
val oldValue = get(key)
if (oldValue == null) return null.asInstanceOf[V]
else {
val newValue = remappingFunction.apply(key, oldValue)
val updated =
if (newValue == null) remove(key, oldValue)
else replace(key, oldValue, newValue)
if (updated) return newValue
true
}
}) ()
// unreachable
null.asInstanceOf[V]
}

override def compute(
key: K,
remappingFunction: BiFunction[? >: K, ? >: V, ? <: V]
): V = {
var oldValue = get(key)
while (true) { // haveOldValue
// if putIfAbsent fails, opportunistically use its return value
val newValue = remappingFunction.apply(key, oldValue)
if (newValue != null) {
if (oldValue != null) {
if (replace(key, oldValue, newValue)) return newValue
} else {
oldValue = putIfAbsent(key, newValue)
if (oldValue == null) return newValue
else () // continue haveOldValue
}
} else if (oldValue == null || remove(key, oldValue)) {
return null.asInstanceOf[V]
} else oldValue = get(key)
}
// unreachable
return null.asInstanceOf[V]
}

override def merge(
key: K,
value: V,
remappingFunction: BiFunction[? >: V, ? >: V, ? <: V]
): V = {
Objects.requireNonNull(remappingFunction)
Objects.requireNonNull(value)
var oldValue = get(key)
while (true) { // haveOldValue
if (oldValue != null) {
val newValue = remappingFunction.apply(oldValue, value)
if (newValue != null) {
if (replace(key, oldValue, newValue)) return newValue
} else if (remove(key, oldValue)) return null.asInstanceOf[V]
else oldValue = get(key)
} else {
oldValue = putIfAbsent(key, value)
if (oldValue == null) return value
else () // continue haveOldValue
}
}
// unreachable
return null.asInstanceOf[V]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,3 @@ scala/util/TryTest.scala
scala/math/BigIntTest.scala
### deadlocks maybe needs j.u.c.ConcurrentLinkedQueue
scala/concurrent/impl/DefaultPromiseTest.scala

# Bug, cannot find statically ConcurrentMap#computeIfPresent
scala/collection/convert/EqualsTest.scala