001package Torello.Java.Additional; 002 003import java.util.function.Function; 004import javax.json.JsonObject; 005 006/** 007 * A promise keeps the processing logic for converting a response from an asychronous connection 008 * into a result that the end-user can utilize. 009 * 010 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=PROMISE> 011 * 012 * @param <RESPONSE> <EMBED CLASS='external-html' DATA-FILE-ID=RESPONSE> 013 * @param <RESULT> <EMBED CLASS='external-html' DATA-FILE-ID=RESULT> 014 */ 015public class Promise<RESPONSE, RESULT> implements java.io.Serializable 016{ 017 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 018 protected static final long serialVersionUID = 1; 019 020 // This value is updated by the 'acceptResponse' method 021 private RESULT result = null; 022 023 // If an error has occured, these two fields will hold more information 024 private boolean response = false; 025 private boolean error = false; 026 private int errorCode = 0; 027 private String errorMessage = null; 028 029 /** 030 * Asks {@code 'this'} instance of {@code Promise} whether or not an error has been reported. 031 * 032 * @return {@code TRUE} if there were errors reported by the asynchronous channel, and 033 * {@code FALSE} otherwise. 034 */ 035 public synchronized boolean hadError() { return error; } 036 037 /** 038 * Asks {@code 'this'} instance of {@code Promise} whether any response has been reported. 039 * This shall return {@code TRUE} if <I>either an error, or a response has been received</I>. 040 * 041 * @return {@code TRUE} if a response or an error has been reported to {@code 'this'} instance, 042 * and {@code FALSE} otherwise. 043 */ 044 public synchronized boolean hadResponse() { return response || error; } 045 046 /** 047 * Gets all current-state of this {@code Promise} 048 * 049 * @return The current state of this {@code Promise}, as an instance of {@link Ret5}. 050 * 051 * <BR /><BR /><UL CLASS=JDUL> 052 * <LI>{@code Ret5.a (Boolean)}: 053 * <BR />Whether or not a response has been reported 054 * <BR /><BR /></LI> 055 * <LI> {@code Ret5.b (Boolean)}: 056 * <BR />Whether or not an error has been reported 057 * <BR /><BR /></LI> 058 * <LI> {@code Ret5.c (RESULT)}: 059 * <BR />The result that has been returned, or null if no result has been received 060 * <BR /><BR /></LI> 061 * <LI> {@code Ret5.d (Integer)}: 062 * <BR />The error-code that was received, or 0 if no errors have been reported 063 * <BR /><BR /></LI> 064 * <LI> {@code Ret5.e (String)}: 065 * <BR />The error-message (if an error was reported), or null. 066 * </LI> 067 * </UL> 068 */ 069 public synchronized Ret5<Boolean, Boolean, RESULT, Integer, String> getState() 070 { return new Ret5<>(response, error, result, errorCode, errorMessage); } 071 072 /** 073 * This allows a user to retrieve the result of {@code 'this'} asynchronous {@code Promise}. 074 * Note that if no result has been received yet, this method throws an exception. 075 * 076 * @return The result of the asynchronous call. 077 * 078 * @throws AynchronousException This throws if this {@code Promise} has not received a result 079 * yet. 080 */ 081 public synchronized RESULT result() 082 { 083 if (! response) throw new AsynchronousException("No response has been received yet"); 084 return result; 085 } 086 087 /** 088 * This is the "Respone Processor". As of the writing of this class, the only use that the 089 * three classes: Script, Promise & Sender has been for implementing the Google Chrome 090 * Browser Remote Debug Protocol. (Although perhaps these classes will one day be used with a 091 * different asychronous protocol) 092 * 093 * In this current case (Browser RDP), this receiver is actually doing the "JSON Binding" to 094 * bind the JsonObject received from the Browser into a Java Class that the user can actually 095 * use. 096 */ 097 public final Function<RESPONSE, RESULT> receiver; 098 099 /** 100 * Constructing an instance of {@code Promise} only requires this response-processor (a.k.a. 101 * a 'receiver'). 102 * 103 * @param receiver This receiver needs to be able to convert the raw asynchronous response 104 * - <I>which is just a {@link JsonObject} when used by the Browser RDP Web-Socket Channel</I> 105 * - into an instance of {@code 'RESULT'} that the end user can actually use. 106 */ 107 public Promise(Function<RESPONSE, RESULT> receiver) 108 { this.receiver = receiver; } 109 110 /** 111 * When building a communication-system that uses these {@code Script & Promise} classes, that 112 * system needs to invoke {@code 'acceptResponse'} whenever a server-response has been received. 113 * 114 * <BR /><BR />With a {@code WebSocket} connection to a Web-Browser via the Remote Debugging 115 * Port, mapping the response & request is done by checking the request-ID, and <I>keeping a 116 * mapping of ID <B>=></B> "un-answered Promise Objects."</I> 117 * 118 * @param response The response received from the communications channel, Web-Socket, browser 119 * etc... 120 */ 121 public synchronized final void acceptResponse(RESPONSE response) 122 { 123 if (this.error) throw new AsynchronousException( 124 "This Promise cannot accept a response object because it has already received " + 125 "an rrror. Current Error Code: [" + 126 this.errorCode + "]" + 127 ((errorMessage != null) ? ("\nCurrent Error Message: \"" + errorMessage + "\"") : "") 128 ); 129 130 if (this.response) throw new AsynchronousException 131 ("This Promise has already received a response. A second response is not allowed."); 132 133 this.result = receiver.apply(response); 134 this.response = true; 135 } 136 137 /** 138 * If an error occurs in communications-channel logic, that error may be reported to this 139 * {@code Promise} by calling {@code 'acceptError'}. 140 * 141 * @param errorCode A meaningful error-code. 142 * @param errorMessage A meaningful error-message 143 * 144 * @throws AsynchronousException If {@code 'this'} instance of {@code 'Promise'} has already 145 * received an error (via {@code 'acceptError'}), or if {@code 'this'} has accepted a response 146 * (via {@link #acceptResponse(Object)}). 147 */ 148 public synchronized final void acceptError(int errorCode, String errorMessage) 149 { 150 if (this.error) throw new AsynchronousException( 151 "There has already been an error reported to this Promise. Current Error Code: [" + 152 this.errorCode + "]" + 153 ((errorMessage != null) ? ("\nError Message: \"" + errorMessage + "\"") : "") 154 ); 155 156 if (this.response) throw new AsynchronousException 157 ("This Promise has already received a response. It is too late to report an error."); 158 159 this.error = true; 160 this.errorCode = errorCode; 161 this.errorMessage = errorMessage; 162 } 163 164 /** 165 * This method will cede the current {@code Thread's} control until the Asynchronous Channel 166 * has called this class' {@link #acceptResponse(Object)} method. 167 * 168 * @return Once {@code 'this'} instance of {@code 'Promise'} has been notified of a result, it 169 * will return that result, as an instance of Type-Parameter {@code 'RESULT'}. 170 * 171 * @throws AsynchronousException If a {@code java.lang.InterruptedException} is thrown while 172 * awating this {@code Promise}, that exception will be wrapped into an instance of 173 * {@code 'AsynchronousException'}, and then thrown. This wrapper-exception is an un-checked, 174 * {@code RuntimeException}. 175 */ 176 public synchronized final RESULT await() 177 { 178 try 179 { 180 // while ((result == null) && (errorCode == 0)) this.wait(); 181 while ((! response) && (! error)) this.wait(); 182 183 return result; 184 } 185 catch (InterruptedException e) 186 { 187 throw new AsynchronousException 188 ("While awaiting this Promise, an InterruptedException was thrown", e); 189 } 190 } 191}