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
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
package Torello.Browser;

import static Torello.Java.StrPrint.I4;

import Torello.Browser.JsonAST.PPR;
import Torello.Java.StrCSV;
import Torello.Java.StrPrint;

import Torello.Java.ReadOnly.ReadOnlyList;

import java.util.TreeMap;
import java.util.function.Function;

// Needed for a JavaDoc comment, only
import javax.json.JsonObject;

/**
 * Serves as the parent class for both {@link NestedDescriptor} and {@link CommandDescriptor}.
 * These two concrete subclass descriptors may be used to construct a 'builder' instance.  Builders
 * allow a programmer to assign values to a class' fields ({@link NestedDescriptor}) or values to 
 * the parameters of a command / method ({@link CommandDescriptor}).
 * 
 * <BR /><BR /><B CLASS=JDDescLabel2>Builder Design Pattern</B>
 * 
 * <BR />
 * 🤷 There are quite a number of types / classes in the CDP which have a very long list of fields.
 * There are also several methods in the domain classes which accept a very long parameter list.  
 * Since many, many of the class fields and method parameters are declared
 * <B STYLE='color: red'>'optional'</B> by CDP, in order to use the constructor for these classes,
 * or invoke these methods, he winds up typing very long lists of 'nulls'.
 * 
 * <BR /><BR />
 * 👍 Using a 'builder' to construct classes with large numbers of fields, or invoke methods
 * with a lengthy parameter list, allows the programmer to only pass the necessary values to these
 * fields &amp; parameters, rather than a very long and sparse list of values, largely replete with
 * many 'nulls'
 * 
 * <BR /><BR /><DIV CLASS=JDHint>
 * 📎 The information returned by this class should be identical to that which would be 
 * generated by the classes in {@code java.lang.reflect.*}.
 * 
 * <BR /><BR />
 * 🔥 Using a precalculated {@code 'descriptor'} class is an order of magnitude easier to read,
 * and likely programatically faster than, using actual Java Reflection
 * </DIV>
 */
