FileDocCategorySizeDatePackage
DefaultImageLoader.javaAPI DocphoneME MR2 API (J2ME)14173Wed May 02 18:00:36 BST 2007com.sun.perseus.model

DefaultImageLoader.java

/*
 *
 *
 * Copyright  1990-2007 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() {
    }

    /**
     * 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.
                    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 {
            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);
        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);
        }
    }
}