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

io.datakernel.csp.ChannelSuppliers Maven / Gradle / Ivy

Go to download

Communicating sequential process via channels, similar to Golang's channels. A channel could be imagine as a pipe which connects some processes.

The newest version!
/*
 * Copyright (C) 2015-2019 SoftIndex LLC.
 *
 * 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 io.datakernel.csp;

import io.datakernel.async.process.Cancellable;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.bytebuf.ByteBufPool;
import io.datakernel.common.MemSize;
import io.datakernel.common.collection.CollectionUtils;
import io.datakernel.common.collection.Try;
import io.datakernel.common.exception.StacklessException;
import io.datakernel.common.exception.UncheckedException;
import io.datakernel.csp.queue.ChannelBuffer;
import io.datakernel.csp.queue.ChannelZeroBuffer;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.promise.Promise;
import io.datakernel.promise.Promises;
import io.datakernel.promise.SettablePromise;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.function.ToIntFunction;

import static io.datakernel.common.Recyclable.deepRecycle;
import static io.datakernel.common.Recyclable.tryRecycle;
import static io.datakernel.common.Utils.nullify;

/**
 * Provides additional functionality for managing {@link ChannelSupplier}s.
 * Includes helper classes: ChannelSupplierOfException, ChannelSupplierOfIterator,
 * ChannelSupplierOfValue, ChannelSupplierEmpty.
 */
public final class ChannelSuppliers {

	/**
	 * @see #concat(Iterator)
	 */
	public static  ChannelSupplier concat(ChannelSupplier supplier1, ChannelSupplier supplier2) {
		return concat(CollectionUtils.asIterator(supplier1, supplier2));
	}

	/**
	 * @see #concat(Iterator)
	 */
	@SafeVarargs
	public static  ChannelSupplier concat(ChannelSupplier... suppliers) {
		return concat(CollectionUtils.asIterator(suppliers));
	}

	/**
	 * Creates a new ChannelSupplier which on {@code get()} call returns
	 * the result wrapped in {@code promise} of the first ChannelSuppliers'
	 * {@code promise} that was successfully completed with a non-null result.
	 * If all of the ChannelSuppliers of the iterator have a {@code null}
	 * {@code promise} result, a {@code promise} of {@code null} will be returned.
	 * 

* If one of the ChannelSuppliers' {@code promises} completes with an exception, * all subsequent elements of the iterator will be closed and a * {@code promise} of exception will be returned. * * @param iterator an iterator of ChannelSuppliers * @param type of data wrapped in the ChannelSuppliers * @return a ChannelSupplier of {@code } */ public static ChannelSupplier concat(Iterator> iterator) { return new AbstractChannelSupplier() { ChannelSupplier current = ChannelSupplier.of(); @Override protected Promise doGet() { return current.get() .thenEx((value, e) -> { if (e == null) { if (value != null) { return Promise.of(value); } else { if (iterator.hasNext()) { current = iterator.next(); return get(); } else { return Promise.of(null); } } } else { while (iterator.hasNext()) { iterator.next().close(e); } return Promise.ofException(e); } }); } @Override protected void onClosed(@NotNull Throwable e) { current.close(e); while (iterator.hasNext()) { iterator.next().close(e); } } }; } /** * Collects data provided by the {@code supplier} asynchronously and returns a * promise of accumulated result. This process will be getting values from the * {@code supplier}, until a promise of {@code null} is returned, which represents * end of stream. *

* If {@code get} returns a promise of exception or there was an exception while * {@code accumulator} accepted values, a promise of {@code exception} will be * returned and the process will stop. * * @param supplier a {@code ChannelSupplier} which provides data to be collected * @param initialValue a value which will accumulate the results of accumulator * @param accumulator a {@link BiConsumer} which may perform some operations over provided * by supplier data and accumulates the result to the initialValue * @param finisher a {@link Function} which performs the final transformation of the * accumulated value * @param a data type provided by the {@code supplier} * @param an intermediate accumulation data type * @param a data type of final result of {@code finisher} * @return a promise of accumulated result, transformed by the {@code finisher} */ public static Promise collect(ChannelSupplier supplier, A initialValue, BiConsumer accumulator, Function finisher) { return Promise.ofCallback(cb -> toCollectorImpl(supplier, initialValue, accumulator, finisher, cb)); } private static void toCollectorImpl(ChannelSupplier supplier, A accumulatedValue, BiConsumer accumulator, Function finisher, SettablePromise cb) { Promise promise; while (true) { promise = supplier.get(); if (!promise.isResult()) break; T item = promise.getResult(); if (item != null) { try { accumulator.accept(accumulatedValue, item); } catch (UncheckedException u) { Throwable cause = u.getCause(); supplier.close(cause); cb.setException(cause); return; } continue; } break; } promise.whenComplete((value, e) -> { if (e == null) { if (value != null) { try { accumulator.accept(accumulatedValue, value); } catch (UncheckedException u) { Throwable cause = u.getCause(); supplier.close(cause); cb.setException(cause); return; } toCollectorImpl(supplier, accumulatedValue, accumulator, finisher, cb); } else { cb.set(finisher.apply(accumulatedValue)); } } else { deepRecycle(finisher.apply(accumulatedValue)); cb.setException(e); } }); } public static Promise streamTo(Promise> supplier, Promise> consumer) { return Promises.toTuple(supplier.toTry(), consumer.toTry()) .then(t -> streamTo(t.getValue1(), t.getValue2())); } public static Promise streamTo(Try> supplier, Try> consumer) { if (supplier.isSuccess() && consumer.isSuccess()) { return streamTo(supplier.get(), consumer.get()); } StacklessException exception = new StacklessException("Channel stream failed"); supplier.consume(Cancellable::cancel, exception::addSuppressed); consumer.consume(Cancellable::cancel, exception::addSuppressed); return Promise.ofException(exception); } /** * Streams data from the {@code supplier} to the {@code consumer} until {@code get()} * of {@code supplier} returns a promise of {@code null}. *

* If {@code get} returns a promise of exception or there was an exception while * {@code consumer} accepted values, a promise of {@code exception} will be * returned and the process will stop. * * @param supplier a supplier which provides some data * @param consumer a consumer which accepts the provided by supplier data * @param a data type of values passed from the supplier to consumer * @return a promise of {@code null} as a marker of completion of stream, * or promise of exception, if there was an exception while streaming */ public static Promise streamTo(ChannelSupplier supplier, ChannelConsumer consumer) { return Promise.ofCallback(cb -> streamToImpl(supplier, consumer, cb)); } private static void streamToImpl(ChannelSupplier supplier, ChannelConsumer consumer, SettablePromise cb) { Promise supplierPromise; while (true) { supplierPromise = supplier.get(); if (!supplierPromise.isResult()) break; T item = supplierPromise.getResult(); if (item == null) break; Promise consumerPromise = consumer.accept(item); if (consumerPromise.isResult()) continue; consumerPromise.whenComplete(($, e) -> { if (e == null) { streamToImpl(supplier, consumer, cb); } else { supplier.close(e); cb.trySetException(e); } }); return; } supplierPromise .whenComplete((item, e1) -> { if (e1 == null) { consumer.accept(item) .whenComplete(($, e2) -> { if (e2 == null) { if (item != null) { streamToImpl(supplier, consumer, cb); } else { cb.trySet(null); } } else { supplier.close(e2); cb.trySetException(e2); } }); } else { consumer.close(e1); cb.trySetException(e1); } }); } public static ChannelSupplier prefetch(int count, ChannelSupplier actual) { ChannelBuffer buffer = new ChannelBuffer<>(count); actual.streamTo(buffer.getConsumer()); return buffer.getSupplier(); } public static ChannelSupplier prefetch(ChannelSupplier actual) { ChannelZeroBuffer buffer = new ChannelZeroBuffer<>(); actual.streamTo(buffer.getConsumer()); return buffer.getSupplier(); } /** * Transforms this {@code ChannelSupplier} data of type with provided {@code fn}, * which returns an {@link Iterator} of a type. Then provides this value to ChannelSupplier of . */ public static ChannelSupplier remap(ChannelSupplier supplier, Function> fn) { return new AbstractChannelSupplier(supplier) { Iterator iterator = CollectionUtils.emptyIterator(); boolean endOfStream; @Override protected Promise doGet() { if (iterator.hasNext()) return Promise.of(iterator.next()); return Promise.ofCallback(this::next); } private void next(SettablePromise cb) { if (!endOfStream) { supplier.get() .whenComplete((item, e) -> { if (e == null) { if (item == null) endOfStream = true; iterator = fn.apply(item); if (iterator.hasNext()) { cb.set(iterator.next()); } else { next(cb); } } else { cb.setException(e); } }); } else { cb.set(null); } } }; } public static ChannelSupplier inputStreamAsChannelSupplier(Executor executor, MemSize bufSize, InputStream is) { return inputStreamAsChannelSupplier(executor, bufSize.toInt(), is); } public static ChannelSupplier inputStreamAsChannelSupplier(Executor executor, int bufSize, InputStream inputStream) { return new AbstractChannelSupplier() { @Override protected Promise doGet() { return Promise.ofBlockingCallable(executor, () -> { ByteBuf buf = ByteBufPool.allocate(bufSize); int readBytes; try { readBytes = inputStream.read(buf.array(), 0, bufSize); } catch (IOException e) { throw new UncheckedException(e); } if (readBytes != -1) { buf.moveTail(readBytes); return buf; } else { buf.recycle(); return null; } }); } @Override protected void onClosed(@NotNull Throwable e) { executor.execute(() -> { try { inputStream.close(); } catch (IOException ignored) { } }); } }; } public static InputStream channelSupplierAsInputStream(Eventloop eventloop, ChannelSupplier channelSupplier) { return new InputStream() { @Nullable ByteBuf current = null; @Override public int read() throws IOException { return doRead(ByteBuf::readByte); } @Override public int read(@NotNull byte[] b, int off, int len) throws IOException { return doRead(buf -> buf.read(b, off, len)); } private int doRead(ToIntFunction reader) throws IOException { ByteBuf peeked = current; if (peeked == null) { ByteBuf buf; do { CompletableFuture future = eventloop.submit(channelSupplier::get); try { buf = future.get(); } catch (InterruptedException e) { eventloop.execute(channelSupplier::cancel); throw new IOException(e); } catch (ExecutionException e) { Throwable cause = e.getCause(); if (cause instanceof IOException) throw (IOException) cause; if (cause instanceof RuntimeException) throw (RuntimeException) cause; if (cause instanceof Exception) throw new IOException(cause); throw (Error) cause; } if (buf == null) { return -1; } } while (!buf.canRead()); peeked = buf; } int result = reader.applyAsInt(peeked); if (peeked.canRead()) { current = peeked; } else { current = null; peeked.recycle(); } return result; } @Override public void close() { current = nullify(current, ByteBuf::recycle); eventloop.execute(channelSupplier::close); } }; } /** * Represents a {@code ChannelSupplier} which always returns * a promise of {@code null}. */ public static class ChannelSupplierEmpty extends AbstractChannelSupplier { @Override protected Promise doGet() { return Promise.of(null); } } /** * Represents a {@code ChannelSupplier} of one value. Returns a promise of the value when * {@code get} is called for the first time, all subsequent calls will return {@code null}. */ public static final class ChannelSupplierOfValue extends AbstractChannelSupplier { private T item; public T getValue() { return item; } public T takeValue() { T item = this.item; this.item = null; return item; } public ChannelSupplierOfValue(@NotNull T item) { this.item = item; } @Override protected Promise doGet() { T item = takeValue(); return Promise.of(item); } @Override protected void onClosed(@NotNull Throwable e) { tryRecycle(item); item = null; } } /** * Represents a {@code ChannelSupplier} which wraps the provided iterator and * returns promises of iterator's values until {@code hasNext()} is true, when * there are no more values left, a promise of {@code null} is returned. */ public static final class ChannelSupplierOfIterator extends AbstractChannelSupplier { private final Iterator iterator; public ChannelSupplierOfIterator(Iterator iterator) { this.iterator = iterator; } @Override protected Promise doGet() { return Promise.of(iterator.hasNext() ? iterator.next() : null); } @Override protected void onClosed(@NotNull Throwable e) { deepRecycle(iterator); } } /** * Represents a {@code ChannelSupplier} which always returns a promise of exception. */ public static final class ChannelSupplierOfException extends AbstractChannelSupplier { private final Throwable e; public ChannelSupplierOfException(Throwable e) { this.e = e; } @Override protected Promise doGet() { return Promise.ofException(e); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy