All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.internal.operators.OnSubscribeJoin Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * 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.exceptions.Exceptions;
import rx.functions.*;
import rx.observers.SerializedSubscriber;
import rx.subscriptions.*;

/**
 * Correlates the elements of two sequences based on overlapping durations.
 *
 * @param  the left value type
 * @param  the right value type
 * @param  the left duration value type
 * @param  the right duration type
 * @param  the result type
 */
public final class OnSubscribeJoin implements OnSubscribe {
    final Observable left;
    final Observable right;
    final Func1> leftDurationSelector;
    final Func1> rightDurationSelector;
    final Func2 resultSelector;

    public OnSubscribeJoin(
            Observable left,
            Observable right,
            Func1> leftDurationSelector,
            Func1> rightDurationSelector,
            Func2 resultSelector) {
        this.left = left;
        this.right = right;
        this.leftDurationSelector = leftDurationSelector;
        this.rightDurationSelector = rightDurationSelector;
        this.resultSelector = resultSelector;
    }

    @Override
    public void call(Subscriber t1) {
        ResultSink result = new ResultSink(new SerializedSubscriber(t1));
        result.run();
    }

    /** Manage the left and right sources. */
    final class ResultSink extends HashMap {
        //HashMap aspect of `this` refers to the `leftMap`

        private static final long serialVersionUID = 3491669543549085380L;

        final CompositeSubscription group;
        final Subscriber subscriber;
        /** Guarded by this. */
        boolean leftDone;
        /** Guarded by this. */
        int leftId;
        /** Guarded by this. */
        boolean rightDone;
        /** Guarded by this. */
        int rightId;
        /** Guarded by this. */
        final Map rightMap;

        public ResultSink(Subscriber subscriber) {
            super();
            this.subscriber = subscriber;
            this.group = new CompositeSubscription();
            //`leftMap` is `this`
            this.rightMap = new HashMap();
        }

        HashMap leftMap() {
            return this;
        }

        public void run() {
            subscriber.add(group);

            Subscriber s1 = new LeftSubscriber();
            Subscriber s2 = new RightSubscriber();

            group.add(s1);
            group.add(s2);

            left.unsafeSubscribe(s1);
            right.unsafeSubscribe(s2);
        }

        /** Observes the left values. */
        final class LeftSubscriber extends Subscriber {

            protected void expire(int id, Subscription resource) {
                boolean complete = false;
                synchronized (ResultSink.this) {
                    if (leftMap().remove(id) != null && leftMap().isEmpty() && leftDone) {
                        complete = true;
                    }
                }
                if (complete) {
                    subscriber.onCompleted();
                    subscriber.unsubscribe();
                } else {
                    group.remove(resource);
                }
            }

            @Override
            public void onNext(TLeft args) {
                int id;
                int highRightId;

                synchronized (ResultSink.this) {
                    id = leftId++;
                    leftMap().put(id, args);
                    highRightId = rightId;
                }

                Observable duration;
                try {
                    duration = leftDurationSelector.call(args);

                    Subscriber d1 = new LeftDurationSubscriber(id);
                    group.add(d1);

                    duration.unsafeSubscribe(d1);

                    List rightValues = new ArrayList();
                    synchronized (ResultSink.this) {
                        for (Map.Entry entry : rightMap.entrySet()) {
                            if (entry.getKey() < highRightId) {
                                rightValues.add(entry.getValue());
                            }
                        }
                    }
                    for (TRight r : rightValues) {
                        R result = resultSelector.call(args, r);
                        subscriber.onNext(result);
                    }
                } catch (Throwable t) {
                    Exceptions.throwOrReport(t, this);
                }
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
                subscriber.unsubscribe();
            }

            @Override
            public void onCompleted() {
                boolean complete = false;
                synchronized (ResultSink.this) {
                    leftDone = true;
                    if (rightDone || leftMap().isEmpty()) {
                        complete = true;
                    }
                }
                if (complete) {
                    subscriber.onCompleted();
                    subscriber.unsubscribe();
                } else {
                    group.remove(this);
                }
            }

            /** Observes the left duration. */
            final class LeftDurationSubscriber extends Subscriber {
                final int id;
                boolean once = true;

                public LeftDurationSubscriber(int id) {
                    this.id = id;
                }

                @Override
                public void onNext(TLeftDuration args) {
                    onCompleted();
                }

                @Override
                public void onError(Throwable e) {
                    LeftSubscriber.this.onError(e);
                }

                @Override
                public void onCompleted() {
                    if (once) {
                        once = false;
                        expire(id, this);
                    }
                }

            }
        }

        /** Observes the right values. */
        final class RightSubscriber extends Subscriber {

            void expire(int id, Subscription resource) {
                boolean complete = false;
                synchronized (ResultSink.this) {
                    if (rightMap.remove(id) != null && rightMap.isEmpty() && rightDone) {
                        complete = true;
                    }
                }
                if (complete) {
                    subscriber.onCompleted();
                    subscriber.unsubscribe();
                } else {
                    group.remove(resource);
                }
            }

            @Override
            public void onNext(TRight args) {
                int id;
                int highLeftId;
                synchronized (ResultSink.this) {
                    id = rightId++;
                    rightMap.put(id, args);
                    highLeftId = leftId;
                }
                SerialSubscription md = new SerialSubscription();
                group.add(md);

                Observable duration;
                try {
                    duration = rightDurationSelector.call(args);

                    Subscriber d2 = new RightDurationSubscriber(id);
                    group.add(d2);

                    duration.unsafeSubscribe(d2);


                    List leftValues = new ArrayList();
                    synchronized (ResultSink.this) {
                        for (Map.Entry entry : leftMap().entrySet()) {
                            if (entry.getKey() < highLeftId) {
                                leftValues.add(entry.getValue());
                            }
                        }
                    }

                    for (TLeft lv : leftValues) {
                        R result = resultSelector.call(lv, args);
                        subscriber.onNext(result);
                    }

                } catch (Throwable t) {
                    Exceptions.throwOrReport(t, this);
                }
            }

            @Override
            public void onError(Throwable e) {
                subscriber.onError(e);
                subscriber.unsubscribe();
            }

            @Override
            public void onCompleted() {
                boolean complete = false;
                synchronized (ResultSink.this) {
                    rightDone = true;
                    if (leftDone || rightMap.isEmpty()) {
                        complete = true;
                    }
                }
                if (complete) {
                    subscriber.onCompleted();
                    subscriber.unsubscribe();
                } else {
                    group.remove(this);
                }
            }

            /** Observe the right duration. */
            final class RightDurationSubscriber extends Subscriber {
                final int id;
                boolean once = true;

                public RightDurationSubscriber(int id) {
                    this.id = id;
                }

                @Override
                public void onNext(TRightDuration args) {
                    onCompleted();
                }

                @Override
                public void onError(Throwable e) {
                    RightSubscriber.this.onError(e);
                }

                @Override
                public void onCompleted() {
                    if (once) {
                        once = false;
                        expire(id, this);
                    }
                }

            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy