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

SVGImageImpl.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.GZIPSupport;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.svg.SVGElement;
import org.w3c.dom.DOMException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import java.util.Vector;

import com.sun.perseus.util.SVGConstants;

import com.sun.perseus.builder.ModelBuilder;
import com.sun.perseus.builder.SVGTinyModelFactory;

import javax.microedition.m2g.SVGImage;
import javax.microedition.m2g.ScalableImage;
import javax.microedition.m2g.ExternalResourceHandler;

import org.xml.sax.SAXParseException;


import com.sun.perseus.j2d.ImageLoaderUtil;
import com.sun.perseus.j2d.RasterImage;

import com.sun.perseus.model.DocumentNode;
import com.sun.perseus.model.ElementNode;
import com.sun.perseus.model.ModelEvent;
import com.sun.perseus.model.ModelNode;
import com.sun.perseus.model.SVG;
import com.sun.perseus.model.Time;
import com.sun.perseus.model.UpdateAdapter;

/**
 *
 */
public class SVGImageImpl extends SVGImage {
    /**
     * Size of the byte buffer used to read image input streams handed
     * by ExternalResourceHandler implementations.
     */
    static final int DEFAULT_IMAGE_READ_BUFFER_LENGTH = 64;

    /**
    * The image document
    */
    DocumentNode documentNode = null;

    /**
    * The default width for an empty SVG image
    */
    final static public int DEFAULT_WIDTH = 100;

    /**
    * The default height for an empty SVG image
    */
    final static public int DEFAULT_HEIGHT = 100;

    /**
     * The SVGImageLoader
     */
    SVGImageLoader svgImageLoader;

    /**
     * The current SVGElement with the focus
     */
    SVGElement lastElement = null;

    /**
     * URI requested by SVGImageLoader's getImageAndWait().
     */
    String waitURI = new String();

    /**
     * Received null resourceData in requestCompleted().  Used in
     * getImageAndWait() case.
     */
    boolean isBrokenImage = false;

    /**
     * Returns the associated <code>Document</code>.
     *
     * @return the associated <code>Document</code>.
     */
    public Document getDocument() {
        return (Document) documentNode;
    }

    /**
     * Private constructor. Requires a non null DocumentNode.
     *
     * @param documentNode the associated DocumentNode. Should not be null.
     * @param ExternalResourceHandler the associated handler. 
     */
    private SVGImageImpl(final DocumentNode documentNode,
                         final ExternalResourceHandler handler) {
        this.documentNode = documentNode;

        if (handler != null) {
            svgImageLoader = new SVGImageLoader(this, handler);
            documentNode.setImageLoader(svgImageLoader);
        } else {
            documentNode.setImageLoader(new DefaultImageLoader());
        }
    }

    // JAVADOC COMMENT ELIDED
    public static SVGImage createEmptyImage(ExternalResourceHandler handler) {
        DocumentNode documentNode = new DocumentNode();

        SVG svg = new SVG(documentNode);
        svg.setWidth((float) DEFAULT_WIDTH);
        svg.setHeight((float) DEFAULT_HEIGHT);
        documentNode.add(svg);

        documentNode.setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

        SVGImageImpl sii = new SVGImageImpl(documentNode, handler);

        // Add prototypes for the SVG Tiny elements.
        Vector prototypes = SVGTinyModelFactory.getPrototypes(documentNode);
        int n = prototypes.size();
        for (int i = 0; i < n; i++) {
            documentNode.addPrototype((ElementNode) prototypes.elementAt(i));
        }

        // Initialize the timing engine and sample at time zero.
        documentNode.initializeTimingEngine();
        documentNode.sample(new Time(0));
        documentNode.setLoaded(true);

        return sii;
    }

    /**
     * This method is used to dispatch a mouse event of the specified
     * <code>type</code> to the document. The mouse position is given as screen
     * coordinates <code>x, y</code>. The only mouse event type supported is
     * "click". Note that when a "click" event is dispatched, a "DOMActivate" is
     * automatically dispatched by the underlying implementation. If a different
     * type is specified, a DOMException with error code NOT_SUPPORTED_ERR is
     * thrown. In the case, where x, y values are outside the viewport area or
     * no target is available for the x, y coordinates, the event is not
     * dispatched.
     *
     *
     * @param type the type of mouse event.
     * @param x the x location of the mouse/pointer in viewport coordinate
     *        system.
     * @param y the y location of the mouse/pointer in viewport coordinate
     *        system.
     *
     * @throws DOMException with error code NOT_SUPPORTED_ERR: if the event 
     * <code>type</code> is not supported.
     * @throws NullPointerException if <code>type</code> is null.
     * @throws IllegalArgumentException if the x or y values are negative.
     *
     */
    public void dispatchMouseEvent(String type, int x, int y)
        throws NullPointerException, IllegalArgumentException, DOMException {

        if (type == null) {
            throw new NullPointerException();
        }

        if (x < 0 || y < 0) {
            throw new IllegalArgumentException();
        }

        if (!type.equals(SVGConstants.SVG_CLICK_EVENT_TYPE)) {
            throw new DOMException(DOMException.NOT_SUPPORTED_ERR, 
                                   "Event type is NOT click.");
        }

        // CHECK x,y outside the viewport area
        if (x > getViewportWidth() || y > getViewportHeight()) {
            return;
        }

        // Get the target hit by the mouse event
        float[] pt = {x, y};
        ModelNode target = documentNode.nodeHitAt(pt);

        if (target == null) {
            return;
        }

        // Dispatch an activate on the element that was clicked on.
        documentNode.dispatchEvent(documentNode.initEngineEvent(
                SVGConstants.SVG_DOMACTIVATE_EVENT_TYPE, 
                target));


        // Now, dispatch the click event.
        documentNode.dispatchEvent
            (documentNode.initEngineEvent(SVGConstants.SVG_CLICK_EVENT_TYPE, 
                                          target));
    };

    /**
     *
     */
    public void activate() {
        if (lastElement == null) {
            return;
        }

        documentNode.dispatchEvent(documentNode.initEngineEvent(
                SVGConstants.SVG_DOMACTIVATE_EVENT_TYPE, 
                (ModelNode) lastElement));
    };

    /**
     *
     */
    public void focusOn(SVGElement element) throws DOMException {
        if (element == null) {
            if (lastElement != null) {
                // remove current focus
                documentNode.dispatchEvent(documentNode.initEngineEvent(
                        SVGConstants.SVG_DOMFOCUSOUT_EVENT_TYPE, 
                        (ModelNode) lastElement));
                lastElement = null;
            }
            return;
        }

        DocumentNode ownerDocument = ((ModelNode) element).getOwnerDocument();
        if (!ownerDocument.equals(documentNode)) {
            throw new DOMException(DOMException.WRONG_DOCUMENT_ERR, 
                                   "Invalid element.");
        }

        // If this element is different from the previous element focused,
        // dispatch a "DOMFocusOut" event to the previous element.
        if (lastElement != element) {
            if (lastElement != null) {
                documentNode.dispatchEvent(documentNode.initEngineEvent(
                        SVGConstants.SVG_DOMFOCUSOUT_EVENT_TYPE, 
                        (ModelNode) lastElement));
            }

            documentNode.dispatchEvent(documentNode.initEngineEvent(
                    SVGConstants.SVG_DOMFOCUSIN_EVENT_TYPE, 
                    (ModelNode) element));

            lastElement = element;
        }
    };

    /**
     *
     */
    public void incrementTime(final float seconds) {
        if (documentNode.updateQueue == null) {
            // We are not running with an update queue, which means we are 
            // not running withing an SVGAnimator. Therefore, we simply
            // increment time.
            documentNode.incrementTime(seconds);
        } else {
            // The impact of changing the time must be made visible immediately
            // because the document is being displayed in an
            // SVGAnimator. Therefore, we force the document to sample and apply
            // animations so that resulting rendering changes get displayed
            // immediately.

            // We are running in an update queue. Check whether or not we are
            // already in the update thread.
            if (Thread.currentThread() 
                == documentNode.updateQueue.getThread()) {
                // We are already in the right thread. 
                documentNode.incrementTime(seconds);
                documentNode.applyAnimations();
            } else {
                // We are in the wrong thread. Move to the right thread.
                documentNode.safeInvokeAndWait(new Runnable() {
                        public void run() {
                            documentNode.incrementTime(seconds);
                            documentNode.applyAnimations();
                        }
                    });
            }
        }
    }


    // Inherited from ScalableImage...

    /**
     *
     */
    public static ScalableImage createImage
        (InputStream stream, 
         ExternalResourceHandler handler) throws IOException {
        if (stream == null) {
            throw new NullPointerException();
        }

        DocumentNode documentNode = new DocumentNode();

        return loadDocument(documentNode, stream, handler);
    }

