]> git.basschouten.com Git - openhab-addons.git/blob
bb4a66e9573bbf4befbd1f779ff11c72ae47d492
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.bluetooth.util;
14
15 import java.util.concurrent.CancellationException;
16 import java.util.concurrent.CompletableFuture;
17 import java.util.concurrent.CompletionStage;
18 import java.util.concurrent.Executor;
19 import java.util.concurrent.Future;
20 import java.util.function.Function;
21 import java.util.function.Supplier;
22
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25
26 /**
27  * The {@code HeritableFuture} class extends {@link CompletableFuture} and adds the ability
28  * to cancel upstream CompletableFuture tasks. Normally when a CompletableFuture
29  * is cancelled only dependent futures cancel. This class will also cancel the parent
30  * HeritableFuture instances as well. All of the {@code CompletionStage} methods will
31  * return HeritableFuture children and thus by only maintaining a reference to the final future
32  * in the task chain it would be possible to cancel the entire chain by calling {@code cancel}.
33  * <p>
34  * Due to child futures now having a link to their parent futures, it is no longer possible
35  * for HeritableFuture to be garbage collected as upstream futures complete. It is highly
36  * advisable to only use a HeritableFuture for defining finite (preferably small) task trees. Do not use
37  * HeritableFuture in situations where you would endlessly append new tasks otherwise you will eventually
38  * cause an OutOfMemoryError.
39  *
40  * @author Connor Petty - Initial contribution
41  *
42  */
43 @NonNullByDefault
44 public class HeritableFuture<T> extends CompletableFuture<T> {
45
46     protected final Object futureLock = new Object();
47     protected @Nullable Future<?> parentFuture;
48
49     public HeritableFuture() {
50     }
51
52     public HeritableFuture(Future<?> parent) {
53         this.parentFuture = parent;
54     }
55
56     /**
57      *
58      * {@inheritDoc}
59      *
60      * @implSpec
61      *           This implementation returns a new HeritableFuture instance that uses
62      *           the current instance as a parent. Cancellation of the child will result in
63      *           cancellation of the parent.
64      */
65     @Override
66     public <U> CompletableFuture<U> newIncompleteFuture() {
67         return new HeritableFuture<>(this);
68     }
69
70     @SuppressWarnings("PMD.CompareObjectsWithEquals")
71     protected void setParentFuture(Supplier<@Nullable Future<?>> futureSupplier) {
72         synchronized (futureLock) {
73             var future = futureSupplier.get();
74             if (future != this) {
75                 if (isCancelled() && future != null) {
76                     future.cancel(true);
77                 } else {
78                     parentFuture = future;
79                 }
80             }
81         }
82     }
83
84     /**
85      *
86      * {@inheritDoc}
87      *
88      * @implSpec
89      *           This implementation cancels this future first, then cancels the parent future.
90      */
91     @Override
92     public boolean cancel(boolean mayInterruptIfRunning) {
93         if (completeExceptionally(new CancellationException())) {
94             synchronized (futureLock) {
95                 var future = parentFuture;
96                 parentFuture = null;
97                 if (future != null) {
98                     future.cancel(mayInterruptIfRunning);
99                 }
100             }
101             return true;
102         }
103         return isCancelled();
104     }
105
106     /**
107      *
108      * {@inheritDoc}
109      *
110      * @implSpec
111      *           This implementation will treat the future returned by the function as a parent future.
112      */
113     @Override
114     @NonNullByDefault({}) // the generics here don't play well with the null checker
115     public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) {
116         return new ComposeFunctionWrapper<>(fn, false, null).returnedFuture;
117     }
118
119     /**
120      *
121      * {@inheritDoc}
122      *
123      * @implSpec
124      *           This implementation will treat the future returned by the function as a parent future.
125      */
126     @Override
127     @NonNullByDefault({}) // the generics here don't play well with the null checker
128     public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn) {
129         return new ComposeFunctionWrapper<>(fn, true, null).returnedFuture;
130     }
131
132     /**
133      *
134      * {@inheritDoc}
135      *
136      * @implSpec
137      *           This implementation will treat the future returned by the function as a parent future.
138      */
139     @Override
140     @NonNullByDefault({}) // the generics here don't play well with the null checker
141     public <U> CompletableFuture<U> thenComposeAsync(Function<? super T, ? extends CompletionStage<U>> fn,
142             Executor executor) {
143         return new ComposeFunctionWrapper<>(fn, true, executor).returnedFuture;
144     }
145
146     /**
147      * This class is responsible for wrapping the supplied compose function.
148      * The instant the function returns the next CompletionStage, the parentFuture of the downstream HeritableFuture
149      * will be reassigned to the completion stage. This way cancellations of
150      * downstream futures will be able to reach the future returned by the supplied function.
151      *
152      * Most of the complexity going on in this class is due to the fact that the apply function might be
153      * called while calling `super.thenCompose`. This would happen if the current future is already complete
154      * since the next stage would be started immediately either on the current thread or asynchronously.
155      *
156      * @param <U> the type to be returned by the composed future
157      */
158     private class ComposeFunctionWrapper<U> implements Function<T, CompletionStage<U>> {
159
160         private final Object fieldsLock = new Object();
161         private final Function<? super T, ? extends CompletionStage<U>> fn;
162         private @Nullable HeritableFuture<U> composedFuture;
163         private @Nullable CompletionStage<U> innerStage;
164         // The final composed future to be used by users of this wrapper class
165         final HeritableFuture<U> returnedFuture;
166
167         public ComposeFunctionWrapper(Function<? super T, ? extends CompletionStage<U>> fn, boolean async,
168                 @Nullable Executor executor) {
169             this.fn = fn;
170
171             var f = (HeritableFuture<U>) thenCompose(async, executor);
172             synchronized (fieldsLock) {
173                 this.composedFuture = f;
174                 var stage = innerStage;
175                 if (stage != null) {
176                     // getting here means that the `apply` function was run before `composedFuture` was initialized.
177                     f.setParentFuture(stage::toCompletableFuture);
178                 }
179             }
180             this.returnedFuture = f;
181         }
182
183         private CompletableFuture<U> thenCompose(boolean async, @Nullable Executor executor) {
184             if (!async) {
185                 return HeritableFuture.super.thenCompose(this);
186             }
187             if (executor == null) {
188                 return HeritableFuture.super.thenComposeAsync(this);
189             }
190             return HeritableFuture.super.thenComposeAsync(this, executor);
191         }
192
193         @Override
194         public CompletionStage<U> apply(T t) {
195             CompletionStage<U> stage = fn.apply(t);
196             synchronized (fieldsLock) {
197                 var f = composedFuture;
198                 if (f == null) {
199                     // We got here before the wrapper finished initializing, so that
200                     // means that the enclosing future was already complete at the time `super.thenCompose` was called.
201                     // In which case the best we can do is save this stage so that the constructor can finish the job.
202                     innerStage = stage;
203                 } else {
204                     f.setParentFuture(stage::toCompletableFuture);
205                 }
206             }
207             return stage;
208         }
209     }
210 }