1 /* 2 * Copyright (c) 2017, 2018, 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 import java.net.http.WebSocket; 25 import java.nio.ByteBuffer; 26 import java.util.ArrayList; 27 import java.util.List; 28 import java.util.Objects; 29 import java.util.concurrent.CompletableFuture; 30 import java.util.concurrent.CompletionStage; 31 import java.util.function.Predicate; 32 33 public class MockListener implements WebSocket.Listener { 34 35 private final long bufferSize; 36 private long count; 37 private final List<Invocation> invocations = new ArrayList<>(); 38 private final CompletableFuture<?> lastCall = new CompletableFuture<>(); 39 private final Predicate<? super Invocation> collectUntil; 40 MockListener()41 public MockListener() { 42 this(1, MockListener::closeOrError); 43 } 44 MockListener(long bufferSize)45 public MockListener(long bufferSize) { 46 this(bufferSize, MockListener::closeOrError); 47 } 48 MockListener(Predicate<? super Invocation> collectUntil)49 public MockListener(Predicate<? super Invocation> collectUntil) { 50 this(1, collectUntil); 51 } 52 53 /* 54 * Typical buffer sizes: 1, n, Long.MAX_VALUE 55 */ MockListener(long bufferSize, Predicate<? super Invocation> collectUntil)56 public MockListener(long bufferSize, 57 Predicate<? super Invocation> collectUntil) { 58 if (bufferSize < 1) { 59 throw new IllegalArgumentException(); 60 } 61 Objects.requireNonNull(collectUntil); 62 this.bufferSize = bufferSize; 63 this.collectUntil = collectUntil; 64 } 65 closeOrError(Invocation i)66 private static boolean closeOrError(Invocation i) { 67 return i instanceof OnClose || i instanceof OnError; 68 } 69 70 @Override onOpen(WebSocket webSocket)71 public void onOpen(WebSocket webSocket) { 72 System.out.printf("onOpen(%s)%n", webSocket); 73 OnOpen inv = new OnOpen(webSocket); 74 synchronized (invocations) { 75 invocations.add(inv); 76 } 77 if (collectUntil.test(inv)) { 78 lastCall.complete(null); 79 } 80 onOpen0(webSocket); 81 } 82 onOpen0(WebSocket webSocket)83 protected void onOpen0(WebSocket webSocket) { 84 count = bufferSize - bufferSize / 2; 85 System.out.printf("request(%d)%n", bufferSize); 86 webSocket.request(bufferSize); 87 } 88 89 @Override onText(WebSocket webSocket, CharSequence message, boolean last)90 public CompletionStage<?> onText(WebSocket webSocket, 91 CharSequence message, 92 boolean last) { 93 System.out.printf("onText(%s, message.length=%s, %s)%n", webSocket, message.length(), last); 94 OnText inv = new OnText(webSocket, message.toString(), last); 95 synchronized (invocations) { 96 invocations.add(inv); 97 } 98 if (collectUntil.test(inv)) { 99 lastCall.complete(null); 100 } 101 return onText0(webSocket, message, last); 102 } 103 onText0(WebSocket webSocket, CharSequence message, boolean last)104 protected CompletionStage<?> onText0(WebSocket webSocket, 105 CharSequence message, 106 boolean last) { 107 replenish(webSocket); 108 return null; 109 } 110 111 @Override onBinary(WebSocket webSocket, ByteBuffer message, boolean last)112 public CompletionStage<?> onBinary(WebSocket webSocket, 113 ByteBuffer message, 114 boolean last) { 115 System.out.printf("onBinary(%s, %s, %s)%n", webSocket, message, last); 116 OnBinary inv = new OnBinary(webSocket, fullCopy(message), last); 117 synchronized (invocations) { 118 invocations.add(inv); 119 } 120 if (collectUntil.test(inv)) { 121 lastCall.complete(null); 122 } 123 return onBinary0(webSocket, message, last); 124 } 125 onBinary0(WebSocket webSocket, ByteBuffer message, boolean last)126 protected CompletionStage<?> onBinary0(WebSocket webSocket, 127 ByteBuffer message, 128 boolean last) { 129 replenish(webSocket); 130 return null; 131 } 132 133 @Override onPing(WebSocket webSocket, ByteBuffer message)134 public CompletionStage<?> onPing(WebSocket webSocket, ByteBuffer message) { 135 System.out.printf("onPing(%s, %s)%n", webSocket, message); 136 OnPing inv = new OnPing(webSocket, fullCopy(message)); 137 synchronized (invocations) { 138 invocations.add(inv); 139 } 140 if (collectUntil.test(inv)) { 141 lastCall.complete(null); 142 } 143 return onPing0(webSocket, message); 144 } 145 onPing0(WebSocket webSocket, ByteBuffer message)146 protected CompletionStage<?> onPing0(WebSocket webSocket, ByteBuffer message) { 147 replenish(webSocket); 148 return null; 149 } 150 151 @Override onPong(WebSocket webSocket, ByteBuffer message)152 public CompletionStage<?> onPong(WebSocket webSocket, ByteBuffer message) { 153 System.out.printf("onPong(%s, %s)%n", webSocket, message); 154 OnPong inv = new OnPong(webSocket, fullCopy(message)); 155 synchronized (invocations) { 156 invocations.add(inv); 157 } 158 if (collectUntil.test(inv)) { 159 lastCall.complete(null); 160 } 161 return onPong0(webSocket, message); 162 } 163 onPong0(WebSocket webSocket, ByteBuffer message)164 protected CompletionStage<?> onPong0(WebSocket webSocket, ByteBuffer message) { 165 replenish(webSocket); 166 return null; 167 } 168 169 @Override onClose(WebSocket webSocket, int statusCode, String reason)170 public CompletionStage<?> onClose(WebSocket webSocket, 171 int statusCode, 172 String reason) { 173 System.out.printf("onClose(%s, %s, %s)%n", webSocket, statusCode, reason); 174 OnClose inv = new OnClose(webSocket, statusCode, reason); 175 synchronized (invocations) { 176 invocations.add(inv); 177 } 178 if (collectUntil.test(inv)) { 179 lastCall.complete(null); 180 } 181 return onClose0(webSocket, statusCode, reason); 182 } 183 onClose0(WebSocket webSocket, int statusCode, String reason)184 protected CompletionStage<?> onClose0(WebSocket webSocket, 185 int statusCode, 186 String reason) { 187 return null; 188 } 189 190 @Override onError(WebSocket webSocket, Throwable error)191 public void onError(WebSocket webSocket, Throwable error) { 192 System.out.printf("onError(%s, %s)%n", webSocket, error); 193 error.printStackTrace(System.out); 194 OnError inv = new OnError(webSocket, error == null ? null : error.getClass()); 195 synchronized (invocations) { 196 invocations.add(inv); 197 } 198 if (collectUntil.test(inv)) { 199 lastCall.complete(null); 200 } 201 onError0(webSocket, error); 202 } 203 onError0(WebSocket webSocket, Throwable error)204 protected void onError0(WebSocket webSocket, Throwable error) { } 205 invocationsSoFar()206 public List<Invocation> invocationsSoFar() { 207 synchronized (invocations) { 208 return new ArrayList<>(invocations); 209 } 210 } 211 invocations()212 public List<Invocation> invocations() { 213 lastCall.join(); 214 synchronized (invocations) { 215 return new ArrayList<>(invocations); 216 } 217 } 218 replenish(WebSocket webSocket)219 protected void replenish(WebSocket webSocket) { 220 if (--count <= 0) { 221 count = bufferSize - bufferSize / 2; 222 webSocket.request(count); 223 System.out.printf("request(%d)%n", count); 224 } 225 } 226 227 public abstract static class Invocation { 228 onOpen(WebSocket webSocket)229 public static OnOpen onOpen(WebSocket webSocket) { 230 return new OnOpen(webSocket); 231 } 232 onText(WebSocket webSocket, String text, boolean last)233 public static OnText onText(WebSocket webSocket, 234 String text, 235 boolean last) { 236 return new OnText(webSocket, text, last); 237 } 238 onBinary(WebSocket webSocket, ByteBuffer data, boolean last)239 public static OnBinary onBinary(WebSocket webSocket, 240 ByteBuffer data, 241 boolean last) { 242 return new OnBinary(webSocket, data, last); 243 } 244 onPing(WebSocket webSocket, ByteBuffer data)245 public static OnPing onPing(WebSocket webSocket, 246 ByteBuffer data) { 247 return new OnPing(webSocket, data); 248 } 249 onPong(WebSocket webSocket, ByteBuffer data)250 public static OnPong onPong(WebSocket webSocket, 251 ByteBuffer data) { 252 return new OnPong(webSocket, data); 253 } 254 onClose(WebSocket webSocket, int statusCode, String reason)255 public static OnClose onClose(WebSocket webSocket, 256 int statusCode, 257 String reason) { 258 return new OnClose(webSocket, statusCode, reason); 259 } 260 onError(WebSocket webSocket, Class<? extends Throwable> clazz)261 public static OnError onError(WebSocket webSocket, 262 Class<? extends Throwable> clazz) { 263 return new OnError(webSocket, clazz); 264 } 265 266 final WebSocket webSocket; 267 Invocation(WebSocket webSocket)268 private Invocation(WebSocket webSocket) { 269 this.webSocket = webSocket; 270 } 271 } 272 273 public static final class OnOpen extends Invocation { 274 OnOpen(WebSocket webSocket)275 public OnOpen(WebSocket webSocket) { 276 super(webSocket); 277 } 278 279 @Override equals(Object o)280 public boolean equals(Object o) { 281 if (this == o) return true; 282 if (o == null || getClass() != o.getClass()) return false; 283 Invocation that = (Invocation) o; 284 return Objects.equals(webSocket, that.webSocket); 285 } 286 287 @Override hashCode()288 public int hashCode() { 289 return Objects.hashCode(webSocket); 290 } 291 292 @Override toString()293 public String toString() { 294 return String.format("onOpen(%s)", webSocket); 295 } 296 } 297 298 public static final class OnText extends Invocation { 299 300 final String text; 301 final boolean last; 302 OnText(WebSocket webSocket, String text, boolean last)303 public OnText(WebSocket webSocket, String text, boolean last) { 304 super(webSocket); 305 this.text = text; 306 this.last = last; 307 } 308 309 @Override equals(Object o)310 public boolean equals(Object o) { 311 if (this == o) return true; 312 if (o == null || getClass() != o.getClass()) return false; 313 OnText onText = (OnText) o; 314 return Objects.equals(text, onText.text) && 315 last == onText.last && 316 Objects.equals(webSocket, onText.webSocket); 317 } 318 319 @Override hashCode()320 public int hashCode() { 321 return Objects.hash(text, last, webSocket); 322 } 323 324 @Override toString()325 public String toString() { 326 return String.format("onText(%s, message.length=%s, %s)", webSocket, text.length(), last); 327 } 328 } 329 330 public static final class OnBinary extends Invocation { 331 332 final ByteBuffer data; 333 final boolean last; 334 OnBinary(WebSocket webSocket, ByteBuffer data, boolean last)335 public OnBinary(WebSocket webSocket, ByteBuffer data, boolean last) { 336 super(webSocket); 337 this.data = data; 338 this.last = last; 339 } 340 341 @Override equals(Object o)342 public boolean equals(Object o) { 343 if (this == o) return true; 344 if (o == null || getClass() != o.getClass()) return false; 345 OnBinary onBinary = (OnBinary) o; 346 return Objects.equals(data, onBinary.data) && 347 last == onBinary.last && 348 Objects.equals(webSocket, onBinary.webSocket); 349 } 350 351 @Override hashCode()352 public int hashCode() { 353 return Objects.hash(data, last, webSocket); 354 } 355 356 @Override toString()357 public String toString() { 358 return String.format("onBinary(%s, %s, %s)", webSocket, data, last); 359 } 360 } 361 362 public static final class OnPing extends Invocation { 363 364 final ByteBuffer data; 365 OnPing(WebSocket webSocket, ByteBuffer data)366 public OnPing(WebSocket webSocket, ByteBuffer data) { 367 super(webSocket); 368 this.data = data; 369 } 370 371 @Override equals(Object o)372 public boolean equals(Object o) { 373 if (this == o) return true; 374 if (o == null || getClass() != o.getClass()) return false; 375 OnPing onPing = (OnPing) o; 376 return Objects.equals(data, onPing.data) && 377 Objects.equals(webSocket, onPing.webSocket); 378 } 379 380 @Override hashCode()381 public int hashCode() { 382 return Objects.hash(data, webSocket); 383 } 384 385 @Override toString()386 public String toString() { 387 return String.format("onPing(%s, %s)", webSocket, data); 388 } 389 } 390 391 public static final class OnPong extends Invocation { 392 393 final ByteBuffer data; 394 OnPong(WebSocket webSocket, ByteBuffer data)395 public OnPong(WebSocket webSocket, ByteBuffer data) { 396 super(webSocket); 397 this.data = data; 398 } 399 400 @Override equals(Object o)401 public boolean equals(Object o) { 402 if (this == o) return true; 403 if (o == null || getClass() != o.getClass()) return false; 404 OnPong onPong = (OnPong) o; 405 return Objects.equals(data, onPong.data) && 406 Objects.equals(webSocket, onPong.webSocket); 407 } 408 409 @Override hashCode()410 public int hashCode() { 411 return Objects.hash(data, webSocket); 412 } 413 414 @Override toString()415 public String toString() { 416 return String.format("onPong(%s, %s)", webSocket, data); 417 } 418 } 419 420 public static final class OnClose extends Invocation { 421 422 final int statusCode; 423 final String reason; 424 OnClose(WebSocket webSocket, int statusCode, String reason)425 public OnClose(WebSocket webSocket, int statusCode, String reason) { 426 super(webSocket); 427 this.statusCode = statusCode; 428 this.reason = reason; 429 } 430 431 @Override equals(Object o)432 public boolean equals(Object o) { 433 if (this == o) return true; 434 if (o == null || getClass() != o.getClass()) return false; 435 OnClose onClose = (OnClose) o; 436 return statusCode == onClose.statusCode && 437 Objects.equals(reason, onClose.reason) && 438 Objects.equals(webSocket, onClose.webSocket); 439 } 440 441 @Override hashCode()442 public int hashCode() { 443 return Objects.hash(statusCode, reason, webSocket); 444 } 445 446 @Override toString()447 public String toString() { 448 return String.format("onClose(%s, %s, %s)", webSocket, statusCode, reason); 449 } 450 } 451 452 public static final class OnError extends Invocation { 453 454 final Class<? extends Throwable> clazz; 455 OnError(WebSocket webSocket, Class<? extends Throwable> clazz)456 public OnError(WebSocket webSocket, Class<? extends Throwable> clazz) { 457 super(webSocket); 458 this.clazz = clazz; 459 } 460 461 @Override equals(Object o)462 public boolean equals(Object o) { 463 if (this == o) return true; 464 if (o == null || getClass() != o.getClass()) return false; 465 OnError onError = (OnError) o; 466 return Objects.equals(clazz, onError.clazz) && 467 Objects.equals(webSocket, onError.webSocket); 468 } 469 470 @Override hashCode()471 public int hashCode() { 472 return Objects.hash(clazz, webSocket); 473 } 474 475 @Override toString()476 public String toString() { 477 return String.format("onError(%s, %s)", webSocket, clazz); 478 } 479 } 480 fullCopy(ByteBuffer src)481 private static ByteBuffer fullCopy(ByteBuffer src) { 482 ByteBuffer copy = ByteBuffer.allocate(src.capacity()); 483 int p = src.position(); 484 int l = src.limit(); 485 src.clear(); 486 copy.put(src).position(p).limit(l); 487 src.position(p).limit(l); 488 return copy; 489 } 490 } 491