LOG4J2-1690 Scala wrapper for context map
diff --git a/log4j-api-scala_2.10/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala b/log4j-api-scala_2.10/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
new file mode 100644
index 0000000..6c91b62
--- /dev/null
+++ b/log4j-api-scala_2.10/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.apache.logging.log4j.ThreadContext
+
+import scala.collection.{immutable, mutable}
+import scala.collection.JavaConverters._
+
+/** Manages the context data (context map, MDC) that is added to log events.
+  *
+  * A wrapper around `org.apache.logging.log4j.ThreadContext`.
+  */
+object LoggingContext extends mutable.Map[String, String] {
+
+  override def +=(kv: (String, String)): LoggingContext.this.type = {
+    ThreadContext.put(kv._1, kv._2)
+    this
+  }
+
+  override def +=(elem1: (String, String), elem2: (String, String), elems: (String, String)*): LoggingContext.this.type = {
+    val builder = immutable.Map.newBuilder[String,String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.putAll(builder.result.asJava)
+    this
+  }
+
+  override def ++=(xs: TraversableOnce[(String, String)]): LoggingContext.this.type = {
+    ThreadContext.putAll(xs.toMap.asJava)
+    this
+  }
+
+  override def -=(key: String): LoggingContext.this.type = {
+    ThreadContext.remove(key)
+    this
+  }
+
+  override def -=(elem1: String, elem2: String, elems: String*): LoggingContext.this.type = {
+    val builder = immutable.Seq.newBuilder[String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.removeAll(builder.result.asJava)
+    this
+  }
+
+  override def --=(xs: TraversableOnce[String]): LoggingContext.this.type = {
+    ThreadContext.removeAll(xs.toSeq.asJava)
+    this
+  }
+
+  override def clear(): Unit = {
+    ThreadContext.clearMap()
+  }
+
+  override def contains(key: String): Boolean = ThreadContext.containsKey(key)
+
+  override def get(key: String): Option[String] = Option(ThreadContext.get(key))
+
+  override def iterator: Iterator[(String, String)] = ThreadContext.getImmutableContext.asScala.iterator
+
+  override def size: Int = ThreadContext.getImmutableContext.size()
+
+  override def isEmpty: Boolean = ThreadContext.isEmpty
+
+}
diff --git a/log4j-api-scala_2.10/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala b/log4j-api-scala_2.10/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
new file mode 100644
index 0000000..def2164
--- /dev/null
+++ b/log4j-api-scala_2.10/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import org.scalatest.{FunSuite, Matchers}
+
+@RunWith(classOf[JUnitRunner])
+class LoggingContextTest extends FunSuite with Matchers {
+
+  test("put single, contains, get") {
+    LoggingContext += "key" -> "value"
+
+    LoggingContext.contains("key") shouldBe true
+    LoggingContext.get("key") shouldBe Some("value")
+    LoggingContext.contains("bogus") shouldBe false
+    LoggingContext.get("bogus") shouldBe None
+  }
+
+  test("put multiple 1") {
+    LoggingContext += ("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("put multiple 2") {
+    LoggingContext ++= Seq("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("remove single") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= "key1"
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe Some("value2")
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 1") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= ("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 2") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext --= Seq("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("clear, size, isEmpty") {
+    LoggingContext += ("key" -> "value")
+
+    LoggingContext.clear()
+
+    LoggingContext.contains("key") shouldBe false
+    LoggingContext.size shouldBe 0
+    LoggingContext.isEmpty shouldBe true
+  }
+
+  test("iterator") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+
+    LoggingContext.iterator.toSet shouldBe Set("key1" -> "value1", "key2" -> "value2")
+  }
+
+}
diff --git a/log4j-api-scala_2.11/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala b/log4j-api-scala_2.11/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
new file mode 100644
index 0000000..70f9ec8
--- /dev/null
+++ b/log4j-api-scala_2.11/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.apache.logging.log4j.ThreadContext
+
+import scala.collection.JavaConverters._
+import scala.collection.{immutable, mutable}
+
+/** Manages the context data (context map, MDC) that is added to log events.
+  *
+  * A wrapper around `org.apache.logging.log4j.ThreadContext`.
+  */
+object LoggingContext extends mutable.Map[String, String] {
+
+  override def +=(kv: (String, String)): LoggingContext.this.type = {
+    ThreadContext.put(kv._1, kv._2)
+    this
+  }
+
+  override def +=(elem1: (String, String), elem2: (String, String), elems: (String, String)*): LoggingContext.this.type = {
+    val builder = immutable.Map.newBuilder[String,String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.putAll(builder.result.asJava)
+    this
+  }
+
+  override def ++=(xs: TraversableOnce[(String, String)]): LoggingContext.this.type = {
+    ThreadContext.putAll(xs.toMap.asJava)
+    this
+  }
+
+  override def -=(key: String): LoggingContext.this.type = {
+    ThreadContext.remove(key)
+    this
+  }
+
+  override def -=(elem1: String, elem2: String, elems: String*): LoggingContext.this.type = {
+    val builder = immutable.Seq.newBuilder[String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.removeAll(builder.result.asJava)
+    this
+  }
+
+  override def --=(xs: TraversableOnce[String]): LoggingContext.this.type = {
+    ThreadContext.removeAll(xs.toSeq.asJava)
+    this
+  }
+
+  override def clear(): Unit = {
+    ThreadContext.clearMap()
+  }
+
+  override def contains(key: String): Boolean = ThreadContext.containsKey(key)
+
+  override def get(key: String): Option[String] = Option(ThreadContext.get(key))
+
+  override def iterator: Iterator[(String, String)] = ThreadContext.getImmutableContext.asScala.iterator
+
+  override def size: Int = ThreadContext.getImmutableContext.size()
+
+  override def isEmpty: Boolean = ThreadContext.isEmpty
+
+}
diff --git a/log4j-api-scala_2.11/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala b/log4j-api-scala_2.11/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
new file mode 100644
index 0000000..def2164
--- /dev/null
+++ b/log4j-api-scala_2.11/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import org.scalatest.{FunSuite, Matchers}
+
+@RunWith(classOf[JUnitRunner])
+class LoggingContextTest extends FunSuite with Matchers {
+
+  test("put single, contains, get") {
+    LoggingContext += "key" -> "value"
+
+    LoggingContext.contains("key") shouldBe true
+    LoggingContext.get("key") shouldBe Some("value")
+    LoggingContext.contains("bogus") shouldBe false
+    LoggingContext.get("bogus") shouldBe None
+  }
+
+  test("put multiple 1") {
+    LoggingContext += ("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("put multiple 2") {
+    LoggingContext ++= Seq("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("remove single") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= "key1"
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe Some("value2")
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 1") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= ("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 2") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext --= Seq("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("clear, size, isEmpty") {
+    LoggingContext += ("key" -> "value")
+
+    LoggingContext.clear()
+
+    LoggingContext.contains("key") shouldBe false
+    LoggingContext.size shouldBe 0
+    LoggingContext.isEmpty shouldBe true
+  }
+
+  test("iterator") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+
+    LoggingContext.iterator.toSet shouldBe Set("key1" -> "value1", "key2" -> "value2")
+  }
+
+}
diff --git a/log4j-api-scala_2.12/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala b/log4j-api-scala_2.12/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
new file mode 100644
index 0000000..70f9ec8
--- /dev/null
+++ b/log4j-api-scala_2.12/src/main/scala/org/apache/logging/log4j/scala/LoggingContext.scala
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.apache.logging.log4j.ThreadContext
+
+import scala.collection.JavaConverters._
+import scala.collection.{immutable, mutable}
+
+/** Manages the context data (context map, MDC) that is added to log events.
+  *
+  * A wrapper around `org.apache.logging.log4j.ThreadContext`.
+  */
+object LoggingContext extends mutable.Map[String, String] {
+
+  override def +=(kv: (String, String)): LoggingContext.this.type = {
+    ThreadContext.put(kv._1, kv._2)
+    this
+  }
+
+  override def +=(elem1: (String, String), elem2: (String, String), elems: (String, String)*): LoggingContext.this.type = {
+    val builder = immutable.Map.newBuilder[String,String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.putAll(builder.result.asJava)
+    this
+  }
+
+  override def ++=(xs: TraversableOnce[(String, String)]): LoggingContext.this.type = {
+    ThreadContext.putAll(xs.toMap.asJava)
+    this
+  }
+
+  override def -=(key: String): LoggingContext.this.type = {
+    ThreadContext.remove(key)
+    this
+  }
+
+  override def -=(elem1: String, elem2: String, elems: String*): LoggingContext.this.type = {
+    val builder = immutable.Seq.newBuilder[String]
+    builder += elem1
+    builder += elem2
+    builder ++= elems
+    ThreadContext.removeAll(builder.result.asJava)
+    this
+  }
+
+  override def --=(xs: TraversableOnce[String]): LoggingContext.this.type = {
+    ThreadContext.removeAll(xs.toSeq.asJava)
+    this
+  }
+
+  override def clear(): Unit = {
+    ThreadContext.clearMap()
+  }
+
+  override def contains(key: String): Boolean = ThreadContext.containsKey(key)
+
+  override def get(key: String): Option[String] = Option(ThreadContext.get(key))
+
+  override def iterator: Iterator[(String, String)] = ThreadContext.getImmutableContext.asScala.iterator
+
+  override def size: Int = ThreadContext.getImmutableContext.size()
+
+  override def isEmpty: Boolean = ThreadContext.isEmpty
+
+}
diff --git a/log4j-api-scala_2.12/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala b/log4j-api-scala_2.12/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
new file mode 100644
index 0000000..def2164
--- /dev/null
+++ b/log4j-api-scala_2.12/src/test/scala/org/apache/logging/log4j/scala/LoggingContextTest.scala
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.logging.log4j.scala
+
+import org.junit.runner.RunWith
+import org.scalatest.junit.JUnitRunner
+import org.scalatest.{FunSuite, Matchers}
+
+@RunWith(classOf[JUnitRunner])
+class LoggingContextTest extends FunSuite with Matchers {
+
+  test("put single, contains, get") {
+    LoggingContext += "key" -> "value"
+
+    LoggingContext.contains("key") shouldBe true
+    LoggingContext.get("key") shouldBe Some("value")
+    LoggingContext.contains("bogus") shouldBe false
+    LoggingContext.get("bogus") shouldBe None
+  }
+
+  test("put multiple 1") {
+    LoggingContext += ("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("put multiple 2") {
+    LoggingContext ++= Seq("key1" -> "value1", "key2" -> "value2")
+
+    LoggingContext.get("key1") shouldBe Some("value1")
+    LoggingContext.get("key2") shouldBe Some("value2")
+  }
+
+  test("remove single") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= "key1"
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe Some("value2")
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 1") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext -= ("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("remove multiple 2") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+    LoggingContext += ("key3" -> "value3")
+
+    LoggingContext --= Seq("key1", "key2")
+
+    LoggingContext.get("key1") shouldBe None
+    LoggingContext.get("key2") shouldBe None
+    LoggingContext.get("key3") shouldBe Some("value3")
+  }
+
+  test("clear, size, isEmpty") {
+    LoggingContext += ("key" -> "value")
+
+    LoggingContext.clear()
+
+    LoggingContext.contains("key") shouldBe false
+    LoggingContext.size shouldBe 0
+    LoggingContext.isEmpty shouldBe true
+  }
+
+  test("iterator") {
+    LoggingContext += ("key1" -> "value1")
+    LoggingContext += ("key2" -> "value2")
+
+    LoggingContext.iterator.toSet shouldBe Set("key1" -> "value1", "key2" -> "value2")
+  }
+
+}