From 73fec02254dd8e7129b674c541690f8fa7d324df Mon Sep 17 00:00:00 2001 From: Mairbek Khadikov Date: Wed, 27 Feb 2013 20:29:00 +0200 Subject: [PATCH 1/2] Implemented single and singleOrDefault methods --- .../rx/lang/groovy/ObservableTests.groovy | 11 + rxjava-core/src/main/java/rx/Observable.java | 214 ++++++++++++++++++ .../java/rx/util/functions/Functions.java | 14 ++ 3 files changed, 239 insertions(+) diff --git a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy index 8d4d4b656a..15dac20e3b 100644 --- a/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy +++ b/language-adaptors/rxjava-groovy/src/test/groovy/rx/lang/groovy/ObservableTests.groovy @@ -233,6 +233,17 @@ def class ObservableTests { } } + @Test + public void testSingle1() { + def s = Observable.toObservable("one").single({ x -> x.length() == 3}) + assertEquals("one", s) + } + + @Test(expected = IllegalStateException.class) + public void testSingle2() { + Observable.toObservable("one", "two").single({ x -> x.length() == 3}) + } + def class AsyncObservable implements Func1, Subscription> { public Subscription call(final Observer observer) { diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index 066134598c..31fc0a6c99 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -408,6 +408,66 @@ public void call(Object args) { }); } + /** + * Returns the only element of an observable sequence and throws an exception if there is not exactly one element in the observable sequence. + * + * @return The single element in the observable sequence. + */ + public T single() { + return single(this); + } + + /** + * Returns the only element of an observable sequence that matches the predicate and throws an exception if there is not exactly one element in the observable sequence. + * + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence. + */ + public T single(Func1 predicate) { + return single(this, predicate); + } + + /** + * Returns the only element of an observable sequence that matches the predicate and throws an exception if there is not exactly one element in the observable sequence. + * + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence. + */ + public T single(Object predicate) { + return single(this, predicate); + } + + /** + * Returns the only element of an observable sequence, or a default value if the observable sequence is empty. + * + * @param defaultValue default value for a sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public T singleOrDefault(T defaultValue) { + return singleOrDefault(this, defaultValue); + } + + /** + * Returns the only element of an observable sequence that matches the predicate, or a default value if no value is found. + * @param defaultValue default value for a sequence. + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public T singleOrDefault(T defaultValue, Func1 predicate) { + return singleOrDefault(this, defaultValue, predicate); + } + + /** + * Returns the only element of an observable sequence that matches the predicate, or a default value if no value is found. + * + * @param defaultValue default value for a sequence. + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public T singleOrDefault(T defaultValue, Object predicate) { + return singleOrDefault(this, defaultValue, predicate); + } + /** * Allow the {@link RxJavaErrorHandler} to receive the exception from onError. * @@ -1593,6 +1653,119 @@ public Iterator iterator() { }; } + /** + * Returns the only element of an observable sequence and throws an exception if there is not exactly one element in the observable sequence. + * + * @param that + * the source Observable + * @return The single element in the observable sequence. + */ + public static T single(Observable that) { + return single(that, Functions.alwaysTrue()); + } + + /** + * Returns the only element of an observable sequence that matches the predicate and throws an exception if there is not exactly one element in the observable sequence. + * + * @param that + * the source Observable + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence. + */ + public static T single(Observable that, Func1 predicate) { + return singleOrDefault(that, false, null, predicate); + } + + /** + * Returns the only element of an observable sequence that matches the predicate and throws an exception if there is not exactly one element in the observable sequence. + * + * @param that + * the source Observable + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence. + */ + public static T single(Observable that, Object predicate) { + final FuncN _f = Functions.from(predicate); + + return single(that, new Func1() { + @Override + public Boolean call(T t) { + return (Boolean) _f.call(t); + } + }); + } + + /** + * Returns the only element of an observable sequence, or a default value if the observable sequence is empty. + * + * @param that + * the source Observable + * @param defaultValue default value for a sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public static T singleOrDefault(Observable that, T defaultValue) { + return singleOrDefault(that, defaultValue, Functions.alwaysTrue()); + } + + /** + * Returns the only element of an observable sequence that matches the predicate, or a default value if no value is found. + * @param that + * the source Observable + * @param defaultValue default value for a sequence. + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public static T singleOrDefault(Observable that, T defaultValue, Func1 predicate) { + return singleOrDefault(that, true, defaultValue, predicate); + } + + /** + * Returns the only element of an observable sequence that matches the predicate, or a default value if no value is found. + * + * @param that + * the source Observable + * @param defaultValue default value for a sequence. + * @param predicate A predicate function to evaluate for elements in the sequence. + * @return The single element in the observable sequence, or a default value if no value is found. + */ + public static T singleOrDefault(Observable that, T defaultValue, Object predicate) { + final FuncN _f = Functions.from(predicate); + + return singleOrDefault(that, defaultValue, new Func1() { + @Override + public Boolean call(T t) { + return (Boolean) _f.call(t); + } + }); + } + + private static T singleOrDefault(Observable that, boolean hasDefault, T defaultVal, Func1 predicate) { + Iterator it = that.toIterable().iterator(); + + if (!it.hasNext()) { + if (hasDefault) { + return defaultVal; + } + throw new IllegalStateException("Expected single entry. Actually empty stream."); + } + + T result = it.next(); + + if (it.hasNext()) { + throw new IllegalStateException("Expected single entry. Actually more than one entry."); + } + + if (!predicate.call(result)) { + if (hasDefault) { + return defaultVal; + } + throw new IllegalStateException("Last value should match the predicate"); + } + + return result; + } + + /** * Converts an Iterable sequence to an Observable sequence. * @@ -2743,6 +2916,47 @@ public Subscription call(Observer observer) { } + @Test + public void testSingle() { + Observable observable = toObservable("one"); + assertEquals("one", observable.single()); + } + + @Test + public void testSingleDefault() { + Observable observable = toObservable(); + assertEquals("default", observable.singleOrDefault("default")); + } + + @Test(expected = IllegalStateException.class) + public void testSingleWrong() { + Observable observable = toObservable(1, 2); + observable.single(); + } + + @Test(expected = IllegalStateException.class) + public void testSingleWrongPredicate() { + Observable observable = toObservable(-1); + observable.single(new Func1() { + @Override + public Boolean call(Integer args) { + return args > 0; + } + }); + } + + @Test + public void testSingleDefaultWrongPredicate() { + Observable observable = toObservable("one"); + String result = observable.singleOrDefault("default", new Func1() { + @Override + public Boolean call(String args) { + return args.length() > 3; + } + }); + assertEquals("default", result); + } + private static class TestException extends RuntimeException { } diff --git a/rxjava-core/src/main/java/rx/util/functions/Functions.java b/rxjava-core/src/main/java/rx/util/functions/Functions.java index 6f6d03bcd6..3267e0f540 100644 --- a/rxjava-core/src/main/java/rx/util/functions/Functions.java +++ b/rxjava-core/src/main/java/rx/util/functions/Functions.java @@ -550,4 +550,18 @@ public Void call(Object... args) { }; } + @SuppressWarnings("unchecked") + public static Func1 alwaysTrue() { + return (Func1) AlwaysTrue.INSTANCE; + } + + private enum AlwaysTrue implements Func1 { + INSTANCE; + + @Override + public Boolean call(Object o) { + return true; + } + } + } From 3cfc1567b5481fb2569e2ef00f49a83d496eee3c Mon Sep 17 00:00:00 2001 From: Mairbek Khadikov Date: Thu, 28 Feb 2013 12:44:44 +0200 Subject: [PATCH 2/2] Updated single/singleOrDefault according to review request --- rxjava-core/src/main/java/rx/Observable.java | 51 ++++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/rxjava-core/src/main/java/rx/Observable.java b/rxjava-core/src/main/java/rx/Observable.java index 31fc0a6c99..6593575e3f 100644 --- a/rxjava-core/src/main/java/rx/Observable.java +++ b/rxjava-core/src/main/java/rx/Observable.java @@ -1661,7 +1661,7 @@ public Iterator iterator() { * @return The single element in the observable sequence. */ public static T single(Observable that) { - return single(that, Functions.alwaysTrue()); + return singleOrDefault(that, false, null); } /** @@ -1673,7 +1673,7 @@ public static T single(Observable that) { * @return The single element in the observable sequence. */ public static T single(Observable that, Func1 predicate) { - return singleOrDefault(that, false, null, predicate); + return single(that.filter(predicate)); } /** @@ -1704,7 +1704,7 @@ public Boolean call(T t) { * @return The single element in the observable sequence, or a default value if no value is found. */ public static T singleOrDefault(Observable that, T defaultValue) { - return singleOrDefault(that, defaultValue, Functions.alwaysTrue()); + return singleOrDefault(that, true, defaultValue); } /** @@ -1716,7 +1716,7 @@ public static T singleOrDefault(Observable that, T defaultValue) { * @return The single element in the observable sequence, or a default value if no value is found. */ public static T singleOrDefault(Observable that, T defaultValue, Func1 predicate) { - return singleOrDefault(that, true, defaultValue, predicate); + return singleOrDefault(that.filter(predicate), defaultValue); } /** @@ -1739,7 +1739,7 @@ public Boolean call(T t) { }); } - private static T singleOrDefault(Observable that, boolean hasDefault, T defaultVal, Func1 predicate) { + private static T singleOrDefault(Observable that, boolean hasDefault, T defaultVal) { Iterator it = that.toIterable().iterator(); if (!it.hasNext()) { @@ -1755,13 +1755,6 @@ private static T singleOrDefault(Observable that, boolean hasDefault, T d throw new IllegalStateException("Expected single entry. Actually more than one entry."); } - if (!predicate.call(result)) { - if (hasDefault) { - return defaultVal; - } - throw new IllegalStateException("Last value should match the predicate"); - } - return result; } @@ -2928,6 +2921,23 @@ public void testSingleDefault() { assertEquals("default", observable.singleOrDefault("default")); } + @Test(expected = IllegalStateException.class) + public void testSingleDefaultWithMoreThanOne() { + Observable observable = toObservable("one", "two", "three"); + observable.singleOrDefault("default"); + } + + @Test + public void testSingleWithPredicateDefault() { + Observable observable = toObservable("one", "two", "four"); + assertEquals("four", observable.single(new Func1() { + @Override + public Boolean call(String s) { + return s.length() == 4; + } + })); + } + @Test(expected = IllegalStateException.class) public void testSingleWrong() { Observable observable = toObservable(1, 2); @@ -2946,17 +2956,28 @@ public Boolean call(Integer args) { } @Test - public void testSingleDefaultWrongPredicate() { - Observable observable = toObservable("one"); + public void testSingleDefaultPredicateMatchesNothing() { + Observable observable = toObservable("one", "two"); String result = observable.singleOrDefault("default", new Func1() { @Override public Boolean call(String args) { - return args.length() > 3; + return args.length() == 4; } }); assertEquals("default", result); } + @Test(expected = IllegalStateException.class) + public void testSingleDefaultPredicateMatchesMoreThanOne() { + Observable observable = toObservable("one", "two"); + String result = observable.singleOrDefault("default", new Func1() { + @Override + public Boolean call(String args) { + return args.length() == 3; + } + }); + } + private static class TestException extends RuntimeException { }