FileDocCategorySizeDatePackage
ModelBuilder.javaAPI DocphoneME MR2 API (J2ME)41348Wed May 02 18:00:36 BST 2007com.sun.perseus.builder

ModelBuilder.java

/*
 *
 *
 * Portions Copyright  2000-2007 Sun Microsystems, Inc. All Rights
 * Reserved.  Use is subject to license terms.
 * 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.
 */

/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package com.sun.perseus.builder;

import com.sun.perseus.platform.ThreadSupport;

import com.sun.perseus.model.CompositeNode;
import com.sun.perseus.model.DocumentNode;
import com.sun.perseus.model.ElementNode;
import com.sun.perseus.model.Font;
import com.sun.perseus.model.FontFace;
import com.sun.perseus.model.ModelNode;
import com.sun.perseus.model.UpdateAdapter;
import com.sun.perseus.model.UpdateListener;
import com.sun.perseus.model.Use;

import com.sun.perseus.util.SimpleTokenizer;

import com.sun.perseus.util.SVGConstants;

import com.sun.perseus.platform.GZIPSupport;

import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import org.w3c.dom.DOMException;

import java.util.Vector;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Reader;

import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;

/**
 * NOTE: need to change currentElement management so that there is only a
 * need to do a getParent() and cast to (ElementNode).
 *
 * <code>ModelBuilder</code> is a SAX2 <code>ContentHandler</code> that
 * builds a <b>Model</b> (i.e. a tree of <code>ModelNode</code>s from 
 * the SAX2 events. <br />
 *
 * This class also offers a static method to synchronously builds a
 * <b>Model</b> given a URI: {@link ModelBuilder#loadDocument loadDocument}.
 *
 * @version $Id: ModelBuilder.java,v 1.10 2006/07/13 00:55:57 st125089 Exp $
 */
public class ModelBuilder extends DefaultHandler {
    /**
     * The default DTD subset used when resolving the DTD entities.
     */
    public static final String DTD_SUBSET =  
        "<!ATTLIST svg\n" +
        "     xmlns CDATA #FIXED \"http://www.w3.org/2000/svg\"\n" +
        "     xmlns:xlink CDATA #FIXED \"http://www.w3.org/1999/xlink\"\n" +
        ">";

    /**
     * The message used in the SAXException when the 
     * loading thread is interrupted
     */
    public static final String LOAD_INTERRUPTED = "Load Interrupted : ";

    /**
     * The accepted DTD public IDs.
     */
    protected static String dtdids = "-//W3C//DTD SVG 1.1 Tiny//EN";

    /**
     * Object keeping track of the current node, i.e., the 
     * last node that was built from an element. 
     */
    protected ElementNode currentElement;

    /**
     * Root of the model tree
     */
    protected DocumentNode modelRoot;

    /**
     * Keeps track of opened streams (entities) so that
     * they can be closed when parsing completes.
     * @see #resolveEntity
     */
    protected Vector entityStreams = new Vector();

    /**
     * Keeps pending namespaceURI to prefix mapping. This is used because
     * the prefix mapping is declared in the startPrefixMapping method
     * _before_ startElement is called. Therefore, the startElement method
     * will use this pendingPrefixMapping vector to declare prefix mappings
     * on the newly created element.
     */
    protected Vector pendingPrefixMapping = null;

    /**
     * Used to allows quick check on pendingPrefixMapping.
     */
    protected Vector pendingPrefixMappingCache = new Vector();

    /**
     * The <code>modelFactory</code> is used to build 
     * <code>ModelNode</code> instances corresponding
     * to individual nodes in the parsed XML documents.
     * This <code>ModelBuilder</code> aggregates the nodes
     * manufactured by the <code>modelFactory</code>
     *
     * @param modelFactoryIn the factory that contains element
     *        prototypes for the supported element types
     * @param modelRootIn the DocumentNode that should be populated 
     *        with the result of the build process.
     */
    ModelBuilder(Vector modelFactoryIn,
                 final DocumentNode modelRootIn) {
        if (modelFactoryIn == null) {
            modelFactoryIn = SVGTinyModelFactory.getPrototypes(modelRootIn);
        }

        this.modelRoot = modelRootIn;

        int n = modelFactoryIn.size();
        for (int i = 0; i < n; i++) {
            modelRoot.addPrototype((ElementNode) modelFactoryIn.elementAt(i));
        }
    }

    /**
     * @return the root of the tree built by this builder.
     * null is returned if no tree was built yet
     */
    final DocumentNode getModelRoot() {
        return modelRoot;
    }

    /**
     * Utility method. Invokes the <code>Runnable</code> on the
     * <code>modelRoot.invokeAndWait</code>.
     *
     * @param runnable the <code>Runnable</code> to run
     * @throws SAXException if the input <code>Runnable</code> is 
     *         interrupted while pending execution or while running.
     */
    protected void invokeAndWait(final Runnable runnable) throws SAXException {
        try {
            if (!ThreadSupport.isInterrupted(Thread.currentThread())) {
                modelRoot.invokeAndWait(runnable);
            } else {
                throw new InterruptedException();
            }
        } catch (InterruptedException ie) {
            throw new SAXException(LOAD_INTERRUPTED
                                   + Thread.currentThread());
        }
    }

    ////////////////////////////////////////////////////////////////////
    // Default implementation of the EntityResolver interface.
    ////////////////////////////////////////////////////////////////////
    /**
     * Resolve an external entity.
     *
     * @param publicId The public identifer, or null if none is
     *                 available.
     * @param systemId The system identifier provided in the XML 
     *                 document.
     * @return The new input source, or null to require the
     *         default behaviour.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.EntityResolver#resolveEntity
     */
    public final InputSource resolveEntity(final String publicId, 
                                           final String systemId) 
        throws SAXException {
        if (publicId == null || dtdids.indexOf(publicId) != -1) {
            // If there is no declared publicId or if the publicId is
            // one of the supported ones (the test is very lose but quick)
            // we assume the input source is an SVG Tiny view and we just
            // process a small DTD subset that includes the default 
            // namespace and xlink namespace declarations. Attribute defaulting
            // is handled by the code, so are default attributes, so there is
            // no need to process the DTDs.
            InputSource is = new InputSource();
            Reader reader = new InputStreamReader(
                    new ByteArrayInputStream(DTD_SUBSET.getBytes()));
            is.setCharacterStream(reader);
            
            // Keep track of opened streams as some SAX 
            // implementations do not close that stream.
            entityStreams.addElement(reader);
            
            return is;
        }
        
        // Let the SAX parser find the entity.
        return null;
    }    

    ////////////////////////////////////////////////////////////////////
    // Default implementation of DTDHandler interface.
    ////////////////////////////////////////////////////////////////////
    
    
    /**
     * Receive notification of a notation declaration.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass if they wish to keep track of the notations
     * declared in a document.</p>
     *
     * @param name The notation name.
     * @param publicId The notation public identifier, or null if not
     *                 available.
     * @param systemId The notation system identifier.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.DTDHandler#notationDecl
     */
    /*
    public void notationDecl(String name, String publicId, String systemId)
            throws SAXException {
        // no op
    } 
    */
    
    
    /**
     * Receive notification of an unparsed entity declaration.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to keep track of the unparsed entities
     * declared in a document.</p>
     *
     * @param name The entity name.
     * @param publicId The entity public identifier, or null if not
     *                 available.
     * @param systemId The entity system identifier.
     * @param notationName The name of the associated notation.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.DTDHandler#unparsedEntityDecl
     */
    /*
    public void unparsedEntityDecl(String name, String publicId,
            String systemId, String notationName) throws SAXException {
        // no op
    }
    */
    
    

