blob: eaeb4e7279d16c8f187be4e5c36b1c8c831b6610 [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau;
import java.io.*;
import java.lang.reflect.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import javax.xml.validation.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.json.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.transforms.*;
import org.apache.juneau.utils.*;
import org.apache.juneau.xml.*;
import org.junit.*;
import org.w3c.dom.*;
import org.w3c.dom.bootstrap.*;
import org.w3c.dom.ls.*;
import org.xml.sax.*;
@SuppressWarnings({"javadoc"})
public class TestUtils {
private static JsonSerializer js = new JsonSerializerBuilder()
.simple()
.trimNullProperties(false)
.build();
private static JsonSerializer jsSorted = new JsonSerializerBuilder()
.simple()
.sortCollections(true)
.sortMaps(true)
.trimNullProperties(false)
.build();
private static JsonSerializer js2 = new JsonSerializerBuilder()
.simple()
.pojoSwaps(IteratorSwap.class, EnumerationSwap.class)
.build();
private static JsonSerializer js3 = new JsonSerializerBuilder()
.simple()
.pojoSwaps(IteratorSwap.class, EnumerationSwap.class)
.sortProperties(true)
.build();
private static final BeanSession beanSession = BeanContext.DEFAULT.createSession();
/**
* Verifies that two objects are equivalent.
* Does this by doing a string comparison after converting both to JSON.
*/
public static void assertEqualObjects(Object o1, Object o2) throws SerializeException {
assertEqualObjects(o1, o2, false);
}
/**
* Verifies that two objects are equivalent.
* Does this by doing a string comparison after converting both to JSON.
* @param sort If <jk>true</jk> sort maps and collections before comparison.
*/
public static void assertEqualObjects(Object o1, Object o2, boolean sort) throws SerializeException {
JsonSerializer s = (sort ? jsSorted : js);
String s1 = s.serialize(o1);
String s2 = s.serialize(o2);
if (s1.equals(s2))
return;
throw new ComparisonFailure(null, s1, s2);
}
/**
* Validates that the whitespace is correct in the specified XML.
*/
public static void checkXmlWhitespace(String out) throws SerializeException {
if (out.indexOf('\u0000') != -1) {
for (String s : out.split("\u0000"))
checkXmlWhitespace(s);
return;
}
int indent = -1;
Pattern startTag = Pattern.compile("^(\\s*)<[^/>]+(\\s+\\S+=['\"]\\S*['\"])*\\s*>$");
Pattern endTag = Pattern.compile("^(\\s*)</[^>]+>$");
Pattern combinedTag = Pattern.compile("^(\\s*)<[^>/]+(\\s+\\S+=['\"]\\S*['\"])*\\s*/>$");
Pattern contentOnly = Pattern.compile("^(\\s*)[^\\s\\<]+$");
Pattern tagWithContent = Pattern.compile("^(\\s*)<[^>]+>.*</[^>]+>$");
String[] lines = out.split("\n");
try {
for (int i = 0; i < lines.length; i++) {
String line = lines[i];
Matcher m = startTag.matcher(line);
if (m.matches()) {
indent++;
if (m.group(1).length() != indent)
throw new SerializeException("Wrong indentation detected on start tag line ''{0}''", i+1);
continue;
}
m = endTag.matcher(line);
if (m.matches()) {
if (m.group(1).length() != indent)
throw new SerializeException("Wrong indentation detected on end tag line ''{0}''", i+1);
indent--;
continue;
}
m = combinedTag.matcher(line);
if (m.matches()) {
indent++;
if (m.group(1).length() != indent)
throw new SerializeException("Wrong indentation detected on combined tag line ''{0}''", i+1);
indent--;
continue;
}
m = contentOnly.matcher(line);
if (m.matches()) {
indent++;
if (m.group(1).length() != indent)
throw new SerializeException("Wrong indentation detected on content-only line ''{0}''", i+1);
indent--;
continue;
}
m = tagWithContent.matcher(line);
if (m.matches()) {
indent++;
if (m.group(1).length() != indent)
throw new SerializeException("Wrong indentation detected on tag-with-content line ''{0}''", i+1);
indent--;
continue;
}
throw new SerializeException("Unmatched whitespace line at line number ''{0}''", i+1);
}
if (indent != -1)
throw new SerializeException("Possible unmatched tag. indent=''{0}''", indent);
} catch (SerializeException e) {
printLines(lines);
throw e;
}
}
private static void printLines(String[] lines) {
for (int i = 0; i < lines.length; i++)
System.err.println(String.format("%4s:" + lines[i], i+1)); // NOT DEBUG
}
/**
* Validates that the specified XML conforms to the specified schema.
*/
private static void validateXml(String xml, String xmlSchema) throws Exception {
// parse an XML document into a DOM tree
DocumentBuilderFactory f = DocumentBuilderFactory.newInstance();
f.setNamespaceAware(true);
DocumentBuilder documentBuilder = f.newDocumentBuilder();
Document document = documentBuilder.parse(new InputSource(new StringReader(xml)));
// create a SchemaFactory capable of understanding WXS schemas
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
if (xmlSchema.indexOf('\u0000') != -1) {
// Break it up into a map of namespaceURI->schema document
final Map<String,String> schemas = new HashMap<String,String>();
String[] ss = xmlSchema.split("\u0000");
xmlSchema = ss[0];
for (String s : ss) {
Matcher m = pTargetNs.matcher(s);
if (m.find())
schemas.put(m.group(1), s);
}
// Create a custom resolver
factory.setResourceResolver(
new LSResourceResolver() {
@Override /* LSResourceResolver */
public LSInput resolveResource(String type, String namespaceURI, String publicId, String systemId, String baseURI) {
String schema = schemas.get(namespaceURI);
if (schema == null)
throw new RuntimeException(MessageFormat.format("No schema found for namespaceURI ''{0}''", namespaceURI));
try {
DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
DOMImplementationLS domImplementationLS = (DOMImplementationLS)registry.getDOMImplementation("LS 3.0");
LSInput in = domImplementationLS.createLSInput();
in.setCharacterStream(new StringReader(schema));
in.setSystemId(systemId);
return in;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
);
}
Schema schema = factory.newSchema(new StreamSource(new StringReader(xmlSchema)));
// create a Validator instance, which can be used to validate an instance document
Validator validator = schema.newValidator();
// validate the DOM tree
validator.validate(new DOMSource(document));
}
private static Pattern pTargetNs = Pattern.compile("targetNamespace=['\"]([^'\"]+)['\"]");
public static void validateXml(Object o) throws Exception {
validateXml(o, XmlSerializer.DEFAULT_NS_SQ);
}
/**
* Test whitespace and generated schema.
*/
public static void validateXml(Object o, XmlSerializer s) throws Exception {
s = s.builder().ws().ns().addNamespaceUrisToRoot(true).build();
String xml = s.serialize(o);
String xmlSchema = null;
try {
xmlSchema = s.getSchemaSerializer().serialize(o);
TestUtils.checkXmlWhitespace(xml);
TestUtils.checkXmlWhitespace(xmlSchema);
TestUtils.validateXml(xml, xmlSchema);
} catch (Exception e) {
System.err.println("---XML---"); // NOT DEBUG
System.err.println(xml); // NOT DEBUG
System.err.println("---XMLSchema---"); // NOT DEBUG
System.err.println(xmlSchema); // NOT DEBUG
throw e;
}
}
/**
* Reads the specified file at the specified path.
* Removes '\r' characters.
* Remove license headers.
*/
public static String readFile(String path) throws Exception {
InputStream is = TestUtils.class.getResourceAsStream(path);
if (is == null) {
is = new FileInputStream(path);
}
String e = IOUtils.read(is);
e = e.replaceAll("\r", "");
if (path.endsWith(".xml")) {
e = e.replaceAll("(?s)\\<\\!\\-\\-(.*)\\-\\-\\>\\s*", "");
e = e.replaceAll("\\<\\?.*\\?\\>\\s*", "");
} else if (path.endsWith(".json")) {
e = e.replaceAll("\\/\\/ \\*.*\\s*", "");
}
return e;
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String toHex(byte b) {
char[] c = new char[2];
int v = b & 0xFF;
c[0] = hexArray[v >>> 4];
c[1] = hexArray[v & 0x0F];
return new String(c);
}
public static void debugOut(Object o) {
try {
System.err.println(StringUtils.decodeHex(JsonSerializer.DEFAULT_LAX.serialize(o))); // NOT DEBUG
} catch (SerializeException e) {
e.printStackTrace();
}
}
/**
* Sort an XML document by element and attribute names.
* This method is primarily meant for debugging purposes.
*/
private static final String sortXml(String xml) throws Exception {
xml = xml.replaceAll("\\w+\\:", "").replaceAll(">\\s+<", "><"); // Strip out all namespaces and whitespace.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new InputSource(new StringReader(xml)));
SortedNode n = new SortedNode(doc.getDocumentElement());
return n.toString();
}
/**
* A sorted node in a DOM tree.
*/
private static class SortedNode implements Comparable<SortedNode> {
public String name, text="", attrs="";
public List<SortedNode> children = new LinkedList<SortedNode>();
SortedNode(Element e) {
this.name = e.getNodeName();
NamedNodeMap attrs = e.getAttributes();
if (attrs != null) {
StringBuilder sb = new StringBuilder();
Set<String> attrNames = new TreeSet<String>();
for (int i = 0; i < attrs.getLength(); i++)
attrNames.add(attrs.item(i).getNodeName());
for (String n : attrNames) {
Node node = attrs.getNamedItem(n);
sb.append(" ").append(n).append("='").append(node.getNodeValue()).append("'");
}
this.attrs = sb.toString();
}
NodeList nl = e.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node n = nl.item(i);
if (n instanceof Element)
children.add(new SortedNode((Element)nl.item(i)));
if (n instanceof Text)
this.text += ((Text)n).getNodeValue();
}
Collections.sort(children);
}
@Override
public int compareTo(SortedNode n) {
int i = name.compareTo(n.name);
if (i != 0)
return i;
i = attrs.compareTo(n.attrs);
if (i != 0)
return i;
i = text.compareTo(n.text);
if (i != 0)
return i;
return 0;
}
@Override
public String toString() {
return toString(0, new StringBuilder()).toString();
}
public StringBuilder toString(int depth ,StringBuilder sb) {
indent(depth, sb).append("<").append(name).append(attrs);
if (children.isEmpty() && text.isEmpty()) {
sb.append("/>\n");
return sb;
}
sb.append(">\n");
if (! text.isEmpty())
indent(depth+1, sb).append(text).append("\n");
for (SortedNode c : children) {
c.toString(depth+1, sb);
}
indent(depth, sb).append("</").append(name).append(">\n");
return sb;
}
}
private static StringBuilder indent(int depth, StringBuilder sb) {
for (int i = 0; i < depth; i++)
sb.append("\t");
return sb;
}
/**
* Compares two XML documents for equality.
* Namespaces are stripped from each and elements/attributes are ordered in alphabetical order,
* then a simple string comparison is performed.
*/
public static final void assertXmlEquals(String expected, String actual) throws Exception {
Assert.assertEquals(sortXml(expected), sortXml(actual));
}
/**
* Assert that the object equals the specified string after running it through JsonSerializer.DEFAULT_LAX.toString().
*/
public static void assertObjectEquals(String s, Object o) {
assertObjectEquals(s, o, js2);
}
/**
* Assert that the object equals the specified string after running it through JsonSerializer.DEFAULT_LAX.toString()
* with BEAN_sortProperties set to true.
*/
public static void assertSortedObjectEquals(String s, Object o) {
assertObjectEquals(s, o, js3);
}
/**
* Assert that the object equals the specified string after running it through ws.toString().
*/
public static void assertObjectEquals(String s, Object o, WriterSerializer ws) {
Assert.assertEquals(s, ws.toString(o));
}
/**
* Replaces all newlines with pipes, then compares the strings.
*/
public static void assertTextEquals(String s, Object o) {
String s2 = o.toString().replaceAll("\\r?\\n", "|");
Assert.assertEquals(s, s2);
}
public static String toReadableBytes(byte[] b) {
StringBuilder sb = new StringBuilder();
for (byte b2 : b)
sb.append((b2 < ' ' || b2 > 'z') ? String.format("[%02X]", b2) : (char)b2 + " ");
sb.append("\n");
for (byte b2 : b)
sb.append(String.format("[%02X]", b2));
return sb.toString();
}
public static String toReadableBytes2(byte[] b) {
StringBuilder sb = new StringBuilder();
for (byte b2 : b)
sb.append(String.format("%02X ", b2));
return sb.toString().trim();
}
/**
* Tries to turn the serialized output to a String.
* If it's a byte[], convert it to a UTF-8 encoded String.
*/
public static String toString(Object o) {
if (o instanceof String)
return (String)o;
try {
return new String((byte[])o, "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
private static ThreadLocal<TimeZone> systemTimeZone = new ThreadLocal<TimeZone>();
private static ThreadLocal<Locale> systemLocale = new ThreadLocal<Locale>();
/**
* Temporarily sets the default system timezone to the specified timezone ID.
* Use {@link #unsetTimeZone()} to unset it.
*
* @param name
*/
public static void setTimeZone(String name) {
systemTimeZone.set(TimeZone.getDefault());
TimeZone.setDefault(TimeZone.getTimeZone(name));
}
public static void unsetTimeZone() {
TimeZone.setDefault(systemTimeZone.get());
}
/**
* Temporarily sets the default system locale to the specified locale.
* Use {@link #unsetLocale()} to unset it.
*
* @param name
*/
public static void setLocale(Locale locale) {
systemLocale.set(Locale.getDefault());
Locale.setDefault(locale);
}
public static void unsetLocale() {
Locale.setDefault(systemLocale.get());
}
public static void assertEqualsAfterSort(String expected, String actual, String msg, Object...args) {
String[] e = expected.trim().split("\n"), a = actual.trim().split("\n");
if (e.length != a.length)
throw new ComparisonFailure(MessageFormat.format(msg, args), expected, actual);
Arrays.sort(e);
Arrays.sort(a);
for (int i = 0; i < e.length; i++)
if (! e[i].equals(a[i]))
throw new ComparisonFailure(MessageFormat.format(msg, args), expected, actual);
}
/**
* Same as {@link Assert#assertEquals(String,String,String) except takes in a MessageFormat-style message.
*/
public static void assertEquals(String expected, String actual, String msg, Object...args) {
if (! StringUtils.isEquals(expected, actual))
throw new ComparisonFailure(MessageFormat.format(msg, args), expected, actual);
}
/**
* Creates a ClassMeta for the given types.
*/
public static Type getType(Type type, Type...args) {
return beanSession.getClassMeta(type, args);
}
/**
* Throws an AssertionError if the object isn't of the specified type.
*/
public static void assertType(Class<?> type, Object o) {
if (type.isInstance(o))
return;
throw new AssertionError(new StringMessage("Expected type {0} but was {1}", type, (o == null ? null : o.getClass())));
}
}