http-client-branch: MinimalFuture deep obtrusion http-client-branch
authorprappo
Mon, 22 Jan 2018 17:29:07 +0000
branchhttp-client-branch
changeset 56035 2f3f5da13c4c
parent 56034 43b531ed872b
child 56036 89a688549f5d
http-client-branch: MinimalFuture deep obtrusion
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseSubscribers.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/MinimalFuture.java
src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLFlowDelegate.java
test/jdk/java/net/httpclient/LineBodyHandlerTest.java
test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java
test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/common/MinimalFutureTest.java
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Mon Jan 22 17:29:07 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -114,7 +114,7 @@
             } else {
                 DEBUG_LOGGER.log(Level.DEBUG, "HTTP/2 connection creation failed "
                                   + "with unexpected exception: %s", (Object)t);
-                return CompletableFuture.failedFuture(t);
+                return MinimalFuture.failedFuture(t);
             }
         }
         if (secure && c== null) {
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2ClientImpl.java	Mon Jan 22 17:29:07 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -98,7 +98,7 @@
         synchronized (this) {
             Http2Connection connection = connections.get(key);
             if (connection != null) { // fast path if connection already exists
-                return CompletableFuture.completedFuture(connection);
+                return MinimalFuture.completedFuture(connection);
             }
 
             if (!req.secure() || failures.contains(key)) {
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseSubscribers.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ResponseSubscribers.java	Mon Jan 22 17:29:07 2018 +0000
@@ -282,7 +282,7 @@
         public CompletionStage<InputStream> getBody() {
             // Returns the stream immediately, before the
             // response body is received.
-            // This makes it possible for senAsync().get().body()
+            // This makes it possible for sendAsync().get().body()
             // to complete before the response body is received.
             return CompletableFuture.completedStage(this);
         }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/MinimalFuture.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/MinimalFuture.java	Mon Jan 22 17:29:07 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -26,14 +26,10 @@
 package jdk.incubator.http.internal.common;
 
 import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
 import java.util.concurrent.CompletionStage;
-import java.util.concurrent.Executor;
-import java.util.function.BiConsumer;
-import java.util.function.Function;
+import java.util.concurrent.atomic.AtomicLong;
 
 import static java.util.Objects.requireNonNull;
-import java.util.concurrent.atomic.AtomicLong;
 
 /*
  * A CompletableFuture which does not allow any obtrusion logic.
@@ -46,8 +42,8 @@
         U get() throws Throwable;
     }
 
-    final static AtomicLong TOKENS = new AtomicLong();
-    final long id;
+    private final static AtomicLong TOKENS = new AtomicLong();
+    private final long id;
 
     public static <U> MinimalFuture<U> completedFuture(U value) {
         MinimalFuture<U> f = new MinimalFuture<>();
@@ -73,73 +69,13 @@
         return cf;
     }
 
-    public static <U> CompletableFuture<U> supply(ExceptionalSupplier<U> supplier, Executor executor) {
-        CompletableFuture<U> cf = new MinimalFuture<>();
-        cf.completeAsync( () -> {
-            try {
-                return supplier.get();
-            } catch (Throwable ex) {
-                throw new CompletionException(ex);
-            }
-        }, executor);
-        return cf;
-    }
-
     public MinimalFuture() {
         super();
         this.id = TOKENS.incrementAndGet();
     }
 
-    /**
-     * Creates a defensive copy of the given {@code CompletionStage}.
-     *
-     * <p> Might be useful both for producers and consumers of {@code
-     * CompletionStage}s.
-     *
-     * <p> Producers are protected from possible uncontrolled modifications
-     * (cancellation, completion, obtrusion, etc.) as well as from executing
-     * unknown potentially lengthy or faulty dependants in the given {@code
-     * CompletionStage}'s default execution facility or synchronously.
-     *
-     * <p> Consumers are protected from some of the aspects of misbehaving
-     * implementations (e.g. accepting results, applying functions, running
-     * tasks, etc. more than once or escape of a reference to their private
-     * executor, etc.) by providing a reliable proxy they use instead.
-     *
-     * @param src
-     *         the {@code CompletionStage} to make a copy from
-     * @param executor
-     *         the executor used to propagate the completion
-     * @param <T>
-     *         the type of the {@code CompletionStage}'s result
-     *
-     * @return a copy of the given stage
-     */
-    public static <T> MinimalFuture<T> copy(CompletionStage<T> src,
-                                            Executor executor) {
-        MinimalFuture<T> copy = new MinimalFuture<>();
-        BiConsumer<T, Throwable> relay =
-                (result, error) -> {
-                    if (error != null) {
-                        copy.completeExceptionally(error);
-                    } else {
-                        copy.complete(result);
-                    }
-                };
-
-        if (src.getClass() == CompletableFuture.class) {
-            // No subclasses! Strictly genuine CompletableFuture.
-            src.whenCompleteAsync(relay, executor);
-            return copy;
-        } else {
-            // Don't give our executor away to an unknown CS!
-            src.whenComplete(relay);
-            return (MinimalFuture<T>)
-                    copy.thenApplyAsync(Function.identity(), executor);
-        }
-    }
-
-    public static <U> MinimalFuture<U> newMinimalFuture() {
+    @Override
+    public <U> MinimalFuture<U> newIncompleteFuture() {
         return new MinimalFuture<>();
     }
 
@@ -159,7 +95,7 @@
     }
 
     public static <U> MinimalFuture<U> of(CompletionStage<U> stage) {
-        MinimalFuture<U> cf = newMinimalFuture();
+        MinimalFuture<U> cf = new MinimalFuture<>();
         stage.whenComplete((r,t) -> complete(cf, r, t));
         return cf;
     }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLFlowDelegate.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/internal/common/SSLFlowDelegate.java	Mon Jan 22 17:29:07 2018 +0000
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,26 +25,27 @@
 
 package jdk.incubator.http.internal.common;
 
-import java.io.IOException;
-import java.lang.System.Logger.Level;
-import java.nio.ByteBuffer;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Flow;
-import java.util.concurrent.Flow.Subscriber;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.atomic.AtomicInteger;
+import jdk.incubator.http.internal.common.SubscriberWrapper.SchedulingAction;
+
 import javax.net.ssl.SSLEngine;
 import javax.net.ssl.SSLEngineResult;
 import javax.net.ssl.SSLEngineResult.HandshakeStatus;
 import javax.net.ssl.SSLEngineResult.Status;
 import javax.net.ssl.SSLException;
-import jdk.incubator.http.internal.common.SubscriberWrapper.SchedulingAction;
+import java.io.IOException;
+import java.lang.System.Logger.Level;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Flow;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Implements SSL using two SubscriberWrappers.
@@ -89,7 +90,7 @@
     final Writer writer;
     final SSLEngine engine;
     final String tubeName; // hack
-    final CompletableFuture<Void> cf;
+    private final CompletableFuture<Void> cf;
     final CompletableFuture<String> alpnCF; // completes on initial handshake
     final static ByteBuffer SENTINEL = Utils.EMPTY_BYTEBUFFER;
     volatile boolean close_notify_received;
@@ -110,8 +111,9 @@
         this.engine = engine;
         this.exec = exec;
         this.handshakeState = new AtomicInteger(NOT_HANDSHAKING);
-        this.cf = CompletableFuture.allOf(reader.completion(), writer.completion())
-                                   .thenRun(this::normalStop);
+        CompletableFuture<Void> cs = CompletableFuture.allOf(
+                reader.completion(), writer.completion()).thenRun(this::normalStop);
+        this.cf = MinimalFuture.of(cs);
         this.alpnCF = new MinimalFuture<>();
 
         // connect the Reader to the downReader and the
--- a/test/jdk/java/net/httpclient/LineBodyHandlerTest.java	Fri Jan 19 16:48:12 2018 +0000
+++ b/test/jdk/java/net/httpclient/LineBodyHandlerTest.java	Mon Jan 22 17:29:07 2018 +0000
@@ -21,6 +21,23 @@
  * questions.
  */
 
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsServer;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.incubator.http.HttpResponse.BodyHandler;
+import jdk.incubator.http.HttpResponse.BodySubscriber;
+import jdk.testlibrary.SimpleSSLContext;
+import org.testng.annotations.AfterTest;
+import org.testng.annotations.BeforeTest;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import javax.net.ssl.SSLContext;
 import java.io.BufferedReader;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -36,6 +53,7 @@
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Flow;
 import java.util.function.Function;
@@ -43,30 +61,13 @@
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import com.sun.net.httpserver.HttpExchange;
-import com.sun.net.httpserver.HttpHandler;
-import com.sun.net.httpserver.HttpServer;
-import com.sun.net.httpserver.HttpsConfigurator;
-import com.sun.net.httpserver.HttpsServer;
-import jdk.incubator.http.HttpClient;
-import jdk.incubator.http.HttpRequest;
-import jdk.incubator.http.HttpResponse;
-import jdk.incubator.http.HttpResponse.BodyHandler;
-import jdk.incubator.http.HttpResponse.BodySubscriber;
-import jdk.testlibrary.SimpleSSLContext;
-import org.testng.annotations.AfterTest;
-import org.testng.annotations.BeforeTest;
-import org.testng.annotations.DataProvider;
-import org.testng.annotations.Test;
-import javax.net.ssl.SSLContext;
-
 import static java.nio.charset.StandardCharsets.UTF_16;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static jdk.incubator.http.HttpRequest.BodyPublisher.fromString;
 import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertNotNull;
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.assertTrue;
-import static org.testng.Assert.assertNotNull;
 
 /*
  * @test
@@ -194,9 +195,11 @@
                 .build();
 
         StringSubscriber subscriber = new StringSubscriber();
-        HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber, Supplier::get,"\n"))
-                .join();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandler.fromLineSubscriber(
+                        subscriber, Supplier::get, "\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -213,8 +216,10 @@
                 .POST(fromString(body))
                 .build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -234,8 +239,11 @@
                 .build();
 
         StringSubscriber subscriber = new StringSubscriber();
-        HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber)).join();
+        CompletableFuture<HttpResponse<Void>> cf
+                = client.sendAsync(request,
+                                   BodyHandler.fromLineSubscriber(subscriber));
+        assertNoObtrusion(cf);
+        HttpResponse<Void> response = cf.join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -251,8 +259,10 @@
                 .POST(fromString(body))
                 .build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -309,8 +319,10 @@
                 .POST(fromString(body))
                 .build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -334,8 +346,10 @@
                 .header("Content-type", "text/text; charset=UTF-8")
                 .POST(fromString(body, UTF_8)).build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -358,8 +372,10 @@
                 .header("Content-type", "text/text; charset=UTF-16")
                 .POST(fromString(body, UTF_16)).build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -384,9 +400,11 @@
                 .build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
-        HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber, ObjectSubscriber::get, "\r\n"))
-                .join();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandler.fromLineSubscriber(
+                        subscriber, ObjectSubscriber::get, "\r\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -406,10 +424,11 @@
                 .header("Content-type", "text/text; charset=UTF-16")
                 .POST(fromString(body, UTF_16)).build();
         ObjectSubscriber subscriber = new ObjectSubscriber();
-        HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber,
-                                               ObjectSubscriber::get,
-                                   null)).join();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandler.fromLineSubscriber(
+                        subscriber, ObjectSubscriber::get, null));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -433,8 +452,11 @@
                 .build();
 
         ObjectSubscriber subscriber = new ObjectSubscriber();
-        HttpResponse<Void> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber)).join();
+        CompletableFuture<HttpResponse<Void>> cf
+                = client.sendAsync(request,
+                                   BodyHandler.fromLineSubscriber(subscriber));
+        assertNoObtrusion(cf);
+        HttpResponse<Void> response = cf.join();
         String text = subscriber.get();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -518,9 +540,11 @@
                 .build();
 
         StringSubscriber subscriber = new StringSubscriber();
-        HttpResponse<String> response = client.sendAsync(request,
-                BodyHandler.fromLineSubscriber(subscriber, Supplier::get,"\r\n"))
-                .join();
+        CompletableFuture<HttpResponse<String>> cf
+                = client.sendAsync(request, BodyHandler.fromLineSubscriber(
+                        subscriber, Supplier::get, "\r\n"));
+        assertNoObtrusion(cf);
+        HttpResponse<String> response = cf.join();
         String text = response.body();
         System.out.println(text);
         assertEquals(response.statusCode(), 200);
@@ -537,8 +561,10 @@
                 .POST(fromString(bigtext))
                 .build();
 
-        HttpResponse<Stream<String>> response = client.sendAsync(request,
-                BodyHandler.asLines()).join();
+        CompletableFuture<HttpResponse<Stream<String>>> cf
+                = client.sendAsync(request, BodyHandler.asLines());
+        assertNoObtrusion(cf);
+        HttpResponse<Stream<String>> response = cf.join();
         Stream<String> stream = response.body();
         List<String> list = stream.collect(Collectors.toList());
         String text = list.stream().collect(Collectors.joining("|"));
@@ -699,4 +725,11 @@
             }
         }
     }
+
+    private static void assertNoObtrusion(CompletableFuture<?> cf) {
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeException(new RuntimeException()));
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeValue(null));
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/MinimalFutureTestDriver.java	Mon Jan 22 17:29:07 2018 +0000
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common
+ * @run testng jdk.incubator.httpclient/jdk.incubator.http.internal.common.MinimalFutureTest
+ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/net/httpclient/whitebox/jdk.incubator.httpclient/jdk/incubator/http/internal/common/MinimalFutureTest.java	Mon Jan 22 17:29:07 2018 +0000
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http.internal.common;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.testng.Assert.assertThrows;
+
+public class MinimalFutureTest {
+
+    @Test(dataProvider = "futures")
+    public void test(CompletableFuture<Object> mf) {
+        ExecutorService executor = Executors.newSingleThreadExecutor();
+        try {
+            assertNoObtrusion(mf.thenApply(MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenApplyAsync(MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.thenAccept(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptAsync(MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.thenRun(MinimalFutureTest::run));
+            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run));
+            assertNoObtrusion(mf.thenRunAsync(MinimalFutureTest::run, executor));
+
+            assertNoObtrusion(mf.thenCombine(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.thenCombineAsync(otherFuture(), MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.thenAcceptBoth(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.thenAcceptBothAsync(otherFuture(), MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.runAfterBoth(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterBothAsync(otherFuture(), MinimalFutureTest::run, executor));
+
+            // "either" methods may return something else if otherFuture() is
+            // not MinimalFuture
+
+            assertNoObtrusion(mf.applyToEither(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply));
+            assertNoObtrusion(mf.applyToEitherAsync(otherFuture(), MinimalFutureTest::apply, executor));
+
+            assertNoObtrusion(mf.acceptEither(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept));
+            assertNoObtrusion(mf.acceptEitherAsync(otherFuture(), MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.runAfterEither(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run));
+            assertNoObtrusion(mf.runAfterEitherAsync(otherFuture(), MinimalFutureTest::run, executor));
+
+            assertNoObtrusion(mf.thenCompose(MinimalFutureTest::completionStageOf));
+            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf));
+            assertNoObtrusion(mf.thenComposeAsync(MinimalFutureTest::completionStageOf, executor));
+
+            assertNoObtrusion(mf.handle(MinimalFutureTest::relay));
+            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay));
+            assertNoObtrusion(mf.handleAsync(MinimalFutureTest::relay, executor));
+
+            assertNoObtrusion(mf.whenComplete(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept));
+            assertNoObtrusion(mf.whenCompleteAsync(MinimalFutureTest::accept, executor));
+
+            assertNoObtrusion(mf.toCompletableFuture());
+            assertNoObtrusion(mf.exceptionally(t -> null));
+
+            assertNoObtrusion(mf);
+            assertNoObtrusion(mf.copy());
+            assertNoObtrusion(mf.newIncompleteFuture());
+        } finally {
+            executor.shutdownNow();
+        }
+    }
+
+    private static CompletableFuture<Object> otherFuture() {
+        return MinimalFuture.completedFuture(new Object());
+    }
+
+    private static Object relay(Object r, Throwable e) {
+        if (e != null)
+            throw new CompletionException(e);
+        else
+            return r;
+    }
+
+    private static CompletableFuture<?> completionStageOf(Object r) {
+        return new CompletableFuture<>();
+    }
+
+    private static void accept(Object arg) {
+    }
+
+    private static void accept(Object arg1, Object arg2) {
+    }
+
+    private static void run() {
+    }
+
+    private static Object apply(Object arg) {
+        return new Object();
+    }
+
+    private static Object apply(Object arg1, Object arg2) {
+        return new Object();
+    }
+
+
+    @DataProvider(name = "futures")
+    public Object[][] futures() {
+
+        MinimalFuture<Object> mf = new MinimalFuture<>();
+        mf.completeExceptionally(new Throwable());
+
+        MinimalFuture<Object> mf1 = new MinimalFuture<>();
+        mf1.complete(new Object());
+
+        return new Object[][]{
+                new Object[]{new MinimalFuture<>()},
+                new Object[]{MinimalFuture.failedFuture(new Throwable())},
+                new Object[]{MinimalFuture.completedFuture(new Object())},
+                new Object[]{mf},
+                new Object[]{mf1},
+        };
+    }
+
+    private void assertNoObtrusion(CompletableFuture<?> cf) {
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeValue(null));
+        assertThrows(UnsupportedOperationException.class,
+                     () -> cf.obtrudeException(new RuntimeException()));
+    }
+}