    ////////////////////////////////////////////////////////////////////
    // Default implementation of ContentHandler interface.
    ////////////////////////////////////////////////////////////////////
    
    
    /**
     * Receive a Locator object for document events.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass if they wish to store the locator for use
     * with other document events.</p>
     *
     * @param locator A locator for all SAX document events.
     * @see org.xml.sax.ContentHandler#setDocumentLocator
     * @see org.xml.sax.Locator
     */
    public final void setDocumentLocator(final Locator locator) {
        // ctx.setLocator(locator);
    }
    
    
    /**
     * <b>SAX</b>: Implements {@link
     * org.xml.sax.ContentHandler#startDocument() ContentHander.startDocument}.
     */
    public final void startDocument() {
    }
    
    
    /**
     * Receive notification of the end of the document.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the end
     * of a document (such as finalising a tree or closing an output
     * file).</p>
     *
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#endDocument
     */
    public final void endDocument() throws SAXException {
        // Validate the document.
        try {
            modelRoot.validate();
        } catch (DOMException de) {
            de.printStackTrace();
            throw new SAXException(de.getMessage());
        }

        invokeAndWait(new Runnable() {
                public void run() {
                    UpdateListener um = modelRoot.getUpdateListener();
                    modelRoot.setLoaded(true);
                    um.loadComplete(modelRoot);
                }
            });
    }


    /**
     * Receive notification of the start of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the start of
     * each Namespace prefix scope (such as storing the prefix mapping).</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @param uri The Namespace URI mapped to the prefix.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#startPrefixMapping
     */
    public void startPrefixMapping(String prefix, String uri)
        throws SAXException {
        pendingPrefixMappingCache.addElement(new String[] {prefix, uri});
        pendingPrefixMapping = pendingPrefixMappingCache;
    }


    /**
     * Receive notification of the end of a Namespace mapping.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions at the end of
     * each prefix mapping.</p>
     *
     * @param prefix The Namespace prefix being declared.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#endPrefixMapping
     */
    /*
    public void endPrefixMapping(String prefix) throws SAXException {
        // no op
    }
    */
    
    /**
     * Receive notification of the start of an element.
     *
     * @param uri The element's namespace uri
     * @param localName The element's local name, i.e., within the given 
     *        namespace
     * @param qName The element's qualified name, i.e., including the namespace 
     *        prefix
     * @param attributes The specified or defaulted attributes.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#startElement
     */
    public final void startElement(final String uri, 
                                    final String localName,
                                    final String qName, 
                                    final Attributes attributes) 
        throws SAXException {
        // ====================================================================
        // First, build a new element from its XML descriptor.
        // ====================================================================
        ElementNode modelNode = null;
        try {
            modelNode = (ElementNode) modelRoot.createElementNS(uri, localName);
            
            // Handle prefix mappings
            if (pendingPrefixMapping != null) {
                int n = pendingPrefixMapping.size();
                for (int i = 0; i < n; i++) {
                    String[] mapping = (String[]) 
                            pendingPrefixMapping.elementAt(i);
                    modelRoot.addNamespacePrefix(mapping[0], 
                            mapping[1], 
                            modelNode);
                }
                pendingPrefixMapping.removeAllElements();
                pendingPrefixMapping = null;
            }

            // Make sure we mark the node as _not_ loaded.
            // This is important because we use the loaded bit to 
            // control certain behaviors, such as the progressive rendering
            // behavior.
            modelNode.setLoaded(false);

            // ================================================
            // Check that required traits have been specified.
            // ================================================
            String[] requiredTraits = modelNode.getRequiredTraits();
            if (requiredTraits != null) {
                for (int i = 0; i < requiredTraits.length; i++) {
                    if (attributes.getValue(requiredTraits[i]) == null) {
                        throw new SAXException(Messages.formatMessage(
                                Messages.ERROR_MISSING_ATTRIBUTE,
                                new Object[] {
                                    requiredTraits[i],
                                    modelNode.getNamespaceURI(),
                                    modelNode.getLocalName()
                                }));
                    }
                }
            }

            String[][] requiredTraitsNS = modelNode.getRequiredTraitsNS();
            if (requiredTraitsNS != null) {
                for (int i = 0; i < requiredTraitsNS.length; i++) {
                    if (attributes.getValue(requiredTraitsNS[i][0],
                                            requiredTraitsNS[i][1]) == null) {
                        throw new SAXException(Messages.formatMessage(
                                Messages.ERROR_MISSING_ATTRIBUTE_NS,
                                new Object[] {
                                    requiredTraitsNS[i][1],
                                    requiredTraitsNS[i][0],
                                    modelNode.getNamespaceURI(),
                                    modelNode.getLocalName()
                                }));
                    }
                }
            }

            // ================================================
            // End of required traits check
            // ================================================

            // ================================================
            // Apply all specified traits.
            // ================================================
            int n = attributes.getLength();
            for (int i = 0; i < n; i++) {
                modelNode.setAttributeNS(attributes.getURI(i),
                                     attributes.getLocalName(i),
                                     attributes.getValue(i));
            }

            // ================================================
            // Apply default traits if they were not specified.
            // ================================================
            String[][] defaultTraits = modelNode.getDefaultTraits();
            if (defaultTraits != null) {
                for (int i = 0; i < defaultTraits.length; i++) {
                    if (attributes.getValue(defaultTraits[i][0]) == null) {
                        modelNode.setAttribute(defaultTraits[i][0],
                                               defaultTraits[i][1]);
                    }
                }
            }

            // ================================================
            // IMPL NOTE
            //
            // If the style attribute is specified, apply the 
            // traits it specified. The reason for handling the
            // style attribute at the parser level is that it 
            // is not a regular trait. It is an in-line 
            // stylesheet. We only support it in order to 
            // support imports from Adobe Illustrator which
            // uses the style attribute on gradients, even
            // when the option to export without the style 
            // attribute is selected.
            // ================================================
            String styleAttribute = 
                attributes.getValue(SVGConstants.SVG_STYLE_ATTRIBUTE);
            if (styleAttribute != null) {
                parseStyleAttribute(styleAttribute, modelNode);
            }

            // ================================================
            // Apply trait aliases
            // ================================================
            String[][] traitAliases = modelNode.getTraitAliases();
            if (traitAliases != null) {
                for (int i = 0; i < traitAliases.length; i++) {
                    if (attributes.getValue(traitAliases[i][0]) == null) {
                        // The trait with alias was not specified.
                        // Check if its alias was specified.
                        String v = attributes.getValue(traitAliases[i][1]);
                        if (v != null) {
                            modelNode.setAttribute(traitAliases[i][0], v);
                        }
                    }
                }
            }
        } catch (DOMException e) {
            e.printStackTrace();
            throw new SAXException(e.getMessage());
        }

        // ====================================================================
        // Append new element to the tree
        // ====================================================================
        if (currentElement != null) {
            addToParent(modelNode, currentElement);
        } else {
            addToParent(modelNode, modelRoot);
        }
        currentElement = modelNode;

        // ====================================================================
        // Notify application that load has begun on a new element
        // ====================================================================
        final ModelNode startedNode = modelNode;
        final UpdateListener ul = modelRoot.getUpdateListener();
        invokeAndWait(new Runnable() {
                public void run() {
                    ul.loadBegun(startedNode);
                    
                }
            });

        // ====================================================================
        // Check if there were any delayed exception. A delayed exception 
        // allows progressive rendering of bad path to happen before the
        // exception which captured the bad path data is actually thrown (below)
        // ====================================================================
        try { 
            modelRoot.checkDelayedException();
        } catch (DOMException e) {
            throw new SAXException(e.getMessage());
        }
    }

    /**
     * Utility method to parse a trait value.
     *
     * @param styleValue the value of the style attribute to parse.
     * @param elt the ElementNode on which trait values should be set.
     */
    private void parseStyleAttribute(final String styleValue, 
                                     final ElementNode elt) {
        SimpleTokenizer st = new SimpleTokenizer(styleValue, 
                                                 SVGConstants.COMMA_STR);
        while (st.hasMoreTokens()) {
            String traitSpec = st.nextToken();
            int ci = traitSpec.indexOf(':');
            String traitName = null;
            String traitValue = null;
            if (ci != -1) {
                traitName = traitSpec.substring(0, ci);
                traitValue = traitSpec.substring(ci + 1);
            } else {
                traitName = "";
                traitValue = "";
            }
            elt.setAttribute(traitName, traitValue);
        }
    }

