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