/* * Copyright (c) 2017-2018 The Regents of the University of California * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. 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. * * 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 * HOLDER 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 edu.berkeley.cs.jqf.examples.xml; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import java.io.IOException; import com.pholser.junit.quickcheck.generator.GenerationStatus; import com.pholser.junit.quickcheck.generator.Generator; import com.pholser.junit.quickcheck.generator.Size; import com.pholser.junit.quickcheck.internal.GeometricDistribution; import com.pholser.junit.quickcheck.random.SourceOfRandomness; import edu.berkeley.cs.jqf.examples.common.AlphaStringGenerator; import edu.berkeley.cs.jqf.examples.common.Dictionary; import edu.berkeley.cs.jqf.examples.common.DictionaryBackedStringGenerator; import org.junit.Assume; import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Text; /** * A generator for XML documents. * * @author Rohan Padhye */ public class XmlDocumentGenerator extends Generator<Document> { private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); private static GeometricDistribution geometricDistribution = new GeometricDistribution(); /** Mean number of child nodes for each XML element. */ private static final double MEAN_NUM_CHILDREN = 4; /** Mean number of attributes for each XML element. */ private static final double MEAN_NUM_ATTRIBUTES = 2; /** * Minimum size of XML tree. * @see {@link #configure(Size)} */ private int minDepth = 0; /** * Maximum size of XML tree. * @see {@link #configure(Size)} */ private int maxDepth = 4; private Generator<String> stringGenerator = new AlphaStringGenerator(); public XmlDocumentGenerator() { super(Document.class); } /** * Configures the minimum/maximum size of the XML document. * * This method is not usually invoked directly. Instead, use * the `@Size` annotation on fuzzed parameters to configure * automatically. * * @param size the min/max size of the XML document */ public void configure(Size size) { minDepth = size.min(); maxDepth = size.max(); } /** * Configures the string generator used by this generator to use * a dictionary. This is useful for overriding the default * arbitrary string generator with something that pulls tag names * from a predefined list. * * @param dict the dictionary file * @throws IOException if the dictionary file cannot be read */ public void configure(Dictionary dict) throws IOException { stringGenerator = new DictionaryBackedStringGenerator(dict.value(), stringGenerator); } /** * Generators a random XML document. * @param random a source of pseudo-random values * @param status generation state * @return a randomly-generated XML document */ @Override public Document generate(SourceOfRandomness random, GenerationStatus status) { DocumentBuilder builder; try { builder = documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new RuntimeException(e); } if (stringGenerator == null) { stringGenerator = gen().type(String.class); } Document document = builder.newDocument(); try { populateDocument(document, random, status); } catch (DOMException e) { Assume.assumeNoException(e); } return document; } private String makeString(SourceOfRandomness random, GenerationStatus status) { return stringGenerator.generate(random, status); } private Document populateDocument(Document document, SourceOfRandomness random, GenerationStatus status) { Element root = document.createElement(makeString(random, status)); populateElement(document, root, random, status, 0); document.appendChild(root); return document; } private void populateElement(Document document, Element elem, SourceOfRandomness random, GenerationStatus status, int depth) { // Add attributes int numAttributes = Math.max(0, geometricDistribution.sampleWithMean(MEAN_NUM_ATTRIBUTES, random)-1); for (int i = 0; i < numAttributes; i++) { elem.setAttribute(makeString(random, status), makeString(random, status)); } // Make children if (depth < minDepth || (depth < maxDepth && random.nextBoolean())) { int numChildren = Math.max(0, geometricDistribution.sampleWithMean(MEAN_NUM_CHILDREN, random)-1); for (int i = 0; i < numChildren; i++) { Element child = document.createElement(makeString(random, status)); populateElement(document, child, random, status, depth+1); elem.appendChild(child); } } else if (random.nextBoolean()) { // Add text Text text = document.createTextNode(makeString(random, status)); elem.appendChild(text); } else if (random.nextBoolean()) { // Add text as CDATA Text text = document.createCDATASection(makeString(random, status)); elem.appendChild(text); } } }