001package Torello.Java.Additional; 002 003import java.util.Objects; 004import java.util.function.Function; 005import javax.json.JsonObject; // Needed for @link 006 007/** 008 * The class script simply sends an asynchronous request, and returns an instance of {@link Promise} 009 * which may be awaited. 010 * 011 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=SCRIPT> 012 * 013 * @param <INPUT> <EMBED CLASS='external-html' DATA-FILE-ID=INPUT> 014 * @param <RESPONSE> <EMBED CLASS='external-html' DATA-FILE-ID=RESPONSE> 015 * @param <RESULT> <EMBED CLASS='external-html' DATA-FILE-ID=RESULT> 016 */ 017public class Script<INPUT, RESPONSE, RESULT> 018 implements java.io.Serializable 019{ 020 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 021 protected static final long serialVersionUID = 1; 022 023 /** 024 * When opening a connection to an asynchronous channel, for instance a Web-Socket to a Google 025 * Chrome Browser, the web-socket connecton to that browser receives messages from this sender. 026 * 027 * <BR /><BR />If a user needs to have more than one browser opened at the same time, a second 028 * sender can be created, and messages may be sent to a different browser by using the 029 * {@link #exec(Sender)} method. 030 */ 031 public final Sender<INPUT> defaultSender; 032 033 /** 034 * ID number that was ascribed to this request. Each request must have an ID because this 035 * script is used in an asynchronous communications context. The only way to link a response 036 * to a request is by matching an ID number received as a response, to an ID that was sent with 037 * a particular request. 038 */ 039 public final int requestID; 040 041 /** 042 * This is the request that the {@code 'sender'} is going to send over the Asynchronous I/O 043 * Channel. 044 */ 045 public final INPUT request; 046 047 /** 048 * The {@link Promise} that is produced by this class, after invoking an {@code 'exec'} method 049 * needs to be able to do any processing on the raw-output that is received from the 050 * Asynchronous Communication's Channel. 051 * 052 * <BR /><BR />If the type of the {@code 'RESPONSE'} was {@code JsonObject}, then this function 053 * pointer would have to perform the <I><B STYLE='color: red;'>Json-Binding</B></I> to convert 054 * the {@link JsonObject} into a Java {@code Object} (ultimately having type {@code 'RESULT'}). 055 */ 056 public final Function<RESPONSE, RESULT> receiver; 057 058 /** 059 * Constructs an instance of this class. This constructor does not actually execute the 060 * asychronous-request, but rather just initializes the fields. The asynchronous call is not 061 * made until the user calls an {@code 'exec'} method. Furthermore, the user will not get a 062 * response until he has awaited the {@link Promise} object that is returned from that 063 * {@code 'exec'} call. 064 * 065 * @param defaultSender This parameter needs to be passed here to actually do the sending. 066 * Perhaps it may be confusing that the sender is passed to this constructor. The reason that 067 * it is not automatically included is because it allows the user to change or modify the 068 * sender used before actually executing this script. 069 * 070 * @param requestID This is just an ID used to send the message. When these asynchronous 071 * classes are applied / used-by the {@code WebSocket's} package for communicating with a 072 * headless browser, this ID is the Web-Socket request ID. The Web-Socket ID allows the 073 * Web-Socket client to match a response to a request. 074 * 075 * @param request The request that is passed to the sender's {@code 'send'} method. 076 * 077 * @param receiver This Function-Pointer needs to be able to convert the Asynchronous Channel's 078 * {@code 'RESPONSE'}-typed object into type {@code 'RESULT'}, which is what the user is 079 * ultimately expecting. 080 * 081 * <BR /><BR /><DIV CLASS=JDHint> 082 * <B STYLE='color: red;'>Note:</B> Thus far in Java HTML Development, the Type-Parameter 083 * {@code 'RESPONSE'} has always been {@link JsonObject}. This {@code 'receiver'} parameter is 084 * actually expected to perform the <B STYLE='color: red;'>JSON-Binding</B> that maps 085 * Json-Properties into 'Plain Old Java Objects' (POJO's). 086 * </DIV> 087 * 088 * <BR />It is conceivable that later uses of these {@code Promise / Script} Generic 089 * Classes wouldn't necessarily operate over Web-Sockets, and wouldn't use Json as a 090 * communication medium / protocol. 091 */ 092 public Script( 093 final Sender<INPUT> defaultSender, 094 final int requestID, 095 final INPUT request, 096 final Function<RESPONSE, RESULT> receiver 097 ) 098 { 099 // Standard Java FAIL-FAST checks 100 if (request == null) throw new NullPointerException("Parameter 'request' is null."); 101 if (receiver == null) throw new NullPointerException("Parameter 'receiver' is null."); 102 103 this.defaultSender = defaultSender; 104 this.requestID = requestID; 105 this.request = request; 106 this.receiver = receiver; 107 } 108 109 /** 110 * Constructs an instance of this class in which there is no internal {@link defaultSender}. 111 * If an attempt is made to execute this {@code Script}-Instance using the 112 * <B STYLE='color:red;'><I>zero argument</I></B> {@link #exec()}-Method, then that method 113 * shall throw a {@link NoSenderException}. 114 */ 115 public Script( 116 final int requestID, 117 final INPUT request, 118 final Function<RESPONSE, RESULT> receiver 119 ) 120 { 121 // Standard Java FAIL-FAST checks 122 if (request == null) throw new NullPointerException("Parameter 'request' is null."); 123 if (receiver == null) throw new NullPointerException("Parameter 'receiver' is null."); 124 125 this.defaultSender = null; 126 this.requestID = requestID; 127 this.request = request; 128 this.receiver = receiver; 129 } 130 131 132 /** 133 * This builds a {@link Promise} object, and then uses the {@code 'defaultSender'} to send a 134 * message to the Asynchronous I/O Channel. 135 * 136 * <BR /><BR />When using {@code Script's} that were generated by the Browser Remote Debug 137 * Protocol API, the {@code 'defaultSender'} is merely a connection to the first 138 * Chrome-Browser opened by the class {@code BDRPC} (which is a a browser connection class). 139 * 140 * <BR /><BR /><DIV CLASS=JDHint> 141 * The instance of {@code Promise} must be created here, not because it would be "dangerous" to 142 * allow {@link Sender} to create it (even though it is a user implemented 143 * functional-interface), but rather because as a Java-Generic, the only way to guarantee that 144 * the Generic-Type parameters are easy to deal with, is by creating the promise here and 145 * returning the generic immediately to the user. 146 * </DIV> 147 * 148 * @return <EMBED CLASS='external-html' DATA-FILE-ID=SCRIPTRET> 149 */ 150 public Promise<RESPONSE, RESULT> exec() 151 { 152 if (defaultSender == null) throw new NoSenderException(); 153 Promise<RESPONSE, RESULT> promise = new Promise<>(receiver); 154 defaultSender.send(requestID, request, promise); 155 return promise; 156 } 157 158 /** 159 * If there are reasons to use a different sender - <I>because certain configurations need to 160 * change</I> - then using this {@code exec} method allows a user to change senders. 161 * 162 * @param userProvidedSender The {@link Sender} Instance to use when executing this 163 * {@link Script} object-instance. This allows for a user to either force a pre-compiled 164 * {@code 'Script'}-Instance to use an alternate {@link Sender}, or provide a {@code 'Sender'} 165 * Instance if none were provided at the time of Construction of this Object. 166 * 167 * <BR /><BR />In the Browser Remote Debug Package (Headless Chrome), the {@code 'Script'} 168 * instances which are generated expect the user to provide their own constructed 169 * {@code Sender} instances to facilitate: 170 * 171 * <BR /><BR /><UL CLASS=JDUL> 172 * <LI>Pre-Compiling Scripts ahead of time for future use, using future Browser-Connections</LI> 173 * <LI>Utilizing alternate & various Page-Connections, for different Open-Pages</LI> 174 * <LI>Re-using Pre-Compiled Script Components</LI> 175 * </UL> 176 * 177 * @return <EMBED CLASS='external-html' DATA-FILE-ID=SCRIPTRET> 178 */ 179 public Promise<RESPONSE, RESULT> exec(final Sender<INPUT> userProvidedSender) 180 { 181 Objects.requireNonNull(userProvidedSender, "Parameter 'userProvidedSender' is null."); 182 Promise<RESPONSE, RESULT> promise = new Promise<>(receiver); 183 userProvidedSender.send(requestID, request, promise); 184 return promise; 185 } 186}