1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
package Torello.Browser;

import Torello.Java.UnreachableError;
import Torello.Java.ReadOnly.ReadOnlyList;

import static Torello.Browser.CDPTypes.*;

import java.io.StringWriter;
import javax.json.Json;
import javax.json.stream.JsonGenerator;

/**
 * Generates JSON Requests from lists of Parameter-Names, Parameter-Types and Parameter-Values.
 * <BR /><BR /><EMBED CLASS='external-html' DATA-FILE-ID=WRITE_JSON>
 */
@Torello.JavaDoc.Annotations.StaticFunctional
@Torello.JavaDoc.Annotations.JDHeaderBackgroundImg(EmbedTagFileID="INTERNAL_USE_JDHBI")
public class WriteJSON
{
    // This is a "Static" class (static functional)
    private WriteJSON() { }

    /**
     * This is <B STYLE='color:red'><I>only used when</B></I> a CDP command accepts just a single
     * parameter.  This eliminates the need for the Code Generator's need to wrap the parameter's
     * name, type &amp; values as arrays.
     * 
     * <BR /><BR />This is very handy for shortening the code needed for all of the CDP commands 
     * that accept just one parameter.  <B STYLE='color:red;'><I>No more, no less!</I></B>
     * 
     * @param paramType The "type" of this method's "lone parameter", encoded as a Java
     * {@code byte} primitive.  You may view all available types in the class {@link CDPTypes}.
     * 
     * @param paramName The name of the method-parameter, as defined in the CDP specs.  This is 
     * critical for ensuring that the browser understands what value is being passed to it, when
     * invoking a browser method.
     * 
     * @param optional Whether the method's lone parameter is optional or not.  If 
     * <B STYLE='color:red'><I>BOTH</I></B> {@code 'false'} is passed here,
     * <B STYLE='color:red'><I>AND</I></B> {@code 'null'} is passed to {@code 'paramValue'}, then
     * an exception shall throw.
     * 
     * @param methodName The name of the CDP method being invoked
     * 
     * @param paramValue The value to place into the JSON property for this browser method's lone
     * parameter.
     * 
     * @return The JSON Web-Socket Request, encoded as a {@code java.lang.String}
     * 
     * @throws UnrecognizedCTABError If the the value passed to {@code 'paramType'} isn't valid.
     * 
     * @see CDPTypes
     */
    public static String get(
            final byte      paramType,
            final String    paramName,
            final boolean   optional,
            final String    methodName,
            final Object    paramValue
        )
    {
        StringWriter   strW = new StringWriter();
        JsonGenerator  jGen = Json.createGenerator(strW);

        jGen.writeStartObject()
            .write("method", methodName);

        jGen.writeStartObject("params");
        get(jGen, paramType, paramName, paramValue, optional);
        jGen.writeEnd();

        jGen.writeEnd();
        jGen.close();

        return strW.toString();
    }

    /**
     * Converts a list of class types, names &amp; values into a CDP Web-Socket Request.
     * This method generates a JSON-{@code String}.
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * This method is strictly intended to be used with CDP functions / commands which 
     * accept at least two parameters.
     * </DIV>
     * 
     * @param cd All pertinent information about the method being invoked
     * @param methodName The name of the command being passed over the Web-Socket Connection.
     * @param parameterValues the values the user has passed to this Web-Socket Request
     * 
     * @return Returns the JSON Web-Socket Request as a {@code java.lang.String}
     */
    public static String get(
            final CommandDescriptor<?>  cd,
            final String                methodName,
            final Object...             parameterValues
        )
    {
        if (parameterValues.length != cd.size) throw new UnreachableError();

        StringWriter   strW = new StringWriter();
        JsonGenerator  jGen = Json.createGenerator(strW);

        jGen.writeStartObject()
            .write("method", methodName)
            .writeStartObject("params");

        for (int i=0; i < cd.size; i++)

            get(
                jGen,
                cd.types.get(i),
                cd.names.get(i),
                parameterValues[i],
                cd.optionals.get(i)
            );

        jGen.writeEnd()
            .writeEnd()
            .close();

        return strW.toString();
    }

    /**
     * Abject human torture
     * 
     * @param jGen Because this method may be used recursively, the current generator that is being
     * used must be passed to this method.
     * 
     * @param paramType parameter type
     * @param paramName parameter name
     * @param paramValue parameter value
     * @param optional indicates if 'null' is allowed as a value
     * 
     * @throws UnrecognizedCTABError If the the value passed to {@code 'paramType'} isn't valid.
     */
    @SuppressWarnings("unchecked")
    public static void get(
            final JsonGenerator jGen,
            final byte          paramType,
            final String        paramName,
            final Object        paramValue,
            final boolean       optional
        )
    {
        // Makes the typing in the switch-statement below easier
        final String n = paramName;

        if (paramValue == null)
        {
            if (! optional) throw new WriteJSONError(
                "The Operating Assertion is that if a Field is null in this class, it must " +
                "have been declared 'optional.'  Field [" + n + "] is null, but not optional."
            );

            else return; // Just leave off of the JSON Completely!
        }

        switch (paramType)
        {
            // This may "seem" counter-intuitive, but 'int' is Auto-Boxed
            // to an "Integer" when it is passed to parameter "paramValue"

            case PRIMITIVE_INT :
            case BOXED_INTEGER :
                jGen.write(n, CHECK(paramValue, Integer.class).intValue());
                break;

            // SAME AS ABOVE, IBID
            case PRIMITIVE_BOOLEAN :
            case BOXED_BOOLEAN :
                jGen.write(n, CHECK(paramValue, Boolean.class).booleanValue());
                break;

            case STRING :
                jGen.write(n, CHECK(paramValue, String.class));
                break;

            case NUMBER :
                NumberCrap.handleNumber(jGen, n, (Number) paramValue);
                break;

            case INT_ARRAY_1D :
                jGen.writeStartArray(n);
                for (final int i : CHECK(paramValue, int[].class)) jGen.write(i);
                jGen.writeEnd();
                break;

            case BOOLEAN_ARRAY_1D :
                jGen.writeStartArray(n);
                for (final boolean b : CHECK(paramValue, boolean[].class)) jGen.write(b);
                jGen.writeEnd();
                break;

            case STRING_ARRAY_1D :
                jGen.writeStartArray(n);
                for (final String s : CHECK(paramValue, String[].class))
                    if (s == null)  jGen.writeNull();
                    else            jGen.write(s);
                jGen.writeEnd();
                break;

            case NUMBER_ARRAY_1D :
                NumberCrap.handleNumberArr(jGen, n, paramValue);
                break;

            case INT_ARRAY_2D :
                jGen.writeStartArray(n);
                for (final int[] iArr : CHECK(paramValue, int[][].class))
                {
                    jGen.writeStartArray();
                    for (final int i : iArr) jGen.write(i);
                    jGen.writeEnd();
                }
                jGen.writeEnd();
                break;

            case BOOLEAN_ARRAY_2D :
                jGen.writeStartArray(n);
                for (final boolean[] bArr : CHECK(paramValue, boolean[][].class))
                {
                    jGen.writeStartArray();
                    for (final boolean b : bArr) jGen.write(b);
                    jGen.writeEnd();
                }
                jGen.writeEnd();
                break;

            case STRING_ARRAY_2D :
                jGen.writeStartArray(n);
                for (final String[] sArr : CHECK(paramValue, String[][].class))
                {
                    jGen.writeStartArray();
                    for (final String s : sArr)
                        if (s == null)  jGen.writeNull();
                        else            jGen.write(s);
                    jGen.writeEnd();
                }
                jGen.writeEnd();
                break;

            case NUMBER_ARRAY_2D :
                NumberCrap.handleNumberArr2D(jGen, n, paramValue);
                break;

            case CDP_TYPE : 
                CHECK(paramValue, BaseType.class).toJSON(n, jGen);
                break;

            case CDP_TYPE_ARRAY_1D :
                jGen.writeStartArray(n);
                for (final BaseType<?> bt : CHECK(paramValue, BaseType[].class))
                    if (bt == null) jGen.writeNull();
                    else            bt.toJSON(null, jGen);
                jGen.writeEnd();
                break;

            default : throw new UnrecognizedCTABError(paramType);
        }
    }

    private static class WriteJSONError extends Error
    {
        protected static final long serialVersionUID = 1L;

        private WriteJSONError(final String message)
        { super(message); }
    }

    private static <T> T CHECK(Object o, Class<T> c)
    {
        if (! c.isAssignableFrom(o.getClass())) throw new WriteJSONError(
            "Expecting Object 'o', of type [" + o.getClass().getSimpleName() + "] to be " +
            "assignable to [" + c.getSimpleName() + ']'
        );

        else return c.cast(o);
    }

}