001package Torello.HTML.Tools.Images; 002 003import Torello.Java.StringParse; 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 /><B CLASS=JDDescLabel>Unitialized at Construction:</B> 248 * <BR />Note that this method will return null if a user attempts to retrieve the file-name 249 * before it has been decided upon and set in this class. This is actually the whole reason 250 * that it cannot be declared final, and therefore cannot be declared public (and requires this 251 * Getter-Method). 252 */ 253 public String fileName() { return fileName; } 254 255 // Package-Private, this is set inside class "ImageScraper" 256 void setFileName(String fileName) { this.fileName = fileName; } 257 258 259 // ******************************************************************************************** 260 // ******************************************************************************************** 261 // interface java.lang.Cloneable 262 // ******************************************************************************************** 263 // ******************************************************************************************** 264 265 266 /** 267 * Generates a <B STYLE='color: red;'>Shallow Copy</B> of {@code 'this'} instance. This means 268 * that the images themselves are not copied - rather only the references to the images are 269 * copied into the clone. 270 * 271 * <BR /><BR />The non-reference, non-instance (primitive-type) fields are all "just copied 272 * like normal" :) 273 * 274 * @return A duplicate instance of this class. 275 */ 276 public ImageInfo clone() 277 { return new ImageInfo(this); } 278 279 // Private Constructor, used only for the 'clone()' method 280 private ImageInfo(ImageInfo r) 281 { 282 // Image-URL (very common) 283 this.url = r.url; 284 285 // Base-64 Image Stuff (rare, but not impossible) 286 this.isB64EncodedImage = r.isB64EncodedImage; 287 this.imageFormatStr = r.imageFormatStr; 288 this.b64EncodedImage = r.b64EncodedImage; 289 290 // The actual downloaded and converted images, themselves 291 this.imgByteArr = r.imgByteArr; 292 this.bufferedImage = r.bufferedImage; 293 294 // Image-Width, Image-Height 295 this.width = r.width; 296 this.height = r.height; 297 298 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 299 this.guessedExtension = r.guessedExtension; 300 this.actualExtension = r.actualExtension; 301 302 // class 'Results' Array-Counters (index-pointers) 303 this.iteratorCounter = r.iteratorCounter; 304 this.successCounter = r.successCounter; 305 306 // This is the lone / only 'non-final' field 307 this.fileName = r.fileName; 308 } 309 310 311 // ******************************************************************************************** 312 // ******************************************************************************************** 313 // java.lang.Object 314 // ******************************************************************************************** 315 // ******************************************************************************************** 316 317 318 /** 319 * Converts this class into a simple, readable {@code String} 320 * @return A {@code java.lang.String} representation of {@code 'this'} instance. 321 */ 322 public String toString() 323 { 324 return 325 ((this.url != null) 326 ? ("URL: " + StrPrint.abbrev(this.url.toString(), 40, true, " ... ", 80)) 327 : "") + 328 329 (this.isB64EncodedImage 330 ? ("B64-Encoded Format: " + this.imageFormatStr + ", IMG: " + 331 StrPrint.abbrev(this.b64EncodedImage, 30, true, " ... ", 60)) 332 : "") + 333 334 '\n' + 335 336 "Byte-Array.length: " + StringParse.commas(this.imgByteArr.length) + '\n' + 337 338 "W: " + StringParse.commas(this.width) + ", " + 339 "H: " + StringParse.commas(this.height) + ", " + 340 "File-Extension: " + Objects.toString(this.actualExtension) + ", " + 341 "URL-Extension: " + Objects.toString(this.guessedExtension) + '\n' + 342 343 "Iterator-Count: " + this.iteratorCounter + ", " + 344 "Downloaded-Count: " + this.successCounter + '\n' + 345 346 "Saving File-Name: " + this.fileName + '\n'; 347 } 348 349 /** 350 * Checks whether {@code 'this'} instance is equal to {@code 'other'}. 351 * 352 * @param other Any Java Object, but only an instance {@code ImageInfo} (or a class that is 353 * assignable to it) could possible generate a {@code TRUE}-return value. 354 * 355 * @return {@code TRUE} If and only if {@code 'other'} is an instance of {@code ImageInfo}, and 356 * if the contents of that are instance are identical to the contents of {@code 'this'} 357 * instance. 358 */ 359 public boolean equals(Object other) 360 { 361 if (other == null) return false; 362 363 if (! ImageInfo.class.isAssignableFrom(other.getClass())) return false; 364 365 ImageInfo ii = (ImageInfo) other; 366 367 // Note that using 'Objects.equals(...)' and 'Objects.deepEquals(...)' primarily prevents 368 // a NullPointerException from being thrown if the left side of an '.equals(...)' were to 369 // be null. It's really nothing more than that. (A small 'Convenience' that makes this 370 // method look less ridiculous than it already does.) 371 // 372 // 'deepEquals(...)' actually checks the entire contents of two array's for equality. 373 374 return 375 376 // Image-URL (very common) 377 Objects.equals(this.url, ii.url) 378 379 // Base-64 Image Stuff (rare, but not impossible) 380 && (this.isB64EncodedImage == ii.isB64EncodedImage) 381 && (Objects.equals(this.imageFormatStr, ii.imageFormatStr)) 382 && (Objects.equals(this.b64EncodedImage, ii.b64EncodedImage)) 383 384 // The actual downloaded and converted images, themselves 385 && Objects.deepEquals(this.imgByteArr, ii.imgByteArr) 386 && Objects.equals(this.bufferedImage, ii.bufferedImage) 387 388 // Image-Width, Image-Height 389 && (this.width == ii.width) 390 && (this.height == ii.height) 391 392 // URL-Aquired Extension & Ultimately-Decided-Upon Extension 393 && Objects.equals(this.guessedExtension, ii.guessedExtension) 394 && Objects.equals(this.actualExtension, ii.actualExtension) 395 396 // class 'Results' Array-Counters (index-pointers) 397 && (this.iteratorCounter == ii.iteratorCounter) 398 && (this.successCounter == ii.successCounter) 399 400 // This is the lone / only 'non-final' field 401 && Objects.equals(this.fileName, ii.fileName); 402 } 403 404 /** 405 * Java's hash-code requirement. The code is computed by summing the first 15 406 * {@link #imgByteArr} array elements. 407 * 408 * @return A hash-code that may be used when storing this node in a java sorted-collection. 409 */ 410 public int hashCode() 411 { 412 if (url != null) return url.toString().hashCode(); 413 414 int sum = 0; 415 416 for (int i=0; (i < 15) && (i < imgByteArr.length); i++) sum += imgByteArr[i]; 417 418 return sum; 419 } 420}