com.sun.tools.xjc.addon.xew.XmlElementWrapperPluginTest.java Source code

Java tutorial

Introduction

Here is the source code for com.sun.tools.xjc.addon.xew.XmlElementWrapperPluginTest.java

Source

/*
 * XmlElementWrapperPluginTest.java
 * 
 * Copyright (C) 2009, Tobias Warneke
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */
package com.sun.tools.xjc.addon.xew;

import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.validation.SchemaFactory;

import com.sun.tools.xjc.BadCommandLineException;
import com.sun.tools.xjc.Driver;
import com.sun.tools.xjc.Options;
import com.sun.tools.xjc.reader.Const;
import com.sun.tools.xjc.reader.internalizer.DOMForest;
import com.sun.tools.xjc.reader.xmlschema.parser.XMLSchemaInternalizationLogic;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.custommonkey.xmlunit.Diff;
import org.custommonkey.xmlunit.XMLUnit;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Testcases for the XEW Plugin.
 * 
 * @author Tobias Warneke
 */
public class XmlElementWrapperPluginTest {

    private static final String PREGENERATED_SOURCES_PREFIX = "src/test/generated_resources/";
    private static final String GENERATED_SOURCES_PREFIX = "target/test/generated_xsd_classes/";

    private static final Log logger = LogFactory.getLog(XmlElementWrapperPluginTest.class);

    @Test
    public void testUsage() throws Exception {
        assertNotNull(new XmlElementWrapperPlugin().getUsage());
    }

    @Test(expected = BadCommandLineException.class)
    public void testUnknownOption() throws Exception {
        assertXsd("different-namespaces", new String[] { "-Xxew:unknown" }, false);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidInstantiationMode() throws Exception {
        assertXsd("element-list-extended", new String[] { "-Xxew:instantiate invalid" }, false);
    }

    @Test(expected = BadCommandLineException.class)
    public void testInvalidControlFile() throws Exception {
        assertXsd("element-list-extended", new String[] { "-Xxew:control invalid" }, false);
    }

    @Test(expected = BadCommandLineException.class)
    public void testInvalidCollectionClass() throws Exception {
        assertXsd("element-list-extended", new String[] { "-Xxew:collection badvalue" }, false);
    }

    @Test(expected = IllegalArgumentException.class)
    public void testInvalidCustomization() throws Exception {
        assertXsd("element-with-invalid-customization", null, false);
    }

    @Test
    public void testInnerElement() throws Exception {
        assertXsd("inner-element",
                new String[] { "-verbose", "-Xxew:instantiate none",
                        "-Xxew:control " + getClass().getResource("inner-element-control.txt").getFile() },
                true, "Filesystem", "Volumes", "package-info");
    }

    @Test
    public void testInnerElementWithValueObjects() throws Exception {
        assertXsd("inner-element-value-objects", new String[] { "-debug" }, false, "Article", "Articles",
                "ArticlesCollections", "Filesystem", "Publisher", "Volume", "package-info", "impl.ArticleImpl",
                "impl.ArticlesImpl", "impl.ArticlesCollectionsImpl", "impl.FilesystemImpl", "impl.PublisherImpl",
                "impl.VolumeImpl", "impl.ObjectFactory", "impl.JAXBContextFactory", "impl.package-info");
    }

    @Test
    public void testAnnotationReference() throws Exception {
        // "Markup.java" cannot be verified for content because the content is changing from
        // one compilation to other as order of @XmlElementRef/@XmlElement annotations is not pre-defined
        // (set is used as their container).
        assertXsd("annotation-reference", new String[] { "-verbose", "-debug" }, false, "ClassCommon", "ClassesEu",
                "ClassesUs", "ClassExt", "Markup", "Para", "SearchEu", "SearchMulti", "package-info");
    }

    @Test
    public void testElementAsParametrisation1() throws Exception {
        assertXsd("element-as-parametrisation-1",
                new String[] { "-Xxew:control "
                        + getClass().getResource("element-as-parametrisation-1-control.txt").getFile() },
                false, "Article", "Articles", "ArticlesCollections", "Publisher", "package-info");
    }

    @Test
    public void testElementAsParametrisation2() throws Exception {
        assertXsd("element-as-parametrisation-2",
                new String[] {
                        "-Xxew:control "
                                + getClass().getResource("element-as-parametrisation-2-control.txt").getFile(),
                        "-Xxew:summary " + GENERATED_SOURCES_PREFIX + "summary.txt" },
                false, "Family", "FamilyMember", "package-info");

        String summaryFile = FileUtils.readFileToString(new File(GENERATED_SOURCES_PREFIX + "summary.txt"));

        assertTrue(summaryFile.contains("1 candidate(s) being considered"));
        assertTrue(summaryFile.contains("0 modification(s) to original code"));
        assertTrue(summaryFile.contains("0 deletion(s) from original code"));
    }

    @Test
    public void testElementWithParent() throws Exception {
        assertXsd("element-with-parent", new String[] { "-debug" }, false, "Alliance", "Group", "Organization",
                "package-info");
    }

    @Test
    public void testElementAny() throws Exception {
        assertXsd("element-any", new String[] { "-quiet", "-Xxew:plural" }, false, "Message", "package-info");
    }

    @Test
    public void testElementAnyType() throws Exception {
        assertXsd("element-any-type", new String[] { "-Xxew:plural" }, false, "Conversion", "Entry",
                "package-info");
    }

    @Test
    public void testElementMixed() throws Exception {
        // Most classes cannot be tested for content
        assertXsd("element-mixed", new String[] { "-debug" }, false, "B", "Br", "I", "AnyText", "package-info");
    }

    @Test
    public void testElementListExtended() throws Exception {
        // This run is configured from XSD (<xew:xew ... >):
        assertXsd("element-list-extended", null, false, "Foo", "package-info");
    }

    @Test
    public void testElementNameCollision() throws Exception {
        // Most classes cannot be tested for content
        assertXsd("element-name-collision", new String[] { "-debug", "-Xxew:instantiate", "lazy" }, false, "Root",
                "package-info");
    }

    @Test
    public void testElementScoped() throws Exception {
        // Most classes cannot be tested for content
        assertXsd("element-scoped", new String[] { "-debug" }, false, "Return", "package-info");
    }

    @Test
    public void testElementWithAdapter() throws Exception {
        // Plural form in this case will have no impact as there is property customization:
        assertXsd("element-with-adapter",
                new String[] { "-Xxew:plural", "-Xxew:collectionInterface java.util.Collection" }, false,
                "Calendar", "Adapter1", "package-info");
    }

    @Test
    public void testElementWithCustomization() throws Exception {
        // This run is additionally configured from XSD (<xew:xew ... >):
        assertXsd("element-with-customization", new String[] { "-debug", "-Xxew:plural" }, false, "PostOffice",
                "Args", "package-info");
    }

    @Test
    public void testElementReservedWord() throws Exception {
        assertXsd("element-reserved-word", null, false, "Class", "Method", "package-info");
    }

    @Test
    public void testSubstitutionGroups() throws Exception {
        assertXsd("substitution-groups", null, false, "Address", "ContactInfo", "Customer", "PhoneNumber",
                "package-info");
    }

    @Test
    public void testUnqualifiedSchema() throws Exception {
        assertXsd("unqualified", null, false, "RootElement", "package-info");
    }

    /**
     * Standard test for XSD examples.
     * 
     * @param testName
     *            the prototype of XSD file name / package name
     * @param extraXewOptions
     *            to be passed to plugin
     * @param generateEpisode
     *            generate episode file and check the list of classes included into it
     * @param classesToCheck
     *            expected classes/files in target directory; these files content is checked if it is present in
     *            resources directory; {@code ObjectFactory.java} is automatically included
     */
    static void assertXsd(String testName, String[] extraXewOptions, boolean generateEpisode,
            String... classesToCheck) throws Exception {
        String resourceXsd = testName + ".xsd";
        String packageName = testName.replace('-', '_');

        // Force plugin to reinitialize the logger:
        System.clearProperty(XmlElementWrapperPlugin.COMMONS_LOGGING_LOG_LEVEL_PROPERTY_KEY);

        URL xsdUrl = XmlElementWrapperPluginTest.class.getResource(resourceXsd);

        File targetDir = new File(GENERATED_SOURCES_PREFIX);

        targetDir.mkdirs();

        PrintStream loggingPrintStream = new PrintStream(
                new LoggingOutputStream(logger, LoggingOutputStream.LogLevel.INFO, "[XJC] "));

        String[] opts = ArrayUtils.addAll(extraXewOptions, "-no-header", "-extension", "-Xxew", "-d",
                targetDir.getPath(), xsdUrl.getFile());

        String episodeFile = new File(targetDir, "episode.xml").getPath();

        // Episode plugin should be triggered after Xew, see https://github.com/dmak/jaxb-xew-plugin/issues/6
        if (generateEpisode) {
            opts = ArrayUtils.addAll(opts, "-episode", episodeFile);
        }

        assertTrue("XJC compilation failed. Checked console for more info.",
                Driver.run(opts, loggingPrintStream, loggingPrintStream) == 0);

        if (generateEpisode) {
            // FIXME: Episode file actually contains only value objects
            Set<String> classReferences = getClassReferencesFromEpisodeFile(episodeFile);

            if (Arrays.asList(classesToCheck).contains("package-info")) {
                classReferences.add(packageName + ".package-info");
            }

            assertEquals("Wrong number of classes in episode file", classesToCheck.length, classReferences.size());

            for (String className : classesToCheck) {
                assertTrue(className + " class is missing in episode file;",
                        classReferences.contains(packageName + "." + className));
            }
        }

        targetDir = new File(targetDir, packageName);

        Collection<String> generatedJavaSources = new HashSet<String>();

        // *.properties files are ignored:
        for (File targetFile : FileUtils.listFiles(targetDir, new String[] { "java" }, true)) {
            // This is effectively the path of targetFile relative to targetDir:
            generatedJavaSources
                    .add(targetFile.getPath().substring(targetDir.getPath().length() + 1).replace('\\', '/'));
        }

        // This class is added and checked by default:
        classesToCheck = ArrayUtils.add(classesToCheck, "ObjectFactory");

        assertEquals("Wrong number of generated classes " + generatedJavaSources + ";", classesToCheck.length,
                generatedJavaSources.size());

        for (String className : classesToCheck) {
            className = className.replace('.', '/') + ".java";

            assertTrue(className + " is missing in target directory", generatedJavaSources.contains(className));
        }

        // Check the contents for those files which exist in resources:
        for (String className : classesToCheck) {
            className = className.replace('.', '/') + ".java";

            File sourceFile = new File(PREGENERATED_SOURCES_PREFIX + packageName, className);

            if (sourceFile.exists()) {
                // To avoid CR/LF conflicts:
                assertEquals("For " + className, FileUtils.readFileToString(sourceFile).replace("\r", ""),
                        FileUtils.readFileToString(new File(targetDir, className)).replace("\r", ""));
            }
        }

        JAXBContext jaxbContext = compileAndLoad(packageName, targetDir, generatedJavaSources);

        URL xmlTestFile = XmlElementWrapperPluginTest.class.getResource(testName + ".xml");

        if (xmlTestFile != null) {
            StringWriter writer = new StringWriter();

            SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

            Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
            unmarshaller.setSchema(schemaFactory.newSchema(xsdUrl));

            Marshaller marshaller = jaxbContext.createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            Object bean = unmarshaller.unmarshal(xmlTestFile);
            marshaller.marshal(bean, writer);

            XMLUnit.setIgnoreComments(true);
            XMLUnit.setIgnoreWhitespace(true);
            Diff xmlDiff = new Diff(IOUtils.toString(xmlTestFile), writer.toString());

            assertXMLEqual("Generated XML is wrong: " + writer.toString(), xmlDiff, true);
        }
    }

    /**
     * The method performs:
     * <ul>
     * <li>Compilation of given set of Java source files
     * <li>Construction of custom class loader
     * <li>Creation of JAXB context
     * </ul>
     * 
     * @param packageName
     *            package name to which java classes belong to
     * @param targetDir
     *            the target directory
     * @param generatedJavaSources
     *            list of Java source files which should become a part of JAXB context
     */
    private static JAXBContext compileAndLoad(String packageName, File targetDir,
            Collection<String> generatedJavaSources) throws MalformedURLException, JAXBException {
        String[] javaSources = new String[generatedJavaSources.size()];

        int i = 0;
        for (String javaSource : generatedJavaSources) {
            javaSources[i++] = (new File(targetDir, javaSource)).toString();
        }

        StringWriter writer = new StringWriter();

        if (com.sun.tools.javac.Main.compile(javaSources, new PrintWriter(writer)) != 0) {
            fail("javac failed with message: " + writer.toString());
        }

        ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();

        URLClassLoader newClassLoader = new URLClassLoader(
                new URL[] { new File(GENERATED_SOURCES_PREFIX).toURI().toURL() }, currentClassLoader);

        return JAXBContext.newInstance(packageName, newClassLoader);
    }

    /**
     * Return values of all {@code <jaxb:class ref="..." />} attributes.
     */
    private static Set<String> getClassReferencesFromEpisodeFile(String episodeFile) throws SAXException {
        DOMForest forest = new DOMForest(new XMLSchemaInternalizationLogic(), new Options());

        Document episodeDocument = forest.parse(new InputSource(episodeFile), true);

        NodeList nodeList = episodeDocument.getElementsByTagNameNS(Const.JAXB_NSURI, "class");
        Set<String> classReferences = new HashSet<String>();

        for (int i = 0, len = nodeList.getLength(); i < len; i++) {
            classReferences.add(((Element) nodeList.item(i)).getAttribute("ref"));
        }

        return classReferences;
    }
}