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
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
package Torello.Java.Additional;

import Torello.Java.UnreachableError;
import Torello.JavaDoc.JDHeaderBackgroundImg;

import java.lang.reflect.Constructor;
import java.util.function.Consumer;
import java.io.IOException;
import java.io.UncheckedIOException;

// Used for the JavaDoc '@see' tag, a few lines directly-below
import Torello.HTML.Tools.Images.ImageScraper;

/**
 * Identical to class {@code java.lang.Appendable}, but shunts the often problematic
 * {@code IOException} that may be thrown by its {@code append(...)}-methods, and replaces it with
 * either a user-customizable {@code Throwable}-class, or with (the default)
 * {@code java.io.UncheckedIOException}.
 * 
 * <BR /><BR />If a tool or application would like to simplify output logging by allows a user to
 * simply pass a reference to a {@code java.lang.Appendable} instance, but worries that that
 * interface's copious {@code 'throws IOException'} will over-complicate their logging-code,
 * <I>then this {@code interface} is for you!</I>.
 * 
 * <BR /><BR />The following five options are available for dealing with the dreaded Checked 
 * {@code IOException} that Java may throw when writing to an {@code Appendable}.
 * 
 * <BR /><BR /><UL CLASS=JDUL>
 * 
 * <LI> Wrap the checked {@code IOException} in a {@code java.io.UncheckedIOException}, and then 
 *      pass it to the unchecked exception's constructor's {@code 'cause'} parameter.  After
 *      construction, throw the newly constructed, unchecked, exception.
 *      <BR /><BR />
 *      </LI>
 * 
 * <LI> Wrap the checked {@code IOException} in an {@link AppendableError}, and then pass it to 
 *      the error's constructor's {@code 'cause'} parameter.  After construction, throw the newly
 *      constructed, unchecked, error.
 *      <BR /><BR />
 *      </LI>
 *
 * <LI> Supress and Ignore the exception competely!  After catching an {@code IOException} which 
 *      has been accidentally generated by one of the underlying {@code Appendable's 'append(...)'}
 *      methods, simply ignore it and let it go out of scope.
 *      <BR /><BR />
 *      </LI>
 * 
 * <LI> Wrap the checked {@code IOException} in a User-Provided {@code RuntimeException}, and then 
 *      pass it to the User-Provided exception's constructor's {@code 'cause'} parameter.  After 
 *      that constructor has finished, throw the newly constructted {@code RuntimeException}.
 * 
 *      <BR /><BR />
 *      <DIV CLASS=JDHint>
 *      <B STYLE='color:red;'>Important:</B> The Class provided to parameter {@link #classRTEX}
 *      must actually have a constructor which accepts a {@code 'cause'}-Throwable.  If the 
 *      User-Provided, "alternate", RuntimeException cannot be constructed using a {@code 'cause'}
 *      Throwable, then the attempt to build this {@code 'AppendableSafe'} instance will fail.
 *      </DIV>
 * 
 *      <BR />
 *      <DIV CLASS=JDHintAlt>
 *      <B STYLE='color:red;'>RuntimeException Constructor Expectations:</B>
 *      The constructor for this variant of {@code AppendableSafe} will check for a constructor
 *      which accepts a {@code Throwable 'cause'} parameter, or a {@code RuntimeException}
 *      constructor which accepts a {@code String 'message'} and {@code Throwable 'cause'}.
 *      </DIV>
 *      <BR />
 *      </LI> 
 * 
 * <LI> Provide a {@code Consumer<? extends IOException> 'handler'} if-and-when the underlying
 *      {@code Appendable's append(...)} methods happen to throw an {@code IOException}.
 *      </LI>
 * 
 *  </UL>
 * @see ImageScraper
 * @see AppendableLog
 */
@JDHeaderBackgroundImg(EmbedTagFileID={"APPENDABLE_EXTENSION", "APPENDABLE_SAFE_JDHBI"})
public class AppendableSafe implements Appendable
{
    // ********************************************************************************************
    // ********************************************************************************************
    // Constants
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Indicates that the user would like to have {@code 'UncheckedIOException'} thrown in place of
     * the standard (checked) exception {@code 'IOException'}.
     */
    public static final byte USE_UNCHECKED_IOEXCEPTION = 1;

    /**
     * Indicates that the user would like to have {@link AppendableError} thrown in place of
     * the standard (checked) exception {@code 'IOException'}.
     */
    public static final byte USE_APPENDABLE_ERROR = 2;

    /**
     * Configures {@code AppendableSafe} to catch any {@code IOException's} that are thrown by the
     * {@code Appendable's} method {@code append(...)}, and suppress / ignore them.
     */
    public static final byte SUPPRESS_AND_IGNORE = 3;

