1.7 generic array handling, updated escaping

This commit is contained in:
Shautvast 2023-07-15 18:18:05 +02:00
parent f483844d65
commit 0a02524d86
6 changed files with 163 additions and 214 deletions

View file

@ -5,12 +5,12 @@
<parent> <parent>
<groupId>nl.sander</groupId> <groupId>nl.sander</groupId>
<artifactId>jsonthingy-pom</artifactId> <artifactId>jsonthingy-pom</artifactId>
<version>1.6</version> <version>1.7</version>
</parent> </parent>
<name>JsonToy-JMH</name> <name>JsonToy-JMH</name>
<artifactId>jsonthingy-jmhtests</artifactId> <artifactId>jsonthingy-jmhtests</artifactId>
<version>1.6</version> <version>1.7</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View file

@ -0,0 +1,79 @@
package nl.sanderhautvast.json.jmh;
import org.openjdk.jmh.annotations.*;
import java.lang.reflect.Array;
import java.util.concurrent.TimeUnit;
@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class ArrayReflectionBenchmarks {
private static final int ITERATIONS = 10;
public static void main(String[] args) {
new ArrayReflectionBenchmarks().testReflectiveArray();
}
@Benchmark
public void testReflectiveArray() {
int[] r1 = {1};
int[] r2 = {1, 2};
int[][] table1 = {r1, r2};
int[][] table2 = {r1, r2};
int[][][] schema = {table1, table2};
for (int i = 0; i < ITERATIONS; i++) {
addArrayElements(schema);
}
}
@Benchmark
public void testNonReflectiveArray() {
int[] r1 = {1};
int[] r2 = {1, 2};
int[][] table1 = {r1, r2};
int[][] table2 = {r1, r2};
int[][][] schema = {table1, table2};
for (int i = 0; i < ITERATIONS; i++) {
addIntegerArray(schema);
}
}
private int addArrayElements(Object o) {
int sum = 0;
if (o.getClass().isArray()) {
int length = Array.getLength(o);
for (int i = 0; i < length; i++) {
sum += addArrayElements(Array.get(o, i));
}
} else {
sum += (Integer) o;
}
return sum;
}
private int addIntegerArray(int[][][] o) {
int sum = 0;
for (int[][] ints : o) {
sum += addIntegerArray2(ints);
}
return sum;
}
private int addIntegerArray2(int[][] o) {
int sum = 0;
for (int[] ints : o) {
sum += addIntegerArray3(ints);
}
return sum;
}
private int addIntegerArray3(int[] o) {
int sum = 0;
for (int j : o) {
sum += j;
}
return sum;
}
}

View file

@ -8,14 +8,14 @@ import org.openjdk.jmh.annotations.*;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@State(Scope.Thread) //@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime) //@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS) //@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Benchmarks { public class Benchmarks {
private static final int ITERATIONS = 10; private static final int ITERATIONS = 10;
@Benchmark // @Benchmark
public void testJson() { public void testJson() {
Bean1 bean1; Bean1 bean1;
Bean2 bean2; Bean2 bean2;
@ -30,7 +30,7 @@ public class Benchmarks {
} }
} }
@Benchmark // @Benchmark
public void testJackson() throws JsonProcessingException { public void testJackson() throws JsonProcessingException {
Bean1 bean1; Bean1 bean1;
Bean2 bean2; Bean2 bean2;

View file

@ -5,12 +5,12 @@
<parent> <parent>
<groupId>nl.sander</groupId> <groupId>nl.sander</groupId>
<artifactId>jsonthingy-pom</artifactId> <artifactId>jsonthingy-pom</artifactId>
<version>1.6</version> <version>1.7</version>
</parent> </parent>
<name>JsonToy</name> <name>JsonToy</name>
<artifactId>jsonthingy</artifactId> <artifactId>jsonthingy</artifactId>
<version>1.6</version> <version>1.7</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<properties> <properties>

View file

@ -4,6 +4,7 @@ import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter; import org.objectweb.asm.ClassWriter;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.BigInteger; import java.math.BigInteger;
@ -20,6 +21,15 @@ public class Mapper {
private static final Map<Class<?>, BaseMapper<?>> mappers = new ConcurrentHashMap<>(); private static final Map<Class<?>, BaseMapper<?>> mappers = new ConcurrentHashMap<>();
private static final ByteClassLoader generatedClassesLoader = new ByteClassLoader(); private static final ByteClassLoader generatedClassesLoader = new ByteClassLoader();
public static final char[] TAB = {'\\', 't'};
public static final char[] DOUBLEQUOTE = {'\\', '\"'};
public static final char[] SLASH = {'\\', '/'};
public static final char[] RETURN = {'\\', 'r'};
public static final char[] BACKSLASH = {'\\', '\\'};
public static final char[] NEWLINE = {'\\', 'n'};
public static final char[] BELL = {'\\', 'b'};
public static final char[] FORMFEED = {'\\', 'f'};
private static final char[][] MAP = createEscapeMap();
/** /**
@ -49,25 +59,7 @@ public class Mapper {
} else { } else {
Class<?> type = value.getClass(); Class<?> type = value.getClass();
if (type.isArray()) { if (type.isArray()) {
if (value instanceof byte[]) { array(b, value);
array(b, (byte[]) value);
} else if (value instanceof int[]) {
array(b, (int[]) value);
} else if (value instanceof short[]) {
array(b, (short[]) value);
} else if (value instanceof boolean[]) {
array(b, (boolean[]) value);
} else if (value instanceof char[]) {
array(b, (char[]) value);
} else if (value instanceof long[]) {
array(b, (long[]) value);
} else if (value instanceof float[]) {
array(b, (float[]) value);
} else if (value instanceof double[]) {
array(b, (double[]) value);
} else {
array(b, (Object[]) value);
}
} else if (value instanceof Collection) { } else if (value instanceof Collection) {
list(b, (Collection) value); list(b, (Collection) value);
} else if (value instanceof Map) { } else if (value instanceof Map) {
@ -104,140 +96,16 @@ public class Mapper {
} }
} }
private static void array(StringBuilder b, Object[] array) { //TODO make this more performant
if (array.length == 0) { private static void array(StringBuilder b, Object value) {
b.append("[]"); b.append("[");
} else { StringJoiner joiner = new StringJoiner(",");
Object first = array[0]; for (int i = 0; i < Array.getLength(value); i++) {
b.append("["); Object arrayElement = Array.get(value, i);
Mapper.json(b, first); joiner.add(Mapper.json(arrayElement)); // recursie
Arrays.stream(array).skip(1)
.forEach(element -> {
b.append(",");
Mapper.json(b, element);
});
b.append("]");
}
}
private static void array(StringBuilder b, byte[] array) {
if (array.length == 0) {
b.append("[]");
} else {
byte first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, short[] array) {
if (array.length == 0) {
b.append("[]");
} else {
short first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, long[] array) {
if (array.length == 0) {
b.append("[]");
} else {
long first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, boolean[] array) {
if (array.length == 0) {
b.append("[]");
} else {
boolean first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, double[] array) {
if (array.length == 0) {
b.append("[]");
} else {
double first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, char[] array) {
if (array.length == 0) {
b.append("[]");
} else {
char first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, float[] array) {
if (array.length == 0) {
b.append("[]");
} else {
float first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
}
}
private static void array(StringBuilder b, int[] array) {
if (array.length == 0) {
b.append("[]");
} else {
int first = array[0];
b.append("[");
json(b, first);
for (int i = 1; i < array.length; i++) {
b.append(",");
json(b, array[i]);
}
b.append("]");
} }
b.append(joiner);
b.append("]");
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
@ -342,61 +210,63 @@ public class Mapper {
escape(b, String.valueOf(c)); escape(b, String.valueOf(c));
} }
static void escape(StringBuilder b, String value) { private static char[][] createEscapeMap() {
int offset = b.length(); char[][] charmap = new char[0x2100][];
b.append(value); for (int i = 0; i < 0x2100; i++) {
int i = offset; char c = (char) i;
while (i < b.length()) { char[] replacement;
char c = b.charAt(i); if (c == '\t') {
switch (c) { replacement = TAB;
case '\t': } else if (c == '\"') {
b.replace(i, i + 1, "\\"); replacement = DOUBLEQUOTE;
b.insert(i + 1, "t"); } else if (c == '/') {
break; replacement = SLASH;
case '\"': } else if (c == '\r') {
b.replace(i, i + 1, "\\"); replacement = RETURN;
b.insert(++i, "\""); } else if (c == '\\') {
break; replacement = BACKSLASH;
case '/': } else if (c == '\n') {
b.replace(i, i + 1, "\\"); replacement = NEWLINE;
b.insert(++i, "/"); } else if (c == '\b') {
break; replacement = BELL;
case '\r': } else if (c == '\f') {
b.replace(i, i + 1, "\\"); replacement = FORMFEED;
b.insert(++i, "r"); } else if ((c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000')) {
break; replacement = new char[6];
case '\n': replacement[0] = '\\';
b.replace(i, i + 1, "\\"); replacement[1] = 'u';
b.insert(++i, "n");
break; String hex = Integer.toHexString(c).toUpperCase();
case '\b': int hexlen = hex.length();
b.replace(i, i + 1, "\\"); for (int k = 0; k < 4 - hexlen; k++) {
b.insert(++i, "b"); replacement[k + 2] = '0';
break; }
case '\f': for (int k = 0; k < hexlen; k++) {
b.replace(i, i + 1, "\\"); replacement[6 - hexlen + k] = hex.charAt(k);
b.insert(++i, "f"); }
break; } else {
case '\\': replacement = new char[]{c};
b.replace(i, i + 1, "\\"); }
b.insert(++i, "\\"); charmap[i] = replacement;
break; }
case '\'':
break; return charmap;
default: }
if ((c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000' && c <= '\u20FF')) {
String ss = Integer.toHexString(c); //both methods are equally slow it seems
b.replace(i, i + 1, "\\"); //mainly because we can't do a batch copy into the stringbuilder and have to map every character individually
b.insert(++i, "u"); static void escape(StringBuilder b, String value) {
for (int k = 0; k < 4 - ss.length(); k++) { for (int i = 0; i < value.length(); i++) {
b.insert(++i, '0'); int c = value.charAt(i);
}
b.insert(++i, ss.toUpperCase()); if (c < 0x2100) {
} b.append(MAP[c]);
} else {
b.append((char) c);
} }
i++;
} }
} }
} }

View file

@ -5,7 +5,7 @@
<name>JsonToy</name> <name>JsonToy</name>
<groupId>nl.sander</groupId> <groupId>nl.sander</groupId>
<artifactId>jsonthingy-pom</artifactId> <artifactId>jsonthingy-pom</artifactId>
<version>1.6</version> <version>1.7</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<modules> <modules>