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