001package Torello.Browser; 002 003import Torello.JavaDoc.Annotations.LinkJavaSource; 004import Torello.JavaDoc.Annotations.IntoHTMLTable; 005import static Torello.JavaDoc.Annotations.IntoHTMLTable.Background.BlueDither; 006import static Torello.JavaDoc.Annotations.IntoHTMLTable.Background.GreenDither; 007 008import Torello.Java.StrCSV; 009import Torello.Java.StrIndent; 010 011import javax.json.JsonObject; 012 013/** 014 * An abstract base class for builder-style objects that collect named assignments 015 * before producing a final CDP-related object. 016 * 017 * <BR /><BR /> 018 * This class provides the common assignment machinery used by both {@link TypeBuilder} 019 * and {@link CommandBuilder}. Each assignment is checked against an 020 * {@link AbstractDescriptor}, which supplies the valid names and expected CDP type 021 * information for the builder. 022 * 023 * <BR /><BR /><DIV CLASS=JDHint> 024 * 📌 Concrete subclasses decide what {@link #build()} produces. A {@link TypeBuilder} 025 * produces a CDP data object, while a {@link CommandBuilder} produces a 026 * {@link Script} that may be invoked against the browser. 027 * </DIV> 028 * 029 * @param <T> The type produced by this builder's {@link #build()} method. 030 * 031 * @see TypeBuilder 032 * @see CommandBuilder 033 * @see AbstractDescriptor 034 */ 035public abstract class AbstractBuilder<T> 036 implements java.io.Serializable, Cloneable 037{ 038 // ******************************************************************************************** 039 // ******************************************************************************************** 040 // Fields 041 // ******************************************************************************************** 042 // ******************************************************************************************** 043 044 045 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 046 protected static final long serialVersionUID = 1L; 047 048 /** 049 * The descriptor used by this builder. It provides the names, expected types, and ordering 050 * for each named assignment accepted by this builder. 051 * 052 * <BR /><BR /><DIV CLASS=JDHint> 053 * 👉 For {@link TypeBuilder}, these names correspond to fields in a CDP data type. For 054 * {@link CommandBuilder}, they correspond to input parameters for a CDP command. 055 * </DIV> 056 */ 057 public final AbstractDescriptor descriptor; 058 059 // Chat-GPT says: 060 // 061 // No. Do NOT initialize them manually. Java already guarantees this completely and absolutely. 062 // I forget how this works all the time... 063 // 064 // already produces: 065 // * every assignments[i] == null 066 // * every assigned[i] == false 067 068 // The values being assigned to the builder 069 final Object[] assignments; 070 071 // Because 'null' is a legitimate value, this array is needed. 072 final boolean[] assigned; 073 074 075 // ******************************************************************************************** 076 // ******************************************************************************************** 077 // (PRIMARY PURPOSE of this class) 078 // ******************************************************************************************** 079 // ******************************************************************************************** 080 081 082 /** 083 * Convert 'this' Builder-Instance into an actual Object-Instance 084 * @return An instance of {@code T} 085 */ 086 public abstract T build(); 087 088 089 // ******************************************************************************************** 090 // ******************************************************************************************** 091 // Constructors & Static "Create a new Builder-Instance" Method 092 // ******************************************************************************************** 093 // ******************************************************************************************** 094 095 096 /** 097 * Protected constructor used by the two {@code AbstractBuilder} subclasses. 098 * @param descriptor The descriptor for the class or object which is ultimately being built 099 * 100 * @see NestedDescriptor 101 * @see NestedHelper#descriptor() 102 * @see CommandDescriptor 103 */ 104 protected AbstractBuilder(final AbstractDescriptor descriptor) 105 { 106 this.descriptor = descriptor; 107 this.assignments = new Object[descriptor.size]; 108 this.assigned = new boolean[descriptor.size]; 109 } 110 111 /** 112 * Protected constructor used by the {@code 'clone()'} method, inside the two concrete 113 * subclasses of this type. 114 * 115 * @param other Another instance of this class 116 */ 117 protected AbstractBuilder(final AbstractBuilder<T> other) 118 { 119 this.descriptor = other.descriptor; 120 this.assignments = other.assignments.clone(); 121 this.assigned = other.assigned.clone(); 122 } 123 124 125 // ******************************************************************************************** 126 // ******************************************************************************************** 127 // The Primary / General Purpose Accept-Method 128 // ******************************************************************************************** 129 // ******************************************************************************************** 130 131 132 // The internally used 'accept()' method, invoked by all the other accept methods in this class 133 private AbstractBuilder<T> accept( 134 final String name, 135 final Object value, 136 final byte providedType 137 ) 138 { 139 final int index = descriptor.names.indexOf(name); 140 141 if (index < 0) throw new AssignmentNameException(name, this.descriptor); 142 143 final byte expectedType = descriptor.types.get(index); 144 145 if (providedType != expectedType) throw new TypeAssignmentException 146 (providedType, expectedType, name, this.descriptor); 147 148 if ((expectedType == CDPTypes.CDP_TYPE) || (expectedType == CDPTypes.CDP_TYPE_ARRAY_1D)) 149 descriptor.checkValueType(value, index); 150 151 assignments[index] = value; 152 assigned[index] = true; 153 154 return this; 155 } 156 157 158 // ******************************************************************************************** 159 // ******************************************************************************************** 160 // Accept-Methods: CDP Types 161 // ******************************************************************************************** 162 // ******************************************************************************************** 163 164 165 /** <EMBED CLASS=external-html DATA-PARAM=bt DATA-TYPE='BaseType<?>' DATA-FILE-ID=AB.IHT> */ 166 @IntoHTMLTable( 167 title="Assign a CDP Class (extends BaseType<>) to a Builder Input", 168 background=BlueDither 169 ) 170 public AbstractBuilder<T> accept(final String name, final BaseType<?> bt) 171 { return accept(name, bt, CDPTypes.CDP_TYPE); } 172 173 /** <EMBED CLASS=external-html DATA-PARAM=btArr DATA-TYPE='BaseType[]' DATA-FILE-ID=AB.IHT> */ 174 @IntoHTMLTable( 175 title="Assign an Array of BaseType<?> (any CDP Class) to a Builder Input", 176 background=GreenDither 177 ) 178 public AbstractBuilder<T> accept(final String name, final BaseType<?>[] btArr) 179 { return accept(name, btArr, CDPTypes.CDP_TYPE_ARRAY_1D); } 180 181 182 // ******************************************************************************************** 183 // ******************************************************************************************** 184 // Accept-Methods: Standard Java Types 185 // ******************************************************************************************** 186 // ******************************************************************************************** 187 188 189 /** <EMBED CLASS=external-html DATA-PARAM=i DATA-TYPE='int' DATA-FILE-ID=AB.IHT> */ 190 @IntoHTMLTable( 191 title="Assign an 'int' Primitive to a Named Builder-Input", 192 background=BlueDither 193 ) 194 public AbstractBuilder<T> acceptInt(final String name, final int i) 195 { return accept(name, i, CDPTypes.PRIMITIVE_INT); } 196 197 /** <EMBED CLASS=external-html DATA-PARAM=i DATA-TYPE='Integer' DATA-FILE-ID=AB.IHT> */ 198 @IntoHTMLTable( 199 title="Assign a java.lang.Integer to a Named Builder-Input", 200 background=GreenDither 201 ) 202 public AbstractBuilder<T> accept(final String name, final Integer i) 203 { return accept(name, i, CDPTypes.BOXED_INTEGER); } 204 205 /** <EMBED CLASS=external-html DATA-PARAM=b DATA-TYPE='boolean' DATA-FILE-ID=AB.IHT> */ 206 @IntoHTMLTable( 207 title="Assign a 'boolean' primitive to a Named Builder-Input", 208 background=BlueDither 209 ) 210 public AbstractBuilder<T> acceptBool(final String name, final boolean b) 211 { return accept(name, b, CDPTypes.PRIMITIVE_BOOLEAN); } 212 213 /** <EMBED CLASS=external-html DATA-PARAM=b DATA-TYPE='Boolean' DATA-FILE-ID=AB.IHT> */ 214 @IntoHTMLTable( 215 title="Assign a java.lang.Boolean to a Named Builder-Input", 216 background=GreenDither 217 ) 218 public AbstractBuilder<T> accept(final String name, final Boolean b) 219 { return accept(name, b, CDPTypes.BOXED_BOOLEAN); } 220 221 /** <EMBED CLASS=external-html DATA-PARAM=s DATA-TYPE='String' DATA-FILE-ID=AB.IHT> */ 222 @IntoHTMLTable( 223 title="Assign a String to a Named Builder-Input", 224 background=BlueDither 225 ) 226 public AbstractBuilder<T> accept(final String name, final String s) 227 { return accept(name, s, CDPTypes.STRING); } 228 229 /** <EMBED CLASS=external-html DATA-PARAM=n DATA-TYPE='Number' DATA-FILE-ID=AB.IHT> */ 230 @IntoHTMLTable( 231 title="Assign a java.lang.Number to a Named Builder-Input", 232 background=GreenDither 233 ) 234 public AbstractBuilder<T> acceptNumber(final String name, final Number n) 235 { return accept(name, n, CDPTypes.NUMBER); } 236 237 238 // ******************************************************************************************** 239 // ******************************************************************************************** 240 // Accept-Methods: One Dimensional Array Types 241 // ******************************************************************************************** 242 // ******************************************************************************************** 243 244 245 /** <EMBED CLASS=external-html DATA-PARAM=arr1D DATA-TYPE='int[]' DATA-FILE-ID=AB.IHT> */ 246 @IntoHTMLTable( 247 title="Assign an int[]-Array to a Named Builder-Input", 248 background=BlueDither 249 ) 250 public AbstractBuilder<T> accept(final String name, final int[] arr1D) 251 { return accept(name, arr1D, CDPTypes.INT_ARRAY_1D); } 252 253 /** <EMBED CLASS=external-html DATA-PARAM=arr1D DATA-TYPE='boolean[]' DATA-FILE-ID=AB.IHT> */ 254 @IntoHTMLTable( 255 title="Assign a boolean[]-Array to a Named Builder-Input", 256 background=GreenDither 257 ) 258 public AbstractBuilder<T> accept(final String name, final boolean[] arr1D) 259 { return accept(name, arr1D, CDPTypes.BOOLEAN_ARRAY_1D); } 260 261 /** <EMBED CLASS=external-html DATA-PARAM=arr1D DATA-TYPE='String[]' DATA-FILE-ID=AB.IHT> */ 262 @IntoHTMLTable( 263 title="Assign a String[]-Array to a Named Builder-Input", 264 background=BlueDither 265 ) 266 public AbstractBuilder<T> accept(final String name, final String[] arr1D) 267 { return accept(name, arr1D, CDPTypes.STRING_ARRAY_1D); } 268 269 /** <EMBED CLASS=external-html DATA-PARAM=arr1D DATA-TYPE='Number[]' DATA-FILE-ID=AB.IHT> */ 270 @IntoHTMLTable( 271 title="Assign a Number[]-Array to a Named Builder-Input", 272 background=GreenDither 273 ) 274 public AbstractBuilder<T> accept(final String name, final Number[] arr1D) 275 { return accept(name, arr1D, CDPTypes.NUMBER_ARRAY_1D); } 276 277 278 // ******************************************************************************************** 279 // ******************************************************************************************** 280 // Accept-Methods: Two Dimensional Array Types 281 // ******************************************************************************************** 282 // ******************************************************************************************** 283 284 285 /** <EMBED CLASS=external-html DATA-PARAM=arr2D DATA-TYPE='int[][]' DATA-FILE-ID=AB.IHT> */ 286 @IntoHTMLTable( 287 title="Assign a Two Dimensional int[][]-Array to a Named Builder-Input", 288 background=BlueDither 289 ) 290 public AbstractBuilder<T> accept(final String name, final int[][] arr2D) 291 { return accept(name, arr2D, CDPTypes.INT_ARRAY_2D); } 292 293 /** <EMBED CLASS=external-html DATA-PARAM=arr2D DATA-TYPE='boolean[][]' DATA-FILE-ID=AB.IHT> */ 294 @IntoHTMLTable( 295 title="Assign a Two Dimensional boolean[][]-Array to a Named Builder-Input", 296 background=GreenDither 297 ) 298 public AbstractBuilder<T> accept(final String name, final boolean[][] arr2D) 299 { return accept(name, arr2D, CDPTypes.BOOLEAN_ARRAY_2D); } 300 301 /** <EMBED CLASS=external-html DATA-PARAM=arr2D DATA-TYPE='String[][]' DATA-FILE-ID=AB.IHT> */ 302 @IntoHTMLTable( 303 title="Assign a Two Dimensional String[][]-Array to a Named Builder-Input", 304 background=BlueDither 305 ) 306 public AbstractBuilder<T> accept(final String name, final String[][] arr2D) 307 { return accept(name, arr2D, CDPTypes.STRING_ARRAY_2D); } 308 309 /** <EMBED CLASS=external-html DATA-PARAM=arr2D DATA-TYPE='Number[][]' DATA-FILE-ID=AB.IHT> */ 310 @IntoHTMLTable( 311 title="Assign a Two Dimensional java.lang.Number[][]-Array to a Named Builder-Input", 312 background=GreenDither 313 ) 314 public AbstractBuilder<T> accept(final String name, final Number[][] arr2D) 315 { return accept(name, arr2D, CDPTypes.NUMBER_ARRAY_2D); } 316 317 318 // ******************************************************************************************** 319 // ******************************************************************************************** 320 // Unknown, Raw JSON 321 // ******************************************************************************************** 322 // ******************************************************************************************** 323 324 325 /** <EMBED CLASS=external-html DATA-PARAM=jo DATA-TYPE='JsonObject' DATA-FILE-ID=AB.IHT> */ 326 @IntoHTMLTable( 327 title="Assign a Raw JsonObject to a Named Builder-Input", 328 background=BlueDither 329 ) 330 public AbstractBuilder<T> accept(final String name, final JsonObject jo) 331 { return accept(name, jo, CDPTypes.RAW_JSON_VALUE); } 332 333 334 // ******************************************************************************************** 335 // ******************************************************************************************** 336 // MISC 337 // ******************************************************************************************** 338 // ******************************************************************************************** 339 340 341 342 /** 343 * Explicitly assigns {@code null} to a Named Builder-Input. 344 * 345 * <BR /><BR /><DIV CLASS=JDHint> 346 * 🤷 If there is ever a need to ensure that an object property found in a {@link JsonObject} 347 * that is being transmitted over a web socket (and towards a browser) has an actual 348 * {@link javax.json.JsonValue#NULL Json-Null} intentionally assigned to it, then invoking this 349 * method should be utilized. 350 * 351 * 🧠 This method guarantees that the internal {@code isPresent} boolean list is assigned 352 * {@code TRUE}, despite the fact that its value is still null. 353 * </DIV> 354 * 355 * @param name The name of the Builder-Input to which {@code null} shall be assigned. 356 * @return {@code 'this'} instance for invocation chaining 357 * 358 * @throws AssignmentNameException if {@code 'name'} is not listed by this builder's 359 * descriptor. 360 * 361 * @see NestedDescriptor#names 362 */ 363 public AbstractBuilder<T> acceptNull(final String name) 364 { 365 final int index = this.descriptor.names.indexOf(name); 366 367 if (index < 0) throw new AssignmentNameException(name, this.descriptor); 368 369 assignments[index] = null; 370 assigned[index] = true; 371 372 return this; 373 } 374 375 /** 376 * Clears any value which may or may not have been assigned to a Named Builder-Input. 377 * 378 * @param name The name of the Builder-Input that the programmer would like cleared. 379 * @return {@code 'this'} instance for invocation chaining 380 * 381 * @throws AssignmentNameException if {@code 'name'} is not listed by this builder's descriptor. 382 * 383 * @see AbstractDescriptor#names 384 */ 385 public AbstractBuilder<T> clear(final String name) 386 { 387 final int index = this.descriptor.names.indexOf(name); 388 389 if (index < 0) throw new AssignmentNameException(name, this.descriptor); 390 391 assignments[index] = null; 392 assigned[index] = false; 393 394 return this; 395 } 396 397 398 // ******************************************************************************************** 399 // ******************************************************************************************** 400 // java.lang.Object 401 // ******************************************************************************************** 402 // ******************************************************************************************** 403 404 405 /** 406 * Generate an insightful String representation of {@code 'this'} instance 407 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod> 408 * @return A Java {@code String} 409 */ 410 @Override 411 public final String toString() 412 { return CDPToString.toString(this); } 413 414 /** 415 * Check whether {@code 'this'} instance is equal to input parameter {@code 'o'} 416 * 417 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod> 418 * 419 * @param o Any Java Object. Only another builder instance of the exact same runtime class, 420 * with the same descriptor and current assignment values, will return {@code TRUE}. 421 * 422 * @return {@code TRUE} if and only if {@code 'this'} is equal to {@code 'o'}. 423 */ 424 @Override 425 public final boolean equals(final Object o) 426 { 427 if (this == o) return true; 428 if (o == null || getClass() != o.getClass()) return false; 429 430 final AbstractBuilder<?> that = (AbstractBuilder<?>) o; 431 432 return 433 this.descriptor.equals(that.descriptor) 434 && java.util.Arrays.equals(this.assignments, that.assignments) 435 && java.util.Arrays.equals(this.assigned, that.assigned); 436 } 437 438 439 /** 440 * Generate a hashcode integer which may be used for placing this object into a hashtable 441 * data structure. 442 * 443 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod> 444 * 445 * @return A (hopefully unique) integer value 446 */ 447 @Override 448 public final int hashCode() 449 { 450 int result = this.descriptor.hashCode(); 451 result = 31 * result + java.util.Arrays.hashCode(this.assignments); 452 return result; 453 } 454 455 456 // ******************************************************************************************** 457 // ******************************************************************************************** 458 // java.lang.Cloneable 459 // ******************************************************************************************** 460 // ******************************************************************************************** 461 462 463 /** 464 * Clone the current instance 465 * 466 * @return An identical copy of {@code 'this'}. 467 * 468 * <BR /><BR /><DIV CLASS=JDHintAlt> 469 * 🔍 The concept known as a "Deep Clone" means that the internals of a type are also 470 * copied to the new instance. What is returned by this method is a "Partial Deep Clone" of 471 * {@code 'this'} instance. 472 * 473 * <BR /><BR /> 474 * 🧠 Since the internal {@code 'descriptor'} instance is a read only, singleton instance, 475 * cloning it wouldn't have any real significance. Thus, the internal {@code 'descriptor'} 476 * isn't cloned into the new instance; its reference is merely copied instead. 477 * 478 * <BR /><BR /> 479 * 🎯 However, the two internal value-assignment arrays are cloned, resulting in a separate 480 * instance which has its own, independent assignments. 481 * </DIV> 482 */ 483 @Override 484 public abstract AbstractBuilder<T> clone(); 485 486}