    /**
     * Adds the input node to the given parent. If there is no
     * associated <code>RunnableQueue</code>, the child is simply
     * added to the parent in the calling thread. If there is
     * a <code>RunnableQueue</code>, the child is added to the parent 
     * the <code>RunnableQueue</code> thread, by invoking a 
     * <code>Runnable</code> on the queue.
     *
     * @param child node to add to the parent
     * @param parent node to which the child is added.
     *
     * @throws SAXException if the child cannot be added to the parent
     *         because the thread was interrupted or if the thread 
     *         was interrupted to begin with.
     */
    void addToParent(final ElementNode child,
                     final CompositeNode parent) 
        throws SAXException {

        invokeAndWait(new Runnable() {
                public void run() {
                    parent.add(child);
                }
            });

        // This may happen, for example, if the loading thread
        // is interrupted by an update listener.
        if (ThreadSupport.isInterrupted(Thread.currentThread())) {
            throw new SAXException(LOAD_INTERRUPTED 
                                   + Thread.currentThread());
        }
    }

    /**
     * Debug: trace element to console
     *
     * @param uri the element's namespace uri
     * @param localName the element's local name
     * @param qName the element's qualified name
     * @param attributes the element's attributes
     */
    /*
    public final void traceAttributes(final String uri, 
                                      final String localName,
                                      final String qName, 
                                      final Attributes attributes) {
        System.out.println(">>>>> startElement <" + localName 
                           + "> \n\turi = " + uri + "\n\tqName = " + qName);

        int n = attributes.getLength();
        for (int i = 0; i < n; i++) {
            System.out.println("=============>");
            System.out.println(" uri[" + i + "] = " + attributes.getURI(i));
            System.out.println(" name[" + i + "] = local(" 
                               + attributes.getLocalName(i) + ") qname(" 
                               + attributes.getQName(i) + ")");
            System.out.println(" value[" + i + "] = ivalue(" 
                               + attributes.getValue(i) 
                               + ") qvalue(" 
                               + attributes.getValue(attributes.getQName(i)) 
                               + ") urivalue(" 
                               + attributes.getValue(attributes.getLocalName(i),
                                                     attributes.getURI(i)) 
                               + ")");
            System.out.println(attributes.getQName(i) + " = " 
                               + attributes.getValue(i));
            System.out.println("<=============");
        }
    }
    */

    /**
     * Updates the <tt>currentElement</tt>.
     *
     * @param uri The element's namespace uri.
     * @param localName The element's local name
     * @param qName The element's qualified name
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#endElement
     */
    public final void endElement(final String uri, 
                                 final String localName, 
                                 final String qName) throws SAXException {
        //
        // See beginElement: currentElement _cannot_ be null
        // 

        // Update the Font data base if a new FontFace was loaded
        if (currentElement instanceof Font) {
            invokeAndWait(new Runnable() {
                    public void run() {
                        ModelNode fc 
                            = currentElement.getFirstChildNode();
                        if (fc != null && fc instanceof FontFace) {
                            modelRoot.addFontFace
                                ((FontFace) fc);
                        }
                    }
                });
        }
        
        invokeAndWait(new Runnable() {
                public void run() {
                    UpdateListener um = modelRoot.getUpdateListener();
                    currentElement.setLoaded(true);
                    um.loadComplete(currentElement);
                }
            });
        
        // Move up the next content node
        ModelNode parent = currentElement.getParent();
        if (parent instanceof ElementNode) {
            currentElement = (ElementNode) parent;
        } else {
            currentElement = null;
        }
    }
    
    /**
     * Receive notification of character data inside an element.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method to take specific actions for each chunk of character data
     * (such as adding the data to a node or buffer, or printing it to
     * a file).</p>
     *
     * @param ch The characters.
     * @param start The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#characters
     */
    public final void characters(final char[] ch, 
                                 final int start, 
                                 final int length) throws SAXException {
        if (currentElement != null) {
            final String text = new String(ch, start, length);
            final UpdateListener ul = modelRoot.getUpdateListener();
            invokeAndWait(new Runnable() {
                    public void run() {
                        currentElement.appendTextChild(text);
                        ul.textInserted(currentElement);
                    }
                });
        } else {
            System.err.println(">>>>>>>>>>>>>> currentElement is null!!!!!!!");
        }
    }
    
    
    /**
     * Receive notification of ignorable whitespace in element content.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method to take specific actions for each chunk of ignorable
     * whitespace (such as adding data to a node or buffer, or printing
     * it to a file).</p>
     *
     * @param ch The whitespace characters.
     * @param start The start position in the character array.
     * @param length The number of characters to use from the
     *               character array.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#ignorableWhitespace
     */
    /*
    public void ignorableWhitespace(char ch[], int start, int length)
            throws SAXException {
        // no op
    } 
    */
    
    
    /**
     * Receive notification of a processing instruction.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param target The processing instruction target.
     * @param data The processing instruction data, or null if
     *             none is supplied.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    /*
    public void processingInstruction(String target, String data)
            throws SAXException {
        // no op
    }
    */


    /**
     * Receive notification of a skipped entity.
     *
     * <p>By default, do nothing.  Application writers may override this
     * method in a subclass to take specific actions for each
     * processing instruction, such as setting status variables or
     * invoking other methods.</p>
     *
     * @param name The name of the skipped entity.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ContentHandler#processingInstruction
     */
    /*
    public void skippedEntity(String name) throws SAXException {
        // no op
    }
    */
    
    

    ////////////////////////////////////////////////////////////////////
    // Default implementation of the ErrorHandler interface.
    ////////////////////////////////////////////////////////////////////
    
    /**
     * Report a fatal XML parsing error.
     *
     * <p>The default implementation throws a SAXParseException.
     * Application writers may override this method in a subclass if
     * they need to take specific actions for each fatal error (such as
     * collecting all of the errors into a single report): in any case,
     * the application must stop all regular processing when this
     * method is invoked, since the document is no longer reliable, and
     * the parser may no longer report parsing events.</p>
     *
     * @param e The error information encoded as an exception.
     * @exception org.xml.sax.SAXException Any SAX exception, possibly
     *            wrapping another exception.
     * @see org.xml.sax.ErrorHandler#fatalError
     * @see org.xml.sax.SAXParseException
     */
    public final void fatalError(final SAXParseException e)
        throws SAXException {
        throw e;
    }

    // =========================================================================
    // Utility method
    // =========================================================================

    /**
     * Loads an SVG Tiny document from a given URI
     *
     * @param svgURI the URI of the SVG document to load.
     * @return the <code>DocumentNode</code> built from the requested svgURI.
     * @throws IOException if the file cannot be loaded.
     */
    public static DocumentNode loadDocument(final String svgURI) 
        throws IOException {

        InputStream is = GZIPSupport.openHandleGZIP(svgURI);
        DocumentNode doc = new DocumentNode();
        doc.setLoaded(false);
        doc.setDocumentURI(svgURI);

        UpdateAdapter updateAdapter = new UpdateAdapter();
        doc.setUpdateListener(updateAdapter);

        loadDocument(is, doc);

        if (!updateAdapter.loadSuccess()) {
            Exception cause = updateAdapter.getLoadingFailedException();
            if (cause == null) {
                throw new IOException();
            } else {
                throw new IOException(cause.getMessage());
            }
        }

        return doc;
    }

    /**
     * Load an SVG Tiny document at the given input stream.
     *
     * This method uses JAXP to define the SAX parser to use.
     *
     * Any error is reported to the input <code>DocumentNode</code>'s 
     * <code>UpdateListener</code>'s <code>loadFailed</code> method.
     *
     * @param is the <code>InputStream</code> from which the SVG content is
     *        read. This might be GZIPed compressed stream. If the input stream
     *        is null, an input stream is opened from the root's document URI.
     * @param root the documentNode to populate with the document's content
     *
     *
     * @throws IllegalArgumentException if the root's URI is null, 
     *         if root is null or if the root's <code>UpdateListener</code> is 
     *         null
     */
    public static void loadDocument(final InputStream is,
                                    final DocumentNode root) {
        loadDocument(is, root, null);
    }

    /**
     * Load an SVG Tiny document at the given input stream.
     *
     * This method uses JAXP to define the SAX parser to use.
     *
     * Any error is reported to the input <code>DocumentNode</code>'s 
     * <code>UpdateListener</code>'s <code>loadFailed</code> method.
     *
     * @param is the <code>InputStream</code> from which the SVG content is
     *        read. This might be GZIPed compressed stream. If the input stream
     *        is null, an input stream is opened from the root's document URI.
     * @param root the documentNode to populate with the document's content
     * @param modelFactory the <code>ModelFactory</code> used to turn XML 
     *        elements into <code>ModelNode</code> instances.
     *
     * @throws IllegalArgumentException if the root's URI is null, 
     *         if root is null or if the root's <code>UpdateListener</code> is 
     *         null
     */
    public static void loadDocument(InputStream is,
                                    final DocumentNode root,
                                    Vector modelFactory) {

        if (root == null) {
            throw new IllegalArgumentException();
        }

        root.setLoaded(false);

        String svgURI = root.getURIBase();

        if (is == null && svgURI == null) {
            throw new IllegalArgumentException();
        }

        final UpdateListener updateListener = root.getUpdateListener();
        if (updateListener == null) {
            throw new IllegalArgumentException();
        }

        // Before parsing the file, we add a default mapping for the
        // SVG and XLink namespaces.
        root.addNamespacePrefix(SVGConstants.XLINK_PREFIX, 
                                SVGConstants.XLINK_NAMESPACE_URI,
                                root);
        root.addNamespacePrefix("",
                                SVGConstants.SVG_NAMESPACE_URI,
                                root);

        ModelBuilder modelBuilder = null;
        InputStream gzipIS = null;

        try {
            // Get a SAX parser through the JAXP API. The 
            // parser does not do validation and is namespace aware
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // System.err.println(">>>>>>>>>>>>>>>> SAXParserFactory class: " 
            //         + factory.getClass().getName());
            factory.setNamespaceAware(true);
            factory.setValidating(false);
            
            SAXParser parser = null;
            parser = factory.newSAXParser();
            final SAXParser saxParser = parser;
            
            // Check the input stream. If the input stream is not null, we
            // load that stream. Otherwise, we build an input stream from
            // the root's URI.
            if (is == null) {
                is = GZIPSupport.openHandleGZIP(svgURI);
            }

            // The following wraps the input stream, if necessary, to handle
            // GZIP compression.
            gzipIS = GZIPSupport.handleGZIP(is);
            final InputStream fgzipIS = gzipIS;

            root.invokeAndWait(new Runnable() {
                    public void run() {
                        // Parse the document now. Our modelBuilder 
                        // handles the parser's SAX events.
                        updateListener.loadStarting(root, fgzipIS);
                    }
                });
            
            modelBuilder 
                = new ModelBuilder(modelFactory, root);
            
            saxParser.parse(gzipIS, modelBuilder);
        } catch (ParserConfigurationException pce) {
            loadingFailed(updateListener, root, pce);
        } catch (SAXParseException spe) {
            loadingFailed(updateListener, root, spe);
        } catch (SAXException se) {
            loadingFailed(updateListener, root, se);
        } catch (IOException ioe) {
            loadingFailed(updateListener, root, ioe);
        } catch (Exception e) {
            loadingFailed(updateListener, root, e);
        } finally {
            try {
                if (gzipIS != null) {
                    gzipIS.close();
                }
            } catch (IOException ioe) {
                // Don't do anything if we got an exception
                // while trying to close the stream.
            }

            if (modelBuilder != null) {
                int n = modelBuilder.entityStreams.size();
                for (int i = 0; i < n; i++) {
                    Reader r = (Reader) modelBuilder.entityStreams.elementAt(i);
                    try {
                        r.close();
                    } catch (IOException ioe) {
                        // Do nothing: this means the stream was 
                        // closed by the SAX parser.
                    }
                }
            }
        }
    }

    /**
     * Utility method to report an exception to the UpdateListener
     * in the proper thread.
     *
     * @param updateListener the <code>UpdateListener</code> to which 
     *        the error should be reported.
     * @param root the <code>DocumentNode</code> which was being loaded.
     * @param e the <code>Exception</code> which caused the failure.
     */
    protected static void loadingFailed(final UpdateListener updateListener,
                                        final DocumentNode root,
                                        final Exception e) {
        System.err.println(">>>>>>>>>>>>>>>>>>> +++++ Loading failed ...");
        e.printStackTrace();
        try {
            root.invokeAndWait(new Runnable() {
                    public void run() {
                        updateListener.loadingFailed(root, e);
                    }
                });
        } catch (InterruptedException ie) {
            // The current thread was interrupted. Loading Failed will
            // not be reported...
            return;
        }
    }
}