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}