001package org.reactivestreams.example.unicast; 002 003import org.reactivestreams.Subscriber; 004import org.reactivestreams.Subscription; 005 006/** 007 * SyncSubscriber is an implementation of Reactive Streams `Subscriber`, 008 * it runs synchronously (on the Publisher's thread) and requests one element 009 * at a time and invokes a user-defined method to process each element. 010 * 011 * NOTE: The code below uses a lot of try-catches to show the reader where exceptions can be expected, and where they are forbidden. 012 */ 013public abstract class SyncSubscriber<T> implements Subscriber<T> { 014 private Subscription subscription; // Obeying rule 3.1, we make this private! 015 private boolean done = false; 016 017 @Override public void onSubscribe(final Subscription s) { 018 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Subscription` is `null` 019 if (s == null) throw null; 020 021 if (subscription != null) { // If someone has made a mistake and added this Subscriber multiple times, let's handle it gracefully 022 try { 023 s.cancel(); // Cancel the additional subscription 024 } catch(final Throwable t) { 025 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 026 (new IllegalStateException(s + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); 027 } 028 } else { 029 // We have to assign it locally before we use it, if we want to be a synchronous `Subscriber` 030 // Because according to rule 3.10, the Subscription is allowed to call `onNext` synchronously from within `request` 031 subscription = s; 032 try { 033 // If we want elements, according to rule 2.1 we need to call `request` 034 // And, according to rule 3.2 we are allowed to call this synchronously from within the `onSubscribe` method 035 s.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time 036 } catch(final Throwable t) { 037 // Subscription.request is not allowed to throw according to rule 3.16 038 (new IllegalStateException(s + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); 039 } 040 } 041 } 042 043 @Override public void onNext(final T element) { 044 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec 045 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onNext prior to onSubscribe.")).printStackTrace(System.err); 046 } else { 047 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `element` is `null` 048 if (element == null) throw null; 049 050 if (!done) { // If we aren't already done 051 try { 052 if (foreach(element)) { 053 try { 054 subscription.request(1); // Our Subscriber is unbuffered and modest, it requests one element at a time 055 } catch (final Throwable t) { 056 // Subscription.request is not allowed to throw according to rule 3.16 057 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.16 by throwing an exception from request.", t)).printStackTrace(System.err); 058 } 059 } else { 060 done(); 061 } 062 } catch (final Throwable t) { 063 done(); 064 try { 065 onError(t); 066 } catch (final Throwable t2) { 067 //Subscriber.onError is not allowed to throw an exception, according to rule 2.13 068 (new IllegalStateException(this + " violated the Reactive Streams rule 2.13 by throwing an exception from onError.", t2)).printStackTrace(System.err); 069 } 070 } 071 } 072 } 073 } 074 075 // Showcases a convenience method to idempotently marking the Subscriber as "done", so we don't want to process more elements 076 // herefor we also need to cancel our `Subscription`. 077 private void done() { 078 //On this line we could add a guard against `!done`, but since rule 3.7 says that `Subscription.cancel()` is idempotent, we don't need to. 079 done = true; // If we `foreach` throws an exception, let's consider ourselves done (not accepting more elements) 080 try { 081 subscription.cancel(); // Cancel the subscription 082 } catch(final Throwable t) { 083 //Subscription.cancel is not allowed to throw an exception, according to rule 3.15 084 (new IllegalStateException(subscription + " violated the Reactive Streams rule 3.15 by throwing an exception from cancel.", t)).printStackTrace(System.err); 085 } 086 } 087 088 // This method is left as an exercise to the reader/extension point 089 // Returns whether more elements are desired or not, and if no more elements are desired 090 protected abstract boolean foreach(final T element); 091 092 @Override public void onError(final Throwable t) { 093 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec 094 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onError prior to onSubscribe.")).printStackTrace(System.err); 095 } else { 096 // As per rule 2.13, we need to throw a `java.lang.NullPointerException` if the `Throwable` is `null` 097 if (t == null) throw null; 098 // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 099 // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 100 } 101 } 102 103 @Override public void onComplete() { 104 if (subscription == null) { // Technically this check is not needed, since we are expecting Publishers to conform to the spec 105 (new IllegalStateException("Publisher violated the Reactive Streams rule 1.09 signalling onComplete prior to onSubscribe.")).printStackTrace(System.err); 106 } else { 107 // Here we are not allowed to call any methods on the `Subscription` or the `Publisher`, as per rule 2.3 108 // And anyway, the `Subscription` is considered to be cancelled if this method gets called, as per rule 2.4 109 } 110 } 111}