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

org.aksw.commons.rx.op.FlowableOperatorCollapseRuns Maven / Gradle / Ivy

There is a newer version: 0.9.9
Show newest version
package org.aksw.commons.rx.op;

import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;

import org.aksw.commons.util.stream.CollapseRunsOperationBase;
import org.aksw.commons.util.stream.CollapseRunsSpec;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import io.reactivex.rxjava3.core.Flowable;
import io.reactivex.rxjava3.core.FlowableOperator;
import io.reactivex.rxjava3.core.FlowableSubscriber;
import io.reactivex.rxjava3.core.FlowableTransformer;
import io.reactivex.rxjava3.internal.subscriptions.SubscriptionHelper;
import io.reactivex.rxjava3.internal.util.BackpressureHelper;

/**
 * Sequential group by; somewhat similar to .toListWhile() but with dedicated support for
 * group keys and accumulators
 *
 * The constructor lambda for accumulators receives the count of so far created accumulators (starting with 0) and the group key.
 * The count can be used to 'skip' accumulation of a certain number of groups.
 *
 * The accumulator constructor function can be null - in which case the accAdd lambda is not invoked.
 * For each group key with a null accumulator a pair (groupKey, null) is emitted.
 *
 * 
 * long skipCount = 5;
 * flow
 *   .lift(FlowableOperatorCollapseRuns.create(
 *       item -> groupKeyOf(item),
 *       (accNum, groupKey) -> accNum < skipCount ? null : new RealAcc(),
 *       (acc, item) -> acc.add(item))
 *   .skip(skipCount)
 *   .map(Entry::getValue)
 * 
* * The items' group keys are expected to arrive in order, hence only a single accumulator is active at a time. * *
{@code
 * Flowable>> list = Flowable
 *     .range(0, 10)
 *     .map(i -> Maps.immutableEntry((int)(i / 3), i))
 *     .lift(FlowableOperatorSequentialGroupBy., Integer, List>create(Entry::getKey, ArrayList::new, (acc, e) -> acc.add(e.getValue())));
 *
 * }
* * @author raven * * @param Item type * @param Group key type * @param Accumulator type */ public final class FlowableOperatorCollapseRuns extends CollapseRunsOperationBase implements FlowableOperator, T> { public static FlowableOperatorCollapseRuns create(CollapseRunsSpec spec) { return new FlowableOperatorCollapseRuns<>(spec); } public FlowableOperatorCollapseRuns(CollapseRunsSpec other) { super(other); } @Override public Subscriber apply(Subscriber> downstream) throws Exception { return new SubscriberImpl(downstream); } /** * Deprecated; Prefer using {@link Flowable#lift(FlowableOperator)} over {@link Flowable#compose(FlowableTransformer)} */ @Deprecated public FlowableTransformer> transformer() { return upstream -> upstream.lift(this); } public class SubscriberImpl implements FlowableSubscriber, Subscription { protected Subscriber> downstream; protected Subscription upstream; protected K priorKey; protected K currentKey; // Number of created accumulators; incremented after accCtor invocation protected long accNum = 0; protected V currentAcc = null; protected volatile boolean isUpstreamComplete = false; protected volatile boolean lastItemSent = false; protected AtomicLong pending = new AtomicLong(); public SubscriberImpl(Subscriber> downstream) { this.downstream = downstream; } @Override public void onSubscribe(Subscription s) { if (upstream != null) { s.cancel(); } else { upstream = s; downstream.onSubscribe(this); } } @Override public void onNext(T item) { // System.out.println("ONNEXT PENDING: " + pending.get() + " " + Thread.currentThread()); // if (pending.get() <= 0) { //// System.out.println("PENDING IS ZERO " + Thread.currentThread()); //// System.out.println("PENDING IS " + pending.get()); // throw new RuntimeException("Received item without any pending requests"); // } currentKey = getGroupKey.apply(item); boolean needMore = true; if (accNum == 0) { // First time init priorKey = currentKey; currentAcc = accCtor.apply(accNum, currentKey); // Objects.requireNonNull(currentAcc, "Got null for an accumulator"); ++accNum; } else if(!groupKeyCompare.test(priorKey, currentKey)) { Entry e = new SimpleEntry<>(priorKey, currentAcc); // System.out.println("Passing on " + e); needMore = pending.decrementAndGet() > 0; downstream.onNext(e); currentAcc = accCtor.apply(accNum, currentKey); // Objects.requireNonNull(currentAcc, "Got null for an accumulator"); ++accNum; } if (currentAcc != null) { accAdd.apply(currentAcc, item); } priorKey = currentKey; // Whether we need more items from upstream in order to complete the current group if (needMore) { // Requesting an additional item eventually triggers either onNext() or onComplete() upstream.request(1); } } /** * If there is a remaining request upon onComplete then the last item can be sent out at that time. * Otherwise we have to wait for additional requests. */ protected void trySendLastItem() { if (isUpstreamComplete && !lastItemSent) { // If we saw any items if (accNum != 0) { if (pending.get() > 0) { // System.out.println("EMITTED ITEM ON COMPLETE"); lastItemSent = true; downstream.onNext(new SimpleEntry<>(currentKey, currentAcc)); downstream.onComplete(); } } else { // If there were no items to send then we have no more pending last item lastItemSent = true; downstream.onComplete(); } } } /** Called when the upstream completes */ @Override public void onComplete() { isUpstreamComplete = true; trySendLastItem(); } @Override public void onError(Throwable t) { downstream.onError(t); } @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { long before = BackpressureHelper.add(pending, n); // pending.addAndGet(n); // System.out.println("BEFORE REQUESTED " + n + " total pending " + pending.get() + " " + Thread.currentThread()); if (before == 0) { upstream.request(1); } // System.out.println("AFTER REQUESTED " + n + " total pending " + pending.get() + " " + Thread.currentThread()); } trySendLastItem(); } @Override public void cancel() { upstream.cancel(); upstream = SubscriptionHelper.CANCELLED; } } // /* Function to derive a group key from an item in the flow */ // protected Function getGroupKey; // // /* Comparision whether two group keys are equal */ // protected BiPredicate groupKeyCompare; // // /* Constructor function for accumulators. Function argument is the group key */ // protected BiFunction accCtor; // // /* Add an item to the accumulator */ // protected BiConsumer accAdd; // /** // * Create method with the following characteristics: // *
    // *
  • the accumulator constructor is a mere supplier (and thus neither depends on the accumulator count nor the group Key)
  • // *
  • Group keys are compared using Objects::equals
  • // *
// */ // public static FlowableOperatorSequentialGroupBy create( // Function getGroupKey, // Supplier accCtor, // BiConsumer accAdd) { // return create(getGroupKey, Objects::equals, groupKey -> accCtor.get(), accAdd); // } // // // /** // * Create method with the following characteristics: // *
    // *
  • the accumulator constructor receives the group key
  • // *
  • Group keys are compared using Objects::equals
  • // *
// */ // public static FlowableOperatorSequentialGroupBy create( // Function getGroupKey, // Function accCtor, // BiConsumer accAdd) { // return create(getGroupKey, Objects::equals, accCtor, accAdd); // } // // /** // * Create method with the following characteristics: // *
    // *
  • the accumulator constructor receives the number of so-far created accumulators (starting with 0) and the group key
  • // *
  • Group keys are compared using Objects::equals
  • // *
// */ // public static FlowableOperatorSequentialGroupBy create( // Function getGroupKey, // BiFunction accCtor, // BiConsumer accAdd) { // return create(getGroupKey, Objects::equals, accCtor, accAdd); // } // // public static FlowableOperatorSequentialGroupBy create( // Function getGroupKey, // BiPredicate groupKeyCompare, // Function accCtor, // BiConsumer accAdd) { // return new FlowableOperatorSequentialGroupBy<>(getGroupKey, groupKeyCompare, (accNum, key) -> accCtor.apply(key), accAdd); // } // // public static FlowableOperatorSequentialGroupBy create( // Function getGroupKey, // BiPredicate groupKeyCompare, // BiFunction accCtor, // BiConsumer accAdd) { // return new FlowableOperatorSequentialGroupBy<>(getGroupKey, groupKeyCompare, accCtor, accAdd); // } // // public FlowableOperatorSequentialGroupBy( // Function getGroupKey, // BiPredicate groupKeyCompare, // BiFunction accCtor, // BiConsumer accAdd) { // super(); // this.getGroupKey = getGroupKey; // this.groupKeyCompare = groupKeyCompare; // this.accCtor = accCtor; // this.accAdd = accAdd; // } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy