/* * 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 java.util.Enumeration; import java.util.Vector; import javax.microedition.m2g.ExternalResourceHandler; import com.sun.perseus.j2d.ImageLoaderUtil; import com.sun.perseus.j2d.RasterImage; /** * JSR 226 implementation of the <code>ImageLoader</code> interface. * */ public class SVGImageLoader extends DefaultImageLoader { /** * Constant used to indicate that an image has been requested to the * ExternalResourceHandler but that the handler has not delivered the * result yet. */ protected final static String IMAGE_REQUESTED = "Image Requested."; /** * Handles any external resource referenced in the image document. */ protected ExternalResourceHandler handler; /** * The SVGImage associated to this SVGImageLoader */ protected SVGImageImpl svgImage; /** * Simple hashtable used to track ImageNode objects. */ protected Hashtable rasterImageConsumerTable = new Hashtable(); /** * ImageLoaderUtil contains helper methods which make this * implementation easier. */ protected ImageLoaderUtil loaderUtil = new ImageLoaderUtil(); /** * Set to true once the associated DocumentNode has fully loaded. */ protected boolean documentLoaded; /** * Keeps track of pending absoluteURI requests. */ protected Vector pendingNeedsURI = new Vector(); /** * Constructor * * @param svgImage the associated SVGImage, should not be null. * @param handler the ExternalResourceHandler which will get the images * data. */ public SVGImageLoader(SVGImageImpl svgImage, ExternalResourceHandler handler) { if (svgImage == null || handler == null) { throw new NullPointerException(); } this.svgImage = svgImage; this.handler = handler; } /** * Notifies the URILoader that the given uri will be needed. * * @param absoluteURI the requested URI content. */ public void needsURI(final String absoluteURI) { // SVGImageLoader waits until the document has loaded before actually // requesting any image from the ResourceHandler. if (documentLoaded) { needsURIDocumentLoaded(absoluteURI); } else { if (!loaderUtil.isDataURI(absoluteURI) && !pendingNeedsURI.contains(absoluteURI)) { // Cache the request for future use (see documentLoaded method) pendingNeedsURI.addElement(absoluteURI); } } } /** * In SVGImageLoader, we wait until the document has been loaded before * acting on required raster images. * * @param absoluteURI the requested URI content. */ protected void needsURIDocumentLoaded(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)) { boolean isRequested = true; synchronized (cache) { // First check if the image is already available (implies that // images has been requested and obtained successfully) Object imgObj = cache.get(absoluteURI); if (null == imgObj) { // The image has not been requested yet isRequested = false; addToCache(absoluteURI, IMAGE_REQUESTED); } else if (IMAGE_REQUESTED != imgObj) { // The image has been requested and retrieved setRasterImageConsumerImage(absoluteURI, (RasterImage) imgObj); return; } } // requestResource() called outside of synchronized block // so that the cache is not unnecessarily locked. handler is an // external implementation and the behavior is unpredictable. if (!isRequested) { System.err.println("handler.requestResource: " + absoluteURI); handler.requestResource(svgImage, absoluteURI); } } } /** * Requests the given image. This call blocks until an image is * returned. * * @param uri the requested URI. * @return the loaded image or the same image as returned by * a <code>getBrokenImage</code> call if the image could * not be loaded. */ 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); } Object img; // We are dealing with a regular URI which requires IO. // The image might already be in the loading queue from // a call in needsURI. synchronized (cache) { img = cache.get(uri); } if ((img != null) && (img != IMAGE_REQUESTED)) { return (RasterImage) img; } // Make the sure the image is requested if (img == null) { needsURI(uri); } // The URI has not been retrieved yet... img = ((SVGImageImpl) svgImage).waitOnRequestCompleted(uri); return (RasterImage) img; } /** * 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. * @param rasterImageConsumer the <code>RasterImageConsumer</code> whose * image member should be set as a result of loading the image. */ public void getImageLater(final String uri, final RasterImageConsumer rasterImageConsumer) { // Only load later images which have not been loaded yet // and which are not data URIs. if (loaderUtil.isDataURI(uri)) { rasterImageConsumer.setImage(loaderUtil.getEmbededImage(uri), uri); return; } Object img = null; synchronized (cache) { img = cache.get(uri); } if ((img != null) && (img != IMAGE_REQUESTED)) { rasterImageConsumer.setImage((RasterImage) img, uri); return; } // Save ImageNode associated with the uri for use with SVGImage's // requestCompleted(). addRasterImageConsumer(uri, rasterImageConsumer); } /** * 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 true; } /** * 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) { // Place a needsURIDocumentLoaded call for each entry in the // pendingNeedsURI vector. See the needsURI method. documentLoaded = true; int n = pendingNeedsURI.size(); for (int i = 0; i < n; i++) { needsURIDocumentLoaded((String) pendingNeedsURI.elementAt(i)); } // Empty pending requests pendingNeedsURI.removeAllElements(); } /** * 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) { if (absoluteURI == null) { return; } synchronized (rasterImageConsumerTable) { Vector v = (Vector) rasterImageConsumerTable.get(absoluteURI); if (v == null) { v = new Vector(1); v.addElement(imageNode); rasterImageConsumerTable.put(absoluteURI, v); } else { if (!v.contains(imageNode)) { v.addElement(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) { if (absoluteURI != null) { synchronized (rasterImageConsumerTable) { Vector v = (Vector) rasterImageConsumerTable.get(absoluteURI); if (v != null) { v.removeElement(imageNode); } } } } /** * Returns the Image that was previously loaded. * * @param uri the key for the Image cache. * @return the Image associated with the key. */ public RasterImage getImageFromCache(final String uri) { Object img; synchronized (cache) { img = cache.get(uri); if (IMAGE_REQUESTED == img) { img = null; } } return (RasterImage) img; } /** * Adds image to the Image cache. * * @param uri the key for the Image cache. * @param image the Image to store. */ void addToCache(final String uri, final Object image) { synchronized (cache) { cache.put(uri, image); } } /** * Implementation helper. * * @param uri the uri identifying the image resource. * @param image the RasterImage to send to the consumers. */ void setRasterImageConsumerImage(final String uri, final RasterImage image) { ((DocumentNode) svgImage.getDocument()).safeInvokeAndWait( new Runnable() { public void run() { synchronized (rasterImageConsumerTable) { Vector v = (Vector) rasterImageConsumerTable.get(uri); if (v != null) { int n = v.size(); for (int i = 0; i < n; i++) { final RasterImageConsumer in = (RasterImageConsumer) v.elementAt(i); in.setImage(image, uri); } } } } }); } }