001package Torello.Browser; 002 003import static Torello.Java.StrPrint.I4; 004 005import Torello.Browser.JsonAST.PPR; 006import Torello.Java.StrCSV; 007import Torello.Java.StrPrint; 008 009import Torello.Java.ReadOnly.ReadOnlyList; 010 011import java.util.TreeMap; 012import java.util.function.Function; 013 014// Needed for a JavaDoc comment, only 015import javax.json.JsonObject; 016 017/** 018 * Serves as the parent class for both {@link NestedDescriptor} and {@link CommandDescriptor}. 019 * These two concrete subclass descriptors may be used to construct a 'builder' instance. Builders 020 * allow a programmer to assign values to a class' fields ({@link NestedDescriptor}) or values to 021 * the parameters of a command / method ({@link CommandDescriptor}). 022 * 023 * <BR /><BR /><B CLASS=JDDescLabel2>Builder Design Pattern</B> 024 * 025 * <BR /> 026 * 🤷 There are quite a number of types / classes in the CDP which have a very long list of fields. 027 * There are also several methods in the domain classes which accept a very long parameter list. 028 * Since many, many of the class fields and method parameters are declared 029 * <B STYLE='color: red'>'optional'</B> by CDP, in order to use the constructor for these classes, 030 * or invoke these methods, he winds up typing very long lists of 'nulls'. 031 * 032 * <BR /><BR /> 033 * 👍 Using a 'builder' to construct classes with large numbers of fields, or invoke methods 034 * with a lengthy parameter list, allows the programmer to only pass the necessary values to these 035 * fields & parameters, rather than a very long and sparse list of values, largely replete with 036 * many 'nulls' 037 * 038 * <BR /><BR /><DIV CLASS=JDHint> 039 * 📎 The information returned by this class should be identical to that which would be 040 * generated by the classes in {@code java.lang.reflect.*}. 041 * 042 * <BR /><BR /> 043 * 🔥 Using a precalculated {@code 'descriptor'} class is an order of magnitude easier to read, 044 * and likely programatically faster than, using actual Java Reflection 045 * </DIV> 046 */ 047public abstract class AbstractDescriptor 048 implements Comparable<AbstractDescriptor>, java.io.Serializable 049{ 050 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 051 protected static final long serialVersionUID = 1L; 052 053 054 // ******************************************************************************************** 055 // ******************************************************************************************** 056 // Fields 057 // ******************************************************************************************** 058 // ******************************************************************************************** 059 060 061 /** This is simply the name of the type or command represented by this descriptor.*/ 062 public final String name; 063 064 /** 065 * The Web Browser "Domain" in which the class / type (or command) represented by this 066 * descriptor exists. 067 * 068 * <BR /><BR /> 069 * 📌 Inside of a Web Browser's Source Code, a "Domain" is essentially the same as a Java 070 * Package, and basically means just that. 071 * 072 * <BR /><BR /><B CLASS=JDDescLabel2>Browser Automation Design</B> 073 * 074 * <BR /> 075 * Java-HTML's Browser Package implements a Web-Browser's Domains <B STYLE='color:red;'><I>as 076 * a single class, rather than an entire package</I></B>. This is because the Java CDP 077 * Implementation does not have any actual browser code; but rather is nothing more than a 078 * series of wrappers and datagram envelopes which communicate with the actual source code 079 * inside of the browser. 080 * 081 * <BR /><BR /> 082 * 👉 As a result, the methods & types of an entire Browser 'Domain' fit inside of a single 083 * Java Class, rather than occupying an entire Java Package. (Browser Domain {@code 'CSS'}, 084 * for instance, is wholly placed into a single Java Class: 085 * {@link Torello.Browser.BrowserAPI.CSS 'CSS'}) 086 */ 087 public final Domains domain; 088 089 /** 090 * Provides the names of the CDP properties or command parameters represented by this descriptor. 091 * For a {@link NestedDescriptor}, these names correspond to the fields of a nested data type. 092 * For a {@link CommandDescriptor}, these names correspond to the parameters accepted by a domain 093 * command. 094 * 095 * <BR /><BR /><B CLASS=JDDescLabel>Specs as an AST</B> 096 * 097 * <BR /> 098 * 💡 Within the Java-HTML Browser AST Parsing package ({@link Torello.Browser.JsonAST}), both 099 * concepts are actually represented by AST {@link PPR PPR's} nodes: named JSON values with a 100 * declared CDP type, optional status, and experimental status. 101 * 102 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note> 103 */ 104 public final ReadOnlyList<String> names; 105 106 /** 107 * The bytes stored in this class are identical to the specified constants in class 108 * {@link CDPTypes}. 109 * 110 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note> 111 */ 112 public final ReadOnlyList<Byte> types; 113 114 /** 115 * Specifies which CDP properties or command parameters have been designated as 116 * <B STYLE='color:red;'><CODE>optional</CODE></B>. Optional values are permitted to be absent 117 * from the {@link JsonObject} which defines them. 118 * 119 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note> 120 */ 121 public final ReadOnlyList<Boolean> optionals; 122 123 /** 124 * Google's Chrome DevTools Protocol occasionally designates certain properties or command 125 * parameters as "experimental." If the element described by a particular list index has been 126 * marked <B STYLE='color:red;'>{@code experimental}</B>, then the same index in this list will 127 * be assigned {@code 'TRUE'}. 128 * 129 * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note> 130 */ 131 public final ReadOnlyList<Boolean> experimentals; 132 133 /** 134 * The number of CDP properties or command parameters described by this descriptor-instance. 135 * 136 * <BR /><BR /><DIV CLASS=JDHint> 137 * 📌 This number will be equal to the {@code 'size()'} of each of the lists in this class. 138 * </DIV> 139 */ 140 public final int size; 141 142 // A map used to remember the classes that have complex CDP types 143 @SuppressWarnings("rawtypes") 144 private final java.util.Map<String, Class> classes; 145 146 147 // ******************************************************************************************** 148 // ******************************************************************************************** 149 // Package-Private Constructor 150 // ******************************************************************************************** 151 // ******************************************************************************************** 152 153 154 // This constructor is invoked by NestedDescriptor & CommandDescriptor 155 AbstractDescriptor( 156 final String name, 157 final Domains domain, 158 final ReadOnlyList<String> names, 159 final ReadOnlyList<Byte> types, 160 final ReadOnlyList<Boolean> optionals, 161 final ReadOnlyList<Boolean> experimentals 162 ) 163 { 164 if ( 165 (names.size() != types.size()) 166 || (types.size() != optionals.size()) 167 || (optionals.size() != experimentals.size()) 168 ) 169 throw new IllegalArgumentException( 170 "Input lists must be of equal lengths.\n" + 171 "names.size(): " + names.size() + '\n' + 172 "types.size(): " + types.size() + '\n' + 173 "optionals.size(): " + optionals.size() + '\n' + 174 "experimentals.size(): " + experimentals.size() 175 ); 176 177 this.domain = domain; 178 this.name = name; 179 this.names = names; 180 this.types = types; 181 this.optionals = optionals; 182 this.experimentals = experimentals; 183 this.size = names.size(); 184 185 this.classes = types.containsOR(CDPTypes.CDP_TYPE, CDPTypes.CDP_TYPE_ARRAY_1D) 186 ? new TreeMap<>() 187 : null; 188 } 189 190 191 // ******************************************************************************************** 192 // ******************************************************************************************** 193 // Methods 194 // ******************************************************************************************** 195 // ******************************************************************************************** 196 197 198 // This method is abstract, and it is used in the concrete method directly below 199 abstract Class<?> getClass(final String name, final int index); 200 201 // Used by the AbstractBuilder to check assignments by the user 202 void checkValueType(Object val, int index) 203 { 204 if ((index < 0) || (index >= this.size)) 205 throw new IndexOutOfBoundsException("index: " + index + " out of bounds"); 206 207 final byte type = this.types.get(index); 208 final String name = this.names.get(index); 209 210 if ((type != CDPTypes.CDP_TYPE) && (type != CDPTypes.CDP_TYPE_ARRAY_1D)) 211 throw new IllegalArgumentException("index: " + index + ", identifies a non-CDPType."); 212 213 @SuppressWarnings("rawtypes") 214 Class c = classes.get(name); 215 216 if (c == null) classes.put(name, c = getClass(name, index)); 217 218 if (! c.isInstance(val)) throw new ClassCastException 219 ("Could not cast the value provided to " + c.getCanonicalName()); 220 } 221 222 223 /** 224 * Retrieves the array index associated with the CDP property or command parameter whose name is 225 * {@code 'fieldName'}. 226 * 227 * <BR /><BR /><DIV CLASS=JDHint> 228 * Remember that the lists in this class are parallel lists! In order to find the type of 229 * a field name, look up the array index for a field's name, and then look at the same location 230 * in the {@link #types} list to find that particular field's type! 231 * </DIV> 232 * 233 * <BR /><DIV CLASS=JDHintAlt> 234 * It isn't mission critical to use this method, and one may simply refer to the Java-Doc 235 * Documentation to find the exact "array index" of a particular field. It isn't rocket 236 * science at all! The first field in the class will have an array index of {@code '0'}, and 237 * the second will have an array index of {@code '1'} - <B>and so on and so forth!</B> 238 * </DIV> 239 * 240 * @param name Must be a {@code java.lang.String} that is identicial to one of the field names 241 * (Java would call these "identifiers") within this class. If the provided name doen't 242 * correspond to any field in this class, then {@link UnknownPropertyException} throws. 243 * 244 * @return The 'number' associated with the field named by {@code 'fieldName'}. If a type or 245 * event type has 4 fields, then this method would return a value between 0 and 3. 246 * 247 * @throws UnknownPropertyException If the User-Provided {@code 'feildName'} isn't found. 248 */ 249 public int nameToIndex(final String name) 250 { 251 final int ret = names.indexOf(name); 252 253 if (ret == -1) throw new UnknownPropertyException(name); 254 255 return ret; 256 } 257 258 259 // ******************************************************************************************** 260 // ******************************************************************************************** 261 // java.lang.Comparable Methods 262 // ******************************************************************************************** 263 // ******************************************************************************************** 264 265 266 /** 267 * Compares this descriptor to another descriptor for ordering purposes. 268 * 269 * <BR /><BR /> 270 * Ordering is performed first by {@link #domain}, and then by {@link #name}. 271 * 272 * <BR /><EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod> 273 * 274 * @param that The descriptor instance being compared to this instance. 275 * 276 * @return A negative integer, zero, or a positive integer if this descriptor 277 * is less than, equal to, or greater than the specified descriptor. 278 */ 279 public final int compareTo(final AbstractDescriptor that) 280 { 281 final int ret = this.domain.compareTo(that.domain); 282 283 if (ret != 0) return ret; 284 else return this.name.compareTo(that.name); 285 } 286 287 288 // ******************************************************************************************** 289 // ******************************************************************************************** 290 // java.lang.Object Methods 291 // ******************************************************************************************** 292 // ******************************************************************************************** 293 294 295 /** 296 * Generates an insightful {@code String} representation of {@code 'this'} instance. 297 * 298 * <BR /><BR /><DIV CLASS=JDHint> 299 * ⚠️ Since this class is an abstract class, with concrete sub-classes doing the actual work, 300 * the output generated by this particular {@code 'toString()'} method is not intended for use 301 * by a programmer, but rather used by the sub-classes to simplify their own 302 * {@code 'toString()'} methods. 303 * </DIV> 304 * 305 * @return A Java {@code String} 306 * @see NestedDescriptor#toString() 307 * @see CommandDescriptor#toString() 308 */ 309 @Override 310 public String toString() 311 { 312 // toCSV(Iterable<T> i, Function<? super T,​String> toString, boolean printNulls, 313 // Integer maxLength) 314 // 315 // Conversion Map: Byte Constant into its Constant-Name, as a String 316 // public static final ReadOnlyMap<java.lang.Byte,​java.lang.String> names 317 318 final String types = StrCSV.toCSV(this.types, CDPTypes.names::get, true, null); 319 320 // toCSV(Iterable<?> i, boolean trim, boolean printNulls, Integer maxLength) 321 final String names = StrCSV.toCSV(this.names, true, true, null); 322 final String optionals = StrCSV.toCSV(this.optionals, true, true, null); 323 final String experimentals = StrCSV.toCSV(this.experimentals, true, true, null); 324 325 326 // wrapToIndentationPlus​(String s, int firstLineLen, int lineLen, int extraSpaces) 327 // 328 // "Experimentals: ".length() ==> 15 329 // I4.length() ==> 4 330 // 331 // firstLineLen = 80 - 15 - 4 ==> 61 332 // lineLen = 80 - 4 ==> 76 333 // 334 // This is just here to make the code below more readable. This is a "Cute Little Lambda" 335 // a.k.a. a Java Function Pointer. 336 337 final Function<String, String> print = (String s) -> 338 StrPrint.wrapToIndentationPlus(s, 61, 76, 4); 339 340 341 // Remember, this class 'AbstractDescriptor' is an abstract class, and this 'toString' 342 // method will only ever be invoked by the concrete subclasses 'toString'. Those 343 // subclasses will "wrap" the output from this toString in curly-braces, and some nicer 344 // looking labels. 345 346 return 347 I4 + "Domain: " + this.domain + '\n' + 348 I4 + "Name: " + this.name + '\n' + 349 I4 + "size: " + this.size + '\n' + 350 '\n' + 351 I4 + "Names: " + print.apply(names) + '\n' + 352 I4 + "Types: " + print.apply(types) + '\n' + 353 I4 + "Optionals: " + print.apply(optionals) + '\n' + 354 I4 + "Experimentals: " + print.apply(experimentals); 355 } 356 357 /** 358 * Check whether {@code 'this'} instance is equal to input parameter {@code 'o'} 359 * 360 * <BR /><BR /><DIV CLASS=JDHint> 361 * 📎 This {@code 'equals'} method merely checks the values assigned to fields 362 * <B>{@link #name}</B> and <B>{@link #domain}</B> for equality. Each type and each command 363 * within CDP has its own, singleton, descriptor instance. These two fields can uniquely 364 * identify any descriptor instance in CDP. 365 * </DIV> 366 * 367 * <EMBED CLASS='external-html' DATA-JDHC=JDHintAlt DATA-FILE-ID=AbstractFinalMethod> 368 * 369 * @param o Any Java Object. Only an instance of {@code AbstractDescriptor} that is identical to 370 * {@code 'this'} instance will return {@code 'true'}. 371 * 372 * @return {@code TRUE} if and only if {@code 'this'} is equal to {@code 'o'}. 373 * 374 * @see #name 375 * @see #domain 376 */ 377 @Override 378 public final boolean equals(final Object o) 379 { 380 if (this == o) return true; 381 if (o == null || getClass() != o.getClass()) return false; 382 383 final AbstractDescriptor that = (AbstractDescriptor) o; 384 385 return 386 this.name.equals(that.name) 387 && this.domain.equals(that.domain); 388 } 389 390 /** 391 * Generates a hashcode for this instance. This method utilizes the same fields for its 392 * hashing computation as the fields used by the {@link #equals(Object)} method. 393 * 394 * <BR /><EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod> 395 * 396 * @return A Java {@code int} 397 * @see #equals(Object) 398 */ 399 @Override 400 public final int hashCode() 401 { return this.name.hashCode() + 31 * this.domain.hashCode(); } 402 403}