NameCoderBenchmark.java
/*
* Copyright (C) 2015, 2017 XStream Committers.
* All rights reserved.
*
* The software in this package is published under the terms of the BSD
* style license a copy of which has been included with this distribution in
* the LICENSE.txt file.
*
* Created on 16. December 2015 by Joerg Schaible, renamed from XmlFriendlyBenchmark
*/
package com.thoughtworks.xstream.benchmark.jmh;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.BenchmarkParams;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.naming.NameCoder;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.Xpp3Driver;
import com.thoughtworks.xstream.security.ArrayTypePermission;
import com.thoughtworks.xstream.security.NoTypePermission;
import com.thoughtworks.xstream.security.PrimitiveTypePermission;
/**
* Benchmark for different {@link NameCoder} implementations.
*
* @author Jörg Schaible
* @since 1.4.9
*/
@BenchmarkMode(Mode.AverageTime)
@Fork(value = 1)
@Measurement(iterations = 25)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Threads(4)
@Warmup(iterations = 5)
public class NameCoderBenchmark {
private XStream xstream;
private String xml;
private _1._2._3._4._5.Unfriendly array[];
/**
* No encoding, will create invalid XML for inner class types.
*
* @since 1.4.9
*/
public static final class NoNameCoder implements NameCoder {
@Override
public String encodeNode(final String name) {
return name;
}
@Override
public String encodeAttribute(final String name) {
return name;
}
@Override
public String decodeNode(final String nodeName) {
return nodeName;
}
@Override
public String decodeAttribute(final String attributeName) {
return attributeName;
}
}
/**
* Dollar encoding, will create invalid XML for class types in the default package.
*
* @since 1.4.9
*/
public static final class DollarNameCoder implements NameCoder {
@Override
public String encodeNode(final String name) {
return name.replace('$', '\u00b7');
}
@Override
public String encodeAttribute(final String name) {
return name.replace('$', '\u00b7');
}
@Override
public String decodeNode(final String nodeName) {
return nodeName.replace('\u00b7', '$');
}
@Override
public String decodeAttribute(final String attributeName) {
return attributeName.replace('\u00b7', '$');
}
}
/**
* Dollar encoding with an escaped underscore, may create invalid XML for class types defined in other languages
* running on the JVM.
*
* @since 1.4.9
*/
public static class EscapedUnderscoreNameCoder implements NameCoder {
@Override
public String encodeNode(final String name) {
final int length = name.length();
final StringBuilder sb = new StringBuilder(length + 20);
for (int i = 0; i < length; ++i) {
final char ch = name.charAt(i);
switch (ch) {
case '$':
sb.append("_-");
break;
case '_':
sb.append("__");
break;
default:
sb.append(ch);
}
}
return sb.toString();
}
@Override
public String encodeAttribute(final String name) {
return encodeNode(name);
}
@Override
public String decodeNode(final String nodeName) {
final int length = nodeName.length();
final StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; ++i) {
char ch = nodeName.charAt(i);
if (ch == '_') {
if (++i == length) {
throw new IllegalStateException();
}
ch = nodeName.charAt(i);
switch (ch) {
case '_':
sb.append(ch);
break;
case '-':
sb.append('$');
break;
default:
throw new IllegalStateException();
}
} else {
sb.append(ch);
}
}
return sb.toString();
}
@Override
public String decodeAttribute(final String attributeName) {
return decodeNode(attributeName);
}
}
/**
* Cached dollar encoding with an escaped underscore, may create invalid XML for class types defined in other
* languages running on the JVM.
*
* @since 1.4.9
*/
public static class CachedEscapedUnderscoreNameCoder extends EscapedUnderscoreNameCoder {
private final ConcurrentMap<String, String> encoderCache = new ConcurrentHashMap<>();
private final ConcurrentMap<String, String> decoderCache = new ConcurrentHashMap<>();
@Override
public String encodeNode(final String name) {
String encoded = encoderCache.get(name);
if (encoded == null) {
encoded = super.encodeNode(name);
encoderCache.putIfAbsent(name, encoded);
decoderCache.putIfAbsent(encoded, name);
}
return encoded;
}
@Override
public String decodeNode(final String nodeName) {
String decoded = decoderCache.get(nodeName);
if (decoded == null) {
decoded = super.decodeNode(nodeName);
decoderCache.putIfAbsent(nodeName, decoded);
encoderCache.putIfAbsent(decoded, nodeName);
}
return decoded;
}
}
private static class _1 {
private static class _2 {
private static class _3 {
private static class _4 {
private static class _5 {
private static class Unfriendly {
@SuppressWarnings("unused")
final int __i__i__;
@SuppressWarnings("unused")
final String x__x__;
@SuppressWarnings("unused")
final Unfriendly __;
public Unfriendly(final int i, final Unfriendly u) {
__i__i__ = i;
x__x__ = Integer.toHexString(i);
__ = u;
}
}
}
}
}
}
}
/**
* Initialize the XML string to deserialize.
*
* @since 1.4.9
*/
@Setup
public void init() {
array = new _1._2._3._4._5.Unfriendly[250];
for (int i = 0; i < array.length / 10; ++i) {
final int idx = i * 10;
for (int j = 0; j < 10; ++j) {
array[idx] = new _1._2._3._4._5.Unfriendly(idx + 9 - j, array[idx]);
if (j < 9) {
array[idx + j + 1] = array[idx];
}
}
}
}
/**
* Setup the data to deserialize.
*
* @param params the parameters of the benchmark
* @since 1.4.9
*/
@Setup(Level.Trial)
public void setUp(final BenchmarkParams params) {
final String benchmark = params.getBenchmark();
final NameCoder nameCoder;
switch (benchmark.substring(NameCoderBenchmark.class.getName().length() + 1)) {
case "noCoding":
nameCoder = new NoNameCoder();
break;
case "dollarCoding":
nameCoder = new DollarNameCoder();
break;
case "escapedUnderscoreCoding":
nameCoder = new EscapedUnderscoreNameCoder();
break;
case "cachedEscapedUnderscoreCoding":
nameCoder = new CachedEscapedUnderscoreNameCoder();
break;
case "xmlFriendlyCoding":
nameCoder = new XmlFriendlyNameCoder();
break;
default:
throw new IllegalStateException("Unsupported benchmark type: " + benchmark);
}
xstream = new XStream(new Xpp3Driver(nameCoder));
xstream.addPermission(NoTypePermission.NONE);
xstream.addPermission(ArrayTypePermission.ARRAYS);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypes(_1._2._3._4._5.Unfriendly.class, String.class);
if (nameCoder.getClass() == NoNameCoder.class) {
xstream.alias(_1._2._3._4._5.Unfriendly.class.getName().replace('$', '\u00b7'),
_1._2._3._4._5.Unfriendly.class);
}
xml = xstream.toXML(array);
// System.out.println(xstream.toXML(array[0]));
}
/**
* No encoding, will produce invalid XML for inner class types.
*
* @since 1.4.9
*/
@Benchmark
public void noCoding() {
run();
}
/**
* Dollar encoding, will produce invalid XML for class types in the default package.
*
* @since 1.4.9
*/
@Benchmark
public void dollarCoding() {
run();
}
/**
* Escaped underscore encoding, can encode any Java identifier.
*
* @since 1.4.9
*/
@Benchmark
public void escapedUnderscoreCoding() {
run();
}
/**
* Escaped underscore encoding with caching, can encode any Java identifier.
*
* @since 1.4.9
*/
@Benchmark
public void cachedEscapedUnderscoreCoding() {
run();
}
/**
* XML friendly encoding used by XStream as default, can encode any invalid XML character.
*
* @since 1.4.9
*/
@Benchmark
public void xmlFriendlyCoding() {
run();
}
private void run() {
final String x = xstream.toXML(xstream.fromXML(xml));
assert x.equals(xml) : "XML differs";
}
}