001package Torello.Java.Additional;
002
003import Torello.Java.UnreachableError;
004import Torello.JavaDoc.JDHeaderBackgroundImg;
005
006import java.lang.reflect.Constructor;
007import java.util.function.Consumer;
008import java.io.IOException;
009import java.io.UncheckedIOException;
010
011// Used for the JavaDoc '@see' tag, a few lines directly-below
012import Torello.HTML.Tools.Images.ImageScraper;
013
014/**
015 * Identical to class {@code java.lang.Appendable}, but shunts the often problematic
016 * {@code IOException} that may be thrown by its {@code append(...)}-methods, and replaces it with
017 * either a user-customizable {@code Throwable}-class, or with (the default)
018 * {@code java.io.UncheckedIOException}.
019 * 
020 * <BR /><BR />If a tool or application would like to simplify output logging by allows a user to
021 * simply pass a reference to a {@code java.lang.Appendable} instance, but worries that that
022 * interface's copious {@code 'throws IOException'} will over-complicate their logging-code,
023 * <I>then this {@code interface} is for you!</I>.
024 * 
025 * <BR /><BR />The following five options are available for dealing with the dreaded Checked 
026 * {@code IOException} that Java may throw when writing to an {@code Appendable}.
027 * 
028 * <BR /><BR /><UL CLASS=JDUL>
029 * 
030 * <LI> Wrap the checked {@code IOException} in a {@code java.io.UncheckedIOException}, and then 
031 *      pass it to the unchecked exception's constructor's {@code 'cause'} parameter.  After
032 *      construction, throw the newly constructed, unchecked, exception.
033 *      <BR /><BR />
034 *      </LI>
035 * 
036 * <LI> Wrap the checked {@code IOException} in an {@link AppendableError}, and then pass it to 
037 *      the error's constructor's {@code 'cause'} parameter.  After construction, throw the newly
038 *      constructed, unchecked, error.
039 *      <BR /><BR />
040 *      </LI>
041 *
042 * <LI> Supress and Ignore the exception competely!  After catching an {@code IOException} which 
043 *      has been accidentally generated by one of the underlying {@code Appendable's 'append(...)'}
044 *      methods, simply ignore it and let it go out of scope.
045 *      <BR /><BR />
046 *      </LI>
047 * 
048 * <LI> Wrap the checked {@code IOException} in a User-Provided {@code RuntimeException}, and then 
049 *      pass it to the User-Provided exception's constructor's {@code 'cause'} parameter.  After 
050 *      that constructor has finished, throw the newly constructted {@code RuntimeException}.
051 * 
052 *      <BR /><BR />
053 *      <DIV CLASS=JDHint>
054 *      <B STYLE='color:red;'>Important:</B> The Class provided to parameter {@link #classRTEX}
055 *      must actually have a constructor which accepts a {@code 'cause'}-Throwable.  If the 
056 *      User-Provided, "alternate", RuntimeException cannot be constructed using a {@code 'cause'}
057 *      Throwable, then the attempt to build this {@code 'AppendableSafe'} instance will fail.
058 *      </DIV>
059 * 
060 *      <BR />
061 *      <DIV CLASS=JDHintAlt>
062 *      <B STYLE='color:red;'>RuntimeException Constructor Expectations:</B>
063 *      The constructor for this variant of {@code AppendableSafe} will check for a constructor
064 *      which accepts a {@code Throwable 'cause'} parameter, or a {@code RuntimeException}
065 *      constructor which accepts a {@code String 'message'} and {@code Throwable 'cause'}.
066 *      </DIV>
067 *      <BR />
068 *      </LI> 
069 * 
070 * <LI> Provide a {@code Consumer<? extends IOException> 'handler'} if-and-when the underlying
071 *      {@code Appendable's append(...)} methods happen to throw an {@code IOException}.
072 *      </LI>
073 * 
074 *  </UL>
075 * @see ImageScraper
076 * @see AppendableLog
077 */
078@JDHeaderBackgroundImg(EmbedTagFileID={"APPENDABLE_EXTENSION", "APPENDABLE_SAFE_JDHBI"})
079public class AppendableSafe implements Appendable
080{
081    // ********************************************************************************************
082    // ********************************************************************************************
083    // Constants
084    // ********************************************************************************************
085    // ********************************************************************************************
086
087
088    /**
089     * Indicates that the user would like to have {@code 'UncheckedIOException'} thrown in place of
090     * the standard (checked) exception {@code 'IOException'}.
091     */
092    public static final byte USE_UNCHECKED_IOEXCEPTION = 1;
093
094    /**
095     * Indicates that the user would like to have {@link AppendableError} thrown in place of
096     * the standard (checked) exception {@code 'IOException'}.
097     */
098    public static final byte USE_APPENDABLE_ERROR = 2;
099
100    /**
101     * Configures {@code AppendableSafe} to catch any {@code IOException's} that are thrown by the
102     * {@code Appendable's} method {@code append(...)}, and suppress / ignore them.
103     */
104    public static final byte SUPPRESS_AND_IGNORE = 3;
105
106    /** The message used by the 'wrapper' {@code Throwable} */
107    public static final String exceptionMessage =
108        "The underlying Appendable instance has thrown an IOException upon invocation of one of " +
109        "its append(...) methods.  Please see this Throwable's getCause() to review the " +
110        "specifics of the cause IOException.";
111
112
113    // ********************************************************************************************
114    // ********************************************************************************************
115    // Fields
116    // ********************************************************************************************
117    // ********************************************************************************************
118
119
120    /**
121     * This is the internally used {@code java.lang.Appendable}.  Class {@code AppendableSafe} is
122     * nothing more than a wrapper class around a {@code java.lang.Appendable} instance.  All that
123     * {@code AppendableSafe} does is to catch any potential {@code IOException} instances that may
124     * or may not be thrown by the {@code Appendable's append(...)} methods, and either suppress
125     * them, or re-wrap them in an "Un-Checked" type of {@code Throwable}.
126     * 
127     * <BR /><BR />This {@code public} field provides access to the wrapped / internal 
128     * {@code Appendable}.
129     */
130    public final Appendable appendable;
131
132    /**
133     * If one of the standard ways of avoiding {@code IOException}-throws by a Java
134     * {@code Appendable} has been chosen, this byte-constant will contain the value of one of the
135     * three {@code static}-constants defined at the top of this class.
136     * 
137     * <BR /><BR />If the user has opted to provide a {@code RuntimeException} class to throw when
138     * an {@code IOException} is caught, this constant-field will be assigned {@code '0'} by the
139     * constructor.
140     */
141    public final byte throwDecision;
142
143    /**
144     * This may be null, and if it is - the value stored in {@link #throwDecision} or the value of
145     * {@link #handler} will be used for deciding what to do when the internal / wrapped
146     * {@code Appendable} throws an {@code IOException} while appending character-data.
147     * 
148     * <BR /><BR />When this 'class' field-constant is non-null, the {@code RuntimeException} 
149     * represented by the class will be thrown whenever the internal / wrapped {@code Appendable}
150     * throws a (Checked) {@code IOException} while writing character-data to it.
151     */
152    public final Class<? extends RuntimeException> classRTEX;
153
154
155    // This is just a simple use of java.lang.reflect to save the constructor that will build an
156    // instance of the previously-mentioned 'throwableClass' whenever it is necessary to build such
157    // an instance.
158
159    private final Constructor<? extends RuntimeException> ctorRTEX;
160    private final byte ctorNumParams;
161
162    /**
163     * This handler is invoked if and when the internal / wrapped {@code Appendable} throws a
164     * (Checked) {@code IOException} while writing character-data to it.  This handler is set by 
165     * User's chosen Constructor, at the time of construction of this object.  This
166     * {@code Consumer}-Field may actually be set directly by calling the
167     * {@link #AppendableSafe(Appendable, Consumer)} constructor.
168     */
169    private final Consumer<? super IOException> handler;
170
171
172    // ********************************************************************************************
173    // ********************************************************************************************
174    // Two Constructors of this class
175    // ********************************************************************************************
176    // ********************************************************************************************
177
178
179    /**
180     * Constructs an instance of this class that wraps the provided {@code java.lang.Appendable}.
181     * The instance that is created will catch any and all {@code IOException's} that are thrown
182     * by the input {@code 'appendable'}-parameter's {@code append(...)} methods.
183     * 
184     * <BR /><BR />If invoking an {@code append(...)} method does cause an {@code IOException} to
185     * throw, then the {@code AppendableSafe} instance that is built right here will be one that,
186     * in turn, throws the {@code Throwable} indicated by the {@code 'throwDecision'} parameter.
187     * 
188     * <BR /><BR />If {@code 'throwDecision'} is passed the {@code byte}-value for
189     * {@link #SUPPRESS_AND_IGNORE}, then when / if an {@code append(...)} throws
190     * {@code IOException}, that exception, instead, will simply be caught and ignored.
191     * 
192     * @param appendable Any instance of {@code java.lang.Appendable}.
193     * 
194     * @param throwDecision Allows a user to decide what happens when / if the provided 
195     * {@code Appendable's} methods for accepting character-data throw an {@code IOException}.
196     * 
197     * <BR /><BR />The allowed values are all {@code byte}-constants, and they are listed at the
198     * top of this class:
199     * 
200     * <BR /><BR /><OL CLASS=JDOL>
201     * <LI>{@link #USE_UNCHECKED_IOEXCEPTION}</LI>
202     * <LI>{@link #USE_APPENDABLE_ERROR}</LI>
203     * <LI>{@link #SUPPRESS_AND_IGNORE}</LI>
204     * </OL>
205     * 
206     * @throws NullPointerException If null is passed to the {@code 'apendable'} parameter.
207     * 
208     * @throws IllegalArgumentException If parameter {@code 'throwDecision'} is not passed one of
209     * the three {@code byte}-value constants provided at the top of this class.
210     */
211    public AppendableSafe(Appendable appendable, byte throwDecision)
212    {
213        if (appendable == null) throw new NullPointerException
214            ("Constructor-Parameter 'appendable' was passed null.");
215
216        if ((throwDecision < 1) || (throwDecision > 3)) throw new IllegalArgumentException(
217            "One of the defined byte-constants may be passed to parameter 'throwDecision'.  " +
218            "You have passed " + throwDecision
219        );
220
221        this.appendable     = appendable;
222        this.throwDecision  = throwDecision;
223        this.classRTEX      = null;
224        this.ctorRTEX       = null;
225        this.ctorNumParams  = 0;
226        this.handler        = this::handleIOE_THROW_DECISION;
227    }
228
229    /**
230     * Constructs an instance of this class, where any potential {@code IOException's} that are 
231     * thrown when appending character data to this {@code Appendable} are wrapped into the
232     * specified-{@code Throwable}.
233     * 
234     * @param appendable This may be any implementation of {@code java.lang.Appendable}.  This
235     * class will supercede calls to this {@code Appendable}, and prevent or catch any exceptions
236     * which may or may not throw.
237     * 
238     * <BR /><BR />If this instance / this parameter {@code 'appendable'} does happen to throw an
239     * exception when utilizing one it's {@code append(...)} methods, then that exception will be
240     * caught, wrapped, and re-thrown as an instance of the specified {@code Throwable}-=class.
241     * 
242     * @param classRTEX This is the class of {@code RuntimeException} that should be expected to
243     * throw when the underlying {@code Appendable} throws an {@code IOException} during one of its
244     * {@code append(...)} method invocations.
245     * 
246     * <BR /><BR />This parameter <I>may not be null</I>, because otherwise a
247     * {@code NullPointerException} will ensue.  The class that is passed here must be an instance
248     * or decendant of {@code java.lang.RuntimeException}, and one having a constructor which
249     * accepts a {@code String} ({@code 'message'}) parameter, and a {@code Throwable}
250     * ({@code 'cause'}) parameter.
251     * 
252     * @throws NoSuchMethodException - This exception will throw if the parameter
253     * {@code 'classRTEX'} is a class which does not have a two-argument constructor - one of which
254     * is a {@code String}, and the other of which is a {@code Throwable}.
255     * 
256     * @throws SecurityException - If a security manager, {@code 's'}, is present and the caller's
257     * class loader is not the same as or an ancestor of the class loader for the current class and
258     * invocation of {@code s.checkPackageAccess()} denies access to the package of this class.
259     * 
260     * @throws NullPointerException if either parameter {@code 'appendable'} or parameter
261     * {@code 'classRTEX'} are null.
262     */
263    public AppendableSafe(Appendable appendable, Class<? extends RuntimeException> classRTEX)
264        throws NoSuchMethodException
265    {
266        if (appendable == null) throw new NullPointerException
267            ("Constructor-Parameter 'appendable' was passed null.");
268
269        if (classRTEX == null) throw new NullPointerException
270            ("Constructor-Parameter 'classRTEX' was passed null.");
271
272        this.appendable     = appendable;
273        this.throwDecision  = 0;
274        this.classRTEX      = classRTEX;
275        this.handler        = this::handleIOE_RTEX;
276
277        // Retrieve a constructor from class-paramter 'classRTEX'
278        //
279        // THROWS:
280        //      NoSuchMethodException: If there is no constructor that accepts a String & Throwable
281        //      SecurityException: If a security manager says so!
282        // 
283        // private final Constructor<? extends RuntimeException> ctorRTEX;
284
285
286        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
287        // Attempt to Retrieve Constructor: ExceptionClass(String message, Throwable cause)
288        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
289
290        Constructor<? extends RuntimeException> cTEMP = null;
291
292        try
293            // NOTICE: Retrieving Constructor that accepts (String message, Throwable cause)
294            { cTEMP = classRTEX.getConstructor(String.class, Throwable.class); }
295
296        catch (NoSuchMethodException | SecurityException e)
297            { cTEMP = null; }
298
299        if (cTEMP != null)
300        {
301            this.ctorRTEX       = cTEMP;
302            this.ctorNumParams  = 2;
303            return;
304        }
305
306
307        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
308        // Attempt to Retrieve Constructor: ExceptionClass(Throwable cause)
309        // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
310        // 
311        // THROWS:
312        //      NoSuchMethodException: If there is no constructor that accepts a String & Throwable
313        //      SecurityException: If a security manager says so!
314        // 
315        // NOTICE: Retrieving Constructor that accepts (Throwable cause)
316
317        this.ctorRTEX       = classRTEX.getConstructor(Throwable.class);
318        this.ctorNumParams  = 1;
319
320        return;
321    }
322
323    /**
324     * Constructs an instance of this class, where any potential {@code IOException's} that are 
325     * thrown when appending character data to this {@code Appendable} are first suppressed, and 
326     * then passed as parameters to the {@code handler} parameter received by this constructor.
327     * 
328     * @param appendable This may be any implementation of {@code java.lang.Appendable}.  This
329     * class will supercede calls to this {@code Appendable}, and prevent or catch any exceptions
330     * which may or may not throw.
331     * 
332     * <BR /><BR />If this instance / this parameter {@code 'appendable'} does happen to throw an
333     * exception when utilizing one it's {@code append(...)} methods, then that exception will be
334     * caught and provided to the {@code handler} provided this constructor.
335     * 
336     * @param ioeHandler A Java Consumer which accepts an {@code IOException} parameter, and
337     * performs whatever User-Decided functions if-and-when an {@code IOException} happens to be
338     * thrown by the {@code 'append(...)'} methods which wrap by the internal {@code 'Appendable'}
339     * instance.
340     */
341    public AppendableSafe(Appendable appendable, Consumer<? super IOException> ioeHandler)
342    {
343        if (appendable == null) throw new NullPointerException
344            ("Constructor-Parameter 'appendable' was passed null.");
345
346        if (ioeHandler == null) throw new NullPointerException
347            ("Constructor-Parameter 'classRTEX' was passed null.");
348
349        this.appendable     = appendable;
350        this.throwDecision  = 0;
351        this.classRTEX      = null;
352        this.ctorRTEX       = null;
353        this.ctorNumParams  = 0;
354        this.handler        = ioeHandler;
355    }
356 
357
358    // ********************************************************************************************
359    // ********************************************************************************************
360    // Standard Appendable Methods
361    // ********************************************************************************************
362    // ********************************************************************************************
363
364
365    /**
366     * Appends the specified character to this {@code Appendable}.
367     * 
368     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
369     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
370     * 
371     * @param c The character to append
372     * @return A reference to this {@code Appendable}.
373     * 
374     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
375     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
376     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
377     */
378    public AppendableSafe append(char c)
379    {
380        try
381            { appendable.append(c); }
382
383        catch (IOException ioe) { handler.accept(ioe); }
384
385        return this;
386    }
387
388    /**
389     * Appends the specified character sequence to this {@code Appendable}.
390     * 
391     * <BR /><BR />Depending on which class implements the character sequence {@code 'csq'}, the
392     * entire sequence may not be appended.  For instance, if {@code 'csq'} is a
393     * {@code 'CharBuffer'} the subsequence to append is defined by the buffer's position and limit.
394     * 
395     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
396     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
397     * 
398     * @param csq The character sequence to append. If csq is null, then the four characters "null"
399     * are appended to this {@code Appendable}.
400     * 
401     * @return A reference to this {@code Appendable}.
402     * 
403     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
404     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
405     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
406     */
407    public AppendableSafe append(CharSequence csq)
408    {
409        try
410            { appendable.append(csq); }
411
412        catch (IOException ioe) { handler.accept(ioe); }
413
414        return this;
415    }
416
417    /**
418     * Appends a subsequence of the specified character sequence to this {@code Appendable}.
419     * 
420     * <BR /><BR />An invocation of this method of the form {@code out.append(csq, start, end)}
421     * when {@code 'csq'} is not null, behaves in exactly the same way as the invocation:
422     * 
423     * <DIV CLASS=LOC>{@code
424     * out.append(csq.subSequence(start, end)) 
425     * }</DIV>
426     * 
427     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
428     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
429     * 
430     * @param csq The character sequence from which a subsequence will be appended. If csq is null,
431     * then the four characters "null" are appended to this {@code Appendable}.
432     * 
433     * @param start The index of the first character in the subsequence
434     * @param end The index of the character following the last character in the subsequence
435     * 
436     * @return A reference to this {@code Appendable}.
437     * 
438     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
439     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
440     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
441     */
442    public AppendableSafe append(CharSequence csq, int start, int end)
443    {
444        try
445            { appendable.append(csq); }
446
447        catch (IOException ioe) { handler.accept(ioe); }
448
449        return this;
450    }
451
452
453    // ********************************************************************************************
454    // ********************************************************************************************
455    // Helper
456    // ********************************************************************************************
457    // ********************************************************************************************
458
459
460    private void handleIOE_THROW_DECISION(IOException ioe)
461    {
462        switch (throwDecision)
463        {
464            case USE_UNCHECKED_IOEXCEPTION: throw new UncheckedIOException(exceptionMessage, ioe);
465            case USE_APPENDABLE_ERROR:      throw new AppendableError(exceptionMessage, ioe);
466            case SUPPRESS_AND_IGNORE:       return;
467            default:                        throw new UnreachableError();
468        }
469    }
470
471    private void handleIOE_RTEX(IOException ioe)
472    {
473        final RuntimeException rtex;
474
475        try 
476        {
477            // assert (ctorNumParams == 2) || (ctorNumParams == 1)
478            rtex = (this.ctorNumParams == 2)
479                ? this.ctorRTEX.newInstance(exceptionMessage, ioe)
480                : this.ctorRTEX.newInstance(ioe);
481        }
482
483        catch (Exception e)
484        {
485            throw new AppendableError(
486                "An Exception was thrown while attempting to invoke the constructor for the " +
487                "provided Exception-Class:\n" +
488                '[' + classRTEX.getCanonicalName() + "]\n" +
489                "Using Constructor:\n" +
490                '[' + this.ctorRTEX.toString() + ']',
491                e
492            );
493        }
494
495        throw rtex;
496    }
497}