001package Torello.Java.JSON; 002 003import Torello.Java.*; 004 005import Torello.Java.Function.IntTFunction; 006import Torello.Java.Function.IntIntTFunc; 007 008import javax.json.*; 009 010import javax.json.stream.JsonGenerator; 011import java.util.Vector; 012import java.lang.reflect.Field; 013import java.lang.reflect.Modifier; 014import java.io.StringWriter; 015 016/** 017 * This class may be used to help serialize Java Objects into Json Objects. 018 * 019 * <BR /><BR />Currently, the primary use of this class is to serialize any Object-Data that is 020 * generated by user-request into a JSON-Object Request. This class is then sent to Chrome, or any 021 * RDP-Enabled Browser, so that user-requests may be processed. 022 */ 023@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="JSON_SERIALIZER_JDHBI") 024public abstract class BaseType 025{ 026 /** Do Nothing Constructor */ 027 protected BaseType () { } 028 029 /** 030 * Implementing this method allows sub-classes to specify which JSON Properties may be absent 031 * or null. When binding a {@link JsonObject} to a Java-Object, if some of the expected 032 * fields for the Java-Object map to Properties which might be left-out or omitted, then that 033 * may be indicated by setting that fields array position {@code TRUE}. 034 * 035 * <BR /><BR /><B>NOTE:</B> This array should have a length equal to the number of fields 036 * contained by the Java Object. The first boolean in the array should specify whether the 037 * first Object Field may by absent. The second boolean should specify whether the second 038 * Object Field is optional in the JSON - <I>and so on and so forth...</I> 039 * 040 * @return A {@code boolean[]} array whose length is precisely equal to the number of fields 041 * in the Java Object. 042 */ 043 public abstract boolean[] optionals(); 044 045 // Helper Functions for StrReplace 046 static final IntTFunction<String, String> STR_QUOTES = 047 (int a, String s) -> '\"' + s + '\"'; 048 049 static final IntIntTFunc<String, String> STR_QUOTES2 = 050 (int a, int b, String s) -> '\"' + s + '\"'; 051 052 053 // ******************************************************************************************** 054 // ******************************************************************************************** 055 // toString() 056 // ******************************************************************************************** 057 // ******************************************************************************************** 058 059 060 /** 061 * This method uses Java Reflection to convert the inheriting object into a {@code String}. 062 * @return A {@code java.lang.String} representation of {@code 'this'} object. 063 */ 064 public String toString() { return toString(0); } 065 066 // The actual implementation of the toString 067 private final String toString(int PREVIOUS_INDENT) 068 { 069 boolean[] optArr = optionals(); 070 Field[] fArr = this.getClass().getFields(); 071 StringBuilder sb = new StringBuilder(); 072 int maxNameLen = 0; 073 int maxTypeLen = 0; 074 int maxValLen = 0; 075 076 ParallelArrayException.check(fArr, "fArr", true, optArr, "optArr"); 077 078 // NOTE: This was "added in" later, since sometimes there are "empty types" 079 if (fArr.length == 0) 080 return '[' + this.getClass().getName() + "] => Empty Marker Type\n"; 081 082 // This loop is just used for determining the "Spacing" values. 083 for (Field f : fArr) 084 { 085 String name = f.getName(); 086 Class<?> c = f.getType(); 087 String cStr = c.getSimpleName(); 088 089 if (name.length() > maxNameLen) maxNameLen = name.length(); 090 if (cStr.length() > maxTypeLen) maxTypeLen = cStr.length(); 091 092 // System.out.print(cStr + ", "); 093 } 094 095 // System.out.println(); 096 097 // Finish setting the "Spacing Variables" - they determine how much white-space to add to 098 // each line of the returned string. 099 100 maxNameLen += 2; 101 maxTypeLen += 2; 102 maxValLen = 80 - maxNameLen - maxTypeLen - PREVIOUS_INDENT; 103 104 for (int i=0; i < fArr.length; i++) 105 { 106 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 107 // Get the Parameters Ready for the Giant If-Then-Switch-Statement 108 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 109 110 // When I die, and they lay me to rest, I'm going to go to the place that's the best 111 // I'm going to the spirit in the sky. ALSO: StringParse.rightSpacePad 112 // I've never been a sinner. I Never sinned. I've got a friend in Jesus 113 // He's going to set me up with the Spirit in the Sky 114 115 Field f = fArr[i]; 116 boolean optional = optArr[i]; 117 Class<?> c = f.getType(); 118 Object o = null; 119 120 // Always output the name, and the type 121 sb.append( 122 StringParse.rightSpacePad(c.getSimpleName(), maxTypeLen) + 123 StringParse.rightSpacePad(f.getName(), maxNameLen) 124 ); 125 126 // This exception should never be thrown, since the Class.getFields() method shold 127 // only return "public" fields. Thusly, the "IllegalAccessException" should be 128 // impossible. 129 130 try 131 { o = f.get(this); } 132 133 catch (IllegalAccessException e) 134 { throw new UnreachableError(); } 135 136 137 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 138 // Primitive Types 139 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 140 // 141 // Any Primitive Type would never be one of the "Optional" values, and therefore would 142 // always have a valid-value. Furthermore, all Primitive Field's 'toString' methods 143 // should always print on a single-line, and no "spacing magic" needs to be performed. 144 145 if (c.isPrimitive()) sb.append(o.toString() + '\n'); 146 147 148 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 149 // 'null' Case - Since it has been established this is not a primitive! 150 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 151 152 // If it is not a Primitive, then it may be null. If it is null, print 'null' 153 else if (o == null) sb.append 154 ("null" + (optional ? " (declared optional)\n" : "(not declared optional\n")); 155 156 157 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 158 // Some Simple Java Types 159 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 160 // 161 // Do the "Sort-of Simple Types." The easiest way to do this is just to abbreviate 162 // strings that are too long. 163 164 else if ( Number.class.isAssignableFrom(c) 165 || Boolean.class.isAssignableFrom(c) 166 ) 167 sb.append(StrPrint.abbrevEnd(o.toString(), true, maxValLen) + '\n'); 168 169 // This is a "Separate Case" from the above if-statement, because the String's look 170 // better when they are wrapped in quotations. 171 172 else if (String.class.isAssignableFrom(c)) 173 sb.append('\"' + StrPrint.abbrevEnd(o.toString(), true, maxValLen-2) + "\"\n"); 174 175 176 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 177 // Handle the case that one of the Field is also a "Base-Type" (very common) 178 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 179 180 else if (BaseType.class.isAssignableFrom(c)) 181 sb.append('\n' + StrIndent.indent(((BaseType) o).toString(PREVIOUS_INDENT + 4), 4)); 182 183 184 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 185 // An *ARRAY* of one of these "BaseTypes" (a.k.a. all the classes that are built) 186 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 187 188 else if (c.isArray() && BaseType.class.isAssignableFrom(c.getComponentType())) 189 { 190 if (StringParse.countCharacters(c.getSimpleName(), '[') > 1) 191 throw new Error("BaseType had an array dimension greater than 1!"); 192 193 sb.append('\n'); 194 195 for (BaseType bt : (BaseType[]) o) sb.append( 196 " arr[" + (i++) + "]:\n" + 197 StrIndent.indent(bt.toString(PREVIOUS_INDENT + 4), 4) 198 ); 199 } 200 201 202 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 203 // Handle all of the Array Cases 204 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 205 206 else switch (c.getSimpleName()) 207 { 208 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 209 // Simple Primitive Arrays 210 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 211 212 case "int[]": 213 sb.append('[' + StrCSV.toCSV((int[]) o, null, maxValLen-2) + "]\n"); 214 break; 215 216 case "boolean[]": 217 sb.append('[' + StrCSV.toCSV((boolean[]) o, null, maxValLen-2) + "]\n"); 218 break; 219 220 221 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 222 // Simple 2-D Primitive Arrays 223 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 224 225 case "int[][]": sb.append( 226 StrIndent.indentAfter2ndLine( 227 StrCSV.toCSV((int[][]) o, null, null, true, maxValLen, 5) + '\n', 228 4, true, true 229 )); 230 break; 231 232 case "boolean[][]": sb.append( 233 StrIndent.indentAfter2ndLine( 234 StrCSV.toCSV((boolean[][]) o, null, null, true, maxValLen, 5) + '\n', 235 4, true, true 236 )); 237 break; 238 239 240 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 241 // Simple Java-Object Arrays 242 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 243 244 case "Integer[]" : 245 case "Number[]" : 246 case "Boolean[]" : 247 248 sb.append('[' + StrCSV.toCSV((Object[]) o, true, true, maxValLen) + "]\n"); 249 break; 250 251 case "String[]": 252 sb.append('[' + StrCSV.toCSV((String[]) o, STR_QUOTES, true, maxValLen) + "]\n"); 253 break; 254 255 256 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 257 // Simple Java 2-D Object Arrays 258 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 259 260 case "Integer[][]" : 261 case "Number[][]" : 262 case "Boolean[][]" : sb.append( 263 StrIndent.indentAfter2ndLine( 264 StrCSV.toCSV((Object[][]) o, null, null, true, maxValLen, 5) + '\n', 265 4, true, true 266 )); 267 break; 268 269 case "String[][]": sb.append( 270 StrIndent.indentAfter2ndLine( 271 StrCSV.toCSV((String[][]) o, STR_QUOTES2, null, true, maxValLen, 5) + 272 '\n', 273 4, true, true 274 )); 275 break; 276 277 case "JsonValue": sb.append( 278 StrPrint.abbrev(o.toString(), 50, true, null, 100) 279 ); 280 break; 281 282 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 283 // Simple Java 2-D Object Arrays 284 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 285 286 default: throw new Error( 287 "This Build Has not allowed for: " + c.getSimpleName() + '\n' + 288 "Somethign has changed!" 289 ); 290 } 291 } 292 293 return sb.toString(); 294 } 295 296 297 // ******************************************************************************************** 298 // ******************************************************************************************** 299 // toJSON() 300 // ******************************************************************************************** 301 // ******************************************************************************************** 302 303 304 /** 305 * Serializes {@code 'this'} object into JSON. 306 * 307 * <BR /><BR /><OL CLASS=JDOL> 308 * <LI>It is OK if 'name' is null.</LI> 309 * <LI>It is NOT-OK if 'jGen' is null.</LI> 310 * </OL> 311 * 312 * @param name The name being assigned to this {@link JsonObject} Property. This name may be 313 * null. If null is passed here, the {@link JsonGenerator} will treat this Object as the top 314 * level JSON Object, not a property of a larger object. 315 * 316 * @param jGen This is the generator instance. Note that often, when serializing Java Objects 317 * to JSON (<I>and vice-versa!</I>), one object may be a property or field of another object. 318 * When serializing an object to JSON, if there is a field that also needs to be serialized as 319 * a {@code JsonObject}, just pass the name and this generator instance in order to include 320 * that field as a sub-property. 321 */ 322 public void toJSON(String name, JsonGenerator jGen) 323 { 324 boolean[] optArr = optionals(); 325 Field[] fArr = this.getClass().getFields(); 326 Vector<Class<?>> cVec = new Vector<>(); 327 Vector<String> nVec = new Vector<>(); 328 Vector<Object> oVec = new Vector<>(); 329 330 if (fArr.length == 0) throw new JsonException( 331 "Likely, this is an error, but not necessarily. You are attempting to serialize a " + 332 "class that has 0 public fields, into JSON" 333 ); 334 335 // Retrieve all of the Fields Available to this class. 336 // These Fields should all be public and final 337 338 try 339 { 340 for (Field f : fArr) if (! Modifier.isStatic(f.getModifiers())) 341 { 342 cVec.add(f.getType()); 343 nVec.add(f.getName()); 344 oVec.add(f.get(this)); 345 } 346 } 347 348 catch (IllegalAccessException e) 349 { 350 System.out.println("Unable to Access the Field."); 351 System.err.println(EXCC.toString(e)); 352 throw new UnreachableError(); 353 } 354 355 // Write the value of these fields to the Json-Generator. If no 'name' was provided, 356 // then write the "Top-Level Json-Object" 357 358 if (name == null) jGen.writeStartObject(); 359 else jGen.writeStartObject(name); 360 361 WriteJSON.get(jGen, cVec, nVec, oVec, optArr); 362 363 // Close the writing of 'this' object. 364 jGen.writeEnd(); 365 } 366}