001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.openide.util;
043:
044: import java.awt.Component;
045: import java.awt.HeadlessException;
046: import java.awt.Image;
047: import java.awt.MediaTracker;
048: import java.awt.Toolkit;
049: import java.awt.Transparency;
050: import java.awt.image.BufferedImage;
051: import java.awt.image.ColorModel;
052: import java.awt.image.ImageObserver;
053: import java.io.IOException;
054: import java.lang.ref.SoftReference;
055: import java.util.HashMap;
056: import java.util.HashSet;
057: import java.util.Iterator;
058: import java.util.Map;
059: import java.util.Set;
060: import java.util.logging.Level;
061: import java.util.logging.Logger;
062: import javax.imageio.ImageIO;
063: import javax.imageio.ImageReadParam;
064: import javax.imageio.ImageReader;
065: import javax.imageio.stream.ImageInputStream;
066:
067: /** Registers all loaded images into the AbstractNode, so nothing is loaded twice.
068: *
069: * @author Jaroslav Tulach
070: */
071: final class IconManager extends Object {
072: /** a value that indicates that the icon does not exists */
073: private static final ActiveRef<String> NO_ICON = new ActiveRef<String>(
074: null, null, null);
075:
076: private static final Map<String, ActiveRef<String>> cache = new HashMap<String, ActiveRef<String>>(
077: 128);
078: private static final Map<String, ActiveRef<String>> localizedCache = new HashMap<String, ActiveRef<String>>(
079: 128);
080: private static final Map<CompositeImageKey, ActiveRef<CompositeImageKey>> compositeCache = new HashMap<CompositeImageKey, ActiveRef<CompositeImageKey>>(
081: 128);
082:
083: /** Resource paths for which we have had to strip initial slash.
084: * @see "#20072"
085: */
086: private static final Set<String> extraInitialSlashes = new HashSet<String>();
087: private static volatile Object currentLoader;
088: private static Lookup.Result<ClassLoader> loaderQuery = null;
089: private static boolean noLoaderWarned = false;
090: private static final Component component = new Component() {
091: };
092:
093: private static final MediaTracker tracker = new MediaTracker(
094: component);
095: private static int mediaTrackerID;
096:
097: private static ImageReader PNG_READER;
098: // private static ImageReader GIF_READER;
099:
100: private static final Logger ERR = Logger
101: .getLogger(IconManager.class.getName());
102:
103: static {
104: ImageIO.setUseCache(false);
105: PNG_READER = ImageIO.getImageReadersByMIMEType("image/png")
106: .next();
107: // GIF_READER = ImageIO.getImageReadersByMIMEType("image/gif").next();
108: }
109:
110: /**
111: * Get the class loader from lookup.
112: * Since this is done very frequently, it is wasteful to query lookup each time.
113: * Instead, remember the last result and just listen for changes.
114: */
115: static ClassLoader getLoader() {
116: Object is = currentLoader;
117: if (is instanceof ClassLoader) {
118: return (ClassLoader) is;
119: }
120:
121: currentLoader = Thread.currentThread();
122:
123: if (loaderQuery == null) {
124: loaderQuery = Lookup.getDefault()
125: .lookup(
126: new Lookup.Template<ClassLoader>(
127: ClassLoader.class));
128: loaderQuery.addLookupListener(new LookupListener() {
129: public void resultChanged(LookupEvent ev) {
130: ERR.fine("Loader cleared"); // NOI18N
131: currentLoader = null;
132: }
133: });
134: }
135:
136: Iterator it = loaderQuery.allInstances().iterator();
137: if (it.hasNext()) {
138: ClassLoader toReturn = (ClassLoader) it.next();
139: if (currentLoader == Thread.currentThread()) {
140: currentLoader = toReturn;
141: }
142: ERR.fine("Loader computed: " + currentLoader); // NOI18N
143: return toReturn;
144: } else {
145: if (!noLoaderWarned) {
146: noLoaderWarned = true;
147: ERR.warning("No ClassLoader instance found in "
148: + Lookup.getDefault() // NOI18N
149: );
150: }
151: return null;
152: }
153: }
154:
155: static Image getIcon(String resource, boolean localized) {
156: if (localized) {
157: synchronized (localizedCache) {
158: ActiveRef<String> ref = localizedCache.get(resource);
159: Image img = null;
160:
161: // no icon for this name (already tested)
162: if (ref == NO_ICON) {
163: return null;
164: }
165:
166: if (ref != null) {
167: // then it is SoftRefrence
168: img = ref.get();
169: }
170:
171: // icon found
172: if (img != null) {
173: return img;
174: }
175:
176: // find localized or base image
177: ClassLoader loader = getLoader();
178:
179: // we'll keep the String probably for long time, optimize it
180: resource = new String(resource).intern(); // NOPMD
181:
182: String base;
183: String ext;
184: int idx = resource.lastIndexOf('.');
185:
186: if ((idx != -1) && (idx > resource.lastIndexOf('/'))) {
187: base = resource.substring(0, idx);
188: ext = resource.substring(idx);
189: } else {
190: base = resource;
191: ext = ""; // NOI18N
192: }
193:
194: // #31008. [PENDING] remove in case package cache is precomputed
195: java.net.URL baseurl = (loader != null) ? loader
196: .getResource(resource) // NOPMD
197: : IconManager.class.getClassLoader()
198: .getResource(resource);
199: Iterator<String> it = NbBundle.getLocalizingSuffixes();
200:
201: while (it.hasNext()) {
202: String suffix = it.next();
203: Image i;
204:
205: if (suffix.length() == 0) {
206: i = getIcon(resource, loader, false);
207: } else {
208: i = getIcon(base + suffix + ext, loader, true);
209: }
210:
211: if (i != null) {
212: localizedCache.put(resource,
213: new ActiveRef<String>(i,
214: localizedCache, resource));
215:
216: return i;
217: }
218: }
219:
220: localizedCache.put(resource, NO_ICON);
221:
222: return null;
223: }
224: } else {
225: return getIcon(resource, getLoader(), false);
226: }
227: }
228:
229: /** Finds image for given resource.
230: * @param name name of the resource
231: * @param loader classloader to use for locating it, or null to use classpath
232: * @param localizedQuery whether the name contains some localization suffix
233: * and is not optimized/interned
234: */
235: private static Image getIcon(String name, ClassLoader loader,
236: boolean localizedQuery) {
237: ActiveRef<String> ref = cache.get(name);
238: Image img = null;
239:
240: // no icon for this name (already tested)
241: if (ref == NO_ICON) {
242: return null;
243: }
244:
245: if (ref != null) {
246: img = ref.get();
247: }
248:
249: // icon found
250: if (img != null) {
251: return img;
252: }
253:
254: synchronized (cache) {
255: // again under the lock
256: ref = cache.get(name);
257:
258: // no icon for this name (already tested)
259: if (ref == NO_ICON) {
260: return null;
261: }
262:
263: if (ref != null) {
264: // then it is SoftRefrence
265: img = ref.get();
266: }
267:
268: if (img != null) {
269: // cannot be NO_ICON, since it never disappears from the map.
270: return img;
271: }
272:
273: // path for bug in classloader
274: String n;
275: boolean warn;
276:
277: if (name.startsWith("/")) { // NOI18N
278: warn = true;
279: n = name.substring(1);
280: } else {
281: warn = false;
282: n = name;
283: }
284:
285: // we have to load it
286: java.net.URL url = (loader != null) ? loader.getResource(n)
287: : IconManager.class.getClassLoader().getResource(n);
288:
289: // img = (url == null) ? null : Toolkit.getDefaultToolkit().createImage(url);
290: Image result = null;
291: try {
292: if (url != null) {
293: if (name.endsWith(".png")) {
294: ImageInputStream stream = ImageIO
295: .createImageInputStream(url
296: .openStream());
297: ImageReadParam param = PNG_READER
298: .getDefaultReadParam();
299: try {
300: PNG_READER.setInput(stream, true, true);
301: result = PNG_READER.read(0, param);
302: } catch (IOException ioe1) {
303: ERR.log(Level.INFO, "Image " + name
304: + " is not PNG", ioe1);
305: }
306: stream.close();
307: }
308: /*
309: else if (name.endsWith(".gif")) {
310: ImageInputStream stream = ImageIO.createImageInputStream(url.openStream());
311: ImageReadParam param = GIF_READER.getDefaultReadParam();
312: try {
313: GIF_READER.setInput(stream, true, true);
314: result = GIF_READER.read(0, param);
315: }
316: catch (IOException ioe1) {
317: ERR.log(Level.INFO, "Image "+name+" is not GIF", ioe1);
318: }
319: stream.close();
320: }
321: */
322:
323: if (result == null) {
324: result = ImageIO.read(url);
325: }
326: }
327: } catch (IOException ioe) {
328: ERR.log(Level.WARNING, "Cannot load image", ioe);
329: }
330:
331: if (result != null) {
332: if (warn && extraInitialSlashes.add(name)) {
333: ERR
334: .warning("Initial slashes in Utilities.loadImage deprecated (cf. #20072): "
335: + name); // NOI18N
336: }
337:
338: // Image img2 = toBufferedImage(result);
339:
340: ERR.log(Level.FINE, "loading icon {0} = {1}",
341: new Object[] { n, result });
342: name = new String(name).intern(); // NOPMD
343:
344: cache.put(name, new ActiveRef<String>(result, cache,
345: name));
346:
347: return result;
348: } else { // no icon found
349:
350: if (!localizedQuery) {
351: cache.put(name, NO_ICON);
352: }
353:
354: return null;
355: }
356: }
357: }
358:
359: /**
360: * Method that attempts to find the merged image in the cache first, then
361: * creates the image if it was not found.
362: */
363: static final Image mergeImages(Image im1, Image im2, int x, int y) {
364: CompositeImageKey k = new CompositeImageKey(im1, im2, x, y);
365: Image cached;
366:
367: synchronized (compositeCache) {
368: ActiveRef<CompositeImageKey> r = compositeCache.get(k);
369:
370: if (r != null) {
371: cached = r.get();
372:
373: if (cached != null) {
374: return cached;
375: }
376: }
377:
378: cached = doMergeImages(im1, im2, x, y);
379: compositeCache.put(k, new ActiveRef<CompositeImageKey>(
380: cached, compositeCache, k));
381:
382: return cached;
383: }
384: }
385:
386: /** The method creates a BufferedImage which represents the same Image as the
387: * parameter but consumes less memory.
388: */
389: static final Image toBufferedImage(Image img) {
390: // load the image
391: new javax.swing.ImageIcon(img, "");
392:
393: if (img.getHeight(null) * img.getWidth(null) > 24 * 24) {
394: return img;
395: }
396: java.awt.image.BufferedImage rep = createBufferedImage(img
397: .getWidth(null), img.getHeight(null));
398: java.awt.Graphics g = rep.createGraphics();
399: g.drawImage(img, 0, 0, null);
400: g.dispose();
401: img.flush();
402:
403: return rep;
404: }
405:
406: private static void ensureLoaded(Image image) {
407: if ((Toolkit.getDefaultToolkit()
408: .checkImage(image, -1, -1, null) & (ImageObserver.ALLBITS | ImageObserver.FRAMEBITS)) != 0) {
409: return;
410: }
411:
412: synchronized (tracker) {
413: int id = ++mediaTrackerID;
414:
415: tracker.addImage(image, id);
416:
417: try {
418: tracker.waitForID(id, 0);
419: } catch (InterruptedException e) {
420: System.out.println("INTERRUPTED while loading Image");
421: }
422:
423: assert (tracker.statusID(id, false) == MediaTracker.COMPLETE) : "Image loaded";
424: tracker.removeImage(image, id);
425: }
426: }
427:
428: private static final Image doMergeImages(Image image1,
429: Image image2, int x, int y) {
430: ensureLoaded(image1);
431: ensureLoaded(image2);
432:
433: int w = Math.max(image1.getWidth(null), x
434: + image2.getWidth(null));
435: int h = Math.max(image1.getHeight(null), y
436: + image2.getHeight(null));
437: boolean bitmask = (image1 instanceof Transparency)
438: && ((Transparency) image1).getTransparency() != Transparency.TRANSLUCENT
439: && (image2 instanceof Transparency)
440: && ((Transparency) image2).getTransparency() != Transparency.TRANSLUCENT;
441:
442: ColorModel model = colorModel(bitmask ? Transparency.BITMASK
443: : Transparency.TRANSLUCENT);
444: java.awt.image.BufferedImage buffImage = new java.awt.image.BufferedImage(
445: model, model.createCompatibleWritableRaster(w, h),
446: model.isAlphaPremultiplied(), null);
447:
448: java.awt.Graphics g = buffImage.createGraphics();
449: g.drawImage(image1, 0, 0, null);
450: g.drawImage(image2, x, y, null);
451: g.dispose();
452:
453: return buffImage;
454: }
455:
456: /** Creates BufferedImage with Transparency.TRANSLUCENT */
457: static final java.awt.image.BufferedImage createBufferedImage(
458: int width, int height) {
459: if (Utilities.isMac()) {
460: return new BufferedImage(width, height,
461: BufferedImage.TYPE_INT_ARGB_PRE);
462: }
463:
464: ColorModel model = colorModel(java.awt.Transparency.TRANSLUCENT);
465: java.awt.image.BufferedImage buffImage = new java.awt.image.BufferedImage(
466: model, model.createCompatibleWritableRaster(width,
467: height), model.isAlphaPremultiplied(), null);
468:
469: return buffImage;
470: }
471:
472: static private ColorModel colorModel(int transparency) {
473: ColorModel model;
474: try {
475: model = java.awt.GraphicsEnvironment
476: .getLocalGraphicsEnvironment()
477: .getDefaultScreenDevice().getDefaultConfiguration()
478: .getColorModel(transparency);
479: } catch (HeadlessException he) {
480: model = ColorModel.getRGBdefault();
481: }
482:
483: return model;
484: }
485:
486: /**
487: * Key used for composite images -- it holds image identities
488: */
489: private static class CompositeImageKey {
490: Image baseImage;
491: Image overlayImage;
492: int x;
493: int y;
494:
495: CompositeImageKey(Image base, Image overlay, int x, int y) {
496: this .x = x;
497: this .y = y;
498: this .baseImage = base;
499: this .overlayImage = overlay;
500: }
501:
502: public boolean equals(Object other) {
503: if (!(other instanceof CompositeImageKey)) {
504: return false;
505: }
506:
507: CompositeImageKey k = (CompositeImageKey) other;
508:
509: return (x == k.x) && (y == k.y)
510: && (baseImage == k.baseImage)
511: && (overlayImage == k.overlayImage);
512: }
513:
514: public int hashCode() {
515: int hash = ((x << 3) ^ y) << 4;
516: hash = hash ^ baseImage.hashCode()
517: ^ overlayImage.hashCode();
518:
519: return hash;
520: }
521:
522: public String toString() {
523: return "Composite key for " + baseImage + " + "
524: + overlayImage + " at [" + x + ", " + y + "]"; // NOI18N
525: }
526: }
527:
528: /** Cleaning reference. */
529: private static final class ActiveRef<T> extends
530: SoftReference<Image> implements Runnable {
531: private Map<T, ActiveRef<T>> holder;
532: private T key;
533:
534: public ActiveRef(Image o, Map<T, ActiveRef<T>> holder, T key) {
535: super (o, Utilities.activeReferenceQueue());
536: this .holder = holder;
537: this .key = key;
538: }
539:
540: public void run() {
541: synchronized (holder) {
542: holder.remove(key);
543: }
544: }
545: }
546: // end of ActiveRef
547: }
|