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}