1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 | package Torello.Java.Additional; import java.util.function.Function; import javax.json.JsonObject; /** * A promise keeps the processing logic for converting a response from an asychronous connection * into a result that the end-user can utilize. * * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=PROMISE> * * @param <RESPONSE> <EMBED CLASS='external-html' DATA-FILE-ID=RESPONSE> * @param <RESULT> <EMBED CLASS='external-html' DATA-FILE-ID=RESULT> */ public class Promise<RESPONSE, RESULT> implements java.io.Serializable { /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ protected static final long serialVersionUID = 1; // This value is updated by the 'acceptResponse' method private RESULT result = null; // If an error has occured, these two fields will hold more information private boolean response = false; private boolean error = false; private int errorCode = 0; private String errorMessage = null; /** * Asks {@code 'this'} instance of {@code Promise} whether or not an error has been reported. * * @return {@code TRUE} if there were errors reported by the asynchronous channel, and * {@code FALSE} otherwise. */ public synchronized boolean hadError() { return error; } /** * Asks {@code 'this'} instance of {@code Promise} whether any response has been reported. * This shall return {@code TRUE} if <I>either an error, or a response has been received</I>. * * @return {@code TRUE} if a response or an error has been reported to {@code 'this'} instance, * and {@code FALSE} otherwise. */ public synchronized boolean hadResponse() { return response || error; } /** * Gets all current-state of this {@code Promise} * * @return The current state of this {@code Promise}, as an instance of {@link Ret5}. * * <BR /><BR /><UL CLASS=JDUL> * <LI>{@code Ret5.a (Boolean)}: * <BR />Whether or not a response has been reported * <BR /><BR /></LI> * <LI> {@code Ret5.b (Boolean)}: * <BR />Whether or not an error has been reported * <BR /><BR /></LI> * <LI> {@code Ret5.c (RESULT)}: * <BR />The result that has been returned, or null if no result has been received * <BR /><BR /></LI> * <LI> {@code Ret5.d (Integer)}: * <BR />The error-code that was received, or 0 if no errors have been reported * <BR /><BR /></LI> * <LI> {@code Ret5.e (String)}: * <BR />The error-message (if an error was reported), or null. * </LI> * </UL> */ public synchronized Ret5<Boolean, Boolean, RESULT, Integer, String> getState() { return new Ret5<>(response, error, result, errorCode, errorMessage); } /** * This allows a user to retrieve the result of {@code 'this'} asynchronous {@code Promise}. * Note that if no result has been received yet, this method throws an exception. * * @return The result of the asynchronous call. * * @throws AynchronousException This throws if this {@code Promise} has not received a result * yet. */ public synchronized RESULT result() { if (! response) throw new AsynchronousException("No response has been received yet"); return result; } /** * This is the "Respone Processor". As of the writing of this class, the only use that the * three classes: Script, Promise & Sender has been for implementing the Google Chrome * Browser Remote Debug Protocol. (Although perhaps these classes will one day be used with a * different asychronous protocol) * * In this current case (Browser RDP), this receiver is actually doing the "JSON Binding" to * bind the JsonObject received from the Browser into a Java Class that the user can actually * use. */ public final Function<RESPONSE, RESULT> receiver; /** * Constructing an instance of {@code Promise} only requires this response-processor (a.k.a. * a 'receiver'). * * @param receiver This receiver needs to be able to convert the raw asynchronous response * - <I>which is just a {@link JsonObject} when used by the Browser RDP Web-Socket Channel</I> * - into an instance of {@code 'RESULT'} that the end user can actually use. */ public Promise(Function<RESPONSE, RESULT> receiver) { this.receiver = receiver; } /** * When building a communication-system that uses these {@code Script & Promise} classes, that * system needs to invoke {@code 'acceptResponse'} whenever a server-response has been received. * * <BR /><BR />With a {@code WebSocket} connection to a Web-Browser via the Remote Debugging * Port, mapping the response & request is done by checking the request-ID, and <I>keeping a * mapping of ID <B>=></B> "un-answered Promise Objects."</I> * * @param response The response received from the communications channel, Web-Socket, browser * etc... */ public synchronized final void acceptResponse(RESPONSE response) { if (this.error) throw new AsynchronousException( "This Promise cannot accept a response object because it has already received " + "an rrror. Current Error Code: [" + this.errorCode + "]" + ((errorMessage != null) ? ("\nCurrent Error Message: \"" + errorMessage + "\"") : "") ); if (this.response) throw new AsynchronousException ("This Promise has already received a response. A second response is not allowed."); this.result = receiver.apply(response); this.response = true; } /** * If an error occurs in communications-channel logic, that error may be reported to this * {@code Promise} by calling {@code 'acceptError'}. * * @param errorCode A meaningful error-code. * @param errorMessage A meaningful error-message * * @throws AsynchronousException If {@code 'this'} instance of {@code 'Promise'} has already * received an error (via {@code 'acceptError'}), or if {@code 'this'} has accepted a response * (via {@link #acceptResponse(Object)}). */ public synchronized final void acceptError(int errorCode, String errorMessage) { if (this.error) throw new AsynchronousException( "There has already been an error reported to this Promise. Current Error Code: [" + this.errorCode + "]" + ((errorMessage != null) ? ("\nError Message: \"" + errorMessage + "\"") : "") ); if (this.response) throw new AsynchronousException ("This Promise has already received a response. It is too late to report an error."); this.error = true; this.errorCode = errorCode; this.errorMessage = errorMessage; } /** * This method will cede the current {@code Thread's} control until the Asynchronous Channel * has called this class' {@link #acceptResponse(Object)} method. * * @return Once {@code 'this'} instance of {@code 'Promise'} has been notified of a result, it * will return that result, as an instance of Type-Parameter {@code 'RESULT'}. * * @throws AsynchronousException If a {@code java.lang.InterruptedException} is thrown while * awating this {@code Promise}, that exception will be wrapped into an instance of * {@code 'AsynchronousException'}, and then thrown. This wrapper-exception is an un-checked, * {@code RuntimeException}. */ public synchronized final RESULT await() { try { // while ((result == null) && (errorCode == 0)) this.wait(); while ((! response) && (! error)) this.wait(); return result; } catch (InterruptedException e) { throw new AsynchronousException ("While awaiting this Promise, an InterruptedException was thrown", e); } } } |