001package Torello.HTML.Tools.Images; 002 003import Torello.HTML.*; 004import Torello.Java.*; 005 006import Torello.Java.Additional.Ret2; 007import Torello.JavaDoc.LinkJavaSource; 008 009import java.util.function.*; 010 011import java.io.Serializable; 012import java.io.File; 013 014import java.net.URL; 015import java.net.MalformedURLException; 016 017import java.util.Vector; 018import java.util.Objects; 019import java.util.concurrent.TimeUnit; 020import java.util.regex.Matcher; 021 022 023/** 024 * Holds all relevant configurations and parameters needed to run the primary download-loop of 025 * class {@link ImageScraper} 026 * 027 * <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST> 028 * <EMBED CLASS='external-html' DATA-FILE-ID=REQ_STR_BUILDER1_EX> 029 */ 030@SuppressWarnings("overrides") 031@Torello.JavaDoc.JDHeaderBackgroundImg(EmbedTagFileID="IMAGE_SCRAPER_CLASS") 032public class Request implements Cloneable, Serializable 033{ 034 /** <EMBED CLASS='external-html' DATA-FILE-ID=SVUID> */ 035 public static final long serialVersionUID = 1; 036 037 038 // ******************************************************************************************** 039 // ******************************************************************************************** 040 // MAIN CONSTRUCTORS 041 // ******************************************************************************************** 042 // ******************************************************************************************** 043 044 045 // There are 5 or 6 'static' builder-methods below. The only reason on earth that those are 046 // static-methods rather than constructors is that their parameter lists all use the same 047 // 'Iterable', but with a different Generic-Parameter. If you convert those to Constructors, 048 // you will get that they have the "Same Erasure", and that compiling cannot continue. 049 // 050 // Instead they are methods that have slightly different names, and the Java-Compiler, instead, 051 // shuts up, and stops complaining. 052 053 Request( 054 Vector<URL> source, int size, URL originalPageURL, Vector<String[]> b64Images, 055 Vector<Exception> tagNodeSRCExceptions 056 ) 057 { 058 this.source = source; 059 this.size = size; 060 this.counterPrinter = getPrinter(size); 061 this.originalPageURL = originalPageURL; 062 this.b64Images = b64Images; 063 this.tagNodeSRCExceptions = tagNodeSRCExceptions; 064 } 065 066 Request(Vector<URL> source, int size, URL originalPageURL) 067 { 068 this.source = source; 069 this.size = size; 070 this.counterPrinter = getPrinter(size); 071 this.originalPageURL = originalPageURL; 072 this.b64Images = null; 073 this.tagNodeSRCExceptions = null; 074 } 075 076 // Used by Clone 077 private Request(final Request other) 078 { 079 this.source = other.source; 080 this.size = other.size; 081 this.counterPrinter = other.counterPrinter; 082 this.originalPageURL = other.originalPageURL; 083 this.b64Images = other.b64Images; 084 this.tagNodeSRCExceptions = other.tagNodeSRCExceptions; 085 this.b64Pos = other.b64Pos; 086 this.tnExPos = other.tnExPos; 087 } 088 089 090 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 091 // Small static constructor-helper 092 // *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** 093 094 private static IntFunction<String> getPrinter(int size) 095 { 096 // Now produce the Printer for the Image-Number. All this does is make sure to do an 097 // appropriate zero-padding for the text-output. 098 099 if (size < 10) return (int i) -> "" + i; 100 else if (size < 100) return StrPrint::zeroPad10e2; 101 else if (size < 1000) return StrPrint::zeroPad; 102 else if (size < 10000) return StrPrint::zeroPad10e4; 103 104 // This case seems extremely unlikely and even largely preposterous, but leaving it like 105 // this means I will never have to analyze this crap ever again. Note that the above case 106 // where size is greater than 1,000 seems a little ridiculous. Usually there are under 100 107 // photos on any one HTML Page. 108 109 else 110 { 111 final int power = (int) Math.floor(Math.log10(size)); 112 return (int i) -> StrPrint.zeroPad(i, power); 113 } 114 } 115 116 117 // ******************************************************************************************** 118 // ******************************************************************************************** 119 // Static Constructor-Like Builder Methods (Cannot Use Constructors because of "Erasure") 120 // ******************************************************************************************** 121 // ******************************************************************************************** 122 123 124 /** 125 * Builds an instance of this class from a list of {@code URL's} as {@code String's} 126 * 127 * @param source <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ITER_STR> 128 * 129 * @return A {@code 'Request'} instance. This may be further configured by assigning values to 130 * any / all fields (which will still have their initialized / default-values) 131 * 132 * @throws NullPointerException If any of the {@code String's} in the {@code Iterable} are null 133 * 134 * @throws IllegalArgumentException If any of the {@code URL's} are {@code String's} which 135 * begin with neither {@code 'http://'} nor {@code 'https://'}. Since this method doesn't 136 * accept the parameter {@code 'originalPageURL'}, each and every {@code URL} in the 137 * {@code 'source'} iterable must be a full & complete {@code URL}. 138 * 139 * <BR /><BR />This exception will also throw if there are any {@code URL's} in the 140 * {@code String}-List that cause a {@code MalformedURLException} to throw when constructing an 141 * instance of {@code java.net.URL} from the {@code String}. In these cases, the original 142 * {@code MalformedURLException} will be assigned to the {@code 'cause'}, and may be retrieved 143 * using the exception's {@code getCause()} method. 144 */ 145 @LinkJavaSource(handle="FromStringIterator", name="build", paramCount=1) 146 public static Request buildFromStrIter(Iterable<String> source) 147 { return FromStringIterator.build(source); } 148 149 /** 150 * Builds an instance of this class from a list of {@code URL's} as {@code String's} 151 * 152 * @param source <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ITER_STR> 153 * @param originalPageURL <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ORIG_PG_URL> 154 * 155 * @param skipDontThrowIfBadStr If an exception is thrown when attempting to resolve a 156 * partial-{@code URL}, and this parameter is {@code TRUE}, then that exception is suppressed 157 * and logged, and the builder-loop continues to the next {@code URL}-as-a-{@code String}. 158 * 159 * <BR /><BR />When this parameter is passed {@code FALSE}, unresolvable {@code URL's} will 160 * generate an {@code IllegalArgumentException}-throw. 161 * 162 * <BR /><BR />Note that the presence of a null in the {@code Iterable 'source'} parameter 163 * will always force this method to throw {@code NullPointerException}. 164 * 165 * @return A {@code 'Request'} instance. This may be further configured by assigning values to 166 * any / all fields (which will still have their initialized / default-values) 167 * 168 * @throws NullPointerException If any of the {@code String's} in the {@code Iterable} are null 169 * 170 * @throws IllegalArgumentException This exception will also throw if there are any 171 * {@code URL's} in the {@code String}-List that cause a {@code MalformedURLException} to throw 172 * when constructing an instance of {@code java.net.URL} from the {@code String}. In these 173 * cases, the generated {@code MalformedURLException} will be assigned to the exception's 174 * {@code 'cause'}, and may therefore be retrieved using this exception's {@code getCause()} 175 * method. 176 */ 177 @LinkJavaSource(handle="FromStringIterator", name="build", paramCount=3) 178 public static Request buildFromStrIter 179 (Iterable<String> source, URL originalPageURL, boolean skipDontThrowIfBadStr) 180 { return FromStringIterator.build(source, originalPageURL, skipDontThrowIfBadStr); } 181 182 /** 183 * Builds an instance of this class using the {@code SRC}-Attribute from a list of 184 * {@link TagNode}'s. 185 * 186 * @param source <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ITER_TGND> 187 * 188 * @param originalPageURL <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ORIG_PG_URL> 189 * 190 * @param skipDontThrowIfBadSRCAttr 191 * <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_SKIP_BOOL> 192 * 193 * @return A {@code 'Request'} instance. This may be further configured by assigning values to 194 * any / all fields (which will still have their initialized / default-values) 195 * 196 * @throws NullPointerException If any of the {@link TagNode}'s in the {@code Iterable} are 197 * null 198 * 199 * @throws SRCException If any of the {@link TagNode}'s in the list do not have a {@code 'SRC'} 200 * Attribute, and {@code 'skipDontThrowIfBadSRCAttr'} is {@code FALSE}. 201 * 202 * <BR /><BR />This exception will also throw if there are any {@code URL's} in the 203 * {@link TagNode}-List that cause a {@code MalformedURLException} to throw when constructing 204 * an instance of {@code java.net.URL} (from the {@code TagNode's SRC}-Attribute). In these 205 * cases, the generated {@code MalformedURLException} will be assigned to the exception's 206 * {@code 'cause'}, and may therefore be retrieved using the exception's {@code getCause()} 207 * method. 208 * 209 * <BR /><BR />If {@code 'skipDontThrowIfBadSRCAttr'} is {@code FALSE}, then this 210 * exception will not throw, and a null will be placed in the query-list. 211 */ 212 @LinkJavaSource(handle="FromTagNodeIterator", name="build", paramCount=3) 213 public static Request buildFromTagNodeIter 214 (Iterable<TagNode> source, URL originalPageURL, boolean skipDontThrowIfBadSRCAttr) 215 { return FromTagNodeIterator.build(source, originalPageURL, skipDontThrowIfBadSRCAttr); } 216 217 /** 218 * Builds an instance of this class using the {@code SRC}-Attribute from a list of 219 * {@link TagNode}'s. 220 * 221 * @param source <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ITER_TGND> 222 * 223 * @param skipDontThrowIfBadSRCAttr 224 * <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_SKIP_BOOL> 225 * 226 * @return A {@code 'Request'} instance. This may be further configured by assigning values to 227 * any / all fields (which will still have their initialized / default-values) 228 * 229 * @throws NullPointerException If any of the {@link TagNode}'s in the {@code Iterable} are 230 * null 231 * 232 * @throws SRCException If any of the {@link TagNode}'s in the list do not have a 233 * {@code 'SRC'}-Attribute, and {@code 'skipDontThrowIfBadSRCAttr'} is {@code FALSE}. 234 * 235 * <BR /><BR />This exception will also throw if any of the {@code URL's} assigned to a 236 * {@code 'SRC'}-Attribute are partial-{@code URL's} which do not begin with {@code 'http://'} 237 * (or {@code 'https://'}), and {@code 'skipDontThrowIfBadSRCAttr'} is {@code FALSE}. 238 * 239 * <BR /><BR />Finally, if any of the {@code URL's} inside a {@link TagNode}'s' 240 * {@code 'SRC'}-Attribute cause a {@code MalformedURLException}, that exception will be 241 * assigned to the {@code cause} of a {@link SRCException}, and thrown (unless 242 * {@code 'skipDontThrowIfBadSRCAttr'} is {@code FALSE}). 243 */ 244 @LinkJavaSource(handle="FromTagNodeIterator", name="build", paramCount=2) 245 public static Request buildFromTagNodeIter 246 (Iterable<TagNode> source, boolean skipDontThrowIfBadSRCAttr) 247 { return FromTagNodeIterator.build(source, skipDontThrowIfBadSRCAttr); } 248 249 /** 250 * Builds an instance of this class using a list of <I><B STYLE='color: red;'>already 251 * prepared</B></I> {@code URL's}. 252 * 253 * @param source <EMBED CLASS='external-html' DATA-FILE-ID=REQUEST_ITER_URL> 254 * 255 * @return A {@code 'Request'} instance. This may be further configured by assigning values to 256 * any / all fields (which will still have their initialized / default-values) 257 * 258 * @throws NullPointerException If any of the {@code URL's} in the {@code Iterable} are null 259 */ 260 @LinkJavaSource(handle="FromURLIterator") 261 public static Request buildFromURLIter(Iterable<URL> source) 262 { return FromURLIterator.build(source); } 263 264 265 // ******************************************************************************************** 266 // ******************************************************************************************** 267 // Package-Visible Utility Methods for ImageScraper, Set by the Constructor. 268 // ******************************************************************************************** 269 // ******************************************************************************************** 270 271 272 // Package-Visibility: Used only in class ImageScraper (to retrieve the Iterable) 273 Iterable<URL> source() { return source; } 274 275 // This Vector-Index Counter is used only once - three lines below 276 private int b64Pos = 0; 277 278 // Package-Visibility: Used only in class Imagescraper (to retrieve a B64-Image String-Array) 279 String[] nextB64Image() 280 { 281 // Since the creation/construction of these Vectors is completely controlled, they should 282 // never be a source of NullPointerException. If for some odd reason they are, it is 283 // better to keep a record indicating that "this really shouldn't have happened" 284 // 285 // These are "assert" statements. There is no reason this method should ever be called 286 // if these are null. In the static-builder, if a null-URL is put into the source-vector 287 // then one of these would be called (b64Images and/or tagNodeSRCExceptions). In such 288 // cases, both of these secondary vectors would already have references put into them. 289 // 290 // Since the "ImageScraper" is heavy-user-interaction class, the paranoia is 10x worse. 291 // This sort of helps mitigate it, although it seems completely superfluous and unnecessary 292 293 if (b64Images == null) throw new UnreachableError(); 294 if (b64Pos >= b64Images.size()) throw new UnreachableError(); 295 296 return b64Images.elementAt(b64Pos++); 297 } 298 299 // This Vector-Index Counter is used only once - three lines below 300 private int tnExPos = 0; 301 302 // Package-Visibility: Used only by class ImageScraper 303 Exception nextTNSRCException() 304 { 305 // Since the creation/construction of these Vectors is completely controlled, they should 306 // never be a source of NullPointerException. If for some odd reason they are, it is 307 // better to keep a record indicating that "this really shouldn't have happened" 308 // 309 // These are "assert" statements. There is no reason this method should ever be called 310 // if these are null. In the static-builder, if a null-URL is put into the source-vector 311 // then one of these would be called (b64Images and/or tagNodeSRCExceptions). In such 312 // cases, both of these secondary vectors would already have references put into them. 313 // 314 // Since the "ImageScraper" is heavy-user-interaction class, the paranoia is 10x worse. 315 // This sort of helps mitigate it, although it seems completely superfluous and unnecessary 316 317 if (tagNodeSRCExceptions == null) throw new UnreachableError(); 318 if (tnExPos >= tagNodeSRCExceptions.size()) throw new UnreachableError(); 319 320 return tagNodeSRCExceptions.elementAt(tnExPos++); 321 } 322 323 324 // ******************************************************************************************** 325 // ******************************************************************************************** 326 // Primary Request Fields 327 // ******************************************************************************************** 328 // ******************************************************************************************** 329 330 331 /** {@code URL} from whence this page has been downloaded */ 332 public final URL originalPageURL; 333 334 // The Source Iterable 335 private final Iterable<URL> source; 336 337 /** The number of Image-{@code URL's} identified inside the {@code 'source'} Iterable. */ 338 public final int size; 339 340 341 // This is just a zero-padding printer. It adjusts for the number of elements in the original 342 // input Iterable. If there are, for example, under 100 elements, then the first 10 elements 343 // will be padded with a zero. 344 345 final IntFunction<String> counterPrinter; 346 347 // Any & all Base-64 Images. This is usually empty, so it is initialized to null 348 private final Vector<String[]> b64Images; 349 350 351 // If the user has built from an Iterable<TagNode>, and requested to suppress-exceptions, then 352 // this vector will save those exceptions so that they are ready for the return/result object. 353 354 private final Vector<Exception> tagNodeSRCExceptions; 355 356 357 // ******************************************************************************************** 358 // ******************************************************************************************** 359 // Public-Fields 01: Verbosity & URL-PreProcessor 360 // ******************************************************************************************** 361 // ******************************************************************************************** 362 363 364 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_verbosity>*/ 365 public Verbosity verbosity = Verbosity.Normal; 366 367 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_urlPreProcessor>*/ 368 public Function<URL, URL> urlPreProcessor = null; 369 370 371 // ******************************************************************************************** 372 // ******************************************************************************************** 373 // Public-Fields 02: Location-Decisions for Saving an Image File, or sending to 'imageReceiver' 374 // ******************************************************************************************** 375 // ******************************************************************************************** 376 377 378 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_targetDirectoryRetriever>*/ 379 public Function<ImageInfo, File> targetDirectoryRetriever = null; 380 381 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_imageReceiver>*/ 382 public Consumer<ImageInfo> imageReceiver = null; 383 384 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_targetDirectory>*/ 385 public String targetDirectory = null; 386 387 388 // ******************************************************************************************** 389 // ******************************************************************************************** 390 // Public-Fields 03: File-Name given to an Image File 391 // ******************************************************************************************** 392 // ******************************************************************************************** 393 394 395 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_fileNamePrefix>*/ 396 public String fileNamePrefix = null; 397 398 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_useDefaultCounterForImageFileNames>*/ 399 public boolean useDefaultCounterForImageFileNames = true; 400 401 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_getImageFileSaveName>*/ 402 public Function<ImageInfo, String> getImageFileSaveName = null; 403 404 405 // ******************************************************************************************** 406 // ******************************************************************************************** 407 // Public-Fields 04: BOOLEANS'S: Continuing or Throwing on Failure & Exception 408 // ******************************************************************************************** 409 // ******************************************************************************************** 410 411 412 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnDownloadException>*/ 413 public boolean skipOnDownloadException = false; 414 415 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnB64DecodeException>*/ 416 public boolean skipOnB64DecodeException = false; 417 418 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnTimeOutException>*/ 419 public boolean skipOnTimeOutException = false; 420 421 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnNullImageException>*/ 422 public boolean skipOnNullImageException = false; 423 424 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnImageWritingFail>*/ 425 public boolean skipOnImageWritingFail = false; 426 427 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipOnUserLambdaException>*/ 428 public boolean skipOnUserLambdaException = false; 429 430 431 // ******************************************************************************************** 432 // ******************************************************************************************** 433 // Public-Fields 05: USER-PREDICATE'S & BOOLEAN'S: Which Image Files to Save, and Which to Skip 434 // ******************************************************************************************** 435 // ******************************************************************************************** 436 437 438 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipURL>*/ 439 public Predicate<URL> skipURL = null; 440 441 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_skipBase64EncodedImages> */ 442 public boolean skipBase64EncodedImages = false; 443 444 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_keeperPredicate> */ 445 public Predicate<ImageInfo> keeperPredicate = null; 446 447 448 // ******************************************************************************************** 449 // ******************************************************************************************** 450 // Public-Fields 06: Avoiding Hangs and Locks with a TimeOut 451 // ******************************************************************************************** 452 // ******************************************************************************************** 453 454 455 /** 456 * <EMBED CLASS='external-html' DATA-FILE-ID=REQ_MAX_WAIT_TIME> 457 * @see #MAX_WAIT_TIME_UNIT 458 * @see #maxDownloadWaitTime 459 */ 460 public static final long MAX_WAIT_TIME = 10; 461 462 /** 463 * <EMBED CLASS='external-html' DATA-FILE-ID=REQ_MAX_WAIT_TIME_UNIT> 464 * @see #MAX_WAIT_TIME 465 * @see #waitTimeUnits 466 */ 467 public static final TimeUnit MAX_WAIT_TIME_UNIT = TimeUnit.SECONDS; 468 469 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_maxDownloadWaitTime> */ 470 public long maxDownloadWaitTime = MAX_WAIT_TIME; 471 472 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_waitTimeUnits> */ 473 public TimeUnit waitTimeUnits = MAX_WAIT_TIME_UNIT; 474 475 476 // ******************************************************************************************** 477 // ******************************************************************************************** 478 // Public-Fields 07: USER-AGENT 479 // ******************************************************************************************** 480 // ******************************************************************************************** 481 482 483 /** 484 * <EMBED CLASS='external-html' DATA-FILE-ID=REQ_DEFAULT_USER_AGENT> 485 * @see Scrape#USER_AGENT; 486 */ 487 public static final String DEFAULT_USER_AGENT = Scrape.USER_AGENT; 488 489 /** 490 * <EMBED CLASS='external-html' DATA-FILE-ID=REQ_userAgent> 491 * @see Scrape#DEFAULT_USER_AGENT; 492 */ 493 public String userAgent = DEFAULT_USER_AGENT; 494 495 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_alwaysUseUserAgent> */ 496 public boolean alwaysUseUserAgent = false; 497 498 /** <EMBED CLASS='external-html' DATA-FILE-ID=REQ_retryWithUserAgent> */ 499 public boolean retryWithUserAgent = true; 500 501 502 // ******************************************************************************************** 503 // ******************************************************************************************** 504 // Check for Validity Method 505 // ******************************************************************************************** 506 // ******************************************************************************************** 507 508 509 void CHECK() { RequestValidity.check(this); } 510 511 512 // ******************************************************************************************** 513 // ******************************************************************************************** 514 // TURN ON **ALL** Exception-Skip Methods 515 // ******************************************************************************************** 516 // ******************************************************************************************** 517 518 519 /** This allows a user to quickly / easily set all {@code 'skipOn'} flags in one method call */ 520 public void skipOnAllExceptions() 521 { 522 // exceptions thrown by Java's ImageIO class when downloading and image 523 skipOnDownloadException = 524 525 // if Java's Base-64 Image-Decoder throws an exception. 526 skipOnB64DecodeException = 527 528 // exception that's thrown when the Monitor-Thread has timed-out. 529 skipOnTimeOutException = 530 531 // exception that's thrown when a downloaded image is null. 532 skipOnNullImageException = 533 534 // exceptions thrown when writing an already downloaded image to the file-system. 535 skipOnImageWritingFail = 536 537 // exceptions thrown by any of the User-Provided Lambda-Target / Functional-Interfaces 538 skipOnUserLambdaException = true; 539 } 540 541 542 // ******************************************************************************************** 543 // ******************************************************************************************** 544 // Standard-Java Object Methods 545 // ******************************************************************************************** 546 // ******************************************************************************************** 547 548 549 /** 550 * Converts {@code 'this'} instance into a simple Java-{@code String} 551 * @return A {@code String} where each field has had a 'best efforts' {@code String}-Conversion 552 */ 553 @LinkJavaSource(handle="RequestToString") 554 public String toString() 555 { return RequestToString.toString(this); } 556 557 558 // ******************************************************************************************** 559 // ******************************************************************************************** 560 // Clone & Clone-Constructor 561 // ******************************************************************************************** 562 // ******************************************************************************************** 563 564 565 /** 566 * Builds a clone of {@code 'this'} instance 567 * 568 * @return The copied instance. Note that this is a <B STYLE='color: red;'>shallow</B> clone, 569 * rather than a <B STYLE='color: red;'>deep</B> clone. The references within the returned 570 * instances are <I>the exact same references as are in {@code 'this'} instance</I>. 571 */ 572 @LinkJavaSource(handle="RequestClone") 573 public Request clone() 574 { 575 final Request cloned = new Request(this); 576 RequestClone.copy(this, cloned); 577 return cloned; 578 } 579 580}