JsonToy/lib/src/main/java/nl/sanderhautvast/json/ser/Mapper.java
2023-06-25 15:40:34 +02:00

403 lines
13 KiB
Java

package nl.sanderhautvast.json.ser;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.temporal.Temporal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Mapper.json(StringBuilder b, ...)
* TODO write to outputstream
*/
public class Mapper {
private static final Map<Class<?>, BaseMapper<?>> mappers = new ConcurrentHashMap<>();
private static final ByteClassLoader generatedClassesLoader = new ByteClassLoader();
/**
* Add a new (custom) mapper implementation for the specified type
*
* @param type The class to serialize to json
* @param mapper the Mapper implementation
*/
@SuppressWarnings("unused")
public static <T> void addMapper(Class<T> type, BaseMapper<T> mapper) {
mappers.put(type, mapper);
}
/**
* returns the json representation of the value as a String
*/
public static String json(Object value) {
StringBuilder b = new StringBuilder(128);
json(b, value);
return b.toString();
}
@SuppressWarnings({"unchecked", "rawtypes", "UnnecessaryToStringCall"})
public static void json(StringBuilder b, Object value) {
if (value == null) {
b.append("null");
} else {
Class<?> type = value.getClass();
if (type.isArray()) {
if (value instanceof byte[]) {
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) {
list(b, (Collection) value);
} else if (value instanceof Map) {
object(b, (Map) value);
} else {
if (type == String.class) {
b.append("\"");
Mapper.escape(b, (String) value);
b.append("\"");
} else if (type == Character.class) {
b.append("\"");
Mapper.escape(b, (Character) value);
b.append("\"");
} else if (type == UUID.class || value instanceof Temporal || type.isEnum()) {
b.append("\"");
b.append(value.toString());
b.append("\"");
} else if (type == Boolean.class
|| type == Integer.class
|| type == Long.class
|| type == Float.class
|| type == Double.class
|| type == Byte.class
|| type == Short.class
|| type == BigInteger.class
|| type == BigDecimal.class
) {
b.append(value.toString()); // prevents another nullcheck
} else {
BaseMapper mapper = mappers.computeIfAbsent(type, key -> createObjectMapper(type));
mapper.json(b, value);
}
}
}
}
private static void array(StringBuilder b, Object[] array) {
if (array.length == 0) {
b.append("[]");
} else {
Object first = array[0];
b.append("[");
Mapper.json(b, first);
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("]");
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void list(StringBuilder b, Collection list) {
if (list.isEmpty()) {
b.append("[]");
} else {
Object first = list.iterator().next();
b.append("[");
Mapper.json(b, first);
list.stream().skip(1)
.forEach(element -> {
b.append(",");
Mapper.json(b, element);
});
b.append("]");
}
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static void object(StringBuilder b, Map map) {
if (map.isEmpty()) {
b.append("{}");
} else {
b.append("{\"");
Set<Map.Entry> entries = map.entrySet();
Map.Entry first = entries.iterator().next();
Object key = first.getKey();
if (key instanceof String) {
escape(b, (String) key);
} else if (key instanceof Character) {
escape(b, (Character) key);
} else {
b.append(key);
}
b.append("\":");
Mapper.json(b, first.getValue());
entries.stream().skip(1)
.forEach(entry -> {
b.append(",\"");
b.append(entry.getKey()).append("\":");
Mapper.json(b, entry.getValue());
});
b.append("}");
}
}
public static void json(StringBuilder b, byte value) {
b.append(value);
}
public static void json(StringBuilder b, boolean value) {
b.append(value);
}
public static void json(StringBuilder b, short value) {
b.append(value);
}
public static void json(StringBuilder b, int value) {
b.append(value);
}
public static void json(StringBuilder b, long value) {
b.append(value);
}
public static void json(StringBuilder b, char value) {
b.append("\"");
escape(b, value);
b.append("\"");
}
public static void json(StringBuilder b, float value) {
b.append(value);
}
public static void json(StringBuilder b, double value) {
b.append(value);
}
@SuppressWarnings("unchecked")
private static <T> BaseMapper<T> createObjectMapper(Class<T> forType) {
try {
ClassReader cr = new ClassReader(forType.getName());
MapperFactory mapperFactory = new MapperFactory();
cr.accept(mapperFactory, 0);
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
mapperFactory.classNode.accept(classWriter);
byte[] byteArray = classWriter.toByteArray();
generatedClassesLoader.addClass(mapperFactory.classNode.name, byteArray);
return (BaseMapper<T>) generatedClassesLoader.loadClass(mapperFactory.classNode.name).getConstructor().newInstance();
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException |
IllegalAccessException | InvocationTargetException e) {
throw new JsonError(e);
}
}
static void escape(StringBuilder b, char c) {
escape(b, String.valueOf(c));
}
static void escape(StringBuilder b, String value) {
int offset = b.length();
b.append(value);
int i = offset;
while (i < b.length()) {
char c = b.charAt(i);
switch (c) {
case '\t':
b.replace(i, i + 1, "\\");
b.insert(i + 1, "t");
break;
case '\"':
b.replace(i, i + 1, "\\");
b.insert(++i, "\"");
break;
case '/':
b.replace(i, i + 1, "\\");
b.insert(++i, "/");
break;
case '\r':
b.replace(i, i + 1, "\\");
b.insert(++i, "r");
break;
case '\n':
b.replace(i, i + 1, "\\");
b.insert(++i, "n");
break;
case '\b':
b.replace(i, i + 1, "\\");
b.insert(++i, "b");
break;
case '\f':
b.replace(i, i + 1, "\\");
b.insert(++i, "f");
break;
case '\\':
b.replace(i, i + 1, "\\");
b.insert(++i, "\\");
break;
case '\'':
break;
default:
if ((c <= '\u001F') || (c >= '\u007F' && c <= '\u009F') || (c >= '\u2000' && c <= '\u20FF')) {
String ss = Integer.toHexString(c);
b.replace(i, i + 1, "\\");
b.insert(++i, "u");
for (int k = 0; k < 4 - ss.length(); k++) {
b.insert(++i, '0');
}
b.insert(++i, ss.toUpperCase());
}
}
i++;
}
}
}