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}