001package Torello.Java.Additional;
002
003import Torello.Java.UnreachableError;
004import Torello.JavaDoc.JDHeaderBackgroundImg;
005
006import java.lang.reflect.Constructor;
007import java.io.IOException;
008import java.io.UncheckedIOException;
009
010// Used for the JavaDoc '@see' tag, a few lines directly-below
011import Torello.HTML.Tools.Images.ImageScraper;
012
013/**
014 * Identical to class {@code java.lang.Appendable}, but shunts the often problematic
015 * {@code IOException} that may be thrown by its {@code append(...)}-methods, and replaces it with
016 * either a user-customizable {@code Throwable}-class, or with (the default)
017 * {@code java.io.UncheckedIOException}.
018 * 
019 * <BR /><BR />If a tool or application would like to simplify output logging by allows a user to
020 * simply pass a reference to a {@code java.lang.Appendable} instance, but worries that that
021 * interface's copious {@code 'throws IOException'} will over-complicate their logging-code,
022 * <I>then this {@code interface} is for you!</I>.
023 *  
024 * @see ImageScraper
025 * @see AppendableLog
026 */
027@JDHeaderBackgroundImg(EmbedTagFileID={"APPENDABLE_EXTENSION", "APPENDABLE_SAFE_JDHBI"})
028public class AppendableSafe implements Appendable
029{
030    // ********************************************************************************************
031    // ********************************************************************************************
032    // Constants
033    // ********************************************************************************************
034    // ********************************************************************************************
035
036
037    /**
038     * Indicates that the user would like to have {@code 'UncheckedIOException'} thrown in place of
039     * the standard (checked) exception {@code 'IOException'}.
040     */
041    public static final byte USE_UNCHECKED_IOEXCEPTION = 1;
042
043    /**
044     * Indicates that the user would like to have {@link AppendableError} thrown in place of
045     * the standard (checked) exception {@code 'IOException'}.
046     */
047    public static final byte USE_APPENDABLE_ERROR = 2;
048
049    /**
050     * Configures {@code AppendableSafe} to catch any {@code IOException's} that are thrown by the
051     * {@code Appendable's} method {@code append(...)}, and suppress / ignore them.
052     */
053    public static final byte SUPPRESS_AND_IGNORE = 3;
054
055    /** The message used by the 'wrapper' {@code Throwable} */
056    public static final String exceptionMessage =
057        "The underlying Appendable instance has thrown an IOException upon invocation of one of " +
058        "its append(...) methods.  Please see this Throwable's getCause() to review the " +
059        "specifics of the cause IOException.";
060
061
062    // ********************************************************************************************
063    // ********************************************************************************************
064    // Fields
065    // ********************************************************************************************
066    // ********************************************************************************************
067
068
069    /**
070     * This is the internally used {@code java.lang.Appendable}.  Class {@code AppendableSafe} is
071     * nothing more than a wrapper class around a {@code java.lang.Appendable} instance.  All that
072     * {@code AppendableSafe} does is to catch any potential {@code IOException} instances that may
073     * or may not be thrown by the {@code Appendable's append(...)} methods, and either suppress
074     * them, or re-wrap them in an "Un-Checked" type of {@code Throwable}.
075     * 
076     * <BR /><BR />This {@code public} field provides access to the wrapped / internal 
077     * {@code Appendable}.
078     */
079    public final Appendable appendable;
080
081    /**
082     * If one of the standard ways of avoiding {@code IOException}-throws by a Java
083     * {@code Appendable} has been chosen, this byte-constant will contain the value of one of the
084     * three {@code static}-constants defined at the top of this class.
085     * 
086     * <BR /><BR />If the user has opted to provide a {@code RuntimeException} class to throw when
087     * an {@code IOException} is caught, this constant-field will be assigned {@code '0'} by the
088     * constructor.
089     */
090    public final byte throwDecision;
091
092    /**
093     * This may be null, and if it is - the value stored {@link #throwDecision} is used for 
094     * deciding what to do when the internal / wrapped {@code Appendable} throws an
095     * {@code IOException} while appending character-data.
096     * 
097     * <BR /><BR />When this 'class' field-constant is non-null, the {@code RuntimeException} 
098     * represented by the class will be thrown whenever the internal / wrapped {@code Appendable}
099     * throws a (Checked) {@code IOException} while writing character-data to it.
100     */
101    public final Class<? extends RuntimeException> classRTEX;
102
103    // This is just a simple use of java.lang.reflect to save the constructor that will build an
104    // instance of the previously-mentioned 'throwableClass' whenever it is necessary to build such
105    // an instance.
106
107    private final Constructor<? extends RuntimeException> ctorRTEX;
108
109
110    // ********************************************************************************************
111    // ********************************************************************************************
112    // Two Constructors of this class
113    // ********************************************************************************************
114    // ********************************************************************************************
115
116
117    /**
118     * Constructs an instance of this class that wraps the provided {@code java.lang.Appendable}.
119     * The instance that is created will catch any and all {@code IOException's} that are thrown
120     * by the input {@code 'appendable'}-parameter's {@code append(...)} methods.
121     * 
122     * <BR /><BR />If invoking an {@code append(...)} method does cause an {@code IOException} to
123     * throw, then the {@code AppendableSafe} instance that is built right here will be one that,
124     * in turn, throws the {@code Throwable} indicated by the {@code 'throwDecision'} parameter.
125     * 
126     * <BR /><BR />If {@code 'throwDecision'} is passed the {@code byte}-value for
127     * {@link #SUPPRESS_AND_IGNORE}, then when / if an {@code append(...)} throws
128     * {@code IOException}, that exception, instead, will simply be caught and ignored.
129     * 
130     * @param appendable Any instance of {@code java.lang.Appendable}.
131     * 
132     * @param throwDecision Allows a user to decide what happens when / if the provided 
133     * {@code Appendable's} methods for accepting character-data throw an {@code IOException}.
134     * 
135     * <BR /><BR />The allowed values are all {@code byte}-constants, and they are listed at the
136     * top of this class:
137     * 
138     * <BR /><BR /><OL CLASS=JDOL>
139     * <LI>{@link #USE_UNCHECKED_IOEXCEPTION}</LI>
140     * <LI>{@link #USE_APPENDABLE_ERROR}</LI>
141     * <LI>{@link #SUPPRESS_AND_IGNORE}</LI>
142     * </OL>
143     * 
144     * @throws NullPointerException If null is passed to the {@code 'apendable'} parameter.
145     * 
146     * @throws IllegalArgumentException If parameter {@code 'throwDecision'} is not passed one of
147     * the three {@code byte}-value constants provided at the top of this class.
148     */
149    public AppendableSafe(Appendable appendable, byte throwDecision)
150    {
151        if (appendable == null) throw new NullPointerException
152            ("Constructor-Parameter 'appendable' was passed null.");
153
154        if ((throwDecision < 1) || (throwDecision > 3)) throw new IllegalArgumentException(
155            "One of the defined byte-constants may be passed to parameter 'throwDecision'.  " +
156            "You have passed " + throwDecision
157        );
158
159        this.appendable     = appendable;
160        this.throwDecision  = throwDecision;
161        this.classRTEX      = null;
162        this.ctorRTEX       = null;
163    }
164
165    /**
166     * Constructs an instance of this class, where any potential {@code IOException's} that are 
167     * thrown when appending character data to this {@code Appendable} are wrapped into the
168     * specified-{@code Throwable}.
169     * 
170     * @param appendable This may be any implementation of {@code java.lang.Appendable}.  This
171     * class will supercede calls to this {@code Appendable}, and prevent or catch any exceptions
172     * which may or may not throw.
173     * 
174     * <BR /><BR />If this instance / this parameter {@code 'appendable'} does happen to throw an
175     * exception when utilizing one it's {@code append(...)} methods, then that exception will be
176     * caught, wrapped, and re-thrown as an instance of the specified {@code Throwable}-=class.
177     * 
178     * @param classRTEX This is the class of {@code RuntimeException} that should be expected to
179     * throw when the underlying {@code Appendable} throws an {@code IOException} during one of its
180     * {@code append(...)} method invocations.
181     * 
182     * <BR /><BR />This parameter <I>may not be null</I>, because otherwise a
183     * {@code NullPointerException} will ensue.  The class that is passed here must be an instance
184     * or decendant of {@code java.lang.RuntimeException}, and one having a constructor which
185     * accepts a {@code String} ({@code 'message'}) parameter, and a {@code Throwable}
186     * ({@code 'cause'}) parameter.
187     * 
188     * @throws NoSuchMethodException - This exception will throw if the parameter
189     * {@code 'classRTEX'} is a class which does not have a two-argument constructor - one of which
190     * is a {@code String}, and the other of which is a {@code Throwable}.
191     * 
192     * @throws SecurityException - If a security manager, {@code 's'}, is present and the caller's
193     * class loader is not the same as or an ancestor of the class loader for the current class and
194     * invocation of {@code s.checkPackageAccess()} denies access to the package of this class.
195     * 
196     * @throws NullPointerException if either parameter {@code 'appendable'} or parameter
197     * {@code 'classRTEX'} are null.
198     */
199    public AppendableSafe(Appendable appendable, Class<? extends RuntimeException> classRTEX)
200        throws NoSuchMethodException
201    {
202        if (appendable == null) throw new NullPointerException
203            ("Constructor-Parameter 'appendable' was passed null.");
204
205        if (classRTEX == null) throw new NullPointerException
206            ("Constructor-Parameter 'classRTEX' was passed null.");
207
208        this.appendable     = appendable;
209        this.throwDecision  = 0;
210        this.classRTEX      = classRTEX;
211
212        // Retrieve a constructor from class-paramter 'classRTEX'
213        //
214        // THROWS:
215        //      NoSuchMethodException: If there is no constructor that accepts a String & Throwable
216        //      SecurityException: If a security manager says so!
217
218        this.ctorRTEX = classRTEX.getConstructor(String.class, Throwable.class);
219    }
220
221
222    // ********************************************************************************************
223    // ********************************************************************************************
224    // Standard Appendable Methods
225    // ********************************************************************************************
226    // ********************************************************************************************
227
228
229    /**
230     * Appends the specified character to this {@code Appendable}.
231     * 
232     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
233     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
234     * 
235     * @param c The character to append
236     * @return A reference to this {@code Appendable}.
237     * 
238     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
239     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
240     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
241     */
242    public AppendableSafe append(char c)
243    {
244        try
245            { appendable.append(c); }
246
247        catch (IOException ioe) { handleIOE(ioe); }
248
249        return this;
250    }
251
252    /**
253     * Appends the specified character sequence to this {@code Appendable}.
254     * 
255     * <BR /><BR />Depending on which class implements the character sequence {@code 'csq'}, the
256     * entire sequence may not be appended.  For instance, if {@code 'csq'} is a
257     * {@code 'CharBuffer'} the subsequence to append is defined by the buffer's position and limit.
258     * 
259     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
260     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
261     * 
262     * @param csq The character sequence to append. If csq is null, then the four characters "null"
263     * are appended to this {@code Appendable}.
264     * 
265     * @return A reference to this {@code Appendable}.
266     * 
267     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
268     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
269     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
270     */
271    public AppendableSafe append(CharSequence csq)
272    {
273        try
274            { appendable.append(csq); }
275
276        catch (IOException ioe) { handleIOE(ioe); }
277
278        return this;
279    }
280
281    /**
282     * Appends a subsequence of the specified character sequence to this {@code Appendable}.
283     * 
284     * <BR /><BR />An invocation of this method of the form {@code out.append(csq, start, end)}
285     * when {@code 'csq'} is not null, behaves in exactly the same way as the invocation:
286     * 
287     * <DIV CLASS=LOC>{@code
288     * out.append(csq.subSequence(start, end)) 
289     * }</DIV>
290     * 
291     * <BR /><BR /><SPAN CLASS=CopiedJDK>Description copied from class:
292     * {@code java.lang.Appendable}, <B>JDK 1.8</B></SPAN>
293     * 
294     * @param csq The character sequence from which a subsequence will be appended. If csq is null,
295     * then the four characters "null" are appended to this {@code Appendable}.
296     * 
297     * @param start The index of the first character in the subsequence
298     * @param end The index of the character following the last character in the subsequence
299     * 
300     * @return A reference to this {@code Appendable}.
301     * 
302     * @throws RuntimeException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_RTEX>
303     * @throws UncheckedIOException <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_UNCH_IOEX>
304     * @throws AppendableError <EMBED CLASS='external-html' DATA-FILE-ID=ASAFE_APP_ERROR>
305     */
306    public AppendableSafe append(CharSequence csq, int start, int end)
307    {
308        try
309            { appendable.append(csq); }
310
311        catch (IOException ioe) { handleIOE(ioe); }
312
313        return this;
314    }
315
316
317    // ********************************************************************************************
318    // ********************************************************************************************
319    // Helper
320    // ********************************************************************************************
321    // ********************************************************************************************
322
323
324    private void handleIOE(IOException ioe)
325    {
326        if (ctorRTEX == null) switch (throwDecision)
327        {
328            case USE_UNCHECKED_IOEXCEPTION: throw new UncheckedIOException(exceptionMessage, ioe);
329            case USE_APPENDABLE_ERROR:      throw new AppendableError(exceptionMessage, ioe);
330            case SUPPRESS_AND_IGNORE:       return;
331            default:                        throw new UnreachableError();
332        }
333
334        RuntimeException rtex = null;
335
336        try 
337            { rtex = ctorRTEX.newInstance(exceptionMessage, ioe); }
338
339        catch (Exception e)
340        {
341            throw new AppendableError(
342                "An Exception was thrown while attempting to invoke the constructor for the " +
343                "provided Exception-Class: " + classRTEX.getName(),
344                e
345            );
346        }
347
348        throw rtex;
349    }
350}