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