    /**
     * Implementation helper.
     *
     * @param documentNode the <code>DocumentNode</code> object into which the 
     *        content of the stream should be loaded.
     * @param is the <code>InputStream</code> to the content to be loaded.
     * @param handler the <code>ExternalResourceHandler</code>. May be null.
     */
    private static ScalableImage loadDocument(
            final DocumentNode documentNode,
            final InputStream is,
            final ExternalResourceHandler handler) 
        throws IOException {
        UpdateAdapter updateAdapter = new UpdateAdapter();
        documentNode.setUpdateListener(updateAdapter);

        SVGImageImpl sii = new SVGImageImpl(documentNode, handler);

        ModelBuilder.loadDocument(is, documentNode);

        if (updateAdapter.hasLoadingFailed()) {
            if (updateAdapter.getLoadingFailedException() != null) {
                throw new IOException
                    (updateAdapter.getLoadingFailedException().getMessage());
            }
            throw new IOException();
        }

        // Now, get image width/height from <svg> element and set it in
        // DocumentNode
        Element root = documentNode.getDocumentElement();
        if (!(root instanceof SVG)) {
            throw new IOException(Messages.formatMessage(
                    Messages.ERROR_NON_SVG_RESOURCE,
                    new String[] {documentNode.getURIBase()}));
        }

        SVG svg = (SVG) root;
        int width = (int) svg.getWidth();
        int height = (int) svg.getHeight();
        documentNode.setSize(width, height);

        // Now, initialize the timing engine and sample at zero.
        documentNode.initializeTimingEngine();
        documentNode.sample(new Time(0));
        return sii;        
    }

    /**
     *
     */
    public static ScalableImage createImage(
            String URL, 
            ExternalResourceHandler handler) throws IOException, 
                                                    SecurityException {
        if (URL == null) {
            throw new NullPointerException();
        }

        DocumentNode documentNode = new DocumentNode();
        documentNode.setDocumentURI(URL);

        InputStream is = null;

        try {
            is = GZIPSupport.openHandleGZIP(URL);
        } catch (IOException ioe) {
            throw new IllegalArgumentException(ioe.getMessage());
        }

        return loadDocument(documentNode, is, handler);
    }

    /**
     * An area where the ScalableImage is rendered is called viewport.
     * If a part of the viewport lays outside of the target clipping
     * rectangle it is clipped. The viewport coordinates are given
     * relative to the target rendering surface.
     *
     */
    public void setViewportWidth(int width) {
        if (width < 0) {
            throw new IllegalArgumentException();
        }

        documentNode.setSize(width, documentNode.getHeight());
    }

    /**
     *
     */
    public void setViewportHeight(int height) {
        if (height < 0) {
            throw new IllegalArgumentException();
        }

        documentNode.setSize(documentNode.getWidth(), height);
    }

    // JAVADOC COMMENT ELIDED
    public int getViewportWidth() {
        return documentNode.getWidth();
    }

    // JAVADOC COMMENT ELIDED
    public int getViewportHeight() {
        return documentNode.getHeight();
    }

    /**
     *
     */
    public void requestCompleted(String uri, 
                                 InputStream resourceData) throws IOException {
        System.err.println(">>>>> requestCompleted : " + uri + " / " 
                            + resourceData);
        if (uri == null) {
            throw new NullPointerException();
        }

        synchronized (this) {
            // set in getImageAndWait()
            boolean isWaitURI = waitURI.equals(uri);

            RasterImage img;

            // null resourceData...
            if (resourceData == null) {
                img = svgImageLoader.getBrokenImage();
                isBrokenImage = true;
            } else {
                // we got a fresh, new image...
                ByteArrayOutputStream bos = new ByteArrayOutputStream(
                        DEFAULT_IMAGE_READ_BUFFER_LENGTH);
                byte[] ib = new byte[DEFAULT_IMAGE_READ_BUFFER_LENGTH];
                int byteRead = -1;
                int totalByteRead = 0;
                while ((byteRead = resourceData.read(ib, 0, ib.length)) != -1) {
                    bos.write(ib, 0, byteRead);
                    totalByteRead += byteRead;
                }
                
                img = svgImageLoader.loaderUtil.createImage(bos.toByteArray());
            }

            // the new image is added to cache...
            svgImageLoader.addToCache(uri, img);

            svgImageLoader.setRasterImageConsumerImage(uri, img);
            
            // request was initiated by getImageAndWait
            if (isWaitURI) {
                notifyAll();
            }
        }
    };


    synchronized RasterImage waitOnRequestCompleted(final String uri) {
        waitURI = uri;

        try {
            while (isBrokenImage == false &&
                   svgImageLoader.getImageFromCache(uri) == null) {
                wait();
            }
        } catch (InterruptedException ie) {
            return svgImageLoader.getBrokenImage();
        }

        waitURI = new String();
        if (isBrokenImage) {
            isBrokenImage = false;
            return svgImageLoader.getBrokenImage();
        } else {
            return svgImageLoader.getImageFromCache(uri);
        }
    }
}