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

org.cometd.common.AsyncFoldLeft Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2020 the original author or authors.
 *
 * 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 org.cometd.common;

import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.IntFunction;

import org.cometd.bayeux.Promise;

/**
 * 

Processes asynchronously a sequence of elements, producing a result that * is the reduction of the results produced by the processing of each element.

*

This class implements the asynchronous version of the following synchronous * {@code for} loop, but where the processing of each element may be asynchronous.

*
 * R result;
 * for (T element : list) {
 *     (result, proceed) = operation.apply(result, element);
 *     if (!proceed) {
 *         break;
 *     }
 * }
 * 
*

Using this class, the loop above becomes:

*
 * R zero;
 * AsyncFoldLeft.run(list, zero, (result, element, loop) -> {
 *     CompletableFuture<R> future = processAsync(element);
 *     future.whenComplete((r, x) -> {
 *         if (x == null) {
 *             R reduced = reduce(result, r);
 *             if (shouldIterate(r)) {
 *                 loop.proceed(reduced);
 *             } else {
 *                 loop.leave(reduced);
 *             }
 *         } else {
 *             loop.fail(x);
 *         }
 *     })
 * }, Promise.complete((r, x) -> {
 *     // Process final result or failure.
 * });
 * 
*/ public class AsyncFoldLeft { public static void run(T[] array, R zero, Operation operation, Promise promise) { int size = array.length; if (size == 0) { promise.succeed(zero); } else { IndexedLoop loop = new IndexedLoop<>(i -> array[i], size, zero, operation, promise); loop.run(); } } /** *

Processes the given {@code collection} of elements.

*

The initial result {@code zero} is returned if the collection is empty.

*

For each element the {@link Operation#apply(Object, Object, Loop) operation} * function is invoked.

*

The {@code collection} should have a "stable" iterator, i.e. it should not be * affected if the collection is modified concurrently by another thread, or by the * same thread in th {@code operation} during the iteration.

* * @param collection the elements to process * @param zero the initial result * @param operation the operation to invoke for each element * @param promise the promise to notify of the final result * @param the type of element * @param the type of the result */ public static void run(Collection collection, R zero, Operation operation, Promise promise) { Iterator iterator = collection.iterator(); if (!iterator.hasNext()) { promise.succeed(zero); } else { IteratorLoop loop = new IteratorLoop<>(iterator, zero, operation, promise); loop.run(); } } /** *

The operation to invoke for each element.

* * @param the type of element * @param the type of the result */ @FunctionalInterface public interface Operation { /** *

Processes the given {@code element}.

*

The processing of the element must produce a result that is the reduction * of the accumulated {@code result} with the result of the processing of the * given {@code element}.

*

After the processing of the given element, the implementation must decide * whether to continue with or break out of the iteration (with the reduced * result) or to fail the iteration (with an exception).

* * @param result the accumulated result so far * @param element the element to process * @param loop the control object that continues or breaks the iteration */ void apply(R result, T element, Loop loop); } /** *

Controls the iteration over a sequence of elements, allowing to * continue the iteration (with a result), break out of the iteration * (with a result), or fail the iteration (with an exception).

* * @param the type of the result */ public interface Loop { /** *

Makes the loop proceed to the next element (or the end of the loop).

* * @param r the result computed in the current iteration */ public default void proceed(R r) { } /** *

Makes the loop exit (similarly to the {@code break} statement).

* * @param r the result computed in the current iteration */ public default void leave(R r) { } /** *

Makes the loop fail (similarly to throwing an exception).

* * @param failure the failure of the current iteration */ public default void fail(Throwable failure) { } } private enum State { LOOP, ASYNC, PROCEED, LEAVE, FAIL } private static abstract class AbstractLoop implements Loop { private final AtomicReference state = new AtomicReference<>(State.LOOP); private final AtomicReference failure = new AtomicReference<>(); private final AtomicReference result; private final Operation operation; private final Promise promise; private AbstractLoop(R zero, Operation operation, Promise promise) { this.result = new AtomicReference<>(zero); this.operation = operation; this.promise = promise; } abstract boolean hasCurrent(); abstract T current(); abstract void next(); void run() { while (hasCurrent()) { state.set(State.LOOP); operation.apply(result.get(), current(), this); loop: while (true) { State current = state.get(); switch (current) { case LOOP: if (state.compareAndSet(current, State.ASYNC)) { return; } break; case PROCEED: next(); break loop; case LEAVE: promise.succeed(result.get()); return; case FAIL: promise.fail(failure.get()); return; default: throw new IllegalStateException(); } } } promise.succeed(result.get()); } @Override public void proceed(R r) { result.set(r); while (true) { State current = state.get(); switch (current) { case LOOP: if (state.compareAndSet(current, State.PROCEED)) { return; } break; case ASYNC: if (state.compareAndSet(current, State.PROCEED)) { next(); run(); return; } break; default: throw new IllegalStateException(); } } } @Override public void leave(R r) { result.set(r); while (true) { State current = state.get(); switch (current) { case LOOP: if (state.compareAndSet(current, State.LEAVE)) { return; } break; case ASYNC: if (state.compareAndSet(current, State.LEAVE)) { promise.succeed(result.get()); return; } break; default: throw new IllegalStateException(); } } } @Override public void fail(Throwable x) { failure.compareAndSet(null, x); while (true) { State current = state.get(); switch (current) { case LOOP: if (state.compareAndSet(current, State.FAIL)) { return; } break; case ASYNC: if (state.compareAndSet(current, State.FAIL)) { promise.fail(x); return; } break; default: throw new IllegalStateException(); } } } } private static class IndexedLoop extends AbstractLoop { private final IntFunction element; private final int size; private int index; private IndexedLoop(IntFunction element, int size, R zero, Operation operation, Promise promise) { super(zero, operation, promise); this.element = element; this.size = size; } @Override boolean hasCurrent() { return index < size; } @Override T current() { return element.apply(index); } @Override void next() { ++index; } } private static class IteratorLoop extends AbstractLoop { private final Iterator iterator; private boolean hasCurrent; private T current; private IteratorLoop(Iterator iterator, R zero, Operation operation, Promise promise) { super(zero, operation, promise); this.iterator = iterator; next(); } @Override boolean hasCurrent() { return hasCurrent; } @Override T current() { if (!hasCurrent) { throw new NoSuchElementException(); } return current; } @Override void next() { hasCurrent = iterator.hasNext(); current = hasCurrent ? iterator.next() : null; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy