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

ModelBuilder

public class ModelBuilder extends DefaultHandler
NOTE: need to change currentElement management so that there is only a need to do a getParent() and cast to (ElementNode). ModelBuilder is a SAX2 ContentHandler that builds a Model (i.e. a tree of ModelNodes from the SAX2 events.
This class also offers a static method to synchronously builds a Model given a URI: {@link ModelBuilder#loadDocument loadDocument}.
version
$Id: ModelBuilder.java,v 1.10 2006/07/13 00:55:57 st125089 Exp $

Fields Summary
public static final String
DTD_SUBSET
The default DTD subset used when resolving the DTD entities.
public static final String
LOAD_INTERRUPTED
The message used in the SAXException when the loading thread is interrupted
protected static String
dtdids
The accepted DTD public IDs.
protected com.sun.perseus.model.ElementNode
currentElement
Object keeping track of the current node, i.e., the last node that was built from an element.
protected com.sun.perseus.model.DocumentNode
modelRoot
Root of the model tree
protected Vector
entityStreams
Keeps track of opened streams (entities) so that they can be closed when parsing completes.
protected Vector
pendingPrefixMapping
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
pendingPrefixMappingCache
Used to allows quick check on pendingPrefixMapping.
Constructors Summary
ModelBuilder(Vector modelFactoryIn, com.sun.perseus.model.DocumentNode modelRootIn)
The modelFactory is used to build ModelNode instances corresponding to individual nodes in the parsed XML documents. This ModelBuilder aggregates the nodes manufactured by the modelFactory

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.


                                                                               
     
                    
        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));
        }
    
Methods Summary
voidaddToParent(com.sun.perseus.model.ElementNode child, com.sun.perseus.model.CompositeNode parent)
Adds the input node to the given parent. If there is no associated RunnableQueue, the child is simply added to the parent in the calling thread. If there is a RunnableQueue, the child is added to the parent the RunnableQueue thread, by invoking a Runnable 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.


        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());
        }
    
public final voidcharacters(char[] ch, int start, int length)
Receive notification of character data inside an element.

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).

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

        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!!!!!!!");
        }
    
public final voidendDocument()
Receive notification of the end of the document.

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).

exception
org.xml.sax.SAXException Any SAX exception, possibly wrapping another exception.
see
org.xml.sax.ContentHandler#endDocument

        // 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);
                }
            });
    
public final voidendElement(java.lang.String uri, java.lang.String localName, java.lang.String qName)
Updates the currentElement.

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

        //
        // 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;
        }
    
public final voidfatalError(org.xml.sax.SAXParseException e)
Report a fatal XML parsing error.

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.

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

        throw e;
    
final com.sun.perseus.model.DocumentNodegetModelRoot()

return
the root of the tree built by this builder. null is returned if no tree was built yet

        return modelRoot;
    
protected voidinvokeAndWait(java.lang.Runnable runnable)
Utility method. Invokes the Runnable on the modelRoot.invokeAndWait.

param
runnable the Runnable to run
throws
SAXException if the input Runnable is interrupted while pending execution or while running.

        try {
            if (!ThreadSupport.isInterrupted(Thread.currentThread())) {
                modelRoot.invokeAndWait(runnable);
            } else {
                throw new InterruptedException();
            }
        } catch (InterruptedException ie) {
            throw new SAXException(LOAD_INTERRUPTED
                                   + Thread.currentThread());
        }
    
public static com.sun.perseus.model.DocumentNodeloadDocument(java.lang.String svgURI)
Loads an SVG Tiny document from a given URI

param
svgURI the URI of the SVG document to load.
return
the DocumentNode built from the requested svgURI.
throws
IOException if the file cannot be loaded.


        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;
    
public static voidloadDocument(java.io.InputStream is, com.sun.perseus.model.DocumentNode root)
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 DocumentNode's UpdateListener's loadFailed method.

param
is the InputStream 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 UpdateListener is null

        loadDocument(is, root, null);
    
public static voidloadDocument(java.io.InputStream is, com.sun.perseus.model.DocumentNode root, java.util.Vector modelFactory)
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 DocumentNode's UpdateListener's loadFailed method.

param
is the InputStream 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 ModelFactory used to turn XML elements into ModelNode instances.
throws
IllegalArgumentException if the root's URI is null, if root is null or if the root's UpdateListener is null


        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.
                    }
                }
            }
        }
    
protected static voidloadingFailed(com.sun.perseus.model.UpdateListener updateListener, com.sun.perseus.model.DocumentNode root, java.lang.Exception e)
Utility method to report an exception to the UpdateListener in the proper thread.

param
updateListener the UpdateListener to which the error should be reported.
param
root the DocumentNode which was being loaded.
param
e the Exception which caused the failure.

        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;
        }
    
private voidparseStyleAttribute(java.lang.String styleValue, com.sun.perseus.model.ElementNode elt)
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.

        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);
        }
    
public final org.xml.sax.InputSourceresolveEntity(java.lang.String publicId, java.lang.String systemId)
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

        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;
    
public final voidsetDocumentLocator(org.xml.sax.Locator locator)
Receive a Locator object for document events.

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.

param
locator A locator for all SAX document events.
see
org.xml.sax.ContentHandler#setDocumentLocator
see
org.xml.sax.Locator

        // ctx.setLocator(locator);
    
public final voidstartDocument()
SAX: Implements {@link org.xml.sax.ContentHandler#startDocument() ContentHander.startDocument}.

    
public final voidstartElement(java.lang.String uri, java.lang.String localName, java.lang.String qName, org.xml.sax.Attributes attributes)
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

        // ====================================================================
        // 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());
        }
    
public voidstartPrefixMapping(java.lang.String prefix, java.lang.String uri)
Receive notification of the start of a Namespace mapping.

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).

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

        pendingPrefixMappingCache.addElement(new String[] {prefix, uri});
        pendingPrefixMapping = pendingPrefixMappingCache;