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
package Torello.Java.Additional;

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

import java.lang.reflect.Constructor;
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>.
 *  
 * @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 {@link #throwDecision} is 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;


    // ********************************************************************************************
    // ********************************************************************************************
    // 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;
    }

    /**
     * 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;

        // 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!

        this.ctorRTEX = classRTEX.getConstructor(String.class, Throwable.class);
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // 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) { handleIOE(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) { handleIOE(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) { handleIOE(ioe); }

        return this;
    }


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


    private void handleIOE(IOException ioe)
    {
        if (ctorRTEX == null) 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();
        }

        RuntimeException rtex = null;

        try 
            { rtex = ctorRTEX.newInstance(exceptionMessage, ioe); }

        catch (Exception e)
        {
            throw new AppendableError(
                "An Exception was thrown while attempting to invoke the constructor for the " +
                "provided Exception-Class: " + classRTEX.getName(),
                e
            );
        }

        throw rtex;
    }
}