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 &amp; 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}