diff --git a/language-adaptors/rxjava-scala/Rationale.md b/language-adaptors/rxjava-scala/Rationale.md index 5afb2392cc..a8cd47d954 100644 --- a/language-adaptors/rxjava-scala/Rationale.md +++ b/language-adaptors/rxjava-scala/Rationale.md @@ -91,7 +91,7 @@ and consumption of Rx values from Java but not for Scala as a producer. If we take that approach, we can make bindings that feels like a completely native Scala library, without needing any complications of the Scala side. ```scala -object Observer { …} +object Observable { …} trait Observable[+T] { def asJavaObservable: rx.Observable[_ <: T] } diff --git a/language-adaptors/rxjava-scala/ReleaseNotes.md b/language-adaptors/rxjava-scala/ReleaseNotes.md new file mode 100644 index 0000000000..4fdb3a06fd --- /dev/null +++ b/language-adaptors/rxjava-scala/ReleaseNotes.md @@ -0,0 +1,228 @@ +RxScala Release Notes +===================== + +This release of the RxScala bindings builds on the previous 0.15 release to make the Rx bindings for Scala +include all Rx types. In particular this release focuses on fleshing out the bindings for the `Subject` and `Scheduler` +types, as well as aligning the constructor functions for `Observable` with those in the RxJava. + +Expect to see ongoing additions to make the Scala binding match the equivalent underlying Java API, +as well as minor changes in the existing API as we keep fine-tuning the experience on our way to a V1.0 release. + +Observer +-------- + +In this release we have made the `asJavaObserver` property in `Observable[T]`as well the the factory method in the +companion object that takes an `rx.Observer` private to the Scala bindings package, thus properly hiding irrelevant +implementation details from the user-facing API. The `Observer[T]` trait now looks like a clean, native Scala type: + +```scala +trait Observer[-T] { + def onNext(value: T): Unit + def onError(error: Throwable): Unit + def onCompleted(): Unit +} + +object Observer {...} +``` + +To create an instance of a specific `Observer`, say `Observer[SensorEvent]` in user code, you can create a new instance +of the `Observer` trait by implementing any of the methods that you care about: +```scala + val printObserver = new Observer[SensorEvent] { + override def onNext(value: SensorEvent): Unit = {...value.toString...} + } +``` + or you can use one of the overloads of the companion `Observer` object by passing in implementations of the `onNext`, + `onError` or `onCompleted` methods. + +Note that typically you do not need to create an `Observer` since all of the methods that accept an `Observer[T]` +(for instance `subscribe`) usually come with overloads that accept the individual methods +`onNext`, `onError`, and `onCompleted` and will automatically create an `Observer` for you under the covers. + +While *technically* it is a breaking change make the `asJavaObserver` property private, you should probably not have +touched `asJavaObserver` in the first place. If you really feel you need to access the underlying `rx.Observer` +call `toJava`. + +Observable +---------- + +Just like for `Observer`, the `Observable` trait now also hides its `asJavaObservable` property and makes the constructor +function in the companion object that takes an `rx.Observable` private (but leaves the companion object itself public). +Again, while *technically* this is a breaking change, this should not have any influence on user code. + +```scala +trait Observable[+T] { + def subscribe(observer: Observer[T]): Subscription = {...} + def apply(observer: Observer[T]): Subscription = {...} + ... +} +object Observable { + def create[T](func: Observer[T] => Subscription): Observable[T] = {...} + ... +} +``` + +The major changes in `Observable` are wrt to the factory methods where too libral use of overloading of the `apply` +method hindered type inference and made Scala code look unnecessarily different than that in other language bindings. +All factory methods now have their own name corresponding to the Java and .NET operators +(plus overloads that take a `Scheduler`). + +* `def from[T](future: Future[T]): Observable[T]`, +* `def from[T](iterable: Iterable[T]): Observable[T]`, +* `def error[T](exception: Throwable): Observable[T]`, +* `def empty[T]: Observable[T]`, +* `def items[T](items: T*): Observable[T], +* Extension method on `toObservable: Observable[T]` on `List[T]`. + +In the *pre-release* of this version, we expose both `apply` and `create` for the mother of all creation functions. +We would like to solicit feedback which of these two names is preferred +(or both, but there is a high probability that only one will be chosen). + +* `def apply[T](subscribe: Observer[T]=>Subscription): Observable[T]` +* `def create[T](subscribe: Observer[T] => Subscription): Observable[T]` + +Subject +------- + +The `Subject` trait now also hides the underlying Java `asJavaSubject: rx.subjects.Subject[_ >: T, _<: T]` +and takes only a single *invariant* type parameter `T`. all existing implementations of `Subject` are parametrized +by a single type, and this reflects that reality. + +```scala +trait Subject[T] extends Observable[T] with Observer[T] {} +object Subject { + def apply(): Subject[T] = {...} +} +``` +For each kind of subject, there is a class with a private constructor and a companion object that you should use +to create a new kind of subject. The subjects that are available are: + +* `AsyncSubject[T]()`, +* `BehaviorSubject[T](value)`, +* `Subject[T]()`, +* `ReplaySubject[T]()`. + +The latter is still missing various overloads http://msdn.microsoft.com/en-us/library/hh211810(v=vs.103).aspx which +you can expect to appear once they are added to the underlying RxJava implementation. + +Compared with release 0.15.1, the breaking changes in `Subject` for this release are +making `asJavaSubject` private, and collapsing its type parameters, neither of these should cause trouble, +and renaming `PublishSubject` to `Subject`. + +Schedulers +---------- + +The biggest breaking change compared to the 0.15.1 release is giving `Scheduler` the same structure as the other types. +The trait itself remains unchanged, except that we made the underlying Java representation hidden as above. +as part of this reshuffling, the scheduler package has been renamed from `rx.lang.scala.concurrency` +to `rx.lang.scala.schedulers`. There is a high probability that this package renaming will also happen in RxJava. + +```scala +trait Scheduler {...} +``` + +In the previous release, you created schedulers by selecting them from the `Schedulers` object, +as in `Schedulers.immediate` or `Schedulers.newThread` where each would return an instance of the `Scheduler` trait. +However, several of the scheduler implementations have additional methods, such as the `TestScheduler`, +which already deviated from the pattern. + +In this release, we changed this to make scheduler more like `Subject` and provide a family of schedulers +that you create using their factory function: + +* `CurrentThreadScheduler()`, +* `ExecutorScheduler(executor)`, +* `ImmediateScheduler()`, +* `NewThreadScheduler()`, +* `ScheduledExecutorServiceScheduler(scheduledExecutorService)`, +* `TestScheduler()`, +* `ThreadPoolForComputationScheduler()`, +* `ThreadPoolForIOScheduler()`. + +In the future we expect that this list will grow further with new schedulers as they are imported from .NET +(http://msdn.microsoft.com/en-us/library/system.reactive.concurrency(v=vs.103).aspx). + +To make your code compile in the new release you will have to change all occurrences of `Schedulers.xxx` +into `XxxScheduler()`, and import `rx.lang.scala.schedulers` instead of `rx.lang.scala.schedulers`. + +Subscriptions +------------- + +The `Subscription` trait in Scala now has `isUnsubscribed` as a member, effectively collapsing the old `Subscription` +and `BooleanSubscription`, and the latter has been removed from the public surface. Pending a bug fix in RxJava, +`SerialSubscription` implements its own `isUnsubscribed`. + + +```scala +trait Subscription { + def unsubscribe(): Unit = { ... } + def isUnsubscribed: Boolean = ... +} + +object Subscription {...} + ``` + + To create a `Subscription` use one of the following factory methods: + + * `Subscription{...}`, `Subscription()`, + * `CompositeSubscription(subscriptions)`, + * `MultipleAssignmentSubscription()`, + * `SerialSubscription()`. + + In case you do feel tempted to call `new Subscription{...}` directly make sure you wire up `isUnsubscribed` + and `unsubscribe()` properly, but for all practical purposes you should just use one of the factory methods. + +Notifications +------------- + +All underlying wrapped `Java` types in the `Notification` trait are made private like all previous types. The companion +objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions: + +```scala +object Notification {...} +trait Notification[+T] { + override def equals(that: Any): Boolean = {...} + override def hashCode(): Int = {...} + def apply[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = {...} +} +``` +The nested companion objects of `Notification` now have both constructor (`apply`) and extractor (`unapply`) functions: +```scala +object Notification { + object OnNext { def apply(...){}; def unapply(...){...} } + object OnError { def apply(...){}; def unapply(...){...} } + object OnCompleted { def apply(...){}; def unapply(...){...} } +} +``` +To construct a `Notification`, you import `rx.lang.scala.Notification._` and use `OnNext("hello")`, +or `OnError(new Exception("Oops!"))`, or `OnCompleted()`. + +To pattern match on a notification you create a partial function like so: `case Notification.OnNext(v) => { ... v ... }`, +or you use the `apply` function to pass in functions for each possibility. + +There are no breaking changes for notifications. + +Java Interop Helpers +-------------------- + +Since the Scala traits *wrap* the underlying Java types, yoo may occasionally will have to wrap an unwrap +between the two representations. The `JavaConversion` object provides helper functions of the form `toJavaXXX` and +`toScalaXXX` for this purpose, properly hiding how precisely the wrapped types are stored. +Note the (un)wrap conversions are defined as implicits in Scala, but in the unlikely event that you do need them +be kind to the reader of your code and call them explicitly. + +```scala +object JavaConversions { + import language.implicitConversions + + implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = {...} + implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = {...} + implicit def toJavaSubscription(s: Subscription): rx.Subscription = {...} + implicit def toScalaSubscription(s: rx.Subscription): Subscription = {...} + implicit def scalaSchedulerToJavaScheduler(s: Scheduler): rx.Scheduler = {...} + implicit def javaSchedulerToScalaScheduler(s: rx.Scheduler): Scheduler = {...} + implicit def toJavaObserver[T](s: Observer[T]): rx.Observer[_ >: T] = {...} + implicit def toScalaObserver[T](s: rx.Observer[_ >: T]): Observer[T] = {...} + implicit def toJavaObservable[T](s: Observable[T]): rx.Observable[_ <: T] = {...} + implicit def toScalaObservable[T](observable: rx.Observable[_ <: T]): Observable[T] = {...} +} +``` \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala index 7a11bdf539..ceea9c3b8e 100644 --- a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/Olympics.scala @@ -21,8 +21,8 @@ import scala.concurrent.duration._ object Olympics { case class Medal(val year: Int, val games: String, val discipline: String, val medal: String, val athlete: String, val country: String) - def mountainBikeMedals: Observable[Medal] = Observable( - Observable( + def mountainBikeMedals: Observable[Medal] = Observable.items( + Observable.items( Medal(1996, "Atlanta 1996", "cross-country men", "Gold", "Bart BRENTJENS", "Netherlands"), Medal(1996, "Atlanta 1996", "cross-country women", "Gold", "Paola PEZZO", "Italy"), Medal(1996, "Atlanta 1996", "cross-country men", "Silver", "Thomas FRISCHKNECHT", "Switzerland"), @@ -31,7 +31,7 @@ object Olympics { Medal(1996, "Atlanta 1996", "cross-country women", "Bronze", "Susan DEMATTEI", "United States of America") ), fourYearsEmpty, - Observable( + Observable.items( Medal(2000, "Sydney 2000", "cross-country women", "Gold", "Paola PEZZO", "Italy"), Medal(2000, "Sydney 2000", "cross-country women", "Silver", "Barbara BLATTER", "Switzerland"), Medal(2000, "Sydney 2000", "cross-country women", "Bronze", "Marga FULLANA", "Spain"), @@ -40,7 +40,7 @@ object Olympics { Medal(2000, "Sydney 2000", "cross-country men", "Bronze", "Christoph SAUSER", "Switzerland") ), fourYearsEmpty, - Observable( + Observable.items( Medal(2004, "Athens 2004", "cross-country men", "Gold", "Julien ABSALON", "France"), Medal(2004, "Athens 2004", "cross-country men", "Silver", "Jose Antonio HERMIDA RAMOS", "Spain"), Medal(2004, "Athens 2004", "cross-country men", "Bronze", "Bart BRENTJENS", "Netherlands"), @@ -49,7 +49,7 @@ object Olympics { Medal(2004, "Athens 2004", "cross-country women", "Bronze", "Sabine SPITZ", "Germany") ), fourYearsEmpty, - Observable( + Observable.items( Medal(2008, "Beijing 2008", "cross-country women", "Gold", "Sabine SPITZ", "Germany"), Medal(2008, "Beijing 2008", "cross-country women", "Silver", "Maja WLOSZCZOWSKA", "Poland"), Medal(2008, "Beijing 2008", "cross-country women", "Bronze", "Irina KALENTYEVA", "Russian Federation"), @@ -58,7 +58,7 @@ object Olympics { Medal(2008, "Beijing 2008", "cross-country men", "Bronze", "Nino SCHURTER", "Switzerland") ), fourYearsEmpty, - Observable( + Observable.items( Medal(2012, "London 2012", "cross-country men", "Gold", "Jaroslav KULHAVY", "Czech Republic"), Medal(2012, "London 2012", "cross-country men", "Silver", "Nino SCHURTER", "Switzerland"), Medal(2012, "London 2012", "cross-country men", "Bronze", "Marco Aurelio FONTANA", "Italy"), @@ -80,7 +80,7 @@ object Olympics { // So we don't use this: // Observable.interval(fourYears).take(1).map(i => neverUsedDummyMedal).filter(m => false) // But we just return empty, which completes immediately - Observable() + Observable.empty[Medal] } } \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala index 3dbd9461fd..7bf832f58e 100644 --- a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/RxScalaDemo.scala @@ -30,7 +30,7 @@ import org.junit.Test import org.scalatest.junit.JUnitSuite import rx.lang.scala._ -import rx.lang.scala.concurrency._ +import rx.lang.scala.schedulers._ @Ignore // Since this doesn't do automatic testing, don't increase build time unnecessarily class RxScalaDemo extends JUnitSuite { @@ -69,14 +69,14 @@ class RxScalaDemo extends JUnitSuite { } @Test def testSwitchOnObservableOfInt() { - // Correctly rejected with error + // Correctly rejected with error // "Cannot prove that Observable[Int] <:< Observable[Observable[U]]" // val o = Observable(1, 2).switch } @Test def testObservableComparison() { - val first = Observable(10, 11, 12) - val second = Observable(10, 11, 12) + val first = Observable.from(List(10, 11, 12)) + val second = Observable.from(List(10, 11, 12)) val b1 = (first zip second) map (p => p._1 == p._2) forall (b => b) @@ -88,8 +88,8 @@ class RxScalaDemo extends JUnitSuite { } @Test def testObservableComparisonWithForComprehension() { - val first = Observable(10, 11, 12) - val second = Observable(10, 11, 12) + val first = Observable.from(List(10, 11, 12)) + val second = Observable.from(List(10, 11, 12)) val booleans = for ((n1, n2) <- (first zip second)) yield (n1 == n2) @@ -99,8 +99,8 @@ class RxScalaDemo extends JUnitSuite { } @Test def testStartWithIsUnnecessary() { - val before = Observable(-2, -1, 0) - val source = Observable(1, 2, 3) + val before = List(-2, -1, 0).toObservable + val source = List(1, 2, 3).toObservable println((before ++ source).toBlockingObservable.toList) } @@ -124,11 +124,11 @@ class RxScalaDemo extends JUnitSuite { @Test def fattenSomeExample() { // To merge some observables which are all known already: - Observable( + List( Observable.interval(200 millis), Observable.interval(400 millis), Observable.interval(800 millis) - ).flatten.take(12).toBlockingObservable.foreach(println(_)) + ).toObservable.flatten.take(12).toBlockingObservable.foreach(println(_)) } @Test def rangeAndBufferExample() { @@ -143,7 +143,7 @@ class RxScalaDemo extends JUnitSuite { } @Test def testReduce() { - assertEquals(10, Observable(1, 2, 3, 4).reduce(_ + _).toBlockingObservable.single) + assertEquals(10, List(1, 2, 3, 4).toObservable.reduce(_ + _).toBlockingObservable.single) } @Test def testForeach() { @@ -157,7 +157,7 @@ class RxScalaDemo extends JUnitSuite { } @Test def testForComprehension() { - val observables = Observable(Observable(1, 2, 3), Observable(10, 20, 30)) + val observables = List(List(1, 2, 3).toObservable, List(10, 20, 30).toObservable).toObservable val squares = (for (o <- observables; i <- o if i % 2 == 0) yield i*i) assertEquals(squares.toBlockingObservable.toList, List(4, 100, 400, 900)) } @@ -185,14 +185,14 @@ class RxScalaDemo extends JUnitSuite { } @Test def testGroupByThenFlatMap() { - val m = Observable(1, 2, 3, 4) + val m = List(1, 2, 3, 4).toObservable val g = m.groupBy(i => i % 2) val t = g.flatMap((p: (Int, Observable[Int])) => p._2) assertEquals(List(1, 2, 3, 4), t.toBlockingObservable.toList) } @Test def testGroupByThenFlatMapByForComprehension() { - val m = Observable(1, 2, 3, 4) + val m = List(1, 2, 3, 4).toObservable val g = m.groupBy(i => i % 2) val t = for ((i, o) <- g; n <- o) yield n assertEquals(List(1, 2, 3, 4), t.toBlockingObservable.toList) @@ -250,13 +250,13 @@ class RxScalaDemo extends JUnitSuite { } @Test def exampleWithoutPublish() { - val unshared = Observable(1 to 4) + val unshared = List(1 to 4).toObservable unshared.subscribe(n => println(s"subscriber 1 gets $n")) unshared.subscribe(n => println(s"subscriber 2 gets $n")) } @Test def exampleWithPublish() { - val unshared = Observable(1 to 4) + val unshared = List(1 to 4).toObservable val (startFunc, shared) = unshared.publish shared.subscribe(n => println(s"subscriber 1 gets $n")) shared.subscribe(n => println(s"subscriber 2 gets $n")) @@ -288,9 +288,9 @@ class RxScalaDemo extends JUnitSuite { } @Test def testSingleOption() { - assertEquals(None, Observable(1, 2).toBlockingObservable.singleOption) - assertEquals(Some(1), Observable(1) .toBlockingObservable.singleOption) - assertEquals(None, Observable() .toBlockingObservable.singleOption) + assertEquals(None, List(1, 2).toObservable.toBlockingObservable.singleOption) + assertEquals(Some(1), List(1).toObservable.toBlockingObservable.singleOption) + assertEquals(None, List().toObservable.toBlockingObservable.singleOption) } // We can't put a general average method into Observable.scala, because Scala's Numeric @@ -301,58 +301,58 @@ class RxScalaDemo extends JUnitSuite { } @Test def averageExample() { - println(doubleAverage(Observable()).toBlockingObservable.single) - println(doubleAverage(Observable(0)).toBlockingObservable.single) - println(doubleAverage(Observable(4.44)).toBlockingObservable.single) - println(doubleAverage(Observable(1, 2, 3.5)).toBlockingObservable.single) + println(doubleAverage(Observable.empty[Double]).toBlockingObservable.single) + println(doubleAverage(List(0.0).toObservable).toBlockingObservable.single) + println(doubleAverage(List(4.44).toObservable).toBlockingObservable.single) + println(doubleAverage(List(1, 2, 3.5).toObservable).toBlockingObservable.single) } @Test def testSum() { - assertEquals(10, Observable(1, 2, 3, 4).sum.toBlockingObservable.single) - assertEquals(6, Observable(4, 2).sum.toBlockingObservable.single) - assertEquals(0, Observable[Int]().sum.toBlockingObservable.single) + assertEquals(10, List(1, 2, 3, 4).toObservable.sum.toBlockingObservable.single) + assertEquals(6, List(4, 2).toObservable.sum.toBlockingObservable.single) + assertEquals(0, List[Int]().toObservable.sum.toBlockingObservable.single) } @Test def testProduct() { - assertEquals(24, Observable(1, 2, 3, 4).product.toBlockingObservable.single) - assertEquals(8, Observable(4, 2).product.toBlockingObservable.single) - assertEquals(1, Observable[Int]().product.toBlockingObservable.single) + assertEquals(24, List(1, 2, 3, 4).toObservable.product.toBlockingObservable.single) + assertEquals(8, List(4, 2).toObservable.product.toBlockingObservable.single) + assertEquals(1, List[Int]().toObservable.product.toBlockingObservable.single) } @Test def mapWithIndexExample() { // We don't need mapWithIndex because we already have zipWithIndex, which we can easily // combine with map: - Observable("a", "b", "c").zipWithIndex.map(pair => pair._1 + " has index " + pair._2) + List("a", "b", "c").toObservable.zipWithIndex.map(pair => pair._1 + " has index " + pair._2) .toBlockingObservable.foreach(println(_)) // Or even nicer with for-comprehension syntax: - (for ((letter, index) <- Observable("a", "b", "c").zipWithIndex) yield letter + " has index " + index) + (for ((letter, index) <- List("a", "b", "c").toObservable.zipWithIndex) yield letter + " has index " + index) .toBlockingObservable.foreach(println(_)) } // source Observables are all known: @Test def zip3Example() { - val o = Observable.zip(Observable(1, 2), Observable(10, 20), Observable(100, 200)) + val o = Observable.zip(List(1, 2).toObservable, List(10, 20).toObservable, List(100, 200).toObservable) (for ((n1, n2, n3) <- o) yield s"$n1, $n2 and $n3") .toBlockingObservable.foreach(println(_)) } // source Observables are in an Observable: @Test def zipManyObservableExample() { - val observables = Observable(Observable(1, 2), Observable(10, 20), Observable(100, 200)) + val observables = List(List(1, 2).toObservable, List(10, 20).toObservable, List(100, 200).toObservable).toObservable (for (seq <- Observable.zip(observables)) yield seq.mkString("(", ", ", ")")) .toBlockingObservable.foreach(println(_)) } @Test def takeFirstWithCondition() { val condition: Int => Boolean = _ >= 3 - assertEquals(3, Observable(1, 2, 3, 4).filter(condition).first.toBlockingObservable.single) + assertEquals(3, List(1, 2, 3, 4).toObservable.filter(condition).first.toBlockingObservable.single) } @Test def firstOrDefaultWithCondition() { val condition: Int => Boolean = _ >= 3 - assertEquals(3, Observable(1, 2, 3, 4).filter(condition).firstOrElse(10).toBlockingObservable.single) - assertEquals(10, Observable(-1, 0, 1).filter(condition).firstOrElse(10).toBlockingObservable.single) + assertEquals(3, List(1, 2, 3, 4).toObservable.filter(condition).firstOrElse(10).toBlockingObservable.single) + assertEquals(10, List(-1, 0, 1).toObservable.filter(condition).firstOrElse(10).toBlockingObservable.single) } def square(x: Int): Int = { @@ -379,9 +379,9 @@ class RxScalaDemo extends JUnitSuite { } @Test def toSortedList() { - assertEquals(Seq(7, 8, 9, 10), Observable(10, 7, 8, 9).toSeq.map(_.sorted).toBlockingObservable.single) + assertEquals(Seq(7, 8, 9, 10), List(10, 7, 8, 9).toObservable.toSeq.map(_.sorted).toBlockingObservable.single) val f = (a: Int, b: Int) => b < a - assertEquals(Seq(10, 9, 8, 7), Observable(10, 7, 8, 9).toSeq.map(_.sortWith(f)).toBlockingObservable.single) + assertEquals(Seq(10, 9, 8, 7), List(10, 7, 8, 9).toObservable.toSeq.map(_.sortWith(f)).toBlockingObservable.single) } @Test def timestampExample() { @@ -410,7 +410,7 @@ class RxScalaDemo extends JUnitSuite { @Test def materializeExample2() { import Notification._ - Observable(1, 2, 3).materialize.subscribe(n => n match { + List(1, 2, 3).toObservable.materialize.subscribe(n => n match { case OnNext(v) => println("Got value " + v) case OnCompleted() => println("Completed") case OnError(err) => println("Error: " + err.getMessage) @@ -418,17 +418,17 @@ class RxScalaDemo extends JUnitSuite { } @Test def elementAtReplacement() { - assertEquals("b", Observable("a", "b", "c").drop(1).first.toBlockingObservable.single) + assertEquals("b", List("a", "b", "c").toObservable.drop(1).first.toBlockingObservable.single) } @Test def elementAtOrDefaultReplacement() { - assertEquals("b", Observable("a", "b", "c").drop(1).firstOrElse("!").toBlockingObservable.single) - assertEquals("!!", Observable("a", "b", "c").drop(10).firstOrElse("!!").toBlockingObservable.single) + assertEquals("b", List("a", "b", "c").toObservable.drop(1).firstOrElse("!").toBlockingObservable.single) + assertEquals("!!", List("a", "b", "c").toObservable.drop(10).firstOrElse("!!").toBlockingObservable.single) } @Test def takeWhileWithIndexAlternative { val condition = true - Observable("a", "b").zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1) + List("a", "b").toObservable.zipWithIndex.takeWhile{case (elem, index) => condition}.map(_._1) } @Test def createExample() { diff --git a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala index bc1817bdf4..2e53f1afe9 100644 --- a/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala +++ b/language-adaptors/rxjava-scala/src/examples/scala/rx/lang/scala/examples/TestSchedulerExample.scala @@ -1,18 +1,19 @@ package rx.lang.scala.examples +import scala.concurrent.duration.DurationInt +import scala.language.postfixOps + import org.junit.Test +import org.mockito.Matchers._ +import org.mockito.Mockito._ import org.scalatest.junit.JUnitSuite -import scala.concurrent.duration._ -import scala.language.postfixOps -import rx.lang.scala.{ Observable, Observer } -import rx.lang.scala.concurrency.TestScheduler + +import rx.lang.scala._ +import rx.lang.scala.schedulers.TestScheduler class TestSchedulerExample extends JUnitSuite { @Test def testInterval() { - import org.mockito.Matchers._ - import org.mockito.Mockito._ - val scheduler = TestScheduler() // Use a Java Observer for Mockito val observer = mock(classOf[rx.Observer[Long]]) @@ -46,3 +47,5 @@ class TestSchedulerExample extends JUnitSuite { } } + + diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala index 276322f935..cc380c463c 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/JavaConversions.scala @@ -9,7 +9,7 @@ package rx.lang.scala object JavaConversions { import language.implicitConversions - implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = s.asJava + implicit def toJavaNotification[T](s: Notification[T]): rx.Notification[_ <: T] = s.asJavaNotification implicit def toScalaNotification[T](s: rx.Notification[_ <: T]): Notification[T] = Notification(s) @@ -29,8 +29,7 @@ object JavaConversions { implicit def toScalaObservable[T](observable: rx.Observable[_ <: T]): Observable[T] = { new Observable[T]{ - def asJavaObservable = observable + val asJavaObservable = observable } } - } \ No newline at end of file diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala index 8a254af08c..616fff22c2 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Notification.scala @@ -19,12 +19,50 @@ package rx.lang.scala * Emitted by Observables returned by [[rx.lang.scala.Observable.materialize]]. */ sealed trait Notification[+T] { - def asJava: rx.Notification[_ <: T] + private [scala] val asJavaNotification: rx.Notification[_ <: T] + override def equals(that: Any): Boolean = that match { - case other: Notification[_] => asJava.equals(other.asJava) + case other: Notification[_] => asJavaNotification.equals(other.asJavaNotification) case _ => false } - override def hashCode(): Int = asJava.hashCode() + override def hashCode(): Int = asJavaNotification.hashCode() + + /** + * Invokes the function corresponding to the notification. + * + * @param onNext + * The function to invoke for an [[rx.lang.scala.Notification.OnNext]] notification. + * @param onError + * The function to invoke for an [[rx.lang.scala.Notification.OnError]] notification. + * @param onCompleted + * The function to invoke for an [[rx.lang.scala.Notification.OnCompleted]] notification. + */ + def accept[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = { + this match { + case Notification.OnNext(value) => onNext(value) + case Notification.OnError(error) => onError(error) + case Notification.OnCompleted() => onCompleted() + } + } + + def apply[R](onNext: T=>R, onError: Throwable=>R, onCompleted: ()=>R): R = + accept(onNext, onError, onCompleted) + + /** + * Invokes the observer corresponding to the notification + * + * @param observer + * The observer that to observe the notification + */ + def accept(observer: Observer[T]): Unit = { + this match { + case Notification.OnNext(value) => observer.onNext(value) + case Notification.OnError(error) => observer.onError(error) + case Notification.OnCompleted() => observer.onCompleted() + } + } + + def apply(observer: Observer[T]): Unit = accept(observer) } /** @@ -34,15 +72,15 @@ sealed trait Notification[+T] { * {{{ * import Notification._ * Observable(1, 2, 3).materialize.subscribe(n => n match { - * case OnNext(v) => println("Got value " + v) + * case OnNext(v) => println("Got value " + v) * case OnCompleted() => println("Completed") - * case OnError(err) => println("Error: " + err.getMessage) + * case OnError(err) => println("Error: " + err.getMessage) * }) * }}} */ object Notification { - def apply[T](n: rx.Notification[_ <: T]): Notification[T] = n.getKind match { + private [scala] def apply[T](n: rx.Notification[_ <: T]): Notification[T] = n.getKind match { case rx.Notification.Kind.OnNext => new OnNext(n) case rx.Notification.Kind.OnCompleted => new OnCompleted(n) case rx.Notification.Kind.OnError => new OnError(n) @@ -50,56 +88,89 @@ object Notification { // OnNext, OnError, OnCompleted are not case classes because we don't want pattern matching // to extract the rx.Notification - - class OnNext[+T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { - def value: T = asJava.getValue - override def toString = s"OnNext($value)" - } - + object OnNext { + /** + * Constructor for onNext notifications. + * + * @param value + * The item passed to the onNext method. + */ def apply[T](value: T): Notification[T] = { Notification(new rx.Notification[T](value)) } - def unapply[U](n: Notification[U]): Option[U] = n match { - case n2: OnNext[U] => Some(n.asJava.getValue) + /** + * Extractor for onNext notifications. + * @param notification + * The [[rx.lang.scala.Notification]] to be destructed. + * @return + * The item contained in this notification. + */ + def unapply[U](notification: Notification[U]): Option[U] = notification match { + case onNext: OnNext[U] => Some(onNext.value) case _ => None } } - - class OnError[+T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { - def error: Throwable = asJava.getThrowable - override def toString = s"OnError($error)" + + class OnNext[+T] private[scala] (val asJavaNotification: rx.Notification[_ <: T]) extends Notification[T] { + def value: T = asJavaNotification.getValue + override def toString = s"OnNext($value)" } - + object OnError { + /** + * Constructor for onError notifications. + * + * @param error + * The exception passed to the onNext method. + */ def apply[T](error: Throwable): Notification[T] = { Notification(new rx.Notification[T](error)) } - def unapply[U](n: Notification[U]): Option[Throwable] = n match { - case n2: OnError[U] => Some(n2.asJava.getThrowable) + /** + * Destructor for onError notifications. + * + * @param notification + * The [[rx.lang.scala.Notification]] to be deconstructed + * @return + * The [[java.lang.Throwable]] value contained in this notification. + */ + def unapply[U](notification: Notification[U]): Option[Throwable] = notification match { + case onError: OnError[U] => Some(onError.error) case _ => None } } - - class OnCompleted[T](val asJava: rx.Notification[_ <: T]) extends Notification[T] { - override def toString = "OnCompleted()" + + class OnError[+T] private[scala] (val asJavaNotification: rx.Notification[_ <: T]) extends Notification[T] { + def error: Throwable = asJavaNotification.getThrowable + override def toString = s"OnError($error)" } - + object OnCompleted { + /** + * Constructor for onCompleted notifications. + */ def apply[T](): Notification[T] = { Notification(new rx.Notification()) } - def unapply[U](n: Notification[U]): Option[Unit] = n match { - case n2: OnCompleted[U] => Some() + /** + * Extractor for onCompleted notifications. + */ + def unapply[U](notification: Notification[U]): Option[Unit] = notification match { + case onCompleted: OnCompleted[U] => Some() case _ => None } } + class OnCompleted[T] private[scala](val asJavaNotification: rx.Notification[_ <: T]) extends Notification[T] { + override def toString = "OnCompleted()" + } + } diff --git a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala index 8d6ffbb48e..6db7d41e25 100644 --- a/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala +++ b/language-adaptors/rxjava-scala/src/main/scala/rx/lang/scala/Observable.scala @@ -19,6 +19,8 @@ package rx.lang.scala import rx.util.functions.FuncN import rx.Observable.OnSubscribeFunc + + /** * The Observable interface that implements the Reactive Pattern. * @@ -79,7 +81,16 @@ trait Observable[+T] import ImplicitFunctionConversions._ import JavaConversions._ - def asJavaObservable: rx.Observable[_ <: T] + private [scala] val asJavaObservable: rx.Observable[_ <: T] + + /** + * $subscribeObserverMain + * + * @return $subscribeAllReturn + */ + def subscribe(): Subscription = { + asJavaObservable.subscribe() + } /** * $subscribeObserverMain @@ -102,9 +113,17 @@ trait Observable[+T] asJavaObservable.subscribe(observer.asJavaObserver) } + /** + * $subscribeObserverMain + * + * @param observer $subscribeObserverParamObserver + * @return $subscribeAllReturn + */ + def apply(observer: Observer[T]): Subscription = subscribe(observer) + /** * $subscribeCallbacksMainNoNotifications - * `` + * * @param onNext $subscribeCallbacksParamOnNext * @return $subscribeAllReturn */ @@ -190,14 +209,13 @@ trait Observable[+T] * * @param subject * the `rx.lang.scala.subjects.Subject` to push source items into - * @tparam R - * result type * @return a pair of a start function and an [[rx.lang.scala.Observable]] such that when the start function * is called, the Observable starts to push results into the specified Subject */ - def multicast[R](subject: rx.lang.scala.Subject[T, R]): (() => Subscription, Observable[R]) = { - val javaCO = asJavaObservable.multicast[R](subject.asJavaSubject) - (() => javaCO.connect(), toScalaObservable[R](javaCO)) + def multicast[R >: T](subject: rx.lang.scala.Subject[R]): (() => Subscription, Observable[R]) = { + val s: rx.subjects.Subject[_ >: T, _<: R] = subject.asJavaSubject + val javaCO: rx.observables.ConnectableObservable[R] = asJavaObservable.multicast(s) + (() => javaCO.connect(), toScalaObservable(javaCO)) } /** @@ -525,7 +543,7 @@ trait Observable[+T] def window[Closing](closings: () => Observable[Closing]): Observable[Observable[T]] = { val func : Func0[_ <: rx.Observable[_ <: Closing]] = closings().asJavaObservable val o1: rx.Observable[_ <: rx.Observable[_]] = asJavaObservable.window[Closing](func) - val o2 = Observable[rx.Observable[_]](o1).map((x: rx.Observable[_]) => { + val o2 = Observable.items(o1).map((x: rx.Observable[_]) => { val x2 = x.asInstanceOf[rx.Observable[_ <: T]] toScalaObservable[T](x2) }) @@ -836,7 +854,7 @@ trait Observable[+T] // with =:= it does not work, why? def dematerialize[U](implicit evidence: Observable[T] <:< Observable[Notification[U]]): Observable[U] = { val o1: Observable[Notification[U]] = this - val o2: Observable[rx.Notification[_ <: U]] = o1.map(_.asJava) + val o2: Observable[rx.Notification[_ <: U]] = o1.map(_.asJavaNotification) val o3 = o2.asJavaObservable.dematerialize[U]() toScalaObservable[U](o3) } @@ -1140,7 +1158,7 @@ trait Observable[+T] */ def forall(predicate: T => Boolean): Observable[Boolean] = { // type mismatch; found : rx.Observable[java.lang.Boolean] required: rx.Observable[_ <: scala.Boolean] - // new Observable[Boolean](asJava.all(predicate)) + // new Observable[Boolean](asJavaNotification.all(predicate)) // it's more fun in Scala: this.map(predicate).foldLeft(true)(_ && _) } @@ -1906,13 +1924,13 @@ object Observable { private[scala] def jObsOfListToScObsOfSeq[T](jObs: rx.Observable[_ <: java.util.List[T]]): Observable[Seq[T]] = { - val oScala1: Observable[java.util.List[T]] = new Observable[java.util.List[T]]{ def asJavaObservable = jObs } + val oScala1: Observable[java.util.List[T]] = new Observable[java.util.List[T]]{ val asJavaObservable = jObs } oScala1.map((lJava: java.util.List[T]) => lJava.asScala) } private[scala] def jObsOfJObsToScObsOfScObs[T](jObs: rx.Observable[_ <: rx.Observable[_ <: T]]): Observable[Observable[T]] = { - val oScala1: Observable[rx.Observable[_ <: T]] = new Observable[rx.Observable[_ <: T]]{ def asJavaObservable = jObs } + val oScala1: Observable[rx.Observable[_ <: T]] = new Observable[rx.Observable[_ <: T]]{ val asJavaObservable = jObs } oScala1.map((oJava: rx.Observable[_ <: T]) => oJava) } @@ -1966,6 +1984,48 @@ object Observable { toScalaObservable[T](rx.Observable.error(exception)) } + /** + * Returns an Observable that emits no data to the [[rx.lang.scala.Observer]] and + * immediately invokes its [[rx.lang.scala.Observer#onCompleted onCompleted]] method + * with the specified scheduler. + *
+ *
+ *
+ * @param scheduler the scheduler to call the
+ [[rx.lang.scala.Observer#onCompleted onCompleted]] method
+ * @param T the type of the items (ostensibly) emitted by the Observable
+ * @return an Observable that returns no data to the [[rx.lang.scala.Observer]] and
+ * immediately invokes the [[rx.lang.scala.Observer]]r's
+ * [[rx.lang.scala.Observer#onCompleted onCompleted]] method with the
+ * specified scheduler
+ * @see RxJava Wiki: empty()
+ * @see MSDN: Observable.Empty Method (IScheduler)
+ */
+ def empty[T]: Observable[T] = {
+ toScalaObservable(rx.Observable.empty[T]())
+ }
+
+ /**
+ * Returns an Observable that emits no data to the [[rx.lang.scala.Observer]] and
+ * immediately invokes its [[rx.lang.scala.Observer#onCompleted onCompleted]] method
+ * with the specified scheduler.
+ *
+ *
+ *
+ * @param scheduler the scheduler to call the
+ [[rx.lang.scala.Observer#onCompleted onCompleted]] method
+ * @param T the type of the items (ostensibly) emitted by the Observable
+ * @return an Observable that returns no data to the [[rx.lang.scala.Observer]] and
+ * immediately invokes the [[rx.lang.scala.Observer]]r's
+ * [[rx.lang.scala.Observer#onCompleted onCompleted]] method with the
+ * specified scheduler
+ * @see RxJava Wiki: empty()
+ * @see MSDN: Observable.Empty Method (IScheduler)
+ */
+ def empty[T](scheduler: Scheduler): Observable[T] = {
+ toScalaObservable(rx.Observable.empty[T](scalaSchedulerToJavaScheduler(scheduler)))
+ }
+
/**
* Converts a sequence of values into an Observable.
*
@@ -1982,7 +2042,7 @@ object Observable {
* resulting Observable
* @return an Observable that emits each item in the source Array
*/
- def apply[T](items: T*): Observable[T] = {
+ def items[T](items: T*): Observable[T] = {
toScalaObservable[T](rx.Observable.from(items.toIterable.asJava))
}
@@ -2015,7 +2075,7 @@ object Observable {
* the sequence before it completes.
*
* @param iterable the source `Iterable` sequence
- * @param
")
}
- def formatScalaCol(s: String): String =
+ def formatScalaCol(s: String): String =
if (s.startsWith("[") && s.endsWith("]")) s.drop(1).dropRight(1) else "`" + s + "`"
def escape(s: String) = s.replaceAllLiterally("[", "<").replaceAllLiterally("]", ">")
-
+
println("""
## Comparison of Scala Observable and Java Observable
-
-Note:
+
+Note:
* This table contains both static methods and instance methods.
* If a signature is too long, move your mouse over it to get the full signature.
-
+
| Java Method | Scala Method |
|-------------|--------------|""")
-
+
val ps = setTodoForMissingMethods(correspondence)
(for (((javaName, scalaCol), pairs) <- ps.groupBy(groupingKey(_)).toList.sortBy(_._1._1)) yield {
@@ -350,5 +350,5 @@ Note:
println(s"\nThis table was generated on ${Calendar.getInstance().getTime}.")
println(s"**Do not edit**. Instead, edit `${getClass.getCanonicalName}`.")
}
-
+
}
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala
new file mode 100644
index 0000000000..e2822405b2
--- /dev/null
+++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ConstructorTest.scala
@@ -0,0 +1,20 @@
+package rx.lang.scala
+import scala.language.postfixOps
+import org.junit.Assert._
+import org.junit.Test
+import org.scalatest.junit.JUnitSuite
+
+class ConstructorTest extends JUnitSuite {
+
+ @Test def toObservable() {
+ val xs = List(1,2,3).toObservable.toBlockingObservable.toList
+ assertEquals(List(1,2,3), xs)
+
+ val ys = Observable.from(List(1,2,3)).toBlockingObservable.toList
+ assertEquals(List(1,2,3), xs)
+
+ val zs = Observable.items(1,2,3).toBlockingObservable.toList
+ assertEquals(List(1,2,3), xs)
+
+ }
+}
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala
new file mode 100644
index 0000000000..759ca8b7ec
--- /dev/null
+++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/NotificationTests.scala
@@ -0,0 +1,43 @@
+package rx.lang.scala
+
+
+import org.junit.{Assert, Test}
+import org.junit.Assert._
+import org.scalatest.junit.JUnitSuite
+import scala.concurrent.duration._
+import scala.language.postfixOps
+import org.mockito.Mockito._
+import org.mockito.Matchers._
+import rx.lang.scala.Notification.{OnCompleted, OnError, OnNext}
+
+
+class NotificationTests extends JUnitSuite {
+ @Test
+ def creation() {
+
+ val onNext = OnNext(42)
+ assertEquals(42, onNext match { case OnNext(value) => value })
+
+ val oops = new Exception("Oops")
+ val onError = OnError(oops)
+ assertEquals(oops, onError match { case OnError(error) => error })
+
+ val onCompleted = OnCompleted()
+ assertEquals((), onCompleted match { case OnCompleted() => () })
+ }
+
+ @Test
+ def accept() {
+
+ val onNext = OnNext(42)
+ assertEquals(42, onNext(x=>42, e=>4711,()=>13))
+
+ val oops = new Exception("Oops")
+ val onError = OnError(oops)
+ assertEquals(4711, onError(x=>42, e=>4711,()=>13))
+
+ val onCompleted = OnCompleted()
+ assertEquals(13, onCompleted(x=>42, e=>4711,()=>13))
+
+ }
+}
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala
index 48c4423373..0b755833da 100644
--- a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala
+++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/ObservableTest.scala
@@ -6,6 +6,12 @@ import scala.concurrent.ExecutionContext.Implicits.global
import org.junit.Assert._
import org.junit.{ Ignore, Test }
import org.scalatest.junit.JUnitSuite
+import scala.concurrent.duration._
+import scala.language.postfixOps
+import rx.lang.scala.schedulers.TestScheduler
+import rx.lang.scala.subjects.BehaviorSubject
+import org.mockito.Mockito._
+import org.mockito.Matchers._
class ObservableTests extends JUnitSuite {
@@ -15,7 +21,7 @@ class ObservableTests extends JUnitSuite {
def testCovariance = {
//println("hey, you shouldn't run this test")
- val o1: Observable[Nothing] = Observable()
+ val o1: Observable[Nothing] = Observable.empty
val o2: Observable[Int] = o1
val o3: Observable[App] = o1
val o4: Observable[Any] = o2
@@ -26,28 +32,28 @@ class ObservableTests extends JUnitSuite {
@Test
def testDematerialize() {
- val o = Observable(1, 2, 3)
+ val o = List(1, 2, 3).toObservable
val mat = o.materialize
val demat = mat.dematerialize
- // correctly rejected:
+ //correctly rejected:
//val wrongDemat = Observable("hello").dematerialize
assertEquals(demat.toBlockingObservable.toIterable.toList, List(1, 2, 3))
- }
+}
// Test that Java's firstOrDefault propagates errors.
// If this changes (i.e. it suppresses errors and returns default) then Scala's firstOrElse
// should be changed accordingly.
@Test def testJavaFirstOrDefault() {
- assertEquals(1, rx.Observable.from(1, 2).firstOrDefault(10).toBlockingObservable.single)
- assertEquals(10, rx.Observable.empty().firstOrDefault(10).toBlockingObservable.single)
+ assertEquals(1, rx.Observable.from(1, 2).firstOrDefault(10).toBlockingObservable().single)
+ assertEquals(10, rx.Observable.empty().firstOrDefault(10).toBlockingObservable().single)
val msg = "msg6251"
var receivedMsg = "none"
try {
- rx.Observable.error(new Exception(msg)).firstOrDefault(10).toBlockingObservable.single
+ rx.Observable.error(new Exception(msg)).firstOrDefault(10).toBlockingObservable().single
} catch {
- case e: Exception => receivedMsg = e.getCause.getMessage
+ case e: Exception => receivedMsg = e.getCause().getMessage()
}
assertEquals(receivedMsg, msg)
}
@@ -55,17 +61,17 @@ class ObservableTests extends JUnitSuite {
@Test def testFirstOrElse() {
def mustNotBeCalled: String = sys.error("this method should not be called")
def mustBeCalled: String = "this is the default value"
- assertEquals("hello", Observable("hello").firstOrElse(mustNotBeCalled).toBlockingObservable.single)
- assertEquals("this is the default value", Observable().firstOrElse(mustBeCalled).toBlockingObservable.single)
+ assertEquals("hello", Observable.items("hello").firstOrElse(mustNotBeCalled).toBlockingObservable.single)
+ assertEquals("this is the default value", Observable.empty.firstOrElse(mustBeCalled).toBlockingObservable.single)
}
- @Test def testFirstOrElseWithError() {
+ @Test def testTestWithError() {
val msg = "msg6251"
var receivedMsg = "none"
try {
Observable.error[Int](new Exception(msg)).firstOrElse(10).toBlockingObservable.single
} catch {
- case e: Exception => receivedMsg = e.getCause.getMessage
+ case e: Exception => receivedMsg = e.getCause().getMessage()
}
assertEquals(receivedMsg, msg)
}
@@ -106,10 +112,4 @@ class ObservableTests extends JUnitSuite {
}
*/
- @Test def testTest() = {
- val a: Observable[Int] = Observable()
- assertEquals(4, Observable(1, 2, 3, 4).toBlockingObservable.toIterable.last)
- //println("This UnitTestSuite.testTest() for rx.lang.scala.Observable")
- }
-
}
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubjectTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubjectTests.scala
new file mode 100644
index 0000000000..50773d5582
--- /dev/null
+++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubjectTests.scala
@@ -0,0 +1,308 @@
+package rx.lang.scala
+
+import org.junit.{Assert, Test}
+import org.scalatest.junit.JUnitSuite
+import scala.concurrent.duration._
+import scala.language.postfixOps
+import rx.lang.scala.schedulers.TestScheduler
+import rx.lang.scala.subjects.{AsyncSubject, ReplaySubject, BehaviorSubject}
+import org.mockito.Mockito._
+import org.mockito.Matchers._
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertFalse
+import org.junit.Ignore
+import org.junit.Test
+import org.scalatest.junit.JUnitSuite
+
+class SubjectTest extends JUnitSuite {
+
+ @Test def SubjectIsAChannel() {
+
+ var lastA: Integer = null
+ var errorA: Throwable = null
+ var completedA: Boolean = false
+ val observerA = Observer[Integer](
+ (next: Integer) => { lastA = next },
+ (error: Throwable) => { errorA = error },
+ () => { completedA = true }
+ )
+
+ var lastB: Integer = null
+ var errorB: Throwable = null
+ var completedB: Boolean = false
+ val observerB = Observer[Integer](
+ (next: Integer) => { lastB = next },
+ (error: Throwable) => { errorB = error },
+ () => { completedB = true }
+ )
+
+ var lastC: Integer = null
+ var errorC: Throwable = null
+ var completedC: Boolean = false
+ val observerC = Observer[Integer](
+ (next: Integer) => { lastC = next },
+ (error: Throwable) => { errorC = error },
+ () => { completedC = true }
+ )
+
+ val channel: Subject[Integer] = Subject[Integer]()
+
+ val a = channel(observerA)
+ val b = channel(observerB)
+
+ assertEquals(null, lastA)
+ assertEquals(null, lastB)
+
+ channel.onNext(42)
+
+ assertEquals(42, lastA)
+ assertEquals(42, lastB)
+
+ a.unsubscribe()
+ channel.onNext(4711)
+
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+
+ channel.onCompleted()
+
+ assertFalse(completedA)
+ assertTrue(completedB)
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+
+ val c = channel.subscribe(observerC)
+ channel.onNext(13)
+
+ assertEquals(null, lastC)
+ assertTrue(completedC)
+
+ assertFalse(completedA)
+ assertTrue(completedB)
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+
+ channel.onError(new Exception("!"))
+
+ assertEquals(null, lastC)
+ assertTrue(completedC)
+
+ assertFalse(completedA)
+ assertTrue(completedB)
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+ }
+
+ @Test def ReplaySubjectIsAChannel() {
+
+ val channel = ReplaySubject[Integer]
+
+ var lastA: Integer = null
+ var errorA, completedA: Boolean = false
+ val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true })
+
+ var lastB: Integer = null
+ var errorB, completedB: Boolean = false
+
+ val b = channel(new Observer[Integer] {
+ override def onNext(value: Integer): Unit = { lastB = value }
+ override def onError(error: Throwable): Unit = { errorB = true }
+ override def onCompleted(): Unit = { completedB = true }
+ })
+
+ channel.onNext(42)
+
+ assertEquals(42, lastA)
+ assertEquals(42, lastB)
+
+ a.unsubscribe()
+
+ channel.onNext(4711)
+
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+
+ channel.onCompleted()
+
+ assertEquals(42, lastA)
+ assertFalse(completedA)
+ assertFalse(errorA)
+
+ assertEquals(4711, lastB)
+ assertTrue(completedB)
+ assertFalse(errorB)
+
+ var lastC: Integer = null
+ var errorC, completedC: Boolean = false
+ val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true })
+
+ assertEquals(4711, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+
+ channel.onNext(13)
+
+ assertEquals(42, lastA)
+ assertFalse(completedA)
+ assertFalse(errorA)
+
+ assertEquals(4711, lastB)
+ assertTrue(completedB)
+ assertFalse(errorB)
+
+ assertEquals(4711, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+
+ channel.onError(new Exception("Boom"))
+
+ assertEquals(42, lastA)
+ assertFalse(completedA)
+ assertFalse(errorA)
+
+ assertEquals(4711, lastB)
+ assertTrue(completedB)
+ assertFalse(errorB)
+
+ assertEquals(4711, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+ }
+
+ @Test def BehaviorSubjectIsACache() {
+
+ val channel = BehaviorSubject(2013)
+
+ var lastA: Integer = null
+ var errorA, completedA: Boolean = false
+ val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true })
+
+ var lastB: Integer = null
+ var errorB, completedB: Boolean = false
+ val b = channel.subscribe(x => { lastB = x}, e => { errorB = true} , () => { completedB = true })
+
+ assertEquals(2013, lastA)
+ assertEquals(2013, lastB)
+
+ channel.onNext(42)
+
+ assertEquals(42, lastA)
+ assertEquals(42, lastB)
+
+ a.unsubscribe()
+
+ channel.onNext(4711)
+
+ assertEquals(42, lastA)
+ assertEquals(4711, lastB)
+
+ channel.onCompleted()
+
+ var lastC: Integer = null
+ var errorC, completedC: Boolean = false
+ val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true })
+
+ assertEquals(null, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+
+ channel.onNext(13)
+
+ assertEquals(42, lastA)
+ assertFalse(completedA)
+ assertFalse(errorA)
+
+ assertEquals(4711, lastB)
+ assertTrue(completedB)
+ assertFalse(errorB)
+
+ assertEquals(null, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+
+ channel.onError(new Exception("Boom"))
+
+ assertEquals(42, lastA)
+ assertFalse(completedA)
+ assertFalse(errorA)
+
+ assertEquals(4711, lastB)
+ assertTrue(completedB)
+ assertFalse(errorB)
+
+ assertEquals(null, lastC)
+ assertTrue(completedC)
+ assertFalse(errorC)
+
+ }
+
+ @Test def AsyncSubjectIsAFuture() {
+
+ val channel = AsyncSubject[Int]()
+
+ var lastA: Integer = null
+ var errorA, completedA: Boolean = false
+ val a = channel.subscribe(x => { lastA = x}, e => { errorA = true} , () => { completedA = true })
+
+ var lastB: Integer = null
+ var errorB, completedB: Boolean = false
+ val b = channel.subscribe(x => { lastB = x}, e => { errorB = true} , () => { completedB = true })
+
+ channel.onNext(42)
+
+ Assert.assertEquals(null, lastA)
+ Assert.assertEquals(null, lastB)
+
+ a.unsubscribe()
+ channel.onNext(4711)
+ channel.onCompleted()
+
+ Assert.assertEquals(null, lastA)
+ Assert.assertFalse(completedA)
+ Assert.assertFalse(errorA)
+
+ Assert.assertEquals(4711, lastB)
+ Assert.assertTrue(completedB)
+ Assert.assertFalse(errorB)
+
+
+ var lastC: Integer = null
+ var errorC, completedC: Boolean = false
+ val c = channel.subscribe(x => { lastC = x}, e => { errorC = true} , () => { completedC = true })
+
+ Assert.assertEquals(4711, lastC)
+ Assert.assertTrue(completedC)
+ Assert.assertFalse(errorC)
+
+ channel.onNext(13)
+
+ Assert.assertEquals(null, lastA)
+ Assert.assertFalse(completedA)
+ Assert.assertFalse(errorA)
+
+ Assert.assertEquals(4711, lastB)
+ Assert.assertTrue(completedB)
+ Assert.assertFalse(errorB)
+
+ Assert.assertEquals(4711, lastC)
+ Assert.assertTrue(completedC)
+ Assert.assertFalse(errorC)
+
+ channel.onError(new Exception("Boom"))
+
+ Assert.assertEquals(null, lastA)
+ Assert.assertFalse(completedA)
+ Assert.assertFalse(errorA)
+
+ Assert.assertEquals(4711, lastB)
+ Assert.assertTrue(completedB)
+ Assert.assertFalse(errorB)
+
+ Assert.assertEquals(4711, lastC)
+ Assert.assertTrue(completedC)
+ Assert.assertFalse(errorC)
+
+ }
+
+}
\ No newline at end of file
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala
new file mode 100644
index 0000000000..71ea4d4a6e
--- /dev/null
+++ b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/SubscriptionTests.scala
@@ -0,0 +1,171 @@
+package rx.lang.scala
+
+
+import org.junit.{Assert, Test}
+import org.junit.Assert
+import org.scalatest.junit.JUnitSuite
+import scala.concurrent.duration._
+import scala.language.postfixOps
+import org.mockito.Mockito._
+import org.mockito.Matchers._
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assert.assertFalse
+import rx.lang.scala.subscriptions.{SerialSubscription, MultipleAssignmentSubscription, CompositeSubscription}
+
+
+
+class SubscriptionTests extends JUnitSuite {
+ @Test
+ def subscriptionCreate() {
+
+ val subscription = Subscription()
+
+ assertFalse(subscription.isUnsubscribed)
+
+ subscription.unsubscribe()
+
+ assertTrue(subscription.isUnsubscribed)
+ }
+
+ @Test
+ def subscriptionUnsubscribeIdempotent() {
+
+ var called = false
+
+ val subscription = Subscription{ called = !called }
+
+ assertFalse(called)
+ assertFalse(subscription.isUnsubscribed)
+
+ subscription.unsubscribe()
+
+ assertTrue(called)
+ assertTrue(subscription.isUnsubscribed)
+
+ subscription.unsubscribe()
+
+ assertTrue(called)
+ assertTrue(subscription.isUnsubscribed)
+ }
+
+ @Test
+ def compositeSubscriptionAdd() {
+
+ val s0 = Subscription()
+ val s1 = Subscription()
+
+ val composite = CompositeSubscription()
+
+ assertFalse(composite.isUnsubscribed)
+
+ composite += s0
+ composite += s1
+
+ composite.unsubscribe()
+
+ assertTrue(composite.isUnsubscribed)
+ assertTrue(s0.isUnsubscribed)
+ assertTrue(s1.isUnsubscribed)
+
+ val s2 = Subscription{}
+
+ assertFalse(s2.isUnsubscribed)
+
+ composite += s2
+
+ assertTrue(s2.isUnsubscribed)
+
+ }
+
+ @Test
+ def compositeSubscriptionRemove() {
+
+ val s0 = Subscription()
+ val composite = CompositeSubscription()
+
+ composite += s0
+ assertFalse(s0.isUnsubscribed)
+
+ composite -= s0
+ assertTrue(s0.isUnsubscribed)
+
+ composite.unsubscribe()
+
+ assertTrue(composite.isUnsubscribed)
+ assertTrue(s0.isUnsubscribed)
+ }
+
+ @Test
+ def multiAssignmentSubscriptionAdd() {
+
+ val s0 = Subscription()
+ val s1 = Subscription()
+ val multiple = MultipleAssignmentSubscription()
+
+ assertFalse(multiple.isUnsubscribed)
+ assertFalse(s0.isUnsubscribed)
+ assertFalse(s1.isUnsubscribed)
+
+ multiple.subscription = s0
+
+ assertFalse(s0.isUnsubscribed)
+ assertFalse(s1.isUnsubscribed)
+
+ multiple.subscription = s1
+
+ assertFalse(s0.isUnsubscribed) // difference with SerialSubscription
+ assertFalse(s1.isUnsubscribed)
+
+ multiple.unsubscribe()
+
+ assertTrue(multiple.isUnsubscribed)
+ assertFalse(s0.isUnsubscribed)
+ assertTrue(s1.isUnsubscribed)
+
+ val s2 = Subscription()
+
+ assertFalse(s2.isUnsubscribed)
+
+ multiple.subscription = s2
+
+ assertTrue(s2.isUnsubscribed)
+ assertFalse(s0.isUnsubscribed)
+ }
+
+ @Test
+ def serialSubscriptionAdd() {
+
+ val s0 = Subscription()
+ val s1 = Subscription()
+ val serial = SerialSubscription()
+
+ assertFalse(serial.isUnsubscribed)
+ assertFalse(s0.isUnsubscribed)
+ assertFalse(s1.isUnsubscribed)
+
+ serial.subscription = s0
+
+ assertFalse(s0.isUnsubscribed)
+ assertFalse(s1.isUnsubscribed)
+
+ serial.subscription = s1
+
+ assertTrue(s0.isUnsubscribed) // difference with MultipleAssignmentSubscription
+ assertFalse(s1.isUnsubscribed)
+
+ serial.unsubscribe()
+
+ assertTrue(serial.isUnsubscribed)
+ assertTrue(s1.isUnsubscribed)
+
+ val s2 = Subscription()
+
+ assertFalse(s2.isUnsubscribed)
+
+ serial.subscription = s2
+
+ assertTrue(s2.isUnsubscribed)
+ }
+
+}
diff --git a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala b/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala
deleted file mode 100644
index 4309967c0a..0000000000
--- a/language-adaptors/rxjava-scala/src/test/scala/rx/lang/scala/subscriptions/SubscriptionTests.scala
+++ /dev/null
@@ -1,118 +0,0 @@
-package rx.lang.scala.subscriptions
-
-import org.junit.Assert._
-import org.junit.Test
-import org.scalatest.junit.JUnitSuite
-
-import rx.lang.scala.Subscription
-
-class SubscriptionTests extends JUnitSuite {
-
- @Test
- def anonymousSubscriptionCreate() {
- val subscription = Subscription{}
- assertNotNull(subscription)
- }
-
- @Test
- def anonymousSubscriptionDispose() {
- var unsubscribed = false
- val subscription = Subscription{ unsubscribed = true }
- assertFalse(unsubscribed)
- subscription.unsubscribe()
- assertTrue(unsubscribed)
- }
-
- @Test
- def emptySubscription() {
- val subscription = Subscription()
- subscription.unsubscribe()
- }
-
- @Test
- def booleanSubscription() {
- val subscription = BooleanSubscription()
- assertFalse(subscription.isUnsubscribed)
- subscription.unsubscribe()
- assertTrue(subscription.isUnsubscribed)
- subscription.unsubscribe()
- assertTrue(subscription.isUnsubscribed)
- }
-
- @Test
- def compositeSubscriptionAdd() {
-
- var u0 = false
- val s0 = BooleanSubscription{ u0 = true }
-
- var u1 = false
- val s1 = Subscription{ u1 = true }
-
- val composite = CompositeSubscription()
-
- assertFalse(composite.isUnsubscribed)
-
- composite += s0
- composite += s1
-
- composite.unsubscribe()
-
- assertTrue(composite.isUnsubscribed)
- assertTrue(s0.isUnsubscribed)
- assertTrue(u0)
- assertTrue(u1)
-
- val s2 = BooleanSubscription()
- assertFalse(s2.isUnsubscribed)
- composite += s2
- assertTrue(s2.isUnsubscribed)
-
- }
-
- @Test
- def compositeSubscriptionRemove() {
-
- val s0 = BooleanSubscription()
- val composite = CompositeSubscription()
-
- composite += s0
- assertFalse(s0.isUnsubscribed)
- composite -= s0
- assertTrue(s0.isUnsubscribed)
-
- composite.unsubscribe()
-
- assertTrue(composite.isUnsubscribed)
- }
-
- @Test
- def multiAssignmentSubscriptionAdd() {
-
- val s0 = BooleanSubscription()
- val s1 = BooleanSubscription()
- val multiple = MultipleAssignmentSubscription()
-
- assertFalse(multiple.isUnsubscribed)
-
- multiple.subscription = s0
- assertEquals(s0.asJavaSubscription, multiple.subscription.asJavaSubscription)
-
- multiple.subscription = s1
- assertEquals(s1.asJavaSubscription, multiple.subscription.asJavaSubscription)
-
- assertFalse(s0.isUnsubscribed)
- assertFalse(s1.isUnsubscribed)
-
- multiple.unsubscribe()
-
- assertTrue(multiple.isUnsubscribed)
- assertFalse(s0.isUnsubscribed)
- assertTrue(s1.isUnsubscribed)
-
- val s2 = BooleanSubscription()
- assertFalse(s2.isUnsubscribed)
- multiple.subscription = s2
- assertTrue(s2.isUnsubscribed)
- }
-
-}