![JAR search and dependency download from the Maven repository](/logo.png)
hu.akarnokd.reactive4java.reactive.CombineLatest Maven / Gradle / Ivy
Show all versions of reactive4java Show documentation
/*
* Copyright 2011-2013 David Karnok
*
* 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 hu.akarnokd.reactive4java.reactive;
import hu.akarnokd.reactive4java.base.Func2;
import hu.akarnokd.reactive4java.base.Observable;
import hu.akarnokd.reactive4java.base.Observer;
import hu.akarnokd.reactive4java.util.Closeables;
import hu.akarnokd.reactive4java.util.CompositeCloseable;
import hu.akarnokd.reactive4java.util.DefaultObserverEx;
import hu.akarnokd.reactive4java.util.R4JConfigManager;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nonnull;
/**
* Helper class for the combineLatest operator implementations.
* @author akarnokd, 2013.01.13.
* @since 0.97
*/
public final class CombineLatest {
/** Helper class. */
private CombineLatest() { }
/**
* Returns an observable which combines the latest values of
* both streams whenever one sends a new value, but only after both sent a value.
* Exception semantics: if any stream throws an exception, the output stream
* throws an exception and all registrations are terminated.
* Completion semantics: The output stream terminates
* after both streams terminate.
* Note that at the beginning, when the left or right fires first, the selector function
* will receive (value, null) or (null, value). If you want to react only in cases when both have sent
* a value, use the {@link Sent} class and combineLatest
operator.
* @param the left element type
* @param the right element type
* @param the result element type
* @author akarnokd, 2013.01.13.
*/
public static final class NullStart implements Observable {
/** The result selector. */
private final Func2 super T, ? super U, ? extends V> selector;
/** The left sequence. */
private final Observable extends T> left;
/** The right sequence. */
private final Observable extends U> right;
/**
* Constructor.
* @param left the left stream
* @param right the right stream
* @param selector the function which combines values from both streams and returns a new value
*/
public NullStart(
Observable extends T> left,
Observable extends U> right,
Func2 super T, ? super U, ? extends V> selector) {
this.selector = selector;
this.left = left;
this.right = right;
}
@Override
@Nonnull
public Closeable register(@Nonnull final Observer super V> observer) {
final Lock lock = new ReentrantLock(R4JConfigManager.get().useFairLocks());
final CompositeCloseable closeBoth = new CompositeCloseable();
final AtomicReference leftRef = new AtomicReference();
final AtomicReference rightRef = new AtomicReference();
final AtomicInteger wip = new AtomicInteger(2);
DefaultObserverEx obs1 = new DefaultObserverEx(lock, false) {
@Override
protected void onError(@Nonnull Throwable ex) {
observer.error(ex);
Closeables.closeSilently(closeBoth);
}
@Override
protected void onFinish() {
if (wip.decrementAndGet() == 0) {
observer.finish();
}
close();
}
@Override
protected void onNext(T value) {
leftRef.set(value);
observer.next(selector.invoke(value, rightRef.get()));
}
};
DefaultObserverEx obs2 = new DefaultObserverEx(lock, false) {
@Override
protected void onError(@Nonnull Throwable ex) {
observer.error(ex);
Closeables.closeSilently(closeBoth);
}
@Override
protected void onFinish() {
if (wip.decrementAndGet() == 0) {
observer.finish();
}
close();
}
@Override
protected void onNext(U value) {
rightRef.set(value);
observer.next(selector.invoke(leftRef.get(), value));
}
};
closeBoth.add(obs1, obs2);
obs1.registerWith(left);
obs2.registerWith(right);
return closeBoth;
}
}
/**
* Returns an observable which combines the latest values of
* both streams whenever one sends a new value.
* Exception semantics: if any stream throws an exception, the output stream
* throws an exception and all registrations are terminated.
* Completion semantics: The output stream terminates
* after both streams terminate.
* The function will start combining the values only when both sides have already sent
* a value.
* @param the left element type
* @param the right element type
* @param the result element type
* @author akarnokd, 2013.01.13.
*/
public static final class Sent implements Observable {
/** The left source. */
private final Observable extends T> left;
/** The right source. */
private final Observable extends U> right;
/** The result selector. */
private final Func2 super T, ? super U, ? extends V> selector;
/**
* Constructor.
* @param left the left stream
* @param right the right stream
* @param selector the function which combines values from both streams and returns a new value
*/
public Sent(
Observable extends T> left,
Observable extends U> right,
Func2 super T, ? super U, ? extends V> selector
) {
this.right = right;
this.selector = selector;
this.left = left;
}
@Override
@Nonnull
public Closeable register(@Nonnull final Observer super V> observer) {
final Lock lock = new ReentrantLock(R4JConfigManager.get().useFairLocks());
final CompositeCloseable closeBoth = new CompositeCloseable();
final AtomicReference leftRef = new AtomicReference();
final AtomicBoolean leftFirst = new AtomicBoolean();
final AtomicReference rightRef = new AtomicReference();
final AtomicBoolean rightFirst = new AtomicBoolean();
final AtomicInteger wip = new AtomicInteger(2);
DefaultObserverEx obs1 = new DefaultObserverEx(lock, false) {
@Override
protected void onError(@Nonnull Throwable ex) {
observer.error(ex);
Closeables.closeSilently(closeBoth);
}
@Override
protected void onFinish() {
if (wip.decrementAndGet() == 0) {
observer.finish();
}
close();
}
@Override
protected void onNext(T value) {
leftRef.set(value);
leftFirst.set(true);
if (rightFirst.get()) {
observer.next(selector.invoke(value, rightRef.get()));
}
}
};
DefaultObserverEx obs2 = new DefaultObserverEx(lock, false) {
@Override
protected void onError(@Nonnull Throwable ex) {
observer.error(ex);
Closeables.closeSilently(closeBoth);
}
@Override
protected void onFinish() {
if (wip.decrementAndGet() == 0) {
observer.finish();
}
close();
}
@Override
protected void onNext(U value) {
rightRef.set(value);
rightFirst.set(true);
if (leftFirst.get()) {
observer.next(selector.invoke(leftRef.get(), value));
}
}
};
closeBoth.add(obs1, obs2);
obs1.registerWith(left);
obs2.registerWith(right);
return closeBoth;
}
}
}