    /** The message used by the 'wrapper' {@code Throwable} */
    public static final String exceptionMessage =
        "The underlying Appendable instance has thrown an IOException upon invocation of one of " +
        "its append(...) methods.  Please see this Throwable's getCause() to review the " +
        "specifics of the cause IOException.";


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


    /**
     * This is the internally used {@code java.lang.Appendable}.  Class {@code AppendableSafe} is
     * nothing more than a wrapper class around a {@code java.lang.Appendable} instance.  All that
     * {@code AppendableSafe} does is to catch any potential {@code IOException} instances that may
     * or may not be thrown by the {@code Appendable's append(...)} methods, and either suppress
     * them, or re-wrap them in an "Un-Checked" type of {@code Throwable}.
     * 
     * <BR /><BR />This {@code public} field provides access to the wrapped / internal 
     * {@code Appendable}.
     */
    public final Appendable appendable;

    /**
     * If one of the standard ways of avoiding {@code IOException}-throws by a Java
     * {@code Appendable} has been chosen, this byte-constant will contain the value of one of the
     * three {@code static}-constants defined at the top of this class.
     * 
     * <BR /><BR />If the user has opted to provide a {@code RuntimeException} class to throw when
     * an {@code IOException} is caught, this constant-field will be assigned {@code '0'} by the
     * constructor.
     */
    public final byte throwDecision;

    /**
     * This may be null, and if it is - the value stored in {@link #throwDecision} or the value of
     * {@link #handler} will be used for deciding what to do when the internal / wrapped
     * {@code Appendable} throws an {@code IOException} while appending character-data.
     * 
     * <BR /><BR />When this 'class' field-constant is non-null, the {@code RuntimeException} 
     * represented by the class will be thrown whenever the internal / wrapped {@code Appendable}
     * throws a (Checked) {@code IOException} while writing character-data to it.
     */
    public final Class<? extends RuntimeException> classRTEX;


    // This is just a simple use of java.lang.reflect to save the constructor that will build an
    // instance of the previously-mentioned 'throwableClass' whenever it is necessary to build such
    // an instance.

    private final Constructor<? extends RuntimeException> ctorRTEX;
    private final byte ctorNumParams;

    /**
     * This handler is invoked if and when the internal / wrapped {@code Appendable} throws a
     * (Checked) {@code IOException} while writing character-data to it.  This handler is set by 
     * User's chosen Constructor, at the time of construction of this object.  This
     * {@code Consumer}-Field may actually be set directly by calling the
     * {@link #AppendableSafe(Appendable, Consumer)} constructor.
     */
    private final Consumer<? super IOException> handler;


