From 20587654852144aadc824ea25cbd202081c6e9db Mon Sep 17 00:00:00 2001 From: Peter Vlugter Date: Thu, 19 Jan 2012 11:09:03 +1300 Subject: [PATCH] Add basic java api for scala stm to transactor module Note: this commit will be reverted once a java api is published for scala stm. Adding to transactor module for M3 release. --- .../scala/scala/concurrent/stm/JavaAPI.scala | 112 ++++++++++++ .../scala/concurrent/stm/JavaAPITests.java | 161 ++++++++++++++++++ .../scala/concurrent/stm/TestException.java | 9 + .../scala/concurrent/stm/JavaAPISuite.scala | 7 + project/AkkaBuild.scala | 2 +- 5 files changed, 290 insertions(+), 1 deletion(-) create mode 100644 akka-transactor/src/main/scala/scala/concurrent/stm/JavaAPI.scala create mode 100644 akka-transactor/src/test/java/scala/concurrent/stm/JavaAPITests.java create mode 100644 akka-transactor/src/test/java/scala/concurrent/stm/TestException.java create mode 100644 akka-transactor/src/test/scala/scala/concurrent/stm/JavaAPISuite.scala diff --git a/akka-transactor/src/main/scala/scala/concurrent/stm/JavaAPI.scala b/akka-transactor/src/main/scala/scala/concurrent/stm/JavaAPI.scala new file mode 100644 index 0000000000..964664fe55 --- /dev/null +++ b/akka-transactor/src/main/scala/scala/concurrent/stm/JavaAPI.scala @@ -0,0 +1,112 @@ +/* scala-stm - (c) 2009-2011, Stanford University, PPL */ + +package scala.concurrent.stm + +import java.util.concurrent.Callable +import scala.runtime.AbstractFunction1 + +/** + * Java-friendly API. + */ +object JavaAPI { + + /** + * Create a Ref with an initial value. Return a `Ref.View`, which does not + * require implicit transactions. + * @param initialValue the initial value for the newly created `Ref.View` + * @return a new `Ref.View` + */ + def newRef[A](initialValue: A): Ref.View[A] = Ref(initialValue).single + + /** + * Create an empty TMap. Return a `TMap.View`, which does not require + * implicit transactions. + * @return a new, empty `TMap.View` + */ + def newTMap[A, B](): TMap.View[A, B] = TMap.empty[A, B].single + + /** + * Create an empty TSet. Return a `TSet.View`, which does not require + * implicit transactions. + * @return a new, empty `TSet.View` + */ + def newTSet[A](): TSet.View[A] = TSet.empty[A].single + + /** + * Create a TArray containing `length` elements. Return a `TArray.View`, + * which does not require implicit transactions. + * @param length the length of the `TArray.View` to be created + * @return a new `TArray.View` containing `length` elements (initially null) + */ + def newTArray[A <: AnyRef](length: Int): TArray.View[A] = TArray.ofDim[A](length)(ClassManifest.classType(AnyRef.getClass)).single + + /** + * Atomic block that takes a `Runnable`. + * @param runnable the `Runnable` to run within a transaction + */ + def atomic(runnable: Runnable): Unit = scala.concurrent.stm.atomic { txn ⇒ runnable.run } + + /** + * Atomic block that takes a `Callable`. + * @param callable the `Callable` to run within a transaction + * @return the value returned by the `Callable` + */ + def atomic[A](callable: Callable[A]): A = scala.concurrent.stm.atomic { txn ⇒ callable.call } + + /** + * Transform the value stored by `ref` by applying the function `f`. + * @param ref the `Ref.View` to be transformed + * @param f the function to be applied + */ + def transform[A](ref: Ref.View[A], f: AbstractFunction1[A, A]): Unit = ref.transform(f) + + /** + * Transform the value stored by `ref` by applying the function `f` and + * return the old value. + * @param ref the `Ref.View` to be transformed + * @param f the function to be applied + * @return the old value of `ref` + */ + def getAndTransform[A](ref: Ref.View[A], f: AbstractFunction1[A, A]): A = ref.getAndTransform(f) + + /** + * Transform the value stored by `ref` by applying the function `f` and + * return the new value. + * @param ref the `Ref.View` to be transformed + * @param f the function to be applied + * @return the new value of `ref` + */ + def transformAndGet[A](ref: Ref.View[A], f: AbstractFunction1[A, A]): A = ref.transformAndGet(f) + + /** + * Increment the `java.lang.Integer` value of a `Ref.View`. + * @param ref the `Ref.View` to be incremented + * @param delta the amount to increment + */ + def increment(ref: Ref.View[java.lang.Integer], delta: Int): Unit = ref.transform { v ⇒ v.intValue + delta } + + /** + * Increment the `java.lang.Long` value of a `Ref.View`. + * @param ref the `Ref.View` to be incremented + * @param delta the amount to increment + */ + def increment(ref: Ref.View[java.lang.Long], delta: Long): Unit = ref.transform { v ⇒ v.longValue + delta } + + /** + * Add a task to run after the current transaction has committed. + * @param task the `Runnable` task to run after transaction commit + */ + def afterCommit(task: Runnable): Unit = { + val txn = Txn.findCurrent + if (txn.isDefined) Txn.afterCommit(status ⇒ task.run)(txn.get) + } + + /** + * Add a task to run after the current transaction has rolled back. + * @param task the `Runnable` task to run after transaction rollback + */ + def afterRollback(task: Runnable): Unit = { + val txn = Txn.findCurrent + if (txn.isDefined) Txn.afterRollback(status ⇒ task.run)(txn.get) + } +} diff --git a/akka-transactor/src/test/java/scala/concurrent/stm/JavaAPITests.java b/akka-transactor/src/test/java/scala/concurrent/stm/JavaAPITests.java new file mode 100644 index 0000000000..e2d0631590 --- /dev/null +++ b/akka-transactor/src/test/java/scala/concurrent/stm/JavaAPITests.java @@ -0,0 +1,161 @@ +/* scala-stm - (c) 2009-2011, Stanford University, PPL */ + +package scala.concurrent.stm; + +import static org.junit.Assert.*; +import org.junit.Test; + +import scala.concurrent.stm.Ref; +import static scala.concurrent.stm.JavaAPI.*; + +import scala.runtime.AbstractFunction1; +import java.util.concurrent.Callable; + +import static scala.collection.JavaConversions.*; +import java.util.Map; +import java.util.Set; +import java.util.List; + +public class JavaAPITests { + @Test + public void createIntegerRef() { + Ref.View ref = newRef(0); + int unboxed = ref.get(); + assertEquals(0, unboxed); + } + + @Test + public void atomicWithRunnable() { + final Ref.View ref = newRef(0); + atomic(new Runnable() { + public void run() { + ref.set(10); + } + }); + int value = ref.get(); + assertEquals(10, value); + } + + @Test + public void atomicWithCallable() { + final Ref.View ref = newRef(0); + int oldValue = atomic(new Callable() { + public Integer call() { + return ref.swap(10); + } + }); + assertEquals(0, oldValue); + int newValue = ref.get(); + assertEquals(10, newValue); + } + + @Test(expected = TestException.class) + public void failingTransaction() { + final Ref.View ref = newRef(0); + try { + atomic(new Runnable() { + public void run() { + ref.set(10); + throw new TestException(); + } + }); + } catch (TestException e) { + int value = ref.get(); + assertEquals(0, value); + throw e; + } + } + + @Test + public void transformInteger() { + Ref.View ref = newRef(0); + transform(ref, new AbstractFunction1() { + public Integer apply(Integer i) { + return i + 10; + } + }); + int value = ref.get(); + assertEquals(10, value); + } + + @Test + public void incrementInteger() { + Ref.View ref = newRef(0); + increment(ref, 10); + int value = ref.get(); + assertEquals(10, value); + } + + @Test + public void incrementLong() { + Ref.View ref = newRef(0L); + increment(ref, 10L); + long value = ref.get(); + assertEquals(10L, value); + } + + @Test + public void createAndUseTMap() { + TMap.View tmap = newTMap(); + Map map = mutableMapAsJavaMap(tmap); + map.put(1, "one"); + map.put(2, "two"); + assertEquals("one", map.get(1)); + assertEquals("two", map.get(2)); + assertTrue(map.containsKey(2)); + map.remove(2); + assertFalse(map.containsKey(2)); + } + + @Test(expected = TestException.class) + public void failingTMapTransaction() { + TMap.View tmap = newTMap(); + final Map map = mutableMapAsJavaMap(tmap); + try { + atomic(new Runnable() { + public void run() { + map.put(1, "one"); + map.put(2, "two"); + assertTrue(map.containsKey(1)); + assertTrue(map.containsKey(2)); + throw new TestException(); + } + }); + } catch (TestException e) { + assertFalse(map.containsKey(1)); + assertFalse(map.containsKey(2)); + throw e; + } + } + + @Test + public void createAndUseTSet() { + TSet.View tset = newTSet(); + Set set = mutableSetAsJavaSet(tset); + set.add("one"); + set.add("two"); + assertTrue(set.contains("one")); + assertTrue(set.contains("two")); + assertEquals(2, set.size()); + set.add("one"); + assertEquals(2, set.size()); + set.remove("two"); + assertFalse(set.contains("two")); + assertEquals(1, set.size()); + } + + @Test + public void createAndUseTArray() { + TArray.View tarray = newTArray(3); + List seq = mutableSeqAsJavaList(tarray); + assertEquals(null, seq.get(0)); + assertEquals(null, seq.get(1)); + assertEquals(null, seq.get(2)); + seq.set(0, "zero"); + seq.set(1, "one"); + seq.set(2, "two"); + assertEquals("zero", seq.get(0)); + assertEquals("one", seq.get(1)); + assertEquals("two", seq.get(2)); + } +} diff --git a/akka-transactor/src/test/java/scala/concurrent/stm/TestException.java b/akka-transactor/src/test/java/scala/concurrent/stm/TestException.java new file mode 100644 index 0000000000..cc810761d4 --- /dev/null +++ b/akka-transactor/src/test/java/scala/concurrent/stm/TestException.java @@ -0,0 +1,9 @@ +/* scala-stm - (c) 2009-2011, Stanford University, PPL */ + +package scala.concurrent.stm; + +public class TestException extends RuntimeException { + public TestException() { + super("Expected failure"); + } +} diff --git a/akka-transactor/src/test/scala/scala/concurrent/stm/JavaAPISuite.scala b/akka-transactor/src/test/scala/scala/concurrent/stm/JavaAPISuite.scala new file mode 100644 index 0000000000..3d0c48e90f --- /dev/null +++ b/akka-transactor/src/test/scala/scala/concurrent/stm/JavaAPISuite.scala @@ -0,0 +1,7 @@ +/* scala-stm - (c) 2009-2011, Stanford University, PPL */ + +package scala.concurrent.stm + +import org.scalatest.junit.JUnitWrapperSuite + +class JavaAPISuite extends JUnitWrapperSuite("scala.concurrent.stm.JavaAPITests", Thread.currentThread.getContextClassLoader) diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 626ede1834..091346de34 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -450,7 +450,7 @@ object Dependency { val Netty = "3.2.5.Final" val Protobuf = "2.4.1" val Rabbit = "2.3.1" - val ScalaStm = "0.5.0-SNAPSHOT" + val ScalaStm = "0.4" val Scalatest = "1.6.1" val Slf4j = "1.6.4" val Spring = "3.0.5.RELEASE"