001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2005-2006, Geotools Project Management Committee (PMC)
005: * (C) 2005, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.coverage;
018:
019: // J2SE dependencies
020: import java.lang.ref.Reference;
021: import java.lang.ref.ReferenceQueue;
022: import java.lang.ref.SoftReference;
023: import java.lang.ref.WeakReference;
024: import java.util.HashMap;
025: import java.util.Iterator;
026: import java.util.Map;
027:
028: // OpenGIS dependencies
029: import org.opengis.coverage.Coverage;
030:
031: // Geotools dependencies
032: import org.geotools.resources.Utilities;
033:
034: /**
035: * A cache for {@linkplain Coverage coverage} instances. Call to {@link #reference}
036: * method returns a {@linkplain WeakReference weak} or {@linkplain SoftReference soft}
037: * reference to the specified coverage. If such a reference previously existed, it is
038: * returned. Otherwise a new reference is created.
039: *
040: * @todo This is just a first draft. The goal is to create soft reference, and transform
041: * automatically soft reference into weak ones once some amount of memory usage is
042: * reached. Do not rely on this API; it may change again.
043: *
044: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/CoverageCache.java $
045: * @version $Id: CoverageCache.java 20970 2006-08-11 07:53:22Z jgarnett $
046: * @author Martin Desruisseaux
047: *
048: * @since 2.1
049: */
050: public class CoverageCache {
051: /**
052: * The thread group for all cleaner threads.
053: * Note: this field must be declared first.
054: */
055: private static final ThreadGroup CLEANERS = new ThreadGroup(
056: "CoverageCache cleaners");
057:
058: /**
059: * The default, system-wide cache for {@linkplain Coverage coverages}.
060: */
061: public static final CoverageCache DEFAULT = new CoverageCache();
062:
063: /**
064: * The map of cached coverages.
065: * All {@link CoverageCache} operations must be synchronized on this cache.
066: * By using this cache as a look instead of {@code CoverageCache} itself,
067: * we make sure that the user can't hold a unwanted look that would block
068: * the {@link Cleaner} thread. Furthermore, we want to performs synchronization
069: * without holding any reference to {@code CoverageCache} in the {@code Cleaner}
070: * thread.
071: */
072: private final Map cache = new HashMap();
073:
074: /**
075: * The reference queue for supression of garbage-collected coverages.
076: */
077: private final ReferenceQueue queue = new ReferenceQueue();
078:
079: /**
080: * The cleaner thread.
081: */
082: private final Cleaner cleaner;
083:
084: /**
085: * Creates a new coverage cache.
086: */
087: public CoverageCache() {
088: cleaner = new Cleaner(cache, queue);
089: cleaner.start();
090: }
091:
092: /**
093: * Returns a reference to the specified coverage. If {@linkplain WeakReference weak}
094: * or {@linkplain SoftReference soft} reference already exists for the specified coverage,
095: * then this reference is returned. Otherwise, a {@linkplain WeakReference weak reference}
096: * is created and returned.
097: *
098: * @param coverage The coverage to reference.
099: * @return A weak of a soft reference to the specified coverage.
100: */
101: public Reference reference(final Coverage coverage) {
102: synchronized (cache) {
103: final Ref candidate = new Ref(coverage);
104: Ref ref = (Ref) cache.get(candidate);
105: if (ref == null) {
106: ref = candidate;
107: cache.put(ref, ref);
108: }
109: return ref.getReference(queue);
110: }
111: }
112:
113: /**
114: * Entries in {@link CoverageCache#cache}.
115: */
116: private static final class Ref {
117: /**
118: * The referenced coverage, as a strong reference to a {@link Coverage}
119: * object or a weak or soft {@linkplan Reference reference}.
120: */
121: private Object referent;
122:
123: /**
124: * Constructs a reference to the specified coverage.
125: */
126: public Ref(final Coverage referent) {
127: this .referent = referent;
128: }
129:
130: /**
131: * Returns the referenced coverage.
132: */
133: public Coverage getCoverage() {
134: if (referent instanceof Reference) {
135: return (Coverage) ((Reference) referent).get();
136: }
137: return (Coverage) referent;
138: }
139:
140: /**
141: * Returns the weak reference to the coverage. This method will changes
142: * strong reference into weak one the first time it is invoked.
143: */
144: public Reference getReference(final ReferenceQueue queue) {
145: if (referent instanceof Coverage) {
146: referent = new WeakReference(referent, queue);
147: }
148: return (Reference) referent;
149: }
150:
151: /**
152: * Returns the hash code value for the coverage.
153: */
154: public int hashCode() {
155: return getCoverage().hashCode();
156: }
157:
158: /**
159: * Compares this object with the specified entry for equality.
160: */
161: public boolean equals(final Object object) {
162: return (object instanceof Ref)
163: && Utilities.equals(getCoverage(), ((Ref) object)
164: .getCoverage());
165: }
166: }
167:
168: /**
169: * The thread for removing dead references. Note: because a dead {@link Ref} object
170: * can't anymore returns a valid hash code or have an 'equals' method working as
171: * expected, we have to iterate through all key entries in the map. This is really
172: * not scalable. However, coverages are usually big objects, so this cache typically
173: * contains few of them (usually less than 10).
174: * <br><br>
175: * <strong>NOTE: Do NOT hold any reference to {@link CoverageCache} in this class,</strong>
176: * because we don't want to prevent {@code CoverageCache} to be garbage-collected. This
177: * thread will stops when the underlying {@code CoverageCache} will be garbage-collected.
178: */
179: private static final class Cleaner extends Thread {
180: /**
181: * The map of cached coverages.
182: */
183: private final Map cache;
184:
185: /**
186: * The reference queue for supression of garbage-collected coverages.
187: */
188: private final ReferenceQueue queue;
189:
190: /**
191: * Set to {@code true} if this thread must stop its execution.
192: */
193: volatile boolean stop;
194:
195: /**
196: * Constructs a cleaner thread with hight priority.
197: * Its execution time should be short.
198: */
199: public Cleaner(final Map cache, final ReferenceQueue queue) {
200: super (CLEANERS, "CoverageCache cleaner #"
201: + CLEANERS.activeCount());
202: setPriority(MAX_PRIORITY - 2);
203: setDaemon(true);
204: this .cache = cache;
205: this .queue = queue;
206: }
207:
208: /**
209: * Waits for a reference to be garbage collected and remove it from the map.
210: */
211: public void run() {
212: while (!stop) {
213: final Reference ref;
214: try {
215: ref = queue.remove();
216: } catch (InterruptedException exception) {
217: // Someone doesn't want to let us sleep.
218: // Ignore and go sleep again.
219: continue;
220: }
221: synchronized (cache) {
222: for (final Iterator it = cache.keySet().iterator(); it
223: .hasNext();) {
224: if (it.next() == ref) {
225: it.remove();
226: }
227: }
228: }
229: }
230: }
231: }
232:
233: /**
234: * Cleanup this coverage cache on garbage collection.
235: */
236: protected void finalize() throws Throwable {
237: synchronized (cache) {
238: cleaner.stop = true;
239: cache.clear();
240: }
241: super.finalize();
242: }
243: }
|