    // ********************************************************************************************
    // ********************************************************************************************
    // Two Constructors of this class
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Constructs an instance of this class that wraps the provided {@code java.lang.Appendable}.
     * The instance that is created will catch any and all {@code IOException's} that are thrown
     * by the input {@code 'appendable'}-parameter's {@code append(...)} methods.
     * 
     * <BR /><BR />If invoking an {@code append(...)} method does cause an {@code IOException} to
     * throw, then the {@code AppendableSafe} instance that is built right here will be one that,
     * in turn, throws the {@code Throwable} indicated by the {@code 'throwDecision'} parameter.
     * 
     * <BR /><BR />If {@code 'throwDecision'} is passed the {@code byte}-value for
     * {@link #SUPPRESS_AND_IGNORE}, then when / if an {@code append(...)} throws
     * {@code IOException}, that exception, instead, will simply be caught and ignored.
     * 
     * @param appendable Any instance of {@code java.lang.Appendable}.
     * 
     * @param throwDecision Allows a user to decide what happens when / if the provided 
     * {@code Appendable's} methods for accepting character-data throw an {@code IOException}.
     * 
     * <BR /><BR />The allowed values are all {@code byte}-constants, and they are listed at the
     * top of this class:
     * 
     * <BR /><BR /><OL CLASS=JDOL>
     * <LI>{@link #USE_UNCHECKED_IOEXCEPTION}</LI>
     * <LI>{@link #USE_APPENDABLE_ERROR}</LI>
     * <LI>{@link #SUPPRESS_AND_IGNORE}</LI>
     * </OL>
     * 
     * @throws NullPointerException If null is passed to the {@code 'apendable'} parameter.
     * 
     * @throws IllegalArgumentException If parameter {@code 'throwDecision'} is not passed one of
     * the three {@code byte}-value constants provided at the top of this class.
     */
    public AppendableSafe(Appendable appendable, byte throwDecision)
    {
        if (appendable == null) throw new NullPointerException
            ("Constructor-Parameter 'appendable' was passed null.");

        if ((throwDecision < 1) || (throwDecision > 3)) throw new IllegalArgumentException(
            "One of the defined byte-constants may be passed to parameter 'throwDecision'.  " +
            "You have passed " + throwDecision
        );

        this.appendable     = appendable;
        this.throwDecision  = throwDecision;
        this.classRTEX      = null;
        this.ctorRTEX       = null;
        this.ctorNumParams  = 0;
        this.handler        = this::handleIOE_THROW_DECISION;
    }

    /**
     * Constructs an instance of this class, where any potential {@code IOException's} that are 
     * thrown when appending character data to this {@code Appendable} are wrapped into the
     * specified-{@code Throwable}.
     * 
     * @param appendable This may be any implementation of {@code java.lang.Appendable}.  This
     * class will supercede calls to this {@code Appendable}, and prevent or catch any exceptions
     * which may or may not throw.
     * 
     * <BR /><BR />If this instance / this parameter {@code 'appendable'} does happen to throw an
     * exception when utilizing one it's {@code append(...)} methods, then that exception will be
     * caught, wrapped, and re-thrown as an instance of the specified {@code Throwable}-=class.
     * 
     * @param classRTEX This is the class of {@code RuntimeException} that should be expected to
     * throw when the underlying {@code Appendable} throws an {@code IOException} during one of its
     * {@code append(...)} method invocations.
     * 
     * <BR /><BR />This parameter <I>may not be null</I>, because otherwise a
     * {@code NullPointerException} will ensue.  The class that is passed here must be an instance
     * or decendant of {@code java.lang.RuntimeException}, and one having a constructor which
     * accepts a {@code String} ({@code 'message'}) parameter, and a {@code Throwable}
     * ({@code 'cause'}) parameter.
     * 
     * @throws NoSuchMethodException - This exception will throw if the parameter
     * {@code 'classRTEX'} is a class which does not have a two-argument constructor - one of which
     * is a {@code String}, and the other of which is a {@code Throwable}.
     * 
     * @throws SecurityException - If a security manager, {@code 's'}, is present and the caller's
     * class loader is not the same as or an ancestor of the class loader for the current class and
     * invocation of {@code s.checkPackageAccess()} denies access to the package of this class.
     * 
     * @throws NullPointerException if either parameter {@code 'appendable'} or parameter
     * {@code 'classRTEX'} are null.
     */
    public AppendableSafe(Appendable appendable, Class<? extends RuntimeException> classRTEX)
        throws NoSuchMethodException
    {
        if (appendable == null) throw new NullPointerException
            ("Constructor-Parameter 'appendable' was passed null.");

        if (classRTEX == null) throw new NullPointerException
            ("Constructor-Parameter 'classRTEX' was passed null.");

        this.appendable     = appendable;
        this.throwDecision  = 0;
        this.classRTEX      = classRTEX;
        this.handler        = this::handleIOE_RTEX;

        // Retrieve a constructor from class-paramter 'classRTEX'
        //
        // THROWS:
        //      NoSuchMethodException: If there is no constructor that accepts a String & Throwable
        //      SecurityException: If a security manager says so!
        // 
        // private final Constructor<? extends RuntimeException> ctorRTEX;


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Attempt to Retrieve Constructor: ExceptionClass(String message, Throwable cause)
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***

        Constructor<? extends RuntimeException> cTEMP = null;

        try
            // NOTICE: Retrieving Constructor that accepts (String message, Throwable cause)
            { cTEMP = classRTEX.getConstructor(String.class, Throwable.class); }

        catch (NoSuchMethodException | SecurityException e)
            { cTEMP = null; }

        if (cTEMP != null)
        {
            this.ctorRTEX       = cTEMP;
            this.ctorNumParams  = 2;
            return;
        }


        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // Attempt to Retrieve Constructor: ExceptionClass(Throwable cause)
        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
        // 
        // THROWS:
        //      NoSuchMethodException: If there is no constructor that accepts a String & Throwable
        //      SecurityException: If a security manager says so!
        // 
        // NOTICE: Retrieving Constructor that accepts (Throwable cause)

        this.ctorRTEX       = classRTEX.getConstructor(Throwable.class);
        this.ctorNumParams  = 1;

        return;
    }

    /**
     * Constructs an instance of this class, where any potential {@code IOException's} that are 
     * thrown when appending character data to this {@code Appendable} are first suppressed, and 
     * then passed as parameters to the {@code handler} parameter received by this constructor.
     * 
     * @param appendable This may be any implementation of {@code java.lang.Appendable}.  This
     * class will supercede calls to this {@code Appendable}, and prevent or catch any exceptions
     * which may or may not throw.
     * 
     * <BR /><BR />If this instance / this parameter {@code 'appendable'} does happen to throw an
     * exception when utilizing one it's {@code append(...)} methods, then that exception will be
     * caught and provided to the {@code handler} provided this constructor.
     * 
     * @param ioeHandler A Java Consumer which accepts an {@code IOException} parameter, and
     * performs whatever User-Decided functions if-and-when an {@code IOException} happens to be
     * thrown by the {@code 'append(...)'} methods which wrap by the internal {@code 'Appendable'}
     * instance.
     */
    public AppendableSafe(Appendable appendable, Consumer<? super IOException> ioeHandler)
    {
        if (appendable == null) throw new NullPointerException
            ("Constructor-Parameter 'appendable' was passed null.");

        if (ioeHandler == null) throw new NullPointerException
            ("Constructor-Parameter 'classRTEX' was passed null.");

        this.appendable     = appendable;
        this.throwDecision  = 0;
        this.classRTEX      = null;
        this.ctorRTEX       = null;
        this.ctorNumParams  = 0;
        this.handler        = ioeHandler;
    }
 

    // ********************************************************************************************
    // ********************************************************************************************
    // Standard Appendable Methods
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Appends the specified character to this {@code Appendable}.
     * 
     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
     * 
     * @param c The character to append
     * @return A reference to this {@code Appendable}.
     * 
     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
     */
    public AppendableSafe append(char c)
    {
        try
            { appendable.append(c); }

        catch (IOException ioe) { handler.accept(ioe); }

        return this;
    }

    /**
     * Appends the specified character sequence to this {@code Appendable}.
     * 
     * <BR /><BR />Depending on which class implements the character sequence {@code 'csq'}, the
     * entire sequence may not be appended.  For instance, if {@code 'csq'} is a
     * {@code 'CharBuffer'} the subsequence to append is defined by the buffer's position and limit.
     * 
     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
     * 
     * @param csq The character sequence to append. If csq is null, then the four characters "null"
     * are appended to this {@code Appendable}.
     * 
     * @return A reference to this {@code Appendable}.
     * 
     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
     */
    public AppendableSafe append(CharSequence csq)
    {
        try
            { appendable.append(csq); }

        catch (IOException ioe) { handler.accept(ioe); }

        return this;
    }

    /**
     * Appends a subsequence of the specified character sequence to this {@code Appendable}.
     * 
     * <BR /><BR />An invocation of this method of the form {@code out.append(csq, start, end)}
     * when {@code 'csq'} is not null, behaves in exactly the same way as the invocation:
     * 
     * <DIV CLASS=LOC>{@code
     * out.append(csq.subSequence(start, end)) 
     * }</DIV>
     * 
     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
     * 
     * @param csq The character sequence from which a subsequence will be appended. If csq is null,
     * then the four characters "null" are appended to this {@code Appendable}.
     * 
     * @param start The index of the first character in the subsequence
     * @param end The index of the character following the last character in the subsequence
     * 
     * @return A reference to this {@code Appendable}.
     * 
     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
     */
    public AppendableSafe append(CharSequence csq, int start, int end)
    {
        try
            { appendable.append(csq); }

        catch (IOException ioe) { handler.accept(ioe); }

        return this;
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Helper
    // ********************************************************************************************
    // ********************************************************************************************


    private void handleIOE_THROW_DECISION(IOException ioe)
    {
        switch (throwDecision)
        {
            case USE_UNCHECKED_IOEXCEPTION: throw new UncheckedIOException(exceptionMessage, ioe);
            case USE_APPENDABLE_ERROR:      throw new AppendableError(exceptionMessage, ioe);
            case SUPPRESS_AND_IGNORE:       return;
            default:                        throw new UnreachableError();
        }
    }

    private void handleIOE_RTEX(IOException ioe)
    {
        final RuntimeException rtex;

        try 
        {
            // assert (ctorNumParams == 2) || (ctorNumParams == 1)
            rtex = (this.ctorNumParams == 2)
                ? this.ctorRTEX.newInstance(exceptionMessage, ioe)
                : this.ctorRTEX.newInstance(ioe);
        }

        catch (Exception e)
        {
            throw new AppendableError(
                "An Exception was thrown while attempting to invoke the constructor for the " +
                "provided Exception-Class:\n" +
                '[' + classRTEX.getCanonicalName() + "]\n" +
                "Using Constructor:\n" +
                '[' + this.ctorRTEX.toString() + ']',
                e
            );
        }

        throw rtex;
    }
}