/* * $RCSfile: DefaultImageLoader.java,v $ * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package com.sun.perseus.model; import com.sun.perseus.platform.URLResolver; import java.util.Hashtable; import com.sun.perseus.util.RunnableQueue; import com.sun.perseus.util.RunnableQueue.RunnableHandler; import com.sun.perseus.util.RunnableQueue.RunnableQueueHandler; import com.sun.perseus.j2d.ImageLoaderUtil; import com.sun.perseus.j2d.RasterImage; /** * Default implementation of the <code>ImageLoader</code> interface. * * @version $Id: DefaultImageLoader.java,v 1.4 2006/06/29 10:47:30 ln156897 Exp $ */ public class DefaultImageLoader implements ImageLoader { /** * Simple hashtable used to cache image objects. */ protected Hashtable cache = new Hashtable(); /** * RunnableQueue used to load image asynchronously * @see #getImageLater */ protected static RunnableQueue loadingQueue; /** * ImageLoaderUtil contains helper methods which make this * implementation easier. */ protected ImageLoaderUtil loaderUtil = new ImageLoaderUtil(); /** * Use a single loading queue for the implementation. */ static { loadingQueue = RunnableQueue.createRunnableQueue(null); loadingQueue.resumeExecution(); } /** * Default constructor */ public DefaultImageLoader() { synchronized (loadingQueue) { // Check if the thread associated with the loadingQueue // has been terminated. If so create a new lodingQueue if(loadingQueue.getThread() == null) { loadingQueue = RunnableQueue.createRunnableQueue(null); loadingQueue.resumeExecution(); } } } /** * Returns the image that should be used to represent * an image which is loading. * * @return the image to use to represent a pending loading. */ public RasterImage getLoadingImage() { return loaderUtil.getLoadingImage(); } /** * Returns the image that should be used to represent an * image which could not be loaded. * * @return the image to represent broken uris or content. */ public RasterImage getBrokenImage() { return loaderUtil.getBrokenImage(); } /** * Resolves the input relative and base URI into an absolute URI * which can be used in subsequent calls to needsURI, getImageAndWait * or getImageLater calls. * @param uri the requested URI content. * @param baseURI the base URI. Needed in case uri is relative. * @return the resolved URI that should be requested in follow on * needsURI, getImageAndWait or getImageLater calls or null if * the URI cannot be resolved. */ public String resolveURI(final String uri, final String baseURI) { if (uri == null) { return null; } // Do not load base64 images as we do not want to // store the base64 string in the cache, because it // might be huge. if (loaderUtil.isDataURI(uri)) { return uri; } try { return URLResolver.resolve(baseURI, uri); } catch (IllegalArgumentException iae) { return null; } } /** * Notifies the URILoader that the given uri will be needed. * * @param absoluteURI the requested URI content. */ public void needsURI(final String absoluteURI) { // Do not load base64 images as we do not want to // store the base64 string in the cache, because it // might be huge. // // Correct content should not have the same base64 // string duplicated. Rather, the same image element // can be referenced by a use element. if (!loaderUtil.isDataURI(absoluteURI)) { // Check if the image is currently loaded synchronized (cache) { if (cache.get(absoluteURI) == null) { // Start loading the image now so that it is ready when // we need it for rendering. synchronized (loadingQueue) { loadingQueue.invokeLater (new ImageLoadRunnable(absoluteURI), null); } } } } } /** * Requests the given image. This call blocks until an image is * returned. * * @param uri the requested URI. Should be a resolved URI returned * from an earlier call to <code>needsURI</code>. * @return the image after it has been loaded. If the image could * not be loaded, this returns the same image as returned * by a call to <code>getBrokenImage</code>. */ public RasterImage getImageAndWait(final String uri) { // If we are dealing with a data URI, decode the image // now. Data URIs do not go in the cache. if (loaderUtil.isDataURI(uri)) { return loaderUtil.getEmbededImage(uri); } // We are dealing with a regular URI which requires IO. // The image might already be in the loading queue if // a call to needsURI was made. synchronized (cache) { RasterImage img = (RasterImage) cache.get(uri); if (img != null) { return img; } } // The URI has not been retrieved at all or the // ImageLoadRunnable has not completed yet. We // simply preempt a new ImageLoadRunnable. When that // one complete, we will be sure the image is in // the cache. ImageLoadRunnable loader = new ImageLoadRunnable(uri); try { synchronized (loadingQueue) { loadingQueue.preemptAndWait(loader, null); } } catch (InterruptedException ie) { // We were interrupted while waiting for the image. // Return brokenImage return loaderUtil.getBrokenImage(); } synchronized (cache) { return (RasterImage) cache.get(uri); } } /** * Requests the given image. This call returns immediately and * the image is set on the input <code>ImageNode</code> when the * image becomes available. * * @param uri the requested URI. Should be a resolved URI returned * from an earlier call to <code>needsURI</code>. * @param rasterImageConsumer the <code>ImageNode</code> whose image * member should be set as a result of loading the * image. */ public void getImageLater(final String uri, final RasterImageConsumer imageNode) { // Only load later images which have not been loaded yet // and which are not data URIs. if (loaderUtil.isDataURI(uri)) { imageNode.setImage(loaderUtil.getEmbededImage(uri), uri); return; } RasterImage img = null; synchronized (cache) { img = (RasterImage) cache.get(uri); } if (img != null) { imageNode.setImage(img, uri); return; } ImageLoadRunnable loader = new ImageLoadRunnable(uri, imageNode); synchronized (loadingQueue) { loadingQueue.invokeLater(loader, null); } } /** * Determines whether this ImageLoader can handle relative uri's * * @return true if this ImageLoader can handle relative uri's; * false otherwise. */ public boolean allowsRelativeURI() { return false; } /** * Some ImageLoader implementations may wish to wait until the end of the * Document load to start retrieving resources. This method notifies * the implementation that the DocumentNode completed loading successfully. * * @param doc the DocumentNode which just finised loading. */ public void documentLoaded(final DocumentNode doc) { // Do nothing. The DefaultImageLoader implementation loads image // as soon as they are notified in needsURI calls. } /** * In cases where the ImageLoader may update the images associated to a URI, * RasterImageConsumer interested in updates need to register their interest * throught this method. * * @param absoluteURI the URI the RasterImageConsumer is interested in. * @param imageNode the RasterImageConsumer interested in the URI. */ public void addRasterImageConsumer(final String absoluteURI, final RasterImageConsumer imageNode) { } /** * In cases where the ImageLoader may update the images associated to a URI, * RasterImageConsumer interested in updates need to de-register their * interest throught this method. * * @param absoluteURI the URI the RasterImageConsumer is interested in. * @param imageNode the RasterImageConsumer interested in the URI. */ public void removeRasterImageConsumer(final String absoluteURI, final RasterImageConsumer imageNode) { } /** * Utility method. Used to wait until all pending load operations are * complete. * * @throws InterruptedException if the thread is interrupted while * waiting in this method call. */ public void waitForAll() throws InterruptedException { loadingQueue.invokeAndWait(new Runnable() { public void run() { } }, null); } // ========================================================================= /** * Simple Runnables used to load images asynchronously. */ class ImageLoadRunnable implements Runnable { /** * The uri from which image content is loaded. */ private String uri; /** * The <code>ImageNode</code> for which image content is * loaded. */ private RasterImageConsumer node; /** * Construct with an absolute URI * * @param uri the uri to load */ public ImageLoadRunnable(final String uri) { this.uri = uri; } /** * Construct with an absolute URI and a node on which * the loaded image should be set. * * @param uri the uri to load * @param node the image consumer on which the image should be * set. */ public ImageLoadRunnable(final String uri, final RasterImageConsumer node) { this.uri = uri; this.node = node; } /** * <code>Runnable</code> implementation. Loads the image and * sets the resulting image on the associated <code>ImageNode</code> */ public void run() { RasterImage img = null; synchronized (cache) { img = (RasterImage) cache.get(uri); } if (img == null) { // If the image was not loaded before, load it now // and put it in the cache. img = loaderUtil.getExternalImage(uri); synchronized (cache) { cache.put(uri, img); } } if (node != null) { // Make sure we update the image content in the // update thread, if there is one if (node.getUpdateQueue() != null) { ImageSetter setter = new ImageSetter(img, node, uri); try { node.getUpdateQueue().invokeAndWait (setter, node.getRunnableHandler()); } catch (InterruptedException ie) { // We were interrupted while setting the image. // This means this image loader's loadingQueue thread // has been interrupted... Not much we can do as we // do not know if the setter has been invoked or not. // Just end gracefully. return; } } else { node.setImage(img, uri); } } } } /** * Simple <code>Runnable</code> implementation used to set * an <code>ImageNode</code>'s image in the update thread. */ static class ImageSetter implements Runnable { /** * The image to set */ private RasterImage img; /** * The node on which the image should be set */ private RasterImageConsumer node; /** * The uri from which the image was loaded. */ private String uri; /** * Construct with an image, an image node and uri * * @param img the image to set on the node * @param node the image node to modify * @param uri the uri that was retrieved */ public ImageSetter(final RasterImage img, final RasterImageConsumer node, final String uri) { this.img = img; this.node = node; this.uri = uri; } /** * <code>Runnable</code> implementation. We simply set the * <code>ImageNode</code> image. */ public void run() { node.setImage(img, uri); } } }