001package Torello.HTML.Tools.Images; 002 003import Torello.Java.StrPrint; 004import Torello.Java.StrPrint; 005 006import java.awt.image.BufferedImage; 007import java.io.Serializable; 008import java.net.URL; 009import java.util.Objects; 010 011/** 012 * Simple Image Data-Class that is instantiated by the {@link ImageScraper}, and passed to any of 013 * the {@code FunctionalInterface}'s or Lambda-Targets that are non-null / available in the user's 014 * {@link Request} object instance. 015 * 016 * <EMBED CLASS='external-html' DATA-FILE-ID=IMAGE_INFO_EXAMPLE> 017 */ 018@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="IMAGE_SCRAPER_CLASS") 019public class ImageInfo implements Cloneable, Serializable 020{ 021 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 022 public static final long serialVersionUID = 1; 023 024 025 // ******************************************************************************************** 026 // ******************************************************************************************** 027 // Public Fields 028 // ******************************************************************************************** 029 // ******************************************************************************************** 030 031 032 /** 033 * The {@code URL} that was used to download the image. Note that anytime this instance of 034 * {@code ImageInfo} represents an image that was located on a web-page encoded using a 035 * Base-64 {@code String}, then this field will be null, and the B-64 Fields will contain the 036 * relevant image data (not this {@code URL}). 037 */ 038 public final URL url; 039 040 /** 041 * If the image whose details are contained by this class-instance are from an image that 042 * was encoded using the {@code String}-literal Base-64 Encoding Algorithm, then this boolean 043 * flag will contain {@code TRUE}. 044 * 045 * <BR /><BR />An HTML {@code <IMG SRC=...>} Tag is the only way to enter a Base-64 Image into 046 * the Image-Scraper class. 047 */ 048 public final boolean isB64EncodedImage; 049 050 /** 051 * A web-page has the ability to inline (smaller) images directly into an HTML 052 * {@code <IMG SRC=...>} tag by encoding the picture into a Base-64 {@code String}. The 053 * {@code String} is saved inside the {@code SRC}-Attribute of the {@code <IMG>} tag, and 054 * contains two separate sub-strings. 055 * 056 * <BR /><BR />This is the first sub-string, and it just identifies / lists the format 057 * ({@code .jpg, .gif, .png} etc...) in which the picture was saved before translating it into 058 * Base-64 Encoded Text. 059 * 060 * <BR /><BR />If the picture represented by this instance of {@code ImageInfo} was downloaded 061 * from a {@code URL}, then this field will be null, and the {@link #url} field will contain 062 * the Image {@code URL}. 063 * 064 * @see #isB64EncodedImage 065 * @see #b64EncodedImage 066 */ 067 public final String imageFormatStr; 068 069 /** 070 * A web-page has the ability to inline (smaller) images directly into an HTML 071 * {@code <IMG SRC=...>} tag by encoding the picture into a Base-64 {@code String}. The 072 * {@code String} is saved inside the {@code SRC}-Attribute of the {@code <IMG>} tag, and 073 * contains two separate sub-strings. 074 * 075 * <BR /><BR />This is the second sub-string, and it is the text after translating the picture 076 * into Base-64 Encoded Text. 077 * 078 * <BR /><BR />If the picture represented by this instance of {@code ImageInfo} was downloaded 079 * from a {@code URL}, then this field will be null, and the {@link #url} field will contain 080 * the Image {@code URL}. 081 * 082 * @see #isB64EncodedImage 083 * @see #imageFormatStr 084 */ 085 public final String b64EncodedImage; 086 087 /** The image, as prepared for saving to disk. */ 088 public final byte[] imgByteArr; 089 090 /** The image, as an instance of {@code java.awt.image.BufferedImage} */ 091 public final BufferedImage bufferedImage; 092 093 /** 094 * The value returned by {@code bufferedImage.getWidth()} - <I>the downloaded image's 095 * width</I>. 096 */ 097 public final int width; 098 099 /** 100 * The value returned by {@code bufferedImage.getHeight()} - <I>the downloaded image's 101 * height</I>. 102 */ 103 public final int height; 104 105 /** 106 * This shall help identify whether the image-in-question is a {@code GIF, JPG, PNG, etc...}. 107 * The field {@code 'guessedImageFormat'} shall simply contain the image-type based on the 108 * extension found in the {@code URL's} file-name. 109 * 110 * <BR /><BR />There are web-pages & web-sites that do not provide a file-name extension 111 * for the images they use on their page(s). In such cases, the downloader will eventually 112 * attempt to 'guess' the format of an image that has been downloaded. In these cases, this 113 * parameter will be passed null, and the parameter {@code actualImageFormat} will contain the 114 * format that was actually used to successfully save the data to disk. 115 */ 116 public final IF guessedExtension; 117 118 /** 119 * If the image has been properly converted, and is ready to be written to disk, this parameter 120 * will contain the {@link IF} / image-format that was used to successfully save the image. 121 * 122 * <BR /><BR />Note that often (but not always), this extension / {@link IF} instance will be 123 * identical to the parameter {@code 'guessedImageFormat'}. There will be cases, as mentioned 124 * above, when {@code 'guessImageFormat'} is null. Furthermore, there may be (very rare) 125 * situations when an image-format for a particular {@code URL} was incorrect, and was saved 126 * properly using a different format & extension. 127 */ 128 public final IF actualExtension; 129 130 /** 131 * Identifies the <B STYLE='color: red;'>count</B> in the {@code Iterator's} retrieval. Since 132 * this {@code int} is used as an array-index pointer, it is initialized to {@code '0'} (zero). 133 * Specifically, if this method were called upon completion of three iterations of 134 * Image-{@code URL} retrieval, this counter would contain the integer {@code '2'} (two). 135 */ 136 public final int iteratorCounter; 137 138 /** 139 * This identifies how many images have successfully downloaded, not the number of images for 140 * which a "download attempt" occurred. Since this {@code int} is used as an array-index 141 * pointer, it is initialized to {@code '0'} (zero). If on the third iteration of the 142 * source-{@code Iterator}, an {@code IOException} occurred between the Java-Virtual-Machine 143 * and the internet, the following invocation of this method would have {@code successCounter} 144 * as {@code '2'}, but the {@code iteratorCounter} would be {@code '3'}. 145 */ 146 public final int successCounter; 147 148 // This data that is saved in this field isn't saved at the time of construction, and therefore 149 // this field cannot be 'final'. If this field cannot be 'final', it cannot be public, so I 150 // guess there just has to be a getter-method for this one. 151 152 private String fileName = null; 153 154 155 // ******************************************************************************************** 156 // ******************************************************************************************** 157 // Package-Private Constructor 158 // ******************************************************************************************** 159 // ******************************************************************************************** 160 161 162 // Used in the "Main Loop Body", only Once 163 ImageInfo( 164 // Image-URL (very common) 165 URL url, 166 167 // Base-64 Image Stuff (rare, but not impossible) 168 boolean isB64EncodedImage, 169 String[] b64ImageData, 170 171 // The actual downloaded and converted images, themselves 172 BufferedImage bufferedImage, 173 byte[] imgByteArr, 174 175 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 176 IF guessedExtension, 177 IF actualExtension, 178 179 // class 'Results' Array-Counters (index-pointers) 180 int iteratorCounter, 181 int successCounter 182 ) 183 { 184 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 185 // This is just "Paranoia" - but fortunately, it isn't very "expensive" paranoia 186 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 187 188 if ((url == null) && (! isB64EncodedImage)) throw new IllegalArgumentException 189 ("(url == null) && (! isB64EncodedImage)"); 190 191 if ((url != null) && isB64EncodedImage) throw new IllegalArgumentException 192 ("(url != null) && isB64EncodedImage"); 193 194 if (isB64EncodedImage && (b64ImageData == null)) throw new IllegalArgumentException 195 ("isB64EncodedImage && (b64ImageData == null)"); 196 197 if ((! isB64EncodedImage) && (b64ImageData != null)) throw new IllegalArgumentException 198 ("(! isB64EncodedImage) && (b64ImageData != null)"); 199 200 201 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 202 // Initialize all 'final' fields! The only one left out is 'fileName' - it is done later 203 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 204 205 // Image-URL (very common) 206 this.url = url; 207 208 // Base-64 Image Stuff (rare, but not impossible) 209 this.isB64EncodedImage = isB64EncodedImage; 210 this.imageFormatStr = isB64EncodedImage ? b64ImageData[0] : null; 211 this.b64EncodedImage = isB64EncodedImage ? b64ImageData[1] : null; 212 213 // The actual downloaded and converted images, themselves 214 this.imgByteArr = imgByteArr; 215 this.bufferedImage = bufferedImage; 216 217 // Note sure how necessary / important this is, but maybe... 218 this.width = bufferedImage.getWidth(); 219 this.height = bufferedImage.getHeight(); 220 221 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 222 this.guessedExtension = guessedExtension; 223 this.actualExtension = actualExtension; 224 225 // class 'Results' Array-Counters (index-pointers) 226 this.iteratorCounter = iteratorCounter; 227 this.successCounter = successCounter; 228 } 229 230 231 // ******************************************************************************************** 232 // ******************************************************************************************** 233 // Lone Accessor Method 234 // ******************************************************************************************** 235 // ******************************************************************************************** 236 237 238 /** 239 * A "getter" (Accessor-Method) for the {@code private}-Field named {@code fileName}. This 240 * field is kept {@code private} because it cannot be declared {@code final} - since it is 241 * initialized several steps after construction. 242 * 243 * <BR /><BR />If a user Lambda-Expression / Functional-Interface has recevied {@code 'this'} 244 * instance of {@code ImageInfo}, and needs access to the ultimately-decided-upon image 245 * file-name, then this field may be retrieved using this 'Getter' Method. 246 * 247 * <BR /><BR /><DIV CLASS=JDHint> 248 * <B STYLE='color:red;'>Unitialized at Construction:</B> This method will return null if a 249 * user attempts to retrieve the file-name before it has been decided upon and set in this 250 * class. This is actually the whole reason that it cannot be declared final, and therefore 251 * cannot be declared public (and requires this Getter-Method). 252 * </DIV> 253 */ 254 public String fileName() { return fileName; } 255 256 // Package-Private, this is set inside class "ImageScraper" 257 void setFileName(String fileName) { this.fileName = fileName; } 258 259 260 // ******************************************************************************************** 261 // ******************************************************************************************** 262 // interface java.lang.Cloneable 263 // ******************************************************************************************** 264 // ******************************************************************************************** 265 266 267 /** 268 * Generates a <B STYLE='color: red;'>Shallow Copy</B> of {@code 'this'} instance. This means 269 * that the images themselves are not copied - rather only the references to the images are 270 * copied into the clone. 271 * 272 * <BR /><BR />The non-reference, non-instance (primitive-type) fields are all "just copied 273 * like normal" :) 274 * 275 * @return A duplicate instance of this class. 276 */ 277 public ImageInfo clone() 278 { return new ImageInfo(this); } 279 280 // Private Constructor, used only for the 'clone()' method 281 private ImageInfo(ImageInfo r) 282 { 283 // Image-URL (very common) 284 this.url = r.url; 285 286 // Base-64 Image Stuff (rare, but not impossible) 287 this.isB64EncodedImage = r.isB64EncodedImage; 288 this.imageFormatStr = r.imageFormatStr; 289 this.b64EncodedImage = r.b64EncodedImage; 290 291 // The actual downloaded and converted images, themselves 292 this.imgByteArr = r.imgByteArr; 293 this.bufferedImage = r.bufferedImage; 294 295 // Image-Width, Image-Height 296 this.width = r.width; 297 this.height = r.height; 298 299 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 300 this.guessedExtension = r.guessedExtension; 301 this.actualExtension = r.actualExtension; 302 303 // class 'Results' Array-Counters (index-pointers) 304 this.iteratorCounter = r.iteratorCounter; 305 this.successCounter = r.successCounter; 306 307 // This is the lone / only 'non-final' field 308 this.fileName = r.fileName; 309 } 310 311 312 // ******************************************************************************************** 313 // ******************************************************************************************** 314 // java.lang.Object 315 // ******************************************************************************************** 316 // ******************************************************************************************** 317 318 319 /** 320 * Converts this class into a simple, readable {@code String} 321 * @return A {@code java.lang.String} representation of {@code 'this'} instance. 322 */ 323 public String toString() 324 { 325 return 326 ((this.url != null) 327 ? ("URL: " + StrPrint.abbrev(this.url.toString(), 40, true, " ... ", 80)) 328 : "") + 329 330 (this.isB64EncodedImage 331 ? ("B64-Encoded Format: " + this.imageFormatStr + ", IMG: " + 332 StrPrint.abbrev(this.b64EncodedImage, 30, true, " ... ", 60)) 333 : "") + 334 335 '\n' + 336 337 "Byte-Array.length: " + StrPrint.commas(this.imgByteArr.length) + '\n' + 338 339 "W: " + StrPrint.commas(this.width) + ", " + 340 "H: " + StrPrint.commas(this.height) + ", " + 341 "File-Extension: " + Objects.toString(this.actualExtension) + ", " + 342 "URL-Extension: " + Objects.toString(this.guessedExtension) + '\n' + 343 344 "Iterator-Count: " + this.iteratorCounter + ", " + 345 "Downloaded-Count: " + this.successCounter + '\n' + 346 347 "Saving File-Name: " + this.fileName + '\n'; 348 } 349 350 /** 351 * Checks whether {@code 'this'} instance is equal to {@code 'other'}. 352 * 353 * @param other Any Java Object, but only an instance {@code ImageInfo} (or a class that is 354 * assignable to it) could possible generate a {@code TRUE}-return value. 355 * 356 * @return {@code TRUE} If and only if {@code 'other'} is an instance of {@code ImageInfo}, and 357 * if the contents of that are instance are identical to the contents of {@code 'this'} 358 * instance. 359 */ 360 public boolean equals(Object other) 361 { 362 if (other == null) return false; 363 364 if (! ImageInfo.class.isAssignableFrom(other.getClass())) return false; 365 366 ImageInfo ii = (ImageInfo) other; 367 368 // Note that using 'Objects.equals(...)' and 'Objects.deepEquals(...)' primarily prevents 369 // a NullPointerException from being thrown if the left side of an '.equals(...)' were to 370 // be null. It's really nothing more than that. (A small 'Convenience' that makes this 371 // method look less ridiculous than it already does.) 372 // 373 // 'deepEquals(...)' actually checks the entire contents of two array's for equality. 374 375 return 376 377 // Image-URL (very common) 378 Objects.equals(this.url, ii.url) 379 380 // Base-64 Image Stuff (rare, but not impossible) 381 && (this.isB64EncodedImage == ii.isB64EncodedImage) 382 && (Objects.equals(this.imageFormatStr, ii.imageFormatStr)) 383 && (Objects.equals(this.b64EncodedImage, ii.b64EncodedImage)) 384 385 // The actual downloaded and converted images, themselves 386 && Objects.deepEquals(this.imgByteArr, ii.imgByteArr) 387 && Objects.equals(this.bufferedImage, ii.bufferedImage) 388 389 // Image-Width, Image-Height 390 && (this.width == ii.width) 391 && (this.height == ii.height) 392 393 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 394 && Objects.equals(this.guessedExtension, ii.guessedExtension) 395 && Objects.equals(this.actualExtension, ii.actualExtension) 396 397 // class 'Results' Array-Counters (index-pointers) 398 && (this.iteratorCounter == ii.iteratorCounter) 399 && (this.successCounter == ii.successCounter) 400 401 // This is the lone / only 'non-final' field 402 && Objects.equals(this.fileName, ii.fileName); 403 } 404 405 /** 406 * Java's hash-code requirement. The code is computed by summing the first 15 407 * {@link #imgByteArr} array elements. 408 * 409 * @return A hash-code that may be used when storing this node in a java sorted-collection. 410 */ 411 public int hashCode() 412 { 413 if (url != null) return url.toString().hashCode(); 414 415 int sum = 0; 416 417 for (int i=0; (i < 15) && (i < imgByteArr.length); i++) sum += imgByteArr[i]; 418 419 return sum; 420 } 421}