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 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 | package Torello.HTML.Tools.Images; import Torello.Java.StringParse; import Torello.Java.StrPrint; import java.awt.image.BufferedImage; import java.io.Serializable; import java.net.URL; import java.util.Objects; /** * Simple Image Data-Class that is instantiated by the {@link ImageScraper}, and passed to any of * the {@code FunctionalInterface}'s or Lambda-Targets that are non-null / available in the user's * {@link Request} object instance. * * <EMBED CLASS='external-html' DATA-FILE-ID=IMAGE_INFO_EXAMPLE> */ @Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="IMAGE_SCRAPER_CLASS") public class ImageInfo implements Cloneable, Serializable { /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ public static final long serialVersionUID = 1; // ******************************************************************************************** // ******************************************************************************************** // Public Fields // ******************************************************************************************** // ******************************************************************************************** /** * The {@code URL} that was used to download the image. Note that anytime this instance of * {@code ImageInfo} represents an image that was located on a web-page encoded using a * Base-64 {@code String}, then this field will be null, and the B-64 Fields will contain the * relevant image data (not this {@code URL}). */ public final URL url; /** * If the image whose details are contained by this class-instance are from an image that * was encoded using the {@code String}-literal Base-64 Encoding Algorithm, then this boolean * flag will contain {@code TRUE}. * * <BR /><BR />An HTML {@code <IMG SRC=...>} Tag is the only way to enter a Base-64 Image into * the Image-Scraper class. */ public final boolean isB64EncodedImage; /** * A web-page has the ability to inline (smaller) images directly into an HTML * {@code <IMG SRC=...>} tag by encoding the picture into a Base-64 {@code String}. The * {@code String} is saved inside the {@code SRC}-Attribute of the {@code <IMG>} tag, and * contains two separate sub-strings. * * <BR /><BR />This is the first sub-string, and it just identifies / lists the format * ({@code .jpg, .gif, .png} etc...) in which the picture was saved before translating it into * Base-64 Encoded Text. * * <BR /><BR />If the picture represented by this instance of {@code ImageInfo} was downloaded * from a {@code URL}, then this field will be null, and the {@link #url} field will contain * the Image {@code URL}. * * @see #isB64EncodedImage * @see #b64EncodedImage */ public final String imageFormatStr; /** * A web-page has the ability to inline (smaller) images directly into an HTML * {@code <IMG SRC=...>} tag by encoding the picture into a Base-64 {@code String}. The * {@code String} is saved inside the {@code SRC}-Attribute of the {@code <IMG>} tag, and * contains two separate sub-strings. * * <BR /><BR />This is the second sub-string, and it is the text after translating the picture * into Base-64 Encoded Text. * * <BR /><BR />If the picture represented by this instance of {@code ImageInfo} was downloaded * from a {@code URL}, then this field will be null, and the {@link #url} field will contain * the Image {@code URL}. * * @see #isB64EncodedImage * @see #imageFormatStr */ public final String b64EncodedImage; /** The image, as prepared for saving to disk. */ public final byte[] imgByteArr; /** The image, as an instance of {@code java.awt.image.BufferedImage} */ public final BufferedImage bufferedImage; /** * The value returned by {@code bufferedImage.getWidth()} - <I>the downloaded image's * width</I>. */ public final int width; /** * The value returned by {@code bufferedImage.getHeight()} - <I>the downloaded image's * height</I>. */ public final int height; /** * This shall help identify whether the image-in-question is a {@code GIF, JPG, PNG, etc...}. * The field {@code 'guessedImageFormat'} shall simply contain the image-type based on the * extension found in the {@code URL's} file-name. * * <BR /><BR />There are web-pages & web-sites that do not provide a file-name extension * for the images they use on their page(s). In such cases, the downloader will eventually * attempt to 'guess' the format of an image that has been downloaded. In these cases, this * parameter will be passed null, and the parameter {@code actualImageFormat} will contain the * format that was actually used to successfully save the data to disk. */ public final IF guessedExtension; /** * If the image has been properly converted, and is ready to be written to disk, this parameter * will contain the {@link IF} / image-format that was used to successfully save the image. * * <BR /><BR />Note that often (but not always), this extension / {@link IF} instance will be * identical to the parameter {@code 'guessedImageFormat'}. There will be cases, as mentioned * above, when {@code 'guessImageFormat'} is null. Furthermore, there may be (very rare) * situations when an image-format for a particular {@code URL} was incorrect, and was saved * properly using a different format & extension. */ public final IF actualExtension; /** * Identifies the <B STYLE='color: red;'>count</B> in the {@code Iterator's} retrieval. Since * this {@code int} is used as an array-index pointer, it is initialized to {@code '0'} (zero). * Specifically, if this method were called upon completion of three iterations of * Image-{@code URL} retrieval, this counter would contain the integer {@code '2'} (two). */ public final int iteratorCounter; /** * This identifies how many images have successfully downloaded, not the number of images for * which a "download attempt" occurred. Since this {@code int} is used as an array-index * pointer, it is initialized to {@code '0'} (zero). If on the third iteration of the * source-{@code Iterator}, an {@code IOException} occurred between the Java-Virtual-Machine * and the internet, the following invocation of this method would have {@code successCounter} * as {@code '2'}, but the {@code iteratorCounter} would be {@code '3'}. */ public final int successCounter; // This data that is saved in this field isn't saved at the time of construction, and therefore // this field cannot be 'final'. If this field cannot be 'final', it cannot be public, so I // guess there just has to be a getter-method for this one. private String fileName = null; // ******************************************************************************************** // ******************************************************************************************** // Package-Private Constructor // ******************************************************************************************** // ******************************************************************************************** // Used in the "Main Loop Body", only Once ImageInfo( // Image-URL (very common) URL url, // Base-64 Image Stuff (rare, but not impossible) boolean isB64EncodedImage, String[] b64ImageData, // The actual downloaded and converted images, themselves BufferedImage bufferedImage, byte[] imgByteArr, // URL-Aquired Extension & Ultimately-Decided-Upon Extension IF guessedExtension, IF actualExtension, // class 'Results' Array-Counters (index-pointers) int iteratorCounter, int successCounter ) { // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** // This is just "Paranoia" - but fortunately, it isn't very "expensive" paranoia // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** if ((url == null) && (! isB64EncodedImage)) throw new IllegalArgumentException ("(url == null) && (! isB64EncodedImage)"); if ((url != null) && isB64EncodedImage) throw new IllegalArgumentException ("(url != null) && isB64EncodedImage"); if (isB64EncodedImage && (b64ImageData == null)) throw new IllegalArgumentException ("isB64EncodedImage && (b64ImageData == null)"); if ((! isB64EncodedImage) && (b64ImageData != null)) throw new IllegalArgumentException ("(! isB64EncodedImage) && (b64ImageData != null)"); // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** // Initialize all 'final' fields! The only one left out is 'fileName' - it is done later // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** // Image-URL (very common) this.url = url; // Base-64 Image Stuff (rare, but not impossible) this.isB64EncodedImage = isB64EncodedImage; this.imageFormatStr = isB64EncodedImage ? b64ImageData[0] : null; this.b64EncodedImage = isB64EncodedImage ? b64ImageData[1] : null; // The actual downloaded and converted images, themselves this.imgByteArr = imgByteArr; this.bufferedImage = bufferedImage; // Note sure how necessary / important this is, but maybe... this.width = bufferedImage.getWidth(); this.height = bufferedImage.getHeight(); // URL-Aquired Extension & Ultimately-Decided-Upon Extension this.guessedExtension = guessedExtension; this.actualExtension = actualExtension; // class 'Results' Array-Counters (index-pointers) this.iteratorCounter = iteratorCounter; this.successCounter = successCounter; } // ******************************************************************************************** // ******************************************************************************************** // Lone Accessor Method // ******************************************************************************************** // ******************************************************************************************** /** * A "getter" (Accessor-Method) for the {@code private}-Field named {@code fileName}. This * field is kept {@code private} because it cannot be declared {@code final} - since it is * initialized several steps after construction. * * <BR /><BR />If a user Lambda-Expression / Functional-Interface has recevied {@code 'this'} * instance of {@code ImageInfo}, and needs access to the ultimately-decided-upon image * file-name, then this field may be retrieved using this 'Getter' Method. * * <BR /><BR /><B CLASS=JDDescLabel>Unitialized at Construction:</B> * <BR />Note that this method will return null if a user attempts to retrieve the file-name * before it has been decided upon and set in this class. This is actually the whole reason * that it cannot be declared final, and therefore cannot be declared public (and requires this * Getter-Method). */ public String fileName() { return fileName; } // Package-Private, this is set inside class "ImageScraper" void setFileName(String fileName) { this.fileName = fileName; } // ******************************************************************************************** // ******************************************************************************************** // interface java.lang.Cloneable // ******************************************************************************************** // ******************************************************************************************** /** * Generates a <B STYLE='color: red;'>Shallow Copy</B> of {@code 'this'} instance. This means * that the images themselves are not copied - rather only the references to the images are * copied into the clone. * * <BR /><BR />The non-reference, non-instance (primitive-type) fields are all "just copied * like normal" :) * * @return A duplicate instance of this class. */ public ImageInfo clone() { return new ImageInfo(this); } // Private Constructor, used only for the 'clone()' method private ImageInfo(ImageInfo r) { // Image-URL (very common) this.url = r.url; // Base-64 Image Stuff (rare, but not impossible) this.isB64EncodedImage = r.isB64EncodedImage; this.imageFormatStr = r.imageFormatStr; this.b64EncodedImage = r.b64EncodedImage; // The actual downloaded and converted images, themselves this.imgByteArr = r.imgByteArr; this.bufferedImage = r.bufferedImage; // Image-Width, Image-Height this.width = r.width; this.height = r.height; // URL-Aquired Extension & Ultimately-Decided-Upon Extension this.guessedExtension = r.guessedExtension; this.actualExtension = r.actualExtension; // class 'Results' Array-Counters (index-pointers) this.iteratorCounter = r.iteratorCounter; this.successCounter = r.successCounter; // This is the lone / only 'non-final' field this.fileName = r.fileName; } // ******************************************************************************************** // ******************************************************************************************** // java.lang.Object // ******************************************************************************************** // ******************************************************************************************** /** * Converts this class into a simple, readable {@code String} * @return A {@code java.lang.String} representation of {@code 'this'} instance. */ public String toString() { return ((this.url != null) ? ("URL: " + StrPrint.abbrev(this.url.toString(), 40, true, " ... ", 80)) : "") + (this.isB64EncodedImage ? ("B64-Encoded Format: " + this.imageFormatStr + ", IMG: " + StrPrint.abbrev(this.b64EncodedImage, 30, true, " ... ", 60)) : "") + '\n' + "Byte-Array.length: " + StringParse.commas(this.imgByteArr.length) + '\n' + "W: " + StringParse.commas(this.width) + ", " + "H: " + StringParse.commas(this.height) + ", " + "File-Extension: " + Objects.toString(this.actualExtension) + ", " + "URL-Extension: " + Objects.toString(this.guessedExtension) + '\n' + "Iterator-Count: " + this.iteratorCounter + ", " + "Downloaded-Count: " + this.successCounter + '\n' + "Saving File-Name: " + this.fileName + '\n'; } /** * Checks whether {@code 'this'} instance is equal to {@code 'other'}. * * @param other Any Java Object, but only an instance {@code ImageInfo} (or a class that is * assignable to it) could possible generate a {@code TRUE}-return value. * * @return {@code TRUE} If and only if {@code 'other'} is an instance of {@code ImageInfo}, and * if the contents of that are instance are identical to the contents of {@code 'this'} * instance. */ public boolean equals(Object other) { if (other == null) return false; if (! ImageInfo.class.isAssignableFrom(other.getClass())) return false; ImageInfo ii = (ImageInfo) other; // Note that using 'Objects.equals(...)' and 'Objects.deepEquals(...)' primarily prevents // a NullPointerException from being thrown if the left side of an '.equals(...)' were to // be null. It's really nothing more than that. (A small 'Convenience' that makes this // method look less ridiculous than it already does.) // // 'deepEquals(...)' actually checks the entire contents of two array's for equality. return // Image-URL (very common) Objects.equals(this.url, ii.url) // Base-64 Image Stuff (rare, but not impossible) && (this.isB64EncodedImage == ii.isB64EncodedImage) && (Objects.equals(this.imageFormatStr, ii.imageFormatStr)) && (Objects.equals(this.b64EncodedImage, ii.b64EncodedImage)) // The actual downloaded and converted images, themselves && Objects.deepEquals(this.imgByteArr, ii.imgByteArr) && Objects.equals(this.bufferedImage, ii.bufferedImage) // Image-Width, Image-Height && (this.width == ii.width) && (this.height == ii.height) // URL-Aquired Extension & Ultimately-Decided-Upon Extension && Objects.equals(this.guessedExtension, ii.guessedExtension) && Objects.equals(this.actualExtension, ii.actualExtension) // class 'Results' Array-Counters (index-pointers) && (this.iteratorCounter == ii.iteratorCounter) && (this.successCounter == ii.successCounter) // This is the lone / only 'non-final' field && Objects.equals(this.fileName, ii.fileName); } /** * Java's hash-code requirement. The code is computed by summing the first 15 * {@link #imgByteArr} array elements. * * @return A hash-code that may be used when storing this node in a java sorted-collection. */ public int hashCode() { if (url != null) return url.toString().hashCode(); int sum = 0; for (int i=0; (i < 15) && (i < imgByteArr.length); i++) sum += imgByteArr[i]; return sum; } } |