public abstract class AbstractDescriptor
    implements Comparable<AbstractDescriptor>, java.io.Serializable
{
    /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */
    protected static final long serialVersionUID = 1L;


    // ********************************************************************************************
    // ********************************************************************************************
    // Fields
    // ********************************************************************************************
    // ********************************************************************************************


    /** This is simply the name of the type or command represented by this descriptor.*/
    public final String name;

    /**
     * The Web Browser "Domain" in which the class / type (or command) represented by this
     * descriptor exists.
     * 
     * <BR /><BR />
     * 📌 Inside of a Web Browser's Source Code, a "Domain" is essentially the same as a Java 
     * Package, and basically means just that.  
     * 
     * <BR /><BR /><B CLASS=JDDescLabel2>Browser Automation Design</B>
     * 
     * <BR />
     * Java-HTML's Browser Package implements a Web-Browser's Domains <B STYLE='color:red;'><I>as
     * a single class, rather than an entire package</I></B>.  This is because the Java CDP
     * Implementation does not have any actual browser code; but rather is nothing more than a 
     * series of wrappers and datagram envelopes which communicate with the actual source code 
     * inside of the browser.
     * 
     * <BR /><BR />
     * 👉 As a result, the methods &amp; types of an entire Browser 'Domain' fit inside of a single
     * Java Class, rather than occupying an entire Java Package.  (Browser Domain {@code 'CSS'}, 
     * for instance, is wholly placed into a single Java Class:
     * {@link Torello.Browser.BrowserAPI.CSS 'CSS'})
     */
    public final Domains domain;

    /**
     * Provides the names of the CDP properties or command parameters represented by this descriptor.
     * For a {@link NestedDescriptor}, these names correspond to the fields of a nested data type.
     * For a {@link CommandDescriptor}, these names correspond to the parameters accepted by a domain
     * command.
     * 
     * <BR /><BR /><B CLASS=JDDescLabel>Specs as an AST</B>
     * 
     * <BR />
     * đź’ˇ Within the Java-HTML Browser AST Parsing package ({@link Torello.Browser.JsonAST}), both
     * concepts are actually represented by AST {@link PPR PPR's} nodes: named JSON values with a
     * declared CDP type, optional status, and experimental status.
     * 
     * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note>
     */
    public final ReadOnlyList<String> names;

    /**
     * The bytes stored in this class are identical to the specified constants in class
     * {@link CDPTypes}.
     * 
     * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note>
     */
    public final ReadOnlyList<Byte> types;

    /**
     * Specifies which CDP properties or command parameters have been designated as
     * <B STYLE='color:red;'><CODE>optional</CODE></B>.  Optional values are permitted to be absent
     * from the {@link JsonObject} which defines them.
     * 
     * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note>
     */
    public final ReadOnlyList<Boolean> optionals;

    /** 
     * Google's Chrome DevTools Protocol occasionally designates certain properties or command
     * parameters as "experimental."  If the element described by a particular list index has been
     * marked <B STYLE='color:red;'>{@code experimental}</B>, then the same index in this list will
     * be assigned {@code 'TRUE'}.
     * 
     * <EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AD.Parallel.Note>
     */
    public final ReadOnlyList<Boolean> experimentals;

    /**
     * The number of CDP properties or command parameters described by this descriptor-instance.
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * 📌 This number will be equal to the {@code 'size()'} of each of the lists in this class.
     * </DIV>
     */
    public final int size;

    // A map used to remember the classes that have complex CDP types
    @SuppressWarnings("rawtypes")
    private final java.util.Map<String, Class> classes;


    // ********************************************************************************************
    // ********************************************************************************************
    // Package-Private Constructor
    // ********************************************************************************************
    // ********************************************************************************************


    // This constructor is invoked by NestedDescriptor & CommandDescriptor
    AbstractDescriptor(
            final String                name,
            final Domains               domain,
            final ReadOnlyList<String>  names,
            final ReadOnlyList<Byte>    types,
            final ReadOnlyList<Boolean> optionals,
            final ReadOnlyList<Boolean> experimentals
        )
    {
        if (
                (names.size() != types.size())
            ||  (types.size() != optionals.size())
            ||  (optionals.size() != experimentals.size())
        )
            throw new IllegalArgumentException(
                "Input lists must be of equal lengths.\n" +
                "names.size():         " + names.size()     + '\n' +
                "types.size():         " + types.size()     + '\n' +
                "optionals.size():     " + optionals.size() + '\n' +
                "experimentals.size(): " + experimentals.size() 
            );

        this.domain         = domain;
        this.name           = name;
        this.names          = names;
        this.types          = types;
        this.optionals      = optionals;
        this.experimentals  = experimentals;
        this.size           = names.size();

        this.classes = types.containsOR(CDPTypes.CDP_TYPE, CDPTypes.CDP_TYPE_ARRAY_1D)
            ? new TreeMap<>()
            : null;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Methods
    // ********************************************************************************************
    // ********************************************************************************************


    // This method is abstract, and it is used in the concrete method directly below
    abstract Class<?> getClass(final String name, final int index);

    // Used by the AbstractBuilder to check assignments by the user
    void checkValueType(Object val, int index)
    {
        if ((index < 0) || (index >= this.size))
            throw new IndexOutOfBoundsException("index: " + index + " out of bounds");

        final byte      type = this.types.get(index);
        final String    name = this.names.get(index);

        if ((type != CDPTypes.CDP_TYPE) && (type != CDPTypes.CDP_TYPE_ARRAY_1D))
            throw new IllegalArgumentException("index: " + index + ", identifies a non-CDPType.");

        @SuppressWarnings("rawtypes")
        Class c = classes.get(name);

        if (c == null) classes.put(name, c = getClass(name, index));

        if (! c.isInstance(val)) throw new ClassCastException
            ("Could not cast the value provided to " + c.getCanonicalName());
    }


    /**
     * Retrieves the array index associated with the CDP property or command parameter whose name is
     * {@code 'fieldName'}.
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * Remember that the lists in this class are parallel lists!  In order to find the type of 
     * a field name, look up the array index for a field's name, and then look at the same location
     * in the {@link #types} list to find that particular field's type!
     * </DIV>
     * 
     * <BR /><DIV CLASS=JDHintAlt>
     * It isn't mission critical to use this method, and one may simply refer to the Java-Doc
     * Documentation to find the exact "array index" of a particular field.  It isn't rocket 
     * science at all!  The first field in the class will have an array index of {@code '0'}, and 
     * the second will have an array index of {@code '1'} - <B>and so on and so forth!</B>
     * </DIV>
     * 
     * @param name Must be a {@code java.lang.String} that is identicial to one of the field names
     * (Java would call these "identifiers") within this class.  If the provided name doen't
     *  correspond to any field in this class, then {@link UnknownPropertyException} throws.
     * 
     * @return The 'number' associated with the field named by {@code 'fieldName'}.  If a type or
     * event type has 4 fields, then this method would return a value between 0 and 3.
     * 
     * @throws UnknownPropertyException If the User-Provided {@code 'feildName'} isn't found.
     */
    public int nameToIndex(final String name)
    {
        final int ret = names.indexOf(name);

        if (ret == -1) throw new UnknownPropertyException(name);

        return ret;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // java.lang.Comparable Methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Compares this descriptor to another descriptor for ordering purposes.
     * 
     * <BR /><BR />
     * Ordering is performed first by {@link #domain}, and then by {@link #name}.
     * 
     * <BR /><EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod>
     * 
     * @param that The descriptor instance being compared to this instance.
     * 
     * @return A negative integer, zero, or a positive integer if this descriptor
     * is less than, equal to, or greater than the specified descriptor.
     */
    public final int compareTo(final AbstractDescriptor that)
    {
        final int ret = this.domain.compareTo(that.domain);

        if (ret != 0)   return ret;
        else            return this.name.compareTo(that.name);
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // java.lang.Object Methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Generates an insightful {@code String} representation of {@code 'this'} instance.
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * ⚠️ Since this class is an abstract class, with concrete sub-classes doing the actual work,
     * the output generated by this particular {@code 'toString()'} method is not intended for use
     * by a programmer, but rather used by the sub-classes to simplify their own
     * {@code 'toString()'} methods.
     * </DIV>
     * 
     * @return A Java {@code String}
     * @see NestedDescriptor#toString()
     * @see CommandDescriptor#toString()
     */
    @Override
    public String toString()
    {
        // toCSV(Iterable<T> i, Function<? super T,​String> toString, boolean printNulls,
        //      Integer maxLength)
        // 
        // Conversion Map: Byte Constant into its Constant-Name, as a String
        // public static final ReadOnlyMap<java.lang.Byte,​java.lang.String> names

        final String types = StrCSV.toCSV(this.types, CDPTypes.names::get, true, null);

        // toCSV(Iterable<?> i, boolean trim, boolean printNulls, Integer maxLength)
        final String names          = StrCSV.toCSV(this.names, true, true, null);
        final String optionals      = StrCSV.toCSV(this.optionals, true, true, null);
        final String experimentals  = StrCSV.toCSV(this.experimentals, true, true, null);


        // wrapToIndentationPlus​(String s, int firstLineLen, int lineLen, int extraSpaces)
        // 
        // "Experimentals: ".length()   ==> 15
        // I4.length()                  ==> 4
        // 
        // firstLineLen = 80 - 15 - 4   ==> 61
        // lineLen      = 80 - 4        ==> 76
        // 
        // This is just here to make the code below more readable.  This is a "Cute Little Lambda"
        // a.k.a. a Java Function Pointer.

        final Function<String, String> print = (String s) ->
            StrPrint.wrapToIndentationPlus(s, 61, 76, 4);


        // Remember, this class 'AbstractDescriptor' is an abstract class, and this 'toString' 
        // method will only ever be invoked by the concrete subclasses 'toString'.  Those 
        // subclasses will "wrap" the output from this toString in curly-braces, and some nicer
        // looking labels.

        return
            I4 + "Domain: " + this.domain + '\n' +
            I4 + "Name:   " + this.name + '\n' +
            I4 + "size:   " + this.size + '\n' +
            '\n' +
            I4 + "Names:         " + print.apply(names)  + '\n' +
            I4 + "Types:         " + print.apply(types) + '\n' +
            I4 + "Optionals:     " + print.apply(optionals) + '\n' +
            I4 + "Experimentals: " + print.apply(experimentals);
    }

    /**
     * Check whether {@code 'this'} instance is equal to input parameter {@code 'o'}
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * 📎 This {@code 'equals'} method merely checks the values assigned to fields
     * <B>{@link #name}</B> and <B>{@link #domain}</B> for equality.  Each type and each command
     * within CDP has its own, singleton, descriptor instance.  These two fields can uniquely
     * identify any descriptor instance in CDP.
     * </DIV>
     * 
     * <EMBED CLASS='external-html' DATA-JDHC=JDHintAlt DATA-FILE-ID=AbstractFinalMethod>
     * 
     * @param o Any Java Object.  Only an instance of {@code AbstractDescriptor} that is identical to
     * {@code 'this'} instance will return {@code 'true'}.
     * 
     * @return {@code TRUE} if and only if {@code 'this'} is equal to {@code 'o'}.
     * 
     * @see #name
     * @see #domain
     */
    @Override
    public final boolean equals(final Object o)
    {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final AbstractDescriptor that = (AbstractDescriptor) o;

        return
                this.name.equals(that.name)
            &&  this.domain.equals(that.domain);
    }

    /**
     * Generates a hashcode for this instance.  This method utilizes the same fields for its 
     * hashing computation as the fields used by the {@link #equals(Object)} method.
     * 
     * <BR /><EMBED CLASS='external-html' DATA-JDHC=JDHint DATA-FILE-ID=AbstractFinalMethod>
     * 
     * @return A Java {@code int}
     * @see #equals(Object)
     */
    @Override
    public final int hashCode()
    { return this.name.hashCode() + 31 * this.domain.hashCode(); }

}