All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jibx.schema.codegen.SchemaDocumentationGenerator Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2011, Dennis M. Sosnoski. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 * disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of
 * JiBX nor the names of its contributors may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jibx.schema.codegen;

import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.log4j.Logger;
import org.jibx.runtime.BindingDirectory;
import org.jibx.runtime.IBindingFactory;
import org.jibx.runtime.IMarshaller;
import org.jibx.runtime.IXMLWriter;
import org.jibx.runtime.JiBXException;
import org.jibx.runtime.QName;
import org.jibx.runtime.impl.MarshallingContext;
import org.jibx.schema.INamed;
import org.jibx.schema.IReference;
import org.jibx.schema.SchemaContextTracker;
import org.jibx.schema.SchemaVisitor;
import org.jibx.schema.TreeWalker;
import org.jibx.schema.codegen.custom.ComponentExtension;
import org.jibx.schema.elements.AnnotatedBase;
import org.jibx.schema.elements.AnnotationElement;
import org.jibx.schema.elements.AttributeElement;
import org.jibx.schema.elements.ElementElement;
import org.jibx.schema.elements.KeyBase;
import org.jibx.schema.elements.OpenAttrBase;
import org.jibx.schema.elements.SchemaBase;
import org.jibx.util.InsertionOrderedSet;
import org.jibx.util.UniqueNameSet;

/**
 * Visitor to write a filtered view of a schema definition matching the data structure of a class. This is used when
 * schema fragments are included in class documentation.
 */
public class SchemaDocumentationGenerator
{
    /** Leading text for comment lines. */
    public static final String COMMENT_LEAD_TEXT = "\n * ";

    /** Schema definitions namespace URI. */
    private static final String SCHEMA_DEFINITIONS_NS = "http://www.w3.org/2001/XMLSchema";

    /** Logger for class. */
    private static final Logger s_logger = Logger.getLogger(SchemaDocumentationGenerator.class.getName());
    
    /** Extract binding factory. */
    private final IBindingFactory m_factory;
    
    /** Schema definitions namespace index. */
    private final int m_schemaIndex;
    
    /** Schema definitions namespace prefix. */
    private final String m_schemaPrefix;
    
    /** Set of namespace URIs defined in binding. */
    private final Set m_namespaceSet;
    
    /** Marshaller instance for writing schema fragments. */
    private final MarshallingContext m_context;
    
    /**
     * Constructor.
     * 
     * @throws JiBXException on error loading binding information
     */
    public SchemaDocumentationGenerator() throws JiBXException {
        m_factory = BindingDirectory.getFactory("schema_extract_binding", "org.jibx.schema.codegen");
        int index = 0;
        String[] uris = m_factory.getNamespaces();
        m_namespaceSet = new HashSet();
        for (int i = 1; i < uris.length; i++) {
            m_namespaceSet.add(uris[i]);
            if (SCHEMA_DEFINITIONS_NS.equals(uris[i])) {
                index = i;
            }
        }
        if (index < 0) {
            throw new JiBXException("Schema namespace definition not found");
        }
        m_schemaIndex = index;
        m_schemaPrefix = m_factory.getPrefixes()[index];
        m_context = (MarshallingContext)m_factory.createMarshallingContext();
        m_context.setIndent(2, COMMENT_LEAD_TEXT, ' ');
    }
    
    /**
     * Scan schema component references from item tree. This recursively constructs (1) a map from schema components
     * represented by separate classes to the corresponding class information, (2) a set of schema global definitions
     * included in the item tree, and (3) a set of namespace URIs for referenced components.
     *
     * @param group item grouping to be processed
     * @param comptoclas map from schema component to corresponding {@link ClassHolder}
     * @param refcomps set of schema global definitions incorporated into this tree
     * @param uritoprefix map from namespaces used by referenced definitions to the corresponding prefixes
     */
    private void scanItemTree(GroupItem group, Map comptoclas, Set refcomps, Map uritoprefix) {
        OpenAttrBase topcomp = group.getSchemaComponent();
        for (Item item = group.getFirstChild(); item != null; item = item.getNext()) {
            if (!item.isIgnored()) {
                
                // start by checking if we've crossed into a different schema definition
                OpenAttrBase comp = item.getSchemaComponent();
                while (comp != topcomp) {
                    if (comp.isGlobal()) {
                        if (!(item instanceof ReferenceItem)) {
                            refcomps.add(comp);
                        }
                        QName qname = ((INamed)comp).getQName();
                        if (qname == null) {
                            throw new IllegalStateException("Internal error - no name on global definition");
                        } else {
                            uritoprefix.put(qname.getUri(), qname.getPrefix());
                        }
                        break;
                    } else {
                        comp = comp.getParent();
                    }
                }
                
                // now check the actual item type
                if (item instanceof GroupItem) {
                    
                    // check non-inlined group which uses separate class
                    GroupItem childgroup = (GroupItem)item;
                    if (!childgroup.isIgnored()) {
                        if (childgroup.isInline()) {
                            scanItemTree(childgroup, comptoclas, refcomps, uritoprefix);
                        } else {
                            
                            // add component to class mapping to stop schema fragment generation when reached
                            TypeData genclas = childgroup.getGenerateClass();
                            if (genclas == null) {
                                throw new IllegalStateException("Internal error - no generate class");
                            } else {
                                comptoclas.put(childgroup.getSchemaComponent(), genclas);
                            }
                            
                            // check for type definition or reference used (with associated namespace reference)
                            QName tname = null;
                            comp = item.getSchemaComponent();
                            switch (comp.type()) {
                                case SchemaBase.ATTRIBUTE_TYPE:
                                    tname = ((AttributeElement)comp).getType();
                                    break;
                                case SchemaBase.ELEMENT_TYPE:
                                    tname = ((ElementElement)comp).getType();
                                    break;
                            }
                            if (tname != null) {
                                uritoprefix.put(tname.getUri(), tname.getPrefix());
                            }
                            QName rname = null;
                            if (comp instanceof IReference) {
                                rname = ((IReference)comp).getRef();
                            }
                            if (rname != null) {
                                uritoprefix.put(rname.getUri(), rname.getPrefix());
                            }
                        }
                    }
                    
                } else if (item instanceof ReferenceItem) {
                    
                    // make sure namespace collected for reference
                    DefinitionItem def = ((ReferenceItem)item).getDefinition();
                    AnnotatedBase defcomp = def.getSchemaComponent();
                    if (defcomp instanceof INamed) {
                        QName qname = ((INamed)defcomp).getQName();
                        if (qname == null && defcomp instanceof IReference) {
                            qname = ((IReference)defcomp).getRef();
                        }
                        if (qname != null) {
                            uritoprefix.put(qname.getUri(), qname.getPrefix());
                        }
                    }
                    TypeData genclas = def.getGenerateClass();
                    if (genclas != null) {
                        comptoclas.put(defcomp, genclas);
                    }
                    
                }
            }
        }
        
        // also check for substitution group used with element
        if (topcomp.type() == SchemaBase.ELEMENT_TYPE) {
            QName sname = ((ElementElement)topcomp).getSubstitutionGroup();
            if (sname != null) {
                uritoprefix.put(sname.getUri(), sname.getPrefix());
            }
        }
    }
    
    /**
     * Escape a special character in a text string.
     *
     * @param chr
     * @param escape
     * @param text
     * @param buff
     */
    private void escapeText(char chr, String escape, String text, StringBuffer buff) {
        int base = 0;
        int scan;
        while ((scan = text.indexOf(chr, base)) >= 0) {
            buff.append(text.substring(base, scan));
            buff.append(escape);
            base = scan + 1;
        }
        buff.append(text.substring(base));
    }

    /**
     * Generate documentation from the schema component corresponding to a class.
     *
     * @param group item group for class
     * @param dropanno delete annotations from schema documentation flag
     * @return schema extract documentation
     */
    public String generate(GroupItem group, boolean dropanno) {
        if (group.isIgnored()) {
            return null;
        } else {
            
            // scan the item tree structure to find schema components needing special handling
            Map comptoclas = new HashMap();
            InsertionOrderedSet refcomps = new InsertionOrderedSet();
            AnnotatedBase grpcomp = group.getSchemaComponent();
            refcomps.add(grpcomp);
            Map uritoprefix = new HashMap();
            if (grpcomp instanceof INamed) {
                QName qname = ((INamed)grpcomp).getQName();
                if (qname == null && grpcomp instanceof IReference) {
                    qname = ((IReference)grpcomp).getRef();
                }
                if (qname != null && qname.getUri() != null) {
                    uritoprefix.put(qname.getUri(), qname.getPrefix());
                }
            }
            scanItemTree(group, comptoclas, refcomps, uritoprefix);
            
            // get full set of namespaces and corresponding prefixes needed for extracts
            UniqueNameSet prefset = new UniqueNameSet();
            prefset.add(m_schemaPrefix);
            for (Iterator iter = uritoprefix.keySet().iterator(); iter.hasNext();) {
                String uri = (String)iter.next();
                if (!SCHEMA_DEFINITIONS_NS.equals(uri)) {
                    String prefix = (String)uritoprefix.get(uri);
                    if (prefix == null) {
                        prefix = "ns";
                    }
                    prefix = prefset.add(prefix);
                    uritoprefix.put(uri, prefix);
                }
                
            }
            
            // set the writer to be used for output
            StringWriter strwriter = new StringWriter();
            m_context.setOutput(strwriter);
            
            // define namespaces as extension to those used in binding
            int count = uritoprefix.size();
            String[] uris = (String[])uritoprefix.keySet().toArray(new String[count]);
            IXMLWriter xmlwriter = m_context.getXmlWriter();
            int base = xmlwriter.getNamespaceCount();
            xmlwriter.pushExtensionNamespaces(uris);
            
            // build the arrays of namespace indexes and prefixes for use on marshalled root elements
            int length = count;
            if (!uritoprefix.containsKey(null)) {
                length++;
            }
            String[] prefixes = new String[length];
            int[] indexes = new int[length];
            int fill = 0;
            for (int i = 0; i < count; i++) {
                String uri = uris[i];
                if (uri != null) {
                    prefixes[fill] = (String)uritoprefix.get(uris[i]);
                    indexes[fill] = base+fill;
                    fill++;
                }
            }
            prefixes[fill] = m_schemaPrefix;
            indexes[fill] = m_schemaIndex;
            
            // build schema extracts from main component and all referenced components
            String clasname = group.getGenerateClass().getFullName();
            TreeWalker walker = new TreeWalker(null, new SchemaContextTracker());
            boolean ref = false;
            List list = refcomps.asList();
            for (Iterator iter = list.iterator(); iter.hasNext();) {
                
                // add a blank line separating components
                if (ref) {
                    strwriter.write(COMMENT_LEAD_TEXT);
                } else {
                    ref = true;
                }
                
                // add any necessary namespace declarations
                AnnotatedBase comp = (AnnotatedBase)iter.next();
                for (Iterator jter = uritoprefix.keySet().iterator(); jter.hasNext();) {
                    String uri = (String)jter.next();
                    comp.addNamespaceDeclaration((String)uritoprefix.get(uri), uri);
                }
                
                // write the documentation
                DocumentationVisitor visitor = new DocumentationVisitor(comptoclas, clasname, comp, dropanno, ref,
                    indexes, prefixes);
                walker.walkElement(comp, visitor);
                strwriter.flush();
                
            }
            
            // convert generated schema fragment to plain text for embedding
            String text = strwriter.toString();
            StringBuffer buff = new StringBuffer(text.length() + text.length() / 4);
            if (text.indexOf('&') >= 0) {
                escapeText('&', "&", text, buff);
                text = buff.toString();
                buff = new StringBuffer(text.length() + text.length() / 4);
            }
            buff.append("Schema fragment(s) for this class:");
            buff.append(COMMENT_LEAD_TEXT);
            buff.append("
");
            buff.append(COMMENT_LEAD_TEXT);
            escapeText('<', "<", text, buff);
            buff.append(COMMENT_LEAD_TEXT);
            buff.append("
"); // make sure there's no embedded comment end marker int index = buff.length(); while ((index = buff.lastIndexOf("*/", index)) >= 0) { buff.replace(index, index+2, "* /"); } return buff.toString(); } } /** * Visitor to write the filtered view of a schema definition matching the data structure of a class. This uses a * supplied map for components which are represented by separate classes, which need to be replaced in the filtered * view by a reference to the appropriate class. */ private class DocumentationVisitor extends SchemaVisitor { /** Map from schema components with separate classes to the class information. */ private final Map m_componentClassMap; /** Fully-qualified name of class containing documentation. */ private final String m_className; /** Root component to be documented. */ private final AnnotatedBase m_component; /** Delete annotations from schema documentation flag. */ private final boolean m_dropAnnotations; /** Reference component with separate class flag. */ private final boolean m_reference; /** Namespace indexes for use on marshalling root element. */ private final int[] m_nsIndexes; /** Namespace prefixes for use on marshalling root element. */ private final String[] m_nsPrefixes; /** * Constructor. * * @param comptoclas map from schema components to class information * @param clasname fully-qualified class name to be stripped from class references * @param comp top-level component for documentation * @param dropanno delete annotations from schema documentation flag * @param ref reference component with separate class flag * @param indexes namespace indexes for use on marshalling root element * @param prefixes namespace prefixes for use on marshalling root element */ public DocumentationVisitor(Map comptoclas, String clasname, AnnotatedBase comp, boolean dropanno, boolean ref, int[] indexes, String[] prefixes) { m_componentClassMap = comptoclas; m_className = clasname; m_component = comp; m_dropAnnotations = dropanno; m_reference = ref; m_nsIndexes = indexes; m_nsPrefixes = prefixes; } /** * Exit a schema node. This just writes the end tag for the node. * * @param node */ public void exit(SchemaBase node) { try { m_context.endTag(m_schemaIndex, node.name()); } catch (JiBXException e) { s_logger.fatal("Binding failed", e); throw new IllegalStateException("Internal error - binding failed: " + e.getMessage()); } } /** * Exit an annotation element. If annotations are being deleted, this just returns without calling the * next-level method, so that no close tag will be written. * * @param node */ public void exit(AnnotationElement node) { if (!m_dropAnnotations) { super.exit(node); } } /** * Exit an element element. If the extension says the element is excluded, this just returns without calling the * next-level method, so that no close tag will be written. * * @param node */ public void exit(ElementElement node) { Object extension = node.getExtension(); if (!(extension instanceof ComponentExtension) || !((ComponentExtension)extension).isIgnored()) { super.exit(node); } } /** * Exit an identity constraint element. This just always returns immediately, since the identity constraint * elements are handled in-line in the binding. * * @param node */ public void exit(KeyBase node) { } /** * Visit a schema node. This first writes the start tag for the node. If the schema node is represented by a * separate class this then just writes text content referencing that class, and returns blocking further * expansion; otherwise, it just returns for further expansion requested. * * @param node * @return true if expanding content, false if content replaced by reference */ public boolean visit(SchemaBase node) { boolean expand = false; try { // first marshal the element start tag and attributes if (node == m_component) { int[] copy = new int[m_nsIndexes.length]; System.arraycopy(m_nsIndexes, 0, copy, 0, m_nsIndexes.length); m_context.startTagNamespaces(m_schemaIndex, node.name(), copy, m_nsPrefixes); } else { m_context.startTagAttributes(m_schemaIndex, node.name()); } IMarshaller marshaller = m_context.getMarshaller(node.getClass().getName()); marshaller.marshal(node, m_context); m_context.closeStartContent(); // check for separate class reference to determine content handling IClassHolder clas = (IClassHolder)m_componentClassMap.get(node); if (clas != null && (m_reference || node != m_component)) { // replace content with comment reference to class holding data String name = clas.getFullName(); StringBuffer text = new StringBuffer(); int length = m_className.length(); if (name.startsWith(m_className) && name.length() > length && name.charAt(length) == '.') { text.append(" Reference to inner class "); text.append(name.substring(length+1)); } else { text.append(" Reference to class "); text.append(name); } text.append(' '); IXMLWriter writer = m_context.getXmlWriter(); writer.indent(); writer.writeComment(text.toString()); } else { expand = true; } } catch (IOException e) { s_logger.fatal("Write failed", e); throw new IllegalStateException("Internal error - write failed: " + e.getMessage()); } catch (JiBXException e) { s_logger.fatal("Error writing schema XML representation", e); throw new IllegalStateException("Internal error - error writing schema XML representation: " + e.getMessage()); } return expand; } /** * Visit an annotation element. If annotations are being deleted this just returns without calling the * next-level method, so that the element will be ignored. * * @param node * @return false if annotations to be deleted, otherwise the result of the next-level method */ public boolean visit(AnnotationElement node) { if (m_dropAnnotations) { return false; } else { return super.visit(node); } } /** * Visit an element element. If the extension says this is excluded the element is dropped from the generated * schema fragment. * * @param node * @return false if element excluded, otherwise the result of the next-level method */ public boolean visit(ElementElement node) { Object extension = node.getExtension(); if (extension instanceof ComponentExtension && ((ComponentExtension)extension).isIgnored()) { return false; } else { return super.visit(node); } } /** * Visit an identity constraint element. This just always returns false, since the identity * constraint elements are handled in-line in the binding. * * @param node * @return false to block further expansion */ public boolean visit(KeyBase node) { return false; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy