@Override public Observable<T> call(Observable<T> observable) { return observable .lift(new Indexed<T>(1L)) .filter(pair -> pair.getLeft() % 2 == 1) .map(pair -> pair.getRight()); }
@Test public void testUnsubscribeAfterTake() { final Subscription s = mock(Subscription.class); TestObservableFunc f = new TestObservableFunc("one", "two", "three"); Observable<String> w = Observable.create(f); @SuppressWarnings("unchecked") Observer<String> observer = mock(Observer.class); Subscriber<String> subscriber = Subscribers.from(observer); subscriber.add(s); Observable<String> take = w.lift(new OperatorTake<String>(1)); take.subscribe(subscriber); // wait for the Observable to complete try { f.t.join(); } catch (Throwable e) { e.printStackTrace(); fail(e.getMessage()); } System.out.println("TestObservable thread finished"); verify(observer, times(1)).onNext("one"); verify(observer, never()).onNext("two"); verify(observer, never()).onNext("three"); verify(observer, times(1)).onCompleted(); verify(s, times(1)).unsubscribe(); verifyNoMoreInteractions(observer); }
/** * Executes the interceptor chain for the passed request and response. * * @param request Request to be executed. * @param response Response to be populated. * @param keyEvaluationContext The context for {@link InterceptorKey} evaluation. * @return The final result of execution after executing all the inbound and outbound interceptors * and the router. */ public Observable<Void> execute(final I request, final O response, C keyEvaluationContext) { final ExecutionContext context = new ExecutionContext(request, keyEvaluationContext); InboundInterceptor<I, O> nextIn = context.nextIn(request); Observable<Void> startingPoint; if (null != nextIn) { startingPoint = nextIn.in(request, response); } else if (context.invokeRouter()) { startingPoint = router.handle(request, response); } else { return Observable.error( new IllegalStateException("No router defined.")); // No router defined. } return startingPoint.lift( new Observable.Operator<Void, Void>() { @Override public Subscriber<? super Void> call(Subscriber<? super Void> child) { SerialSubscription subscription = new SerialSubscription(); ChainSubscriber chainSubscriber = new ChainSubscriber(subscription, context, request, response, child); subscription.set(chainSubscriber); child.add(subscription); return chainSubscriber; } }); }
/** * Rechunks the strings based on a regex pattern and works on infinite stream. * * <p>resplit(["boo:an", "d:foo"], ":") --> ["boo", "and", "foo"] resplit(["boo:an", "d:foo"], * "o") --> ["b", "", ":and:f", "", ""] * * <p>See {@link Pattern} * * @param src * @param regex * @return */ public static Observable<String> split(final Observable<String> src, String regex) { final Pattern pattern = Pattern.compile(regex); return src.lift( new Operator<String, String>() { @Override public Subscriber<? super String> call(final Subscriber<? super String> o) { return new Subscriber<String>(o) { private String leftOver = null; @Override public void onCompleted() { output(leftOver); if (!o.isUnsubscribed()) o.onCompleted(); } @Override public void onError(Throwable e) { output(leftOver); if (!o.isUnsubscribed()) o.onError(e); } @Override public void onNext(String segment) { String[] parts = pattern.split(segment, -1); if (leftOver != null) parts[0] = leftOver + parts[0]; for (int i = 0; i < parts.length - 1; i++) { String part = parts[i]; output(part); } leftOver = parts[parts.length - 1]; } private int emptyPartCount = 0; /** * when limit == 0 trailing empty parts are not emitted. * * @param part */ private void output(String part) { if (part.isEmpty()) { emptyPartCount++; } else { for (; emptyPartCount > 0; emptyPartCount--) if (!o.isUnsubscribed()) o.onNext(""); if (!o.isUnsubscribed()) o.onNext(part); } } }; } }); }
@Test public void testTake2() { Observable<String> w = Observable.from(Arrays.asList("one", "two", "three")); Observable<String> take = w.lift(new OperatorTake<String>(1)); @SuppressWarnings("unchecked") Observer<String> observer = mock(Observer.class); take.subscribe(observer); verify(observer, times(1)).onNext("one"); verify(observer, never()).onNext("two"); verify(observer, never()).onNext("three"); verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); }
@Test public void testSimpleObject() { TestSubscriber<Person> subscriber = new TestSubscriber<>(); observable.lift(new GsonConverter<Person>()).subscribe(subscriber); subscriber.assertNoErrors(); subscriber.assertTerminalEvent(); List<Person> persons = subscriber.getOnNextEvents(); assertEquals(1, persons.size()); Person person = persons.get(0); assertEquals("John", person.name); assertEquals(25, person.age); assertTrue(person.isDev); }
@Test public void testTakeZeroDoesntLeakError() { final AtomicBoolean subscribed = new AtomicBoolean(false); final AtomicBoolean unSubscribed = new AtomicBoolean(false); Observable<String> source = Observable.create( new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> observer) { subscribed.set(true); observer.add( new Subscription() { @Override public void unsubscribe() { unSubscribed.set(true); } @Override public boolean isUnsubscribed() { return unSubscribed.get(); } }); observer.onError(new Throwable("test failed")); } }); @SuppressWarnings("unchecked") Observer<String> observer = mock(Observer.class); source.lift(new OperatorTake<String>(0)).subscribe(observer); assertTrue("source subscribed", subscribed.get()); assertTrue("source unsubscribed", unSubscribed.get()); verify(observer, never()).onNext(anyString()); // even though onError is called we take(0) so shouldn't see it verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); verifyNoMoreInteractions(observer); }
@Test public void testTakeDoesntLeakErrors() { Observable<String> source = Observable.create( new Observable.OnSubscribe<String>() { @Override public void call(Subscriber<? super String> observer) { observer.onNext("one"); observer.onError(new Throwable("test failed")); } }); @SuppressWarnings("unchecked") Observer<String> observer = mock(Observer.class); source.lift(new OperatorTake<String>(1)).subscribe(observer); verify(observer, times(1)).onNext("one"); // even though onError is called we take(1) so shouldn't see it verify(observer, never()).onError(any(Throwable.class)); verify(observer, times(1)).onCompleted(); verifyNoMoreInteractions(observer); }
/** * Concatenates the sequence of values by adding a separator between them and emitting the result * once the source completes. * * <p>The conversion from the value type to String is performed via {@link * java.lang.String#valueOf(java.lang.Object)} calls. * * <p>For example: * * <pre> * Observable<Object> source = Observable.from("a", 1, "c"); * Observable<String> result = join(source, ", "); * </pre> * * will yield a single element equal to "a, 1, c". * * @param source the source sequence of CharSequence values * @param separator the separator to a * @return an Observable which emits a single String value having the concatenated values of the * source observable with the separator between elements */ public static <T> Observable<String> join( final Observable<T> source, final CharSequence separator) { return source.lift( new Operator<String, T>() { @Override public Subscriber<T> call(final Subscriber<? super String> o) { return new Subscriber<T>(o) { boolean mayAddSeparator; StringBuilder b = new StringBuilder(); @Override public void onCompleted() { String str = b.toString(); b = null; if (!o.isUnsubscribed()) o.onNext(str); if (!o.isUnsubscribed()) o.onCompleted(); } @Override public void onError(Throwable e) { b = null; if (!o.isUnsubscribed()) o.onError(e); } @Override public void onNext(Object t) { if (mayAddSeparator) { b.append(separator); } mayAddSeparator = true; b.append(String.valueOf(t)); } }; } }); }
/** * Decodes a stream the multibyte chunks into a stream of strings that works on infinite streams * and where handles when a multibyte character spans two chunks. This method allows for more * control over how malformed and unmappable characters are handled. * * @param src * @param charsetDecoder * @return */ public static Observable<String> decode( final Observable<byte[]> src, final CharsetDecoder charsetDecoder) { return src.lift( new Operator<String, byte[]>() { @Override public Subscriber<? super byte[]> call(final Subscriber<? super String> o) { return new Subscriber<byte[]>(o) { private ByteBuffer leftOver = null; @Override public void onCompleted() { if (process(null, leftOver, true)) o.onCompleted(); } @Override public void onError(Throwable e) { if (process(null, leftOver, true)) o.onError(e); } @Override public void onNext(byte[] bytes) { process(bytes, leftOver, false); } public boolean process(byte[] next, ByteBuffer last, boolean endOfInput) { ByteBuffer bb; if (last != null) { if (next != null) { // merge leftover in front of the next bytes bb = ByteBuffer.allocate(last.remaining() + next.length); bb.put(last); bb.put(next); bb.flip(); } else { // next == null bb = last; } } else { // last == null if (next != null) { bb = ByteBuffer.wrap(next); } else { // next == null return true; } } CharBuffer cb = CharBuffer.allocate((int) (bb.limit() * charsetDecoder.averageCharsPerByte())); CoderResult cr = charsetDecoder.decode(bb, cb, endOfInput); cb.flip(); if (cr.isError()) { try { cr.throwException(); } catch (CharacterCodingException e) { o.onError(e); return false; } } if (bb.remaining() > 0) { leftOver = bb; } else { leftOver = null; } String string = cb.toString(); if (!string.isEmpty()) o.onNext(string); return true; } }; } }); }