1 /* 2 3 * Created on Jun 8, 2007 4 * Created by Paul Gardner 5 * Copyright (C) Azureus Software, Inc, All Rights Reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * You should have received a copy of the GNU General Public License 16 * along with this program; if not, write to the Free Software 17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 18 * 19 */ 20 21 22 package org.gudy.azureus2.core3.util; 23 24 import java.io.File; 25 import java.lang.ref.ReferenceQueue; 26 import java.lang.ref.WeakReference; 27 import java.net.URL; 28 import java.util.*; 29 import java.util.concurrent.locks.ReadWriteLock; 30 import java.util.concurrent.locks.ReentrantReadWriteLock; 31 32 import com.aelitis.azureus.core.util.HashCodeUtils; 33 34 35 public class 36 StringInterner 37 { 38 public static boolean DISABLE_INTERNING = false; 39 40 private static final int SCHEDULED_CLEANUP_INTERVAL = 60*1000; 41 42 private static final boolean TRACE_CLEANUP = false; 43 private static final boolean TRACE_MULTIHITS = false; 44 45 46 private static final int IMMEDIATE_CLEANUP_TRIGGER = 2000; 47 private static final int IMMEDIATE_CLEANUP_GOAL = 1500; 48 private static final int SCHEDULED_CLEANUP_TRIGGER = 1500; 49 private static final int SCHEDULED_CLEANUP_GOAL = 1000; 50 private static final int SCHEDULED_AGING_THRESHOLD = 750; 51 52 private static LightHashSet managedInterningSet = new LightHashSet(800); 53 private static LightHashSet unmanagedInterningSet = new LightHashSet(); 54 private static ReadWriteLock managedSetLock = new ReentrantReadWriteLock(); 55 56 private final static ReferenceQueue managedRefQueue = new ReferenceQueue(); 57 private final static ReferenceQueue unmanagedRefQueue = new ReferenceQueue(); 58 59 private static final String[] COMMON_KEYS = { 60 "src","port","prot","ip","udpport","azver","httpport","downloaded", 61 "Content","Refresh On","path.utf-8","uploaded","completed","persistent","attributes","encoding", 62 "azureus_properties","stats.download.added.time","networks","p1","resume data","dndflags","blocks","resume", 63 "primaryfile","resumecomplete","data","peersources","name.utf-8","valid","torrent filename","parameters", 64 "secrets","timesincedl","tracker_cache","filedownloaded","timesinceul","tracker_peers","trackerclientextensions","GlobalRating", 65 "comment.utf-8","Count","String","stats.counted","Thumbnail","Plugin.<internal>.DDBaseTTTorrent::sha1","type","Title", 66 "displayname","Publisher","Creation Date","Revision Date","Content Hash","flags","stats.download.completed.time","Description", 67 "Progressive","Content Type","QOS Class","DRM","hash","ver","id", 68 "body","seed","eip","rid","iip","dp2","tp","orig", 69 "dp","Quality","private","dht_backup_enable","max.uploads","filelinks","Speed Bps","cdn_properties", 70 "sha1","ed2k","DRM Key","Plugin.aeseedingengine.attributes","initial_seed","dht_backup_requested","ta","size", 71 "DIRECTOR PUBLISH","Plugin.azdirector.ContentMap","dateadded","bytesin","announces","status","bytesout","scrapes", 72 "passive", 73 }; 74 75 private static final ByteArrayHashMap byte_map = new ByteArrayHashMap( COMMON_KEYS.length ); 76 77 static{ 78 try{ 79 for (int i=0;i<COMMON_KEYS.length;i++){ 80 byte_map.put( COMMON_KEYS[i].getBytes(Constants.BYTE_ENCODING), COMMON_KEYS[i] )81 byte_map.put( COMMON_KEYS[i].getBytes(Constants.BYTE_ENCODING), COMMON_KEYS[i] ); managedInterningSet.add(new WeakStringEntry(COMMON_KEYS[i]))82 managedInterningSet.add(new WeakStringEntry(COMMON_KEYS[i])); 83 } 84 }catch( Throwable e ){ 85 86 e.printStackTrace(); 87 } 88 89 // initialisation nightmare - we have to create periodic event async to avoid 90 // circular class loading issues when azureus.config is borkified 91 92 new AEThread2( "asyncify", true ) 93 { 94 public void run()95 run() 96 { 97 SimpleTimer.addPeriodicEvent("StringInterner:cleaner", SCHEDULED_CLEANUP_INTERVAL, new TimerEventPerformer() { 98 public void perform(TimerEvent event) { 99 managedSetLock.writeLock().lock(); 100 try { 101 sanitize(true); 102 } finally { 103 managedSetLock.writeLock().unlock(); 104 } 105 106 107 sanitizeLight(); 108 } 109 }); 110 } start()111 }.start(); 112 } 113 114 // private final static ReferenceQueue queue = new ReferenceQueue(); 115 116 117 public static String intern( byte[] bytes )118 intern( 119 byte[] bytes ) 120 { 121 String res = (String)byte_map.get( bytes ); 122 123 // System.out.println( new String( bytes ) + " -> " + res ); 124 125 return( res ); 126 } 127 128 /** 129 * A generic interning facilty for heavyweight or frequently duplicated 130 * Objects that have a reasonable <code>equals()</code> implementation.<br> 131 * <br> 132 * Important: The objects should have a limited lifespan, the interning set 133 * used by this method is unmanaged, i.e. does not clean out old entries! 134 * Entries without strong references are still removed. 135 * 136 */ internObject(Object toIntern)137 public static Object internObject(Object toIntern) 138 { 139 if ( DISABLE_INTERNING ){ 140 return( toIntern ); 141 } 142 143 if(toIntern == null) 144 return null; 145 146 Object internedItem; 147 148 WeakEntry checkEntry = new WeakEntry(toIntern,unmanagedRefQueue); 149 150 synchronized( unmanagedInterningSet ){ 151 152 WeakEntry internedEntry = (WeakEntry) unmanagedInterningSet.get(checkEntry); 153 154 if (internedEntry == null || (internedItem = (internedEntry.get())) == null) 155 { 156 internedItem = toIntern; 157 if(!unmanagedInterningSet.add(checkEntry)) 158 System.out.println("unexpected modification"); // should not happen 159 } 160 161 sanitizeLight(); 162 } 163 164 // should not happen 165 if(!toIntern.equals(internedItem)) 166 System.err.println("mismatch"); 167 168 return internedItem; 169 } 170 intern(String toIntern)171 public static String intern(String toIntern) { 172 173 if ( DISABLE_INTERNING ){ 174 return( toIntern ); 175 } 176 177 if(toIntern == null) 178 return null; 179 180 String internedString; 181 182 WeakStringEntry checkEntry = new WeakStringEntry(toIntern); 183 184 WeakStringEntry internedEntry = null; 185 boolean hit = false; 186 187 managedSetLock.readLock().lock(); 188 try { 189 190 internedEntry = (WeakStringEntry) managedInterningSet.get(checkEntry); 191 192 if (internedEntry != null && (internedString = internedEntry.getString()) != null) 193 hit = true; 194 else 195 { 196 managedSetLock.readLock().unlock(); 197 managedSetLock.writeLock().lock(); 198 try{ 199 sanitize(false); 200 201 // get again, weakrefs might have expired and been added by another thread concurrently 202 internedEntry = (WeakStringEntry) managedInterningSet.get(checkEntry); 203 204 if (internedEntry != null && (internedString = internedEntry.getString()) != null) 205 hit = true; 206 else { 207 toIntern = new String( toIntern ); // this trims any baggage that might be included in the original string due to char[] sharing for substrings etc 208 checkEntry = new WeakStringEntry( toIntern ); 209 managedInterningSet.add(checkEntry); 210 internedString = toIntern; 211 } 212 }finally{ 213 managedSetLock.readLock().lock(); 214 managedSetLock.writeLock().unlock(); 215 } 216 } 217 } finally { 218 managedSetLock.readLock().unlock(); 219 } 220 221 if(hit) { 222 internedEntry.incHits(); 223 checkEntry.destroy(); 224 if(TRACE_MULTIHITS && internedEntry.hits % 10 == 0) 225 System.out.println("multihit "+internedEntry); 226 } 227 228 229 return internedString; 230 } 231 intern(char[] toIntern)232 public static char[] intern(char[] toIntern) { 233 234 if ( DISABLE_INTERNING ){ 235 return( toIntern ); 236 } 237 238 if(toIntern == null) 239 return null; 240 241 char[] internedCharArray; 242 243 WeakCharArrayEntry checkEntry = new WeakCharArrayEntry(toIntern); 244 245 WeakCharArrayEntry internedEntry = null; 246 boolean hit = false; 247 248 managedSetLock.readLock().lock(); 249 try { 250 251 internedEntry = (WeakCharArrayEntry) managedInterningSet.get(checkEntry); 252 253 if (internedEntry != null && (internedCharArray = internedEntry.getCharArray()) != null) 254 hit = true; 255 else 256 { 257 managedSetLock.readLock().unlock(); 258 managedSetLock.writeLock().lock(); 259 try{ 260 sanitize(false); 261 262 // get again, weakrefs might have expired and been added by another thread concurrently 263 internedEntry = (WeakCharArrayEntry) managedInterningSet.get(checkEntry); 264 265 if (internedEntry != null && (internedCharArray = internedEntry.getCharArray()) != null) 266 hit = true; 267 else { 268 managedInterningSet.add(checkEntry); 269 internedCharArray = toIntern; 270 } 271 }finally{ 272 managedSetLock.readLock().lock(); 273 managedSetLock.writeLock().unlock(); 274 } 275 } 276 } finally { 277 managedSetLock.readLock().unlock(); 278 } 279 280 if(hit) { 281 System.out.println( "hit for " + new String(toIntern )); 282 internedEntry.incHits(); 283 checkEntry.destroy(); 284 if(TRACE_MULTIHITS && internedEntry.hits % 10 == 0) 285 System.out.println("multihit "+internedEntry); 286 } 287 288 289 return internedCharArray; 290 } 291 292 293 294 internBytes(byte[] toIntern)295 public static byte[] internBytes(byte[] toIntern) { 296 297 if ( DISABLE_INTERNING ){ 298 return( toIntern ); 299 } 300 301 if(toIntern == null) 302 return null; 303 304 byte[] internedArray; 305 306 WeakByteArrayEntry checkEntry = new WeakByteArrayEntry(toIntern); 307 308 WeakByteArrayEntry internedEntry = null; 309 boolean hit = false; 310 managedSetLock.readLock().lock(); 311 try 312 { 313 internedEntry = (WeakByteArrayEntry) managedInterningSet.get(checkEntry); 314 if (internedEntry != null && (internedArray = internedEntry.getArray()) != null) 315 hit = true; 316 else 317 { 318 managedSetLock.readLock().unlock(); 319 managedSetLock.writeLock().lock(); 320 try{ 321 sanitize(false); 322 // get again, weakrefs might have expired and been added by another thread concurrently 323 internedEntry = (WeakByteArrayEntry) managedInterningSet.get(checkEntry); 324 if (internedEntry != null && (internedArray = internedEntry.getArray()) != null) 325 hit = true; 326 else 327 { 328 managedInterningSet.add(checkEntry); 329 internedArray = toIntern; 330 } 331 }finally{ 332 managedSetLock.readLock().lock(); 333 managedSetLock.writeLock().unlock(); 334 } 335 } 336 } finally 337 { 338 managedSetLock.readLock().unlock(); 339 } 340 if (hit) 341 { 342 internedEntry.incHits(); 343 checkEntry.destroy(); 344 if (TRACE_MULTIHITS && internedEntry.hits % 10 == 0) 345 System.out.println("multihit " + internedEntry); 346 } 347 348 // should not happen 349 if(!Arrays.equals(toIntern, internedArray)) 350 System.err.println("mismatch"); 351 352 return internedArray; 353 } 354 355 /** 356 * This is based on File.hashCode() and File.equals(), which can return different values for different representations of the same paths. 357 * Thus internFile should be used with canonized Files exclusively 358 */ internFile(File toIntern)359 public static File internFile(File toIntern) { 360 361 if ( DISABLE_INTERNING ){ 362 return( toIntern ); 363 } 364 365 if(toIntern == null) 366 return null; 367 368 File internedFile; 369 370 WeakFileEntry checkEntry = new WeakFileEntry(toIntern); 371 372 WeakFileEntry internedEntry = null; 373 boolean hit = false; 374 managedSetLock.readLock().lock(); 375 try 376 { 377 internedEntry = (WeakFileEntry) managedInterningSet.get(checkEntry); 378 if (internedEntry != null && (internedFile = internedEntry.getFile()) != null) 379 hit = true; 380 else 381 { 382 managedSetLock.readLock().unlock(); 383 managedSetLock.writeLock().lock(); 384 try{ 385 sanitize(false); 386 // get again, weakrefs might have expired and been added by another thread concurrently 387 internedEntry = (WeakFileEntry) managedInterningSet.get(checkEntry); 388 if (internedEntry != null && (internedFile = internedEntry.getFile()) != null) 389 hit = true; 390 else 391 { 392 managedInterningSet.add(checkEntry); 393 internedFile = toIntern; 394 } 395 }finally{ 396 managedSetLock.readLock().lock(); 397 managedSetLock.writeLock().unlock(); 398 } 399 } 400 } finally 401 { 402 managedSetLock.readLock().unlock(); 403 } 404 405 if (hit) 406 { 407 internedEntry.incHits(); 408 checkEntry.destroy(); 409 if (TRACE_MULTIHITS && internedEntry.hits % 10 == 0) 410 System.out.println("multihit " + internedEntry); 411 } 412 413 // should not happen 414 if(!toIntern.equals(internedFile)) 415 System.err.println("mismatch"); 416 417 return internedFile; 418 } 419 internURL(URL toIntern)420 public static URL internURL(URL toIntern) { 421 422 if ( DISABLE_INTERNING ){ 423 return( toIntern ); 424 } 425 426 if(toIntern == null) 427 return null; 428 429 URL internedURL; 430 431 WeakURLEntry checkEntry = new WeakURLEntry(toIntern); 432 433 WeakURLEntry internedEntry = null; 434 boolean hit = false; 435 managedSetLock.readLock().lock(); 436 try 437 { 438 internedEntry = (WeakURLEntry) managedInterningSet.get(checkEntry); 439 if (internedEntry != null && (internedURL = internedEntry.getURL()) != null) 440 hit = true; 441 else 442 { 443 managedSetLock.readLock().unlock(); 444 managedSetLock.writeLock().lock(); 445 try{ 446 sanitize(false); 447 // get again, weakrefs might have expired and been added by another thread concurrently 448 internedEntry = (WeakURLEntry) managedInterningSet.get(checkEntry); 449 if (internedEntry != null && (internedURL = internedEntry.getURL()) != null) 450 hit = true; 451 else 452 { 453 managedInterningSet.add(checkEntry); 454 internedURL = toIntern; 455 } 456 }finally{ 457 managedSetLock.readLock().lock(); 458 managedSetLock.writeLock().unlock(); 459 } 460 } 461 } finally 462 { 463 managedSetLock.readLock().unlock(); 464 } 465 466 if (hit) 467 { 468 internedEntry.incHits(); 469 checkEntry.destroy(); 470 if (TRACE_MULTIHITS && internedEntry.hits % 10 == 0) 471 System.out.println("multihit " + internedEntry); 472 } 473 474 // should not happen 475 if(!toIntern.toExternalForm().equals(internedURL.toExternalForm())) 476 System.err.println("mismatch"); 477 478 return internedURL; 479 } 480 481 482 private final static Comparator savingsComp = new Comparator() 483 { 484 public int compare(Object o1, Object o2) { 485 WeakWeightedEntry w1 = (WeakWeightedEntry) o1; 486 WeakWeightedEntry w2 = (WeakWeightedEntry) o2; 487 return w1.hits * w1.size - w2.hits * w2.size; 488 } 489 }; 490 sanitizeLight()491 private static void sanitizeLight() 492 { 493 synchronized (unmanagedInterningSet) 494 { 495 WeakEntry ref; 496 while((ref = (WeakEntry)(unmanagedRefQueue.poll())) != null) 497 unmanagedInterningSet.remove(ref); 498 499 unmanagedInterningSet.compactify(-1f); 500 } 501 } 502 sanitize(boolean scheduled)503 private static void sanitize(boolean scheduled) 504 { 505 WeakWeightedEntry ref; 506 while ((ref = (WeakWeightedEntry) (managedRefQueue.poll())) != null) 507 { 508 if (!ref.isDestroyed()) 509 { 510 managedInterningSet.remove(ref); 511 if (TRACE_CLEANUP && ref.hits > 30) 512 System.out.println("queue remove:" + ref); 513 } else 514 {// should not happen 515 System.err.println("double removal " + ref); 516 } 517 } 518 int currentSetSize = managedInterningSet.size(); 519 aging: 520 { 521 cleanup: 522 { 523 // unscheduled cleanup/aging only in case of emergency 524 if (currentSetSize < IMMEDIATE_CLEANUP_TRIGGER && !scheduled) 525 break aging; 526 if (TRACE_CLEANUP) 527 System.out.println("Doing cleanup " + currentSetSize); 528 ArrayList remaining = new ArrayList(); 529 // remove objects that aren't shared by multiple holders first (interning is useless) 530 for (Iterator it = managedInterningSet.iterator(); it.hasNext();) 531 { 532 if (managedInterningSet.size() < IMMEDIATE_CLEANUP_GOAL && !scheduled) 533 break aging; 534 WeakWeightedEntry entry = (WeakWeightedEntry) it.next(); 535 if (entry.hits == 0) 536 { 537 if (TRACE_CLEANUP) 538 System.out.println("0-remove: " + entry); 539 it.remove(); 540 } else 541 remaining.add(entry); 542 } 543 currentSetSize = managedInterningSet.size(); 544 if (currentSetSize < SCHEDULED_CLEANUP_TRIGGER && scheduled) 545 break cleanup; 546 if (currentSetSize < IMMEDIATE_CLEANUP_GOAL && !scheduled) 547 break aging; 548 Collections.sort(remaining, savingsComp); 549 // remove those objects that saved the least amount first 550 weightedRemove: for (int i = 0; i < remaining.size(); i++) 551 { 552 currentSetSize = managedInterningSet.size(); 553 if (currentSetSize < SCHEDULED_CLEANUP_GOAL && scheduled) 554 break weightedRemove; 555 if (currentSetSize < IMMEDIATE_CLEANUP_GOAL && !scheduled) 556 break aging; 557 WeakWeightedEntry entry = (WeakWeightedEntry) remaining.get(i); 558 if (TRACE_CLEANUP) 559 System.out.println("weighted remove: " + entry); 560 managedInterningSet.remove(entry); 561 } 562 } 563 currentSetSize = managedInterningSet.size(); 564 if (currentSetSize < SCHEDULED_AGING_THRESHOLD && scheduled) 565 break aging; 566 if (currentSetSize < IMMEDIATE_CLEANUP_GOAL && !scheduled) 567 break aging; 568 for (Iterator it = managedInterningSet.iterator(); it.hasNext();) 569 ((WeakWeightedEntry) it.next()).decHits(); 570 } 571 if (TRACE_CLEANUP && scheduled) 572 { 573 List weightTraceSorted = new ArrayList(managedInterningSet); 574 Collections.sort(weightTraceSorted, savingsComp); 575 System.out.println("Remaining elements after cleanup:"); 576 for (Iterator it = weightTraceSorted.iterator(); it.hasNext();) 577 System.out.println("\t" + it.next()); 578 } 579 if (scheduled) 580 managedInterningSet.compactify(-1f); 581 } 582 583 private static class WeakEntry extends WeakReference { 584 private final int hash; 585 WeakEntry(Object o, ReferenceQueue q, int hash)586 protected WeakEntry(Object o, ReferenceQueue q, int hash) 587 { 588 super(o, q); 589 this.hash = hash; 590 } 591 WeakEntry(Object o, ReferenceQueue q)592 public WeakEntry(Object o, ReferenceQueue q) 593 { 594 super(o, q); 595 this.hash = o.hashCode(); 596 } 597 equals(Object obj)598 public boolean equals(Object obj) { 599 if (this == obj) 600 return true; 601 if (obj instanceof WeakEntry) 602 { 603 Object myObj = get(); 604 Object otherObj = ((WeakEntry) obj).get(); 605 return myObj == null ? false : myObj.equals(otherObj); 606 } 607 return false; 608 } 609 hashCode()610 public final int hashCode() { 611 return hash; 612 } 613 } 614 615 private static abstract class WeakWeightedEntry extends WeakEntry { 616 final short size; 617 short hits; 618 WeakWeightedEntry(Object o, int hash, int size)619 public WeakWeightedEntry(Object o, int hash, int size) 620 { 621 super(o, managedRefQueue,hash); 622 this.size = (short) (size & 0x7FFF); 623 } 624 incHits()625 public void incHits() { 626 if (hits < Short.MAX_VALUE) 627 hits++; 628 } 629 decHits()630 public void decHits() { 631 if (hits > 0) 632 hits--; 633 } 634 toString()635 public String toString() { 636 return this.getClass().getName().replaceAll("^.*\\..\\w+$", "") + " h=" + (int) hits + ";s=" + (int) size; 637 } 638 destroy()639 public void destroy() { 640 hits = -1; 641 } 642 isDestroyed()643 public boolean isDestroyed() { 644 return hits == -1; 645 } 646 } 647 648 private static class WeakByteArrayEntry extends WeakWeightedEntry { WeakByteArrayEntry(byte[] array)649 public WeakByteArrayEntry(byte[] array) 650 { 651 // byte-array object 652 super(array, HashCodeUtils.hashCode(array), array.length + 8); 653 } 654 655 /** 656 * override equals since byte arrays need Arrays.equals 657 */ equals(Object obj)658 public boolean equals(Object obj) { 659 if (this == obj) 660 return true; 661 if (obj instanceof WeakByteArrayEntry) 662 { 663 byte[] myArray = getArray(); 664 byte[] otherArray = ((WeakByteArrayEntry) obj).getArray(); 665 return myArray == null ? false : Arrays.equals(myArray, otherArray); 666 } 667 return false; 668 } 669 getArray()670 public byte[] getArray() { 671 return (byte[]) get(); 672 } 673 toString()674 public String toString() { 675 return super.toString() + " " + (getArray() == null ? "null" : new String(getArray())); 676 } 677 } 678 679 private static class WeakCharArrayEntry extends WeakWeightedEntry { WeakCharArrayEntry(char[] array)680 public WeakCharArrayEntry(char[] array) 681 { 682 // byte-array object 683 super(array, HashCodeUtils.hashCode(array), array.length + 8); 684 } 685 686 /** 687 * override equals since byte arrays need Arrays.equals 688 */ equals(Object obj)689 public boolean equals(Object obj) { 690 if (this == obj) 691 return true; 692 if (obj instanceof WeakCharArrayEntry) 693 { 694 char[] myArray = getCharArray(); 695 char[] otherArray = ((WeakCharArrayEntry) obj).getCharArray(); 696 return myArray == null ? false : Arrays.equals(myArray, otherArray); 697 } 698 return false; 699 } 700 getCharArray()701 public char[] getCharArray() { 702 return (char[]) get(); 703 } 704 toString()705 public String toString() { 706 return super.toString() + " " + (getCharArray() == null ? "null" : new String(getCharArray())); 707 } 708 } 709 710 private static class WeakStringEntry extends WeakWeightedEntry { WeakStringEntry(String entry)711 public WeakStringEntry(String entry) 712 { 713 // string object with 2 fields, char-array object 714 super(entry, entry.hashCode(), 16 + 8 + entry.length() * 2); 715 } 716 getString()717 public String getString() { 718 return (String) get(); 719 } 720 toString()721 public String toString() { 722 return super.toString() + " " + getString(); 723 } 724 } 725 726 private static class WeakFileEntry extends WeakWeightedEntry { WeakFileEntry(File entry)727 public WeakFileEntry(File entry) 728 { 729 // file object with 2 fields, string object with 2 fields, char-array object 730 super(entry, entry.hashCode(), 16 + 16 + 8 + entry.getPath().length() * 2); 731 } 732 getFile()733 public File getFile() { 734 return (File) get(); 735 } 736 toString()737 public String toString() { 738 return super.toString() + " " + getFile(); 739 } 740 } 741 742 private static class WeakURLEntry extends WeakWeightedEntry { WeakURLEntry(URL entry)743 public WeakURLEntry(URL entry) 744 { 745 // url object with 12 fields, ~4 string objects with 2 fields, 1 shared char-array object 746 // use URL.toExternalForm().hashCode since URL.hashCode tries to resolve hostnames :( 747 super(entry, entry.toExternalForm().hashCode(), 13 * 8 + 4 * 16 + 8 + entry.toString().length() * 2); 748 } 749 getURL()750 public URL getURL() { 751 return (URL) get(); 752 } 753 754 /** 755 * override equals since byte arrays need Arrays.equals 756 */ equals(Object obj)757 public boolean equals(Object obj) { 758 if (this == obj) 759 return true; 760 if (obj instanceof WeakURLEntry) 761 { 762 URL my = getURL(); 763 URL other = ((WeakURLEntry) obj).getURL(); 764 765 if ( my == other ){ 766 return( true ); 767 } 768 if ( my == null || other == null ){ 769 return( false ); 770 } 771 // use string compare as URL.equals tries to resolve hostnames 772 return my.toExternalForm().equals(other.toExternalForm()); 773 } 774 return false; 775 } 776 toString()777 public String toString() { 778 return super.toString() + " " + getURL(); 779 } 780 } 781 } 782