1 /*
   2  * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.incubator.http.internal.websocket;
  25 
  26 import jdk.incubator.http.WebSocket;
  27 import jdk.incubator.http.WebSocket.MessagePart;
  28 
  29 import java.nio.ByteBuffer;
  30 import java.util.ArrayList;
  31 import java.util.List;
  32 import java.util.Objects;
  33 import java.util.concurrent.CompletableFuture;
  34 import java.util.concurrent.CompletionStage;
  35 
  36 import static jdk.incubator.http.internal.websocket.TestSupport.fullCopy;
  37 
  38 public class MockListener implements WebSocket.Listener {
  39 
  40     private final long bufferSize;
  41     private long count;
  42     private final List<ListenerInvocation> invocations = new ArrayList<>();
  43     private final CompletableFuture<?> lastCall = new CompletableFuture<>();
  44 
  45     /*
  46      * Typical buffer sizes: 1, n, Long.MAX_VALUE
  47      */
  48     public MockListener(long bufferSize) {
  49         if (bufferSize < 1) {
  50             throw new IllegalArgumentException();
  51         }
  52         this.bufferSize = bufferSize;
  53     }
  54 
  55     @Override
  56     public void onOpen(WebSocket webSocket) {
  57         System.out.printf("onOpen(%s)%n", webSocket);
  58         invocations.add(new OnOpen(webSocket));
  59         onOpen0(webSocket);
  60     }
  61 
  62     protected void onOpen0(WebSocket webSocket) {
  63         replenish(webSocket);
  64     }
  65 
  66     @Override
  67     public CompletionStage<?> onText(WebSocket webSocket,
  68                                      CharSequence message,
  69                                      MessagePart part) {
  70         System.out.printf("onText(%s, %s, %s)%n", webSocket, message, part);
  71         invocations.add(new OnText(webSocket, message.toString(), part));
  72         return onText0(webSocket, message, part);
  73     }
  74 
  75     protected CompletionStage<?> onText0(WebSocket webSocket,
  76                                          CharSequence message,
  77                                          MessagePart part) {
  78         replenish(webSocket);
  79         return null;
  80     }
  81 
  82     @Override
  83     public CompletionStage<?> onBinary(WebSocket webSocket,
  84                                        ByteBuffer message,
  85                                        MessagePart part) {
  86         System.out.printf("onBinary(%s, %s, %s)%n", webSocket, message, part);
  87         invocations.add(new OnBinary(webSocket, fullCopy(message), part));
  88         return onBinary0(webSocket, message, part);
  89     }
  90 
  91     protected CompletionStage<?> onBinary0(WebSocket webSocket,
  92                                            ByteBuffer message,
  93                                            MessagePart part) {
  94         replenish(webSocket);
  95         return null;
  96     }
  97 
  98     @Override
  99     public CompletionStage<?> onPing(WebSocket webSocket, ByteBuffer message) {
 100         System.out.printf("onPing(%s, %s)%n", webSocket, message);
 101         invocations.add(new OnPing(webSocket, fullCopy(message)));
 102         return onPing0(webSocket, message);
 103     }
 104 
 105     protected CompletionStage<?> onPing0(WebSocket webSocket, ByteBuffer message) {
 106         replenish(webSocket);
 107         return null;
 108     }
 109 
 110     @Override
 111     public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) {
 112         System.out.printf("onPong(%s, %s)%n", webSocket, message);
 113         invocations.add(new OnPong(webSocket, fullCopy(message)));
 114         return onPong0(webSocket, message);
 115     }
 116 
 117     protected CompletionStage<?> onPong0(WebSocket webSocket, ByteBuffer message) {
 118         replenish(webSocket);
 119         return null;
 120     }
 121 
 122     @Override
 123     public CompletionStage<?> onClose(WebSocket webSocket,
 124                                       int statusCode,
 125                                       String reason) {
 126         System.out.printf("onClose(%s, %s, %s)%n", webSocket, statusCode, reason);
 127         invocations.add(new OnClose(webSocket, statusCode, reason));
 128         lastCall.complete(null);
 129         return null;
 130     }
 131 
 132     @Override
 133     public void onError(WebSocket webSocket, Throwable error) {
 134         System.out.printf("onError(%s, %s)%n", webSocket, error);
 135         invocations.add(new OnError(webSocket, error == null ? null : error.getClass()));
 136         lastCall.complete(null);
 137     }
 138 
 139     public CompletableFuture<?> onCloseOrOnErrorCalled() {
 140         return lastCall.copy();
 141     }
 142 
 143     protected void replenish(WebSocket webSocket) {
 144         if (--count <= 0) {
 145             count = bufferSize - bufferSize / 2;
 146         }
 147         webSocket.request(count);
 148     }
 149 
 150     public List<ListenerInvocation> invocations() {
 151         return new ArrayList<>(invocations);
 152     }
 153 
 154     public abstract static class ListenerInvocation {
 155 
 156         public static OnOpen onOpen(WebSocket webSocket) {
 157             return new OnOpen(webSocket);
 158         }
 159 
 160         public static OnText onText(WebSocket webSocket,
 161                                     String text,
 162                                     MessagePart part) {
 163             return new OnText(webSocket, text, part);
 164         }
 165 
 166         public static OnBinary onBinary(WebSocket webSocket,
 167                                         ByteBuffer data,
 168                                         MessagePart part) {
 169             return new OnBinary(webSocket, data, part);
 170         }
 171 
 172         public static OnPing onPing(WebSocket webSocket,
 173                                     ByteBuffer data) {
 174             return new OnPing(webSocket, data);
 175         }
 176 
 177         public static OnPong onPong(WebSocket webSocket,
 178                                     ByteBuffer data) {
 179             return new OnPong(webSocket, data);
 180         }
 181 
 182         public static OnClose onClose(WebSocket webSocket,
 183                                       int statusCode,
 184                                       String reason) {
 185             return new OnClose(webSocket, statusCode, reason);
 186         }
 187 
 188         public static OnError onError(WebSocket webSocket,
 189                                       Class<? extends Throwable> clazz) {
 190             return new OnError(webSocket, clazz);
 191         }
 192 
 193         final WebSocket webSocket;
 194 
 195         private ListenerInvocation(WebSocket webSocket) {
 196             this.webSocket = webSocket;
 197         }
 198     }
 199 
 200     public static final class OnOpen extends ListenerInvocation {
 201 
 202         public OnOpen(WebSocket webSocket) {
 203             super(webSocket);
 204         }
 205 
 206         @Override
 207         public boolean equals(Object o) {
 208             if (this == o) return true;
 209             if (o == null || getClass() != o.getClass()) return false;
 210             ListenerInvocation that = (ListenerInvocation) o;
 211             return Objects.equals(webSocket, that.webSocket);
 212         }
 213 
 214         @Override
 215         public int hashCode() {
 216             return Objects.hashCode(webSocket);
 217         }
 218     }
 219 
 220     public static final class OnText extends ListenerInvocation {
 221 
 222         final String text;
 223         final MessagePart part;
 224 
 225         public OnText(WebSocket webSocket, String text, MessagePart part) {
 226             super(webSocket);
 227             this.text = text;
 228             this.part = part;
 229         }
 230 
 231         @Override
 232         public boolean equals(Object o) {
 233             if (this == o) return true;
 234             if (o == null || getClass() != o.getClass()) return false;
 235             OnText onText = (OnText) o;
 236             return Objects.equals(text, onText.text) &&
 237                     part == onText.part &&
 238                     Objects.equals(webSocket, onText.webSocket);
 239         }
 240 
 241         @Override
 242         public int hashCode() {
 243             return Objects.hash(text, part, webSocket);
 244         }
 245     }
 246 
 247     public static final class OnBinary extends ListenerInvocation {
 248 
 249         final ByteBuffer data;
 250         final MessagePart part;
 251 
 252         public OnBinary(WebSocket webSocket, ByteBuffer data, MessagePart part) {
 253             super(webSocket);
 254             this.data = data;
 255             this.part = part;
 256         }
 257 
 258         @Override
 259         public boolean equals(Object o) {
 260             if (this == o) return true;
 261             if (o == null || getClass() != o.getClass()) return false;
 262             OnBinary onBinary = (OnBinary) o;
 263             return Objects.equals(data, onBinary.data) &&
 264                     part == onBinary.part &&
 265                     Objects.equals(webSocket, onBinary.webSocket);
 266         }
 267 
 268         @Override
 269         public int hashCode() {
 270             return Objects.hash(data, part, webSocket);
 271         }
 272     }
 273 
 274     public static final class OnPing extends ListenerInvocation {
 275 
 276         final ByteBuffer data;
 277 
 278         public OnPing(WebSocket webSocket, ByteBuffer data) {
 279             super(webSocket);
 280             this.data = data;
 281         }
 282 
 283         @Override
 284         public boolean equals(Object o) {
 285             if (this == o) return true;
 286             if (o == null || getClass() != o.getClass()) return false;
 287             OnPing onPing = (OnPing) o;
 288             return Objects.equals(data, onPing.data) &&
 289                     Objects.equals(webSocket, onPing.webSocket);
 290         }
 291 
 292         @Override
 293         public int hashCode() {
 294             return Objects.hash(data, webSocket);
 295         }
 296     }
 297 
 298     public static final class OnPong extends ListenerInvocation {
 299 
 300         final ByteBuffer data;
 301 
 302         public OnPong(WebSocket webSocket, ByteBuffer data) {
 303             super(webSocket);
 304             this.data = data;
 305         }
 306 
 307         @Override
 308         public boolean equals(Object o) {
 309             if (this == o) return true;
 310             if (o == null || getClass() != o.getClass()) return false;
 311             OnPong onPong = (OnPong) o;
 312             return Objects.equals(data, onPong.data) &&
 313                     Objects.equals(webSocket, onPong.webSocket);
 314         }
 315 
 316         @Override
 317         public int hashCode() {
 318             return Objects.hash(data, webSocket);
 319         }
 320     }
 321 
 322     public static final class OnClose extends ListenerInvocation {
 323 
 324         final int statusCode;
 325         final String reason;
 326 
 327         public OnClose(WebSocket webSocket, int statusCode, String reason) {
 328             super(webSocket);
 329             this.statusCode = statusCode;
 330             this.reason = reason;
 331         }
 332 
 333         @Override
 334         public boolean equals(Object o) {
 335             if (this == o) return true;
 336             if (o == null || getClass() != o.getClass()) return false;
 337             OnClose onClose = (OnClose) o;
 338             return statusCode == onClose.statusCode &&
 339                     Objects.equals(reason, onClose.reason) &&
 340                     Objects.equals(webSocket, onClose.webSocket);
 341         }
 342 
 343         @Override
 344         public int hashCode() {
 345             return Objects.hash(statusCode, reason, webSocket);
 346         }
 347     }
 348 
 349     public static final class OnError extends ListenerInvocation {
 350 
 351         final Class<? extends Throwable> clazz;
 352 
 353         public OnError(WebSocket webSocket, Class<? extends Throwable> clazz) {
 354             super(webSocket);
 355             this.clazz = clazz;
 356         }
 357 
 358         @Override
 359         public boolean equals(Object o) {
 360             if (this == o) return true;
 361             if (o == null || getClass() != o.getClass()) return false;
 362             OnError onError = (OnError) o;
 363             return Objects.equals(clazz, onError.clazz) &&
 364                     Objects.equals(webSocket, onError.webSocket);
 365         }
 366 
 367         @Override
 368         public int hashCode() {
 369             return Objects.hash(clazz, webSocket);
 370         }
 371     }
 372 }