IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  XML | Java technology  >

package org.enhydra.xml.binding;

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


级别: 初级

Brett McLaughlin

XML error: Please enter a value for the author element's jobtitle attribute, or the company-name element, or both.



2000 年 9 月 01 日

Part three of this data-binding series shows you how to convert XML elements and attributes to Java objects using the mechanisms specified in JSR-031: Data Binding, the Sun Data Binding Specification Request. This installment looks at moving from an XML representation of data to a Java instance that your application code can easily use. Part three covers unmarshalling the nested elements in the XML documents into Java objects, testing, and putting the new tools into action with some practical examples.
package org.enhydra.xml.binding;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.List;
// JDOM classes used for document representation
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
/**
 * <p>
 *  <code>SchemaMapper</code> handles generation of Java interfaces and classes
 *    from an XML Schema, essentially allowing data contracts to be set up
 *    for the binding of XML instance documents to Java objects.
 * </p>
 *
 * @author Brett McLaughlin
 */
public class SchemaMapper {
    /** Storage for code for interfaces */
    private Map interfaces;
    /** Storage for code for implementations */
    private Map implementations;
    /** Properties that accessor/mutators should be created for */
    protected Map properties;
    /** XML Schema Namespace */
    private Namespace schemaNamespace;
    /** XML Schema Namespace URI */
    private static final String SCHEMA_NAMESPACE_URI =
        "http://www.w3.org/1999/XMLSchema";
    /**
     * <p>
     *  Allocate storage and set up defaults.
     * </p>
     */
    public SchemaMapper() {
        interfaces = new HashMap();
        implementations = new HashMap();
        properties = new HashMap();
        schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI);
    }
    /**
     * <p>
     *  This is the "entry point" for generation of Java classes from an XML
     *    Schema. It allows a schema to be supplied, via <code>URL</code>,
     *    and that schema is used for input to generation.
     * </p>
     *
     * @param schemaURL <code>URL</code> at which XML Schema is located.
     * @throws <code>IOException</code> - when problems in generation occur.
     */
    public void generateClasses(URL schemaURL) throws IOException {
        /**
         * Create builder to generate JDOM representation of XML Schema,
         *   without validation and using Apache Xerces.
         */ 
        // XXX: Allow validation, and allow alternate parsers
        SAXBuilder builder = new SAXBuilder();
        try {
            Document schemaDoc = builder.build(schemaURL);
            // Handle complex types
            List complexTypes = schemaDoc.getRootElement()
                                         .getChildren("complexType", 
                                                      schemaNamespace);
            for (Iterator i = complexTypes.iterator(); i.hasNext(); ) {
                // Iterate and handle
                Element complexType = (Element)i.next();
                handleComplexType(complexType);
            }
            // Handle elements (at top level)
        } catch (JDOMException e) {
            throw new IOException(e.getMessage());
        }
    }
    /**
     * <p>
     *  This will write out the generated classes to the supplied stream.
     * </p>
     *
     * @param directory <code>File</code> to write to (should be a directory).
     * @throws <code>IOException</code> - when output errors occur.
     */
    public void writeClasses(File dir) throws IOException {
        if (!dir.isDirectory()) {
            throw new IOException("A directory must be supplied for output.");
        }
        // First do interfaces
        for (Iterator i = interfaces.keySet().iterator(); i.hasNext(); ) {
            String interfaceName = (String)i.next();
            outputFile(dir, interfaceName + ".java", 
                       (String)interfaces.get(interfaceName));
        }
        // Next do implementation classes
        for (Iterator i = implementations.keySet().iterator(); i.hasNext(); ) {
            String implementationName = (String)i.next();
            outputFile(dir, implementationName + ".java", 
                       (String)implementations.get(implementationName));
        }
    }
    /**
     * <p>
     *  This will handle the generation/conversion of a 
     *    <code>complexType</code> into a Java interface.
     * </p>
     *
     * @param complexType <code>Element</code> to generate from.
     * @throws <code>IOException</code> - when generation fails.
     */
    private void handleComplexType(Element complexType) throws IOException {
        // Determine if this is an explict or implicit type
        String type = null;
        // Handle extension, if needed
        String baseType = null;
        // Assume that we are dealing with an explicit type
        Attribute typeAttribute = complexType.getAttribute("name");
        if (typeAttribute != null) {
            type = typeAttribute.getValue();
        } else {
            /*
             * It is safe with an implicit type to assume that the parent
             *   is of type "element", has no "type" attribute, and that we
             *   can derive the type as the value of the element's "name" 
             *   attribute with the word "Type" appended to it.
             * It's also sage to assume that any correctly formed XML Schema
             *   will have a "name" attribute, so we don't need to check
             *   for null values here.
             */
            type = new StringBuffer()
                .append(BindingUtils.initialCaps(
                    complexType.getParent()
                               .getAttribute("name")
                               .getValue()))
                .append("Type")
                .toString();
        }
        // See if extension is occurring
        Attribute baseTypeAttribute = complexType.getAttribute("baseType");
        if (baseTypeAttribute != null) {
            baseType = baseTypeAttribute.getValue();
            // Assume extension by default
            String derivedBy = "extension";
            Attribute derivedByAttribute = complexType.getAttribute("derivedBy");
            if (derivedByAttribute != null) {
                derivedBy = derivedByAttribute.getValue();
            }
            
            // Only support extension, not restriction
            if (!derivedBy.equals("extension")) {
                throw new IOException(
                    "Derivation of types is only allowed by extension.");
            }
        }
        // Capitalize for name of interface
        String interfaceName = BindingUtils.initialCaps(type);
           
        // Get name of implementation class
        String implementationName = new StringBuffer()
            .append(interfaceName)
            .append("Impl")
            .toString();
        // If this is an explicit type, begin class generation
        StringBuffer interfaceCode = new StringBuffer();
        StringBuffer implementationCode = new StringBuffer();
        /*
         * Start writing out the interface and implementation class
         *   definitions.
         */
        interfaceCode.append("public interface ")
                     .append(interfaceName);
        // Add in extension if appropriate
        if (baseType != null) {
            interfaceCode.append(" extends ")
                         .append(baseType);
        }
        interfaceCode.append(" {\n");
        implementationCode.append("public class ")
                          .append(implementationName);
        
        // Add in extension if appropriate
        if (baseType != null) {
            implementationCode.append(" extends ")
                              .append(baseType)
                              .append("Impl");
        }
        implementationCode.append(" implements ")
                          .append(interfaceName)
                          .append(" {\n");
        // Allocate storage for the properties of the interface/impl
        properties.put(interfaceName, new HashMap());
        // First, generate properties from elements and attributes
        implementationCode.append(
            getPropertiesCode(complexType.getChildren(), interfaceName));
        // Generate accessor and mutator methods from properties
        interfaceCode.append(
            getInterfaceMethodsCode(interfaceName));
        implementationCode.append(
            getImplementationMethodsCode(interfaceName));
        // Now handle elements
        List elements = complexType.getChildren("element", schemaNamespace);
        // Now create method declarations from elements
        for (Iterator i = elements.iterator(); i.hasNext(); ) {
            Element element = (Element)i.next();
            //handleElement(element);
        }
        // Close up interface and implementation classes
        interfaceCode.append("}");
        implementationCode.append("}");
        // Add code to overall storage for later output
        interfaces.put(interfaceName, 
            interfaceCode.toString());
        implementations.put(implementationName, 
            implementationCode.toString());
    }
    /**
     * <p>
     *  This handles the generation of properties from a <code>List</code>
     *    of JDOM <code>Element</code>s. For each declared attribute, and 
     *    for each declared element without an implicit type, a property 
     *    is generated. For each declared element with an implicit type, 
     *    the type is queried for the variable type, and the property 
     *    is typed accordingly.
     * </p>
     *
     * @param list <code>List</code> of children.
     * @param interfaceName <code>String</code> name of interface being coded.
     * @return <code>String</code> - content to add to calling code.
     * @throws <code>IOException</code> - when XML Schema is wrong.
     */
    protected String getPropertiesCode(List list, String interfaceName) 
        throws IOException {
        StringBuffer code = new StringBuffer();
        for (Iterator i = list.iterator(); i.hasNext(); ) {
            Element element = (Element)i.next();
            String name = element.getName();
            String propertyName = null;
            String propertyType = null;
            Attribute propertyNameAttribute = element.getAttribute("name");
            if (propertyNameAttribute != null) {
                propertyName = propertyNameAttribute.getValue();
            } else {
                throw new IOException("All elements and attributes must be " +
                                      "named for data binding.");
            }
            // If an attribute, add as a property
            if (name.equals("attribute")) {
                Attribute propertyTypeAttribute = element.getAttribute("type");
                if (propertyTypeAttribute != null) {
                    propertyType = 
                        DataMapping.getInstance()
                                   .getJavaType(propertyTypeAttribute.getValue());
                                            
                    code.append("    private ")
                        .append(propertyType)
                        .append(" ")
                        .append(propertyName)
                        .append(";\n");
                } else {
                    throw new IOException("All attributes must be typed " +
                                          "for data binding.");
                }
            } else if (name.equals("element")) {
                // Either get type explicitly, or from complex type
                Attribute propertyTypeAttribute = element.getAttribute("type");
                if (propertyTypeAttribute != null) {
                    // If no type attribute, this will jump to the else block
                    propertyType =
                        DataMapping.getInstance()
                                   .getJavaType(propertyTypeAttribute.getValue());
                    code.append("    private ")
                        .append(propertyType)
                        .append(" ")
                        .append(propertyName)
                        .append(";\n");
                    
                } else {
                    /*
                     * Since Java names can start with an underscore,
                     *   it isn't safe to use an uppercase version of the
                     *   property name (port -> Port), so we append type
                     *   to the uppercase version of the property name. 
                     */
                    propertyType = new StringBuffer()
                        .append(BindingUtils.initialCaps(propertyName))
                        .append("Type")
                        .toString();
                    code.append("    private ")
                        .append(propertyType)
                        .append(" ")
                        .append(propertyName)
                        .append(";\n");
                    // Generate the interface/class for this complexType
                    Element complexTypeElement = 
                        element.getChild("complexType", schemaNamespace);
                    if (complexTypeElement != null) {
                        handleComplexType(complexTypeElement);
                    } else {
                        throw new IOException("All elements must either " +
                            "reference an existing type, or define a new one.");
                    }
                }
            }
               
            // Let this interface know about the new property
            ((HashMap)properties.get(interfaceName))
                .put(propertyName, propertyType);
        }
        return code.toString();
    }
    /**
     * <p>
     *  This will generate the accessor and mutator methods for the supplied
     *    interface, based on the properties stored for that interface within
     *    the <code>{@link #getProperties}</code> method.
     * </p>
     *
     * @param interfaceName <code>String</code> name of interface to code.
     */
    protected String getInterfaceMethodsCode(String interfaceName) {
        StringBuffer code = new StringBuffer();
        Map propertiesMap = (Map)properties.get(interfaceName); 
        for (Iterator i = propertiesMap.keySet().iterator(); i.hasNext(); ) {
            String propertyName = (String)i.next();
            String propertyType = (String)propertiesMap.get(propertyName);
            // Build mutator method
            code.append("    public void set")
                .append(BindingUtils.initialCaps(propertyName))
                .append("(")
                .append(propertyType)
                .append(" ")
                .append(propertyName)
                .append(");\n");
            // Build accessor method
            code.append("    public ")
                .append(propertyType)
                .append(" get")
                .append(BindingUtils.initialCaps(propertyName))
                .append("();\n");
        }
        return code.toString();
    }
    /**
     * <p>
     *  This will generate the accessor and mutator methods for the 
     *   implementation of the supplied interface, based on the properties 
     *   stored for that interface within the 
     *   <code>{@link #getProperties}</code> method.
     * </p>
     *
     * @param interfaceName <code>String</code> name of interface that this
     *        implementation implements.
     */
    protected String getImplementationMethodsCode(String interfaceName) {
        StringBuffer code = new StringBuffer();
        Map propertiesMap = (Map)properties.get(interfaceName); 
        for (Iterator i = propertiesMap.keySet().iterator(); i.hasNext(); ) {
            String propertyName = (String)i.next();
            String propertyType = (String)propertiesMap.get(propertyName);
            // Build mutator method
            code.append("\n    public void set")
                .append(BindingUtils.initialCaps(propertyName))
                .append("(")
                .append(propertyType)
                .append(" ")
                .append(propertyName)
                .append(") {\n")
                .append("        this.")
                .append(propertyName)
                .append(" = ")
                .append(propertyName)
                .append(";\n")
                .append("    }\n");
            // Build accessor method
            code.append("\n    public ")
                .append(propertyType)
                .append(" get")
                .append(BindingUtils.initialCaps(propertyName))
                .append("() {\n")
                .append("        return ")
                .append(propertyName)
                .append(";\n")
                .append("    }\n");
        }
        return code.toString();
    }
    /**
     * <p>
     *  Simple utility method to output content to a new file on the local
     *    file system.
     * </p>
     *
     * @param dir <code>File</code> directory to create file in.
     * @param filename <code>String</code> name for new file.
     * @param content <code>String</code> content to put in file.
     * @throws <code>IOException</code> - when errors occur in output.
     */
    private void outputFile(File dir, String filename, String content)
        throws IOException {
        File file = new File(dir, filename);
        FileWriter writer = new FileWriter(file);
        writer.write(content);
        writer.flush();
        writer.close();
    }
    /**
     * <p>
     *  This provides a static entry point for class generation from
     *    XML Schemas.
     * </p>
     *
     * @param args <code>String[]</code> list of files to parse.
     */
    public static void main(String[] args) {
        SchemaMapper mapper = new SchemaMapper();
        try {
            for (int i=0; i<args.length; i++) {
                File file = new File(args[i]);
                mapper.generateClasses(file.toURL());
                mapper.writeClasses(new File("."));
            }
        } catch (FileNotFoundException e) {
            System.out.println("Could not locate XML Schema: ");
            e.printStackTrace();
        } catch (IOException e) {
            System.out.println("Java class generation failed: ");
            e.printStackTrace();
        }
    }
}



关于作者

Brett McLaughlin 是 Lutris 科技公司的 Enhydra 战略家,其专长是分布式系统的体系结构。他是《 Java and XML 》(O'Reilly) 一书的作者。Brett 涉足多种技术,如 Java servlets、Enterprise JavaBeans 技术、XML 和企业对企业的应用程序等。最近他与 Jason Hunter 一起建立了 JDOM方案,该方案提供简单的 API 来在 Java 应用程序中操作 XML。 他还是 Apache Cocoon 项目和 EJBoss EJB 服务器的积极开发人员,并且是 Apache Turbine 项目的共同创始人之一。可以通过 brett@newInstance.com与 Brett 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?







回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款