001package Torello.Browser;
002
003import Torello.Java.UnreachableError;
004import Torello.Java.ReadOnly.ReadOnlyList;
005
006import static Torello.Browser.CDPTypes.*;
007
008import java.io.StringWriter;
009import javax.json.Json;
010import javax.json.stream.JsonGenerator;
011
012/**
013 * Generates JSON Requests from lists of Parameter-Names, Parameter-Types and Parameter-Values.
014 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=WRITE_JSON>
015 */
016@Torello.JavaDoc.Annotations.StaticFunctional
017@Torello.JavaDoc.Annotations.JDHeaderBackgroundImg(EmbedTagFileID="INTERNAL_USE_JDHBI")
018public class WriteJSON
019{
020    // This is a "Static" class (static functional)
021    private WriteJSON() { }
022
023    /**
024     * This is <B STYLE='color:red'><I>only used when</B></I> a CDP command accepts just a single
025     * parameter.  This eliminates the need for the Code Generator's need to wrap the parameter's
026     * name, type &amp; values as arrays.
027     * 
028     * <BR /><BR />This is very handy for shortening the code needed for all of the CDP commands 
029     * that accept just one parameter.  <B STYLE='color:red;'><I>No more, no less!</I></B>
030     * 
031     * @param paramType The "type" of this method's "lone parameter", encoded as a Java
032     * {@code byte} primitive.  You may view all available types in the class {@link CDPTypes}.
033     * 
034     * @param paramName The name of the method-parameter, as defined in the CDP specs.  This is 
035     * critical for ensuring that the browser understands what value is being passed to it, when
036     * invoking a browser method.
037     * 
038     * @param optional Whether the method's lone parameter is optional or not.  If 
039     * <B STYLE='color:red'><I>BOTH</I></B> {@code 'false'} is passed here,
040     * <B STYLE='color:red'><I>AND</I></B> {@code 'null'} is passed to {@code 'paramValue'}, then
041     * an exception shall throw.
042     * 
043     * @param methodName The name of the CDP method being invoked
044     * 
045     * @param paramValue The value to place into the JSON property for this browser method's lone
046     * parameter.
047     * 
048     * @return The JSON Web-Socket Request, encoded as a {@code java.lang.String}
049     * 
050     * @throws UnrecognizedCTABError If the the value passed to {@code 'paramType'} isn't valid.
051     * 
052     * @see CDPTypes
053     */
054    public static String get(
055            final byte      paramType,
056            final String    paramName,
057            final boolean   optional,
058            final String    methodName,
059            final Object    paramValue
060        )
061    {
062        StringWriter   strW = new StringWriter();
063        JsonGenerator  jGen = Json.createGenerator(strW);
064
065        jGen.writeStartObject()
066            .write("method", methodName);
067
068        jGen.writeStartObject("params");
069        get(jGen, paramType, paramName, paramValue, optional);
070        jGen.writeEnd();
071
072        jGen.writeEnd();
073        jGen.close();
074
075        return strW.toString();
076    }
077
078    /**
079     * Converts a list of class types, names &amp; values into a CDP Web-Socket Request.
080     * This method generates a JSON-{@code String}.
081     * 
082     * <BR /><BR /><DIV CLASS=JDHint>
083     * This method is strictly intended to be used with CDP functions / commands which 
084     * accept at least two parameters.
085     * </DIV>
086     * 
087     * @param cd All pertinent information about the method being invoked
088     * @param methodName The name of the command being passed over the Web-Socket Connection.
089     * @param parameterValues the values the user has passed to this Web-Socket Request
090     * 
091     * @return Returns the JSON Web-Socket Request as a {@code java.lang.String}
092     */
093    public static String get(
094            final CommandDescriptor<?>  cd,
095            final String                methodName,
096            final Object...             parameterValues
097        )
098    {
099        if (parameterValues.length != cd.size) throw new UnreachableError();
100
101        StringWriter   strW = new StringWriter();
102        JsonGenerator  jGen = Json.createGenerator(strW);
103
104        jGen.writeStartObject()
105            .write("method", methodName)
106            .writeStartObject("params");
107
108        for (int i=0; i < cd.size; i++)
109
110            get(
111                jGen,
112                cd.types.get(i),
113                cd.names.get(i),
114                parameterValues[i],
115                cd.optionals.get(i)
116            );
117
118        jGen.writeEnd()
119            .writeEnd()
120            .close();
121
122        return strW.toString();
123    }
124
125    /**
126     * Abject human torture
127     * 
128     * @param jGen Because this method may be used recursively, the current generator that is being
129     * used must be passed to this method.
130     * 
131     * @param paramType parameter type
132     * @param paramName parameter name
133     * @param paramValue parameter value
134     * @param optional indicates if 'null' is allowed as a value
135     * 
136     * @throws UnrecognizedCTABError If the the value passed to {@code 'paramType'} isn't valid.
137     */
138    @SuppressWarnings("unchecked")
139    public static void get(
140            final JsonGenerator jGen,
141            final byte          paramType,
142            final String        paramName,
143            final Object        paramValue,
144            final boolean       optional
145        )
146    {
147        // Makes the typing in the switch-statement below easier
148        final String n = paramName;
149
150        if (paramValue == null)
151        {
152            if (! optional) throw new WriteJSONError(
153                "The Operating Assertion is that if a Field is null in this class, it must " +
154                "have been declared 'optional.'  Field [" + n + "] is null, but not optional."
155            );
156
157            else return; // Just leave off of the JSON Completely!
158        }
159
160        switch (paramType)
161        {
162            // This may "seem" counter-intuitive, but 'int' is Auto-Boxed
163            // to an "Integer" when it is passed to parameter "paramValue"
164
165            case PRIMITIVE_INT :
166            case BOXED_INTEGER :
167                jGen.write(n, CHECK(paramValue, Integer.class).intValue());
168                break;
169
170            // SAME AS ABOVE, IBID
171            case PRIMITIVE_BOOLEAN :
172            case BOXED_BOOLEAN :
173                jGen.write(n, CHECK(paramValue, Boolean.class).booleanValue());
174                break;
175
176            case STRING :
177                jGen.write(n, CHECK(paramValue, String.class));
178                break;
179
180            case NUMBER :
181                NumberCrap.handleNumber(jGen, n, (Number) paramValue);
182                break;
183
184            case INT_ARRAY_1D :
185                jGen.writeStartArray(n);
186                for (final int i : CHECK(paramValue, int[].class)) jGen.write(i);
187                jGen.writeEnd();
188                break;
189
190            case BOOLEAN_ARRAY_1D :
191                jGen.writeStartArray(n);
192                for (final boolean b : CHECK(paramValue, boolean[].class)) jGen.write(b);
193                jGen.writeEnd();
194                break;
195
196            case STRING_ARRAY_1D :
197                jGen.writeStartArray(n);
198                for (final String s : CHECK(paramValue, String[].class))
199                    if (s == null)  jGen.writeNull();
200                    else            jGen.write(s);
201                jGen.writeEnd();
202                break;
203
204            case NUMBER_ARRAY_1D :
205                NumberCrap.handleNumberArr(jGen, n, paramValue);
206                break;
207
208            case INT_ARRAY_2D :
209                jGen.writeStartArray(n);
210                for (final int[] iArr : CHECK(paramValue, int[][].class))
211                {
212                    jGen.writeStartArray();
213                    for (final int i : iArr) jGen.write(i);
214                    jGen.writeEnd();
215                }
216                jGen.writeEnd();
217                break;
218
219            case BOOLEAN_ARRAY_2D :
220                jGen.writeStartArray(n);
221                for (final boolean[] bArr : CHECK(paramValue, boolean[][].class))
222                {
223                    jGen.writeStartArray();
224                    for (final boolean b : bArr) jGen.write(b);
225                    jGen.writeEnd();
226                }
227                jGen.writeEnd();
228                break;
229
230            case STRING_ARRAY_2D :
231                jGen.writeStartArray(n);
232                for (final String[] sArr : CHECK(paramValue, String[][].class))
233                {
234                    jGen.writeStartArray();
235                    for (final String s : sArr)
236                        if (s == null)  jGen.writeNull();
237                        else            jGen.write(s);
238                    jGen.writeEnd();
239                }
240                jGen.writeEnd();
241                break;
242
243            case NUMBER_ARRAY_2D :
244                NumberCrap.handleNumberArr2D(jGen, n, paramValue);
245                break;
246
247            case CDP_TYPE : 
248                CHECK(paramValue, BaseType.class).toJSON(n, jGen);
249                break;
250
251            case CDP_TYPE_ARRAY_1D :
252                jGen.writeStartArray(n);
253                for (final BaseType<?> bt : CHECK(paramValue, BaseType[].class))
254                    if (bt == null) jGen.writeNull();
255                    else            bt.toJSON(null, jGen);
256                jGen.writeEnd();
257                break;
258
259            default : throw new UnrecognizedCTABError(paramType);
260        }
261    }
262
263    private static class WriteJSONError extends Error
264    {
265        protected static final long serialVersionUID = 1L;
266
267        private WriteJSONError(final String message)
268        { super(message); }
269    }
270
271    private static <T> T CHECK(Object o, Class<T> c)
272    {
273        if (! c.isAssignableFrom(o.getClass())) throw new WriteJSONError(
274            "Expecting Object 'o', of type [" + o.getClass().getSimpleName() + "] to be " +
275            "assignable to [" + c.getSimpleName() + ']'
276        );
277
278        else return c.cast(o);
279    }
280
281}