/** * Copyright 2014 Netflix, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package rx.internal.operators; import java.util.*; import rx.*; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; import rx.exceptions.Exceptions; import rx.functions.*; import rx.observers.*; import rx.subjects.*; import rx.subscriptions.*; /** * Correlates two sequences when they overlap and groups the results. * * @see MSDN: Observable.GroupJoin * @param the left value type * @param the right value type * @param the value type of the left duration * @param the value type of the right duration * @param the result value type */ public final class OnSubscribeGroupJoin implements OnSubscribe { final Observable left; final Observable right; final Func1> leftDuration; final Func1> rightDuration; final Func2, ? extends R> resultSelector; public OnSubscribeGroupJoin( Observable left, Observable right, Func1> leftDuration, Func1> rightDuration, Func2, ? extends R> resultSelector) { this.left = left; this.right = right; this.leftDuration = leftDuration; this.rightDuration = rightDuration; this.resultSelector = resultSelector; } @Override public void call(Subscriber child) { ResultManager ro = new ResultManager(new SerializedSubscriber(child)); child.add(ro); ro.init(); } /** Manages sub-observers and subscriptions. */ final class ResultManager extends HashMap>implements Subscription { // HashMap aspect of `this` refers to `leftMap` private static final long serialVersionUID = -3035156013812425335L; final RefCountSubscription cancel; final Subscriber subscriber; final CompositeSubscription group; /** Guarded by this. */ int leftIds; /** Guarded by this. */ int rightIds; /** Guarded by this. */ final Map rightMap = new HashMap(); // NOPMD /** Guarded by this. */ boolean leftDone; /** Guarded by this. */ boolean rightDone; public ResultManager(Subscriber subscriber) { super(); this.subscriber = subscriber; this.group = new CompositeSubscription(); this.cancel = new RefCountSubscription(group); } public void init() { Subscriber s1 = new LeftObserver(); Subscriber s2 = new RightObserver(); group.add(s1); group.add(s2); left.unsafeSubscribe(s1); right.unsafeSubscribe(s2); } @Override public void unsubscribe() { cancel.unsubscribe(); } @Override public boolean isUnsubscribed() { return cancel.isUnsubscribed(); } Map> leftMap() { return this; } /** * Notify everyone and cleanup. * @param e the exception */ void errorAll(Throwable e) { List> list; synchronized (ResultManager.this) { list = new ArrayList>(leftMap().values()); leftMap().clear(); rightMap.clear(); } for (Observer o : list) { o.onError(e); } subscriber.onError(e); cancel.unsubscribe(); } /** * Notify only the main subscriber and cleanup. * @param e the exception */ void errorMain(Throwable e) { synchronized (ResultManager.this) { leftMap().clear(); rightMap.clear(); } subscriber.onError(e); cancel.unsubscribe(); } void complete(List> list) { if (list != null) { for (Observer o : list) { o.onCompleted(); } subscriber.onCompleted(); cancel.unsubscribe(); } } /** Observe the left source. */ final class LeftObserver extends Subscriber { @Override public void onNext(T1 args) { try { int id; Subject subj = PublishSubject.create(); Observer subjSerial = new SerializedObserver(subj); synchronized (ResultManager.this) { id = leftIds++; leftMap().put(id, subjSerial); } Observable window = Observable.create(new WindowObservableFunc(subj, cancel)); Observable duration = leftDuration.call(args); Subscriber d1 = new LeftDurationObserver(id); group.add(d1); duration.unsafeSubscribe(d1); R result = resultSelector.call(args, window); List rightMapValues; synchronized (ResultManager.this) { rightMapValues = new ArrayList(rightMap.values()); } subscriber.onNext(result); for (T2 t2 : rightMapValues) { subjSerial.onNext(t2); } } catch (Throwable t) { Exceptions.throwOrReport(t, this); } } @Override public void onCompleted() { List> list = null; synchronized (ResultManager.this) { leftDone = true; if (rightDone) { list = new ArrayList>(leftMap().values()); leftMap().clear(); rightMap.clear(); } } complete(list); } @Override public void onError(Throwable e) { errorAll(e); } } /** Observe the right source. */ final class RightObserver extends Subscriber { @Override public void onNext(T2 args) { try { int id; synchronized (ResultManager.this) { id = rightIds++; rightMap.put(id, args); } Observable duration = rightDuration.call(args); Subscriber d2 = new RightDurationObserver(id); group.add(d2); duration.unsafeSubscribe(d2); List> list; synchronized (ResultManager.this) { list = new ArrayList>(leftMap().values()); } for (Observer o : list) { o.onNext(args); } } catch (Throwable t) { Exceptions.throwOrReport(t, this); } } @Override public void onCompleted() { List> list = null; synchronized (ResultManager.this) { rightDone = true; if (leftDone) { list = new ArrayList>(leftMap().values()); leftMap().clear(); rightMap.clear(); } } complete(list); } @Override public void onError(Throwable e) { errorAll(e); } } /** Observe left duration and apply termination. */ final class LeftDurationObserver extends Subscriber { final int id; boolean once = true; public LeftDurationObserver(int id) { this.id = id; } @Override public void onCompleted() { if (once) { once = false; Observer gr; synchronized (ResultManager.this) { gr = leftMap().remove(id); } if (gr != null) { gr.onCompleted(); } group.remove(this); } } @Override public void onError(Throwable e) { errorMain(e); } @Override public void onNext(D1 args) { onCompleted(); } } /** Observe right duration and apply termination. */ final class RightDurationObserver extends Subscriber { final int id; boolean once = true; public RightDurationObserver(int id) { this.id = id; } @Override public void onCompleted() { if (once) { once = false; synchronized (ResultManager.this) { rightMap.remove(id); } group.remove(this); } } @Override public void onError(Throwable e) { errorMain(e); } @Override public void onNext(D2 args) { onCompleted(); } } } /** * The reference-counted window observable. * Subscribes to the underlying Observable by using a reference-counted * subscription. */ final static class WindowObservableFunc implements OnSubscribe { final RefCountSubscription refCount; final Observable underlying; public WindowObservableFunc(Observable underlying, RefCountSubscription refCount) { this.refCount = refCount; this.underlying = underlying; } @Override public void call(Subscriber t1) { Subscription ref = refCount.get(); WindowSubscriber wo = new WindowSubscriber(t1, ref); wo.add(ref); underlying.unsafeSubscribe(wo); } /** Observe activities on the window. */ final class WindowSubscriber extends Subscriber { final Subscriber subscriber; private final Subscription ref; public WindowSubscriber(Subscriber subscriber, Subscription ref) { super(subscriber); this.subscriber = subscriber; this.ref = ref; } @Override public void onNext(T args) { subscriber.onNext(args); } @Override public void onError(Throwable e) { subscriber.onError(e); ref.unsubscribe(); } @Override public void onCompleted() { subscriber.onCompleted(); ref.unsubscribe(); } } } }