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