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