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