| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.apache.sis.internal.processing.image; |
| |
| import java.util.Set; |
| import java.util.HashSet; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.CancellationException; |
| import org.apache.sis.internal.util.CollectionsExt; |
| import org.apache.sis.internal.feature.Resources; |
| |
| |
| /** |
| * The result of multiple asynchronous computations. |
| * This {@code Future} is considered completed when all components are completed. |
| * |
| * @author Martin Desruisseaux (Geomatys) |
| * @version 1.1 |
| * @since 1.1 |
| * @module |
| */ |
| final class CompoundFuture<R> implements Future<R> { |
| /** |
| * The elements making this computation. |
| */ |
| private final Future<R>[] components; |
| |
| /** |
| * Creates a new future with the given components. |
| */ |
| private CompoundFuture(final Future<R>[] components) { |
| this.components = components; |
| } |
| |
| /** |
| * Returns a future waiting for all given tasks to complete. |
| * If the array length is 1, then this method returns directly its singleton element. |
| * |
| * @param <R> type if result computed by tasks. |
| * @param components the sub-tasks to execute. This array is not cloned; do not modify. |
| * @return a future containing all given tasks. |
| */ |
| public static <R> Future<R> create(final Future<R>[] components) { |
| switch (components.length) { |
| case 0: return null; |
| case 1: return components[0]; |
| } |
| return new CompoundFuture<>(components); |
| } |
| |
| /** |
| * Attempts to cancel execution of this task. After this method return, subsequent calls |
| * to {@link #isCancelled()} return {@code true} if this method returned {@code true}. |
| * |
| * <h4>Departure from specification</h4> |
| * {@code Future} specification requires that after this method returns, subsequent calls |
| * to {@link #isDone()} return {@code true}. This is not guaranteed in this implementation. |
| * |
| * @param mayInterruptIfRunning whether the thread executing tasks should be interrupted. |
| * @return {@code true} if at least one component task could be interrupted. |
| */ |
| @Override |
| public boolean cancel(final boolean mayInterruptIfRunning) { |
| boolean canceled = false; |
| for (final Future<R> c : components) { |
| canceled |= c.cancel(mayInterruptIfRunning); |
| } |
| return canceled; |
| } |
| |
| /** |
| * Returns {@code true} if this task was cancelled before it completed normally. |
| * This task is considered cancelled if at least one component has been cancelled. |
| * |
| * @return {@code true} if at least one component task was cancelled before it completed. |
| */ |
| @Override |
| public boolean isCancelled() { |
| for (final Future<R> c : components) { |
| if (c.isCancelled()) return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Returns {@code true} if this task completed. |
| * Completion may be due to normal termination, an exception, or cancellation. |
| * |
| * @return {@code true} if all component tasks completed. |
| */ |
| @Override |
| public boolean isDone() { |
| for (final Future<R> c : components) { |
| if (!c.isDone()) return false; |
| } |
| return true; |
| } |
| |
| /** |
| * Waits if necessary for all computations to complete, and then retrieves the result. |
| * If all task components return either {@code null} or the same {@code <R>} value, |
| * then that result is returned. Otherwise the various {@code <R>} values are given |
| * to {@link #merge(Collection)} for obtaining a single result. |
| * |
| * @return the computed result. |
| * @throws CancellationException if at least one computation was cancelled. |
| * @throws ExecutionException if at least one computation threw an exception. |
| * @throws InterruptedException if the current thread was interrupted while waiting. |
| */ |
| @Override |
| public R get() throws InterruptedException, ExecutionException { |
| try { |
| return get(0, true); |
| } catch (TimeoutException e) { |
| // Should never happen because we specified `noTimeOut = true` |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Same as {@link #get()} but with a timeout. The given timeout is the total timeout; |
| * each component task may have a smaller timeout for keeping the total equal to the |
| * given value. |
| * |
| * @param timeout the maximum time to wait. |
| * @param unit the time unit of the timeout argument. |
| * @throws CancellationException if at least one computation was cancelled. |
| * @throws ExecutionException if at least one computation threw an exception. |
| * @throws InterruptedException if the current thread was interrupted while waiting. |
| * @throws TimeoutException if the wait timed out. |
| */ |
| @Override |
| public R get(final long timeout, final TimeUnit unit) |
| throws InterruptedException, ExecutionException, TimeoutException |
| { |
| return get(System.nanoTime() + TimeUnit.NANOSECONDS.convert(timeout, unit), false); |
| } |
| |
| /** |
| * Implementation of public {@code get(…)} methods. |
| * The timeout given to this method, if not ignored, is an absolute timeout. |
| * |
| * @param timeout {@link System#nanoTime()} value when to stop waiting. |
| * @param noTimeOut {@code true} if {@code timeout} should be ignored. |
| */ |
| private R get(final long timeout, final boolean noTimeOut) |
| throws InterruptedException, ExecutionException, TimeoutException |
| { |
| R singleton = null; |
| Set<R> results = null; |
| for (final Future<R> c : components) { |
| final R r = noTimeOut ? c.get() : c.get(Math.max(0, timeout - System.nanoTime()), TimeUnit.NANOSECONDS); |
| if (r != null) { |
| if (singleton == null) { |
| singleton = r; |
| } else if (r != singleton) { |
| if (results == null) { |
| results = new HashSet<>(); |
| results.add(singleton); |
| } |
| results.add(r); |
| } |
| } |
| } |
| if (results != null) { |
| singleton = merge(results); |
| } |
| return singleton; |
| } |
| |
| /** |
| * Invoked by {@code get(…)} if there is more than one non-null instance. |
| * The default implementation throws an exception. |
| * |
| * @param results all non-null instances found. |
| * @return the unique instance to return. |
| */ |
| protected R merge(final Set<R> results) { |
| final R singleton = CollectionsExt.singletonOrNull(results); |
| if (singleton != null) { |
| return singleton; |
| } |
| throw new IllegalStateException(Resources.format(Resources.Keys.NotASingleton_1, "get()")); |
| } |
| } |