/* * 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; } }