JsonFormatterBack.java
package space.sunqian.fs.data.json;
import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.fs.Fs;
import space.sunqian.fs.base.chars.CharsKit;
import space.sunqian.fs.data.DataFormattingException;
import space.sunqian.fs.io.IOKit;
import space.sunqian.fs.object.annotation.DatePattern;
import space.sunqian.fs.object.annotation.NumPattern;
import space.sunqian.fs.object.convert.ConvertKit;
import space.sunqian.fs.object.convert.ObjectConverter;
import space.sunqian.fs.object.schema.ObjectProperty;
import space.sunqian.fs.object.schema.ObjectSchema;
import space.sunqian.fs.object.schema.ObjectSchemaParser;
import space.sunqian.fs.utils.codec.Base64Kit;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
final class JsonFormatterBack {
static @Nonnull JsonFormatterImpl defaultFormatter() {
return JsonFormatterImpl.DEFAULT;
}
static @Nonnull JsonFormatterImpl newFormatter(boolean ignoreNullValue) {
return newFormatter(
ObjectSchemaParser.defaultCachedParser(),
ObjectConverter.defaultConverter(),
ignoreNullValue
);
}
static @Nonnull JsonFormatterImpl newFormatter(
@Nonnull ObjectSchemaParser objectParser,
@Nonnull ObjectConverter objectConverter,
boolean ignoreNullValue
) {
return new JsonFormatterImpl(objectParser, objectConverter, ignoreNullValue);
}
private static final class JsonFormatterImpl implements JsonFormatter {
private static final @Nonnull JsonFormatterImpl DEFAULT = newFormatter(
false
);
private interface Formatter {
void formatTo(
@Nonnull JsonFormatterImpl impl,
@Nonnull Object data,
@Nonnull Appendable appender
) throws Exception;
}
private static final @Nonnull Map<@Nonnull Type, @Nonnull Formatter> FORMAT_MAP;
static {
FORMAT_MAP = new HashMap<>();
FORMAT_MAP.put(String.class, (impl, obj, appender) -> impl.writeString((String) obj, appender));
// FORMAT_MAP.put(boolean.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Boolean.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Byte.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(short.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Short.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(char.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(int.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Integer.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(long.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Long.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(float.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Float.class, (impl, obj, appender) -> appender.append(obj.toString()));
// FORMAT_MAP.put(double.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(Double.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(BigInteger.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(BigDecimal.class, (impl, obj, appender) -> appender.append(obj.toString()));
FORMAT_MAP.put(boolean[].class, (impl, data, appender) -> impl.writeArray((boolean[]) data, appender));
FORMAT_MAP.put(short[].class, (impl, data, appender) -> impl.writeArray((short[]) data, appender));
FORMAT_MAP.put(int[].class, (impl, data, appender) -> impl.writeArray((int[]) data, appender));
FORMAT_MAP.put(long[].class, (impl, data, appender) -> impl.writeArray((long[]) data, appender));
FORMAT_MAP.put(float[].class, (impl, data, appender) -> impl.writeArray((float[]) data, appender));
FORMAT_MAP.put(double[].class, (impl, data, appender) -> impl.writeArray((double[]) data, appender));
// as base64
// FORMAT_MAP.put(byte[].class, (impl, data, appender) ->
// impl.writeString(Base64Kit.encoder().encodeToString((byte[]) data), appender));
// as string
FORMAT_MAP.put(char[].class, (impl, data, appender) ->
impl.writeString(new String((char[]) data), appender));
// as char
FORMAT_MAP.put(Character.class, (impl, obj, appender) -> {
char c = (char) obj;
appender.append('\"');
String escaped = ControlTables.toUnicodeEscape(c);
if (escaped != null) {
appender.append(escaped);
} else {
appender.append(c);
}
appender.append('\"');
});
}
private final @Nonnull ObjectSchemaParser objectParser;
private final @Nonnull ObjectConverter objectConverter;
private final boolean ignoreNullValue;
JsonFormatterImpl(
@Nonnull ObjectSchemaParser objectParser,
@Nonnull ObjectConverter objectConverter,
boolean ignoreNullValue
) {
this.objectParser = objectParser;
this.objectConverter = objectConverter;
this.ignoreNullValue = ignoreNullValue;
}
@Override
public void formatTo(@Nullable Object data, @Nonnull Appendable appender) throws DataFormattingException {
try {
writeAny(data, appender);
} catch (Exception e) {
throw new DataFormattingException(e);
}
}
private void writeAny(@Nullable Object any, @Nonnull Appendable appender) throws Exception {
Object actualValue = preProcess(any);
if (actualValue == null) {
appender.append(Fs.NULL_STRING);
return;
}
// if (any instanceof JsonData) {
// ((JsonData) any).writeTo(appender);
// return;
// }
Formatter formatter = FORMAT_MAP.get(actualValue.getClass());
if (formatter != null) {
formatter.formatTo(this, actualValue, appender);
return;
}
if (actualValue instanceof CharSequence) {
writeString(actualValue.toString(), appender);
return;
}
if (actualValue instanceof Date) {
writeString(actualValue.toString(), appender);
return;
}
if (actualValue instanceof TemporalAccessor) {
writeString(actualValue.toString(), appender);
return;
}
if (actualValue instanceof Enum) {
writeString(actualValue.toString(), appender);
return;
}
if (actualValue instanceof Map<?, ?>) {
writeMap((Map<?, ?>) actualValue, appender);
return;
}
if (actualValue instanceof Iterable<?>) {
writeArray((Iterable<?>) actualValue, appender);
return;
}
if (actualValue instanceof Object[]) {
writeArray((Object[]) actualValue, appender);
return;
}
writeObject(actualValue, appender);
}
private void writeString(@Nonnull String string, @Nonnull Appendable appender) throws Exception {
appender.append('\"');
int s = 0;
for (int i = 0; i < string.length(); i++) {
char c = string.charAt(i);
switch (c) {
case '"':
appender.append(string, s, i);
s = i + 1;
appender.append("\\\"");
continue;
case '\\':
appender.append(string, s, i);
s = i + 1;
appender.append("\\\\");
continue;
default:
String escaped = ControlTables.toUnicodeEscape(c);
if (escaped != null) {
appender.append(string, s, i);
s = i + 1;
appender.append(escaped);
}
}
}
appender.append(string, s, string.length());
appender.append('\"');
}
private void writeMap(@Nonnull Map<?, ?> map, @Nonnull Appendable appender) throws Exception {
appender.append('{');
boolean[] isFirst = {true};
map.forEach((key, value) -> {
Object actualValue = preProcess(value);
if (ignoreNullValue && actualValue == null) {
return;
}
try {
if (isFirst[0]) {
isFirst[0] = false;
} else {
appender.append(',');
}
writeString(String.valueOf(key), appender);
appender.append(':');
writeAny(actualValue, appender);
} catch (Exception e) {
throw new JsonDataException(e);
}
});
appender.append('}');
}
private void writeObject(@Nonnull Object object, @Nonnull Appendable appender) throws Exception {
ObjectSchema schema = objectParser.parse(object.getClass());
appender.append('{');
boolean[] isFirst = {true};
schema.properties().forEach((key, property) -> {
try {
// ignore class
if ("class".equals(property.name())) {
return;
}
Object value = property.getValue(object);
Object actualValue = preProcess(value);
if (ignoreNullValue && actualValue == null) {
return;
}
if (isFirst[0]) {
isFirst[0] = false;
} else {
appender.append(',');
}
writeProperty(property, actualValue, appender);
} catch (Exception e) {
throw new JsonDataException(e);
}
});
appender.append('}');
}
private @Nullable Object preProcess(@Nullable Object any) {
if (any instanceof byte[]) {
if (((byte[]) any).length != 0) {
return Base64Kit.encoder().encodeToString((byte[]) any);
}
return "";
}
if (any instanceof ByteBuffer) {
// as base64
if (((ByteBuffer) any).hasRemaining()) {
return Base64Kit.encoder().encodeToString((ByteBuffer) any);
}
return "";
}
if (any instanceof InputStream) {
// as base64
byte[] bytes = IOKit.read((InputStream) any);
if (bytes != null) {
return Base64Kit.encoder().encodeToString(bytes);
}
return "";
}
if (any instanceof ReadableByteChannel) {
// as base64
byte[] bytes = IOKit.readBytes((ReadableByteChannel) any);
if (bytes != null) {
return Base64Kit.encoder().encodeToString(bytes);
}
return "";
}
return any;
}
private void writeProperty(
@Nonnull ObjectProperty property, @Nullable Object value, @Nonnull Appendable appender
) throws Exception {
writeString(property.name(), appender);
appender.append(':');
if (value != null) {
if (value instanceof Date || value instanceof TemporalAccessor) {
DatePattern datePattern = property.getAnnotation(DatePattern.class);
if (datePattern != null) {
String dateString = objectConverter.convert(
value,
String.class,
ConvertKit.getDateFormatterOption(datePattern)
);
writeString(dateString, appender);
return;
}
}
if (value instanceof Number) {
NumPattern numPattern = property.getAnnotation(NumPattern.class);
if (numPattern != null) {
String numString = objectConverter.convert(
value,
String.class,
ConvertKit.getNumFormatterOption(numPattern)
);
appender.append(numString);
return;
}
}
}
writeAny(value, appender);
}
private void writeArray(@Nonnull Iterable<?> array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (Object object : array) {
if (comma) {
appender.append(',');
}
writeAny(object, appender);
comma = true;
}
appender.append(']');
}
private void writeArray(Object @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (Object object : array) {
if (comma) {
appender.append(',');
}
writeAny(object, appender);
comma = true;
}
appender.append(']');
}
private void writeArray(boolean @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (boolean element : array) {
if (comma) {
appender.append(',');
}
appender.append(Boolean.toString(element));
comma = true;
}
appender.append(']');
}
private void writeArray(short @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (short element : array) {
if (comma) {
appender.append(',');
}
appender.append(String.valueOf(element));
comma = true;
}
appender.append(']');
}
private void writeArray(int @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (int element : array) {
if (comma) {
appender.append(',');
}
appender.append(String.valueOf(element));
comma = true;
}
appender.append(']');
}
private void writeArray(long @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (long l : array) {
if (comma) {
appender.append(',');
}
appender.append(String.valueOf(l));
comma = true;
}
appender.append(']');
}
private void writeArray(float @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (float element : array) {
if (comma) {
appender.append(',');
}
appender.append(String.valueOf(element));
comma = true;
}
appender.append(']');
}
private void writeArray(double @Nonnull [] array, @Nonnull Appendable appender) throws Exception {
appender.append('[');
boolean comma = false;
for (double element : array) {
if (comma) {
appender.append(',');
}
appender.append(String.valueOf(element));
comma = true;
}
appender.append(']');
}
}
private static final class ControlTables {
private static final @Nonnull String @Nonnull [] CONTROL_CHAR_ESCAPE_TABLE;
static {
CONTROL_CHAR_ESCAPE_TABLE = new String[32];
for (int i = 0; i < CONTROL_CHAR_ESCAPE_TABLE.length; i++) {
CONTROL_CHAR_ESCAPE_TABLE[i] = CharsKit.toUnicode((char) i);
}
CONTROL_CHAR_ESCAPE_TABLE['\r'] = "\\r";
CONTROL_CHAR_ESCAPE_TABLE['\n'] = "\\n";
CONTROL_CHAR_ESCAPE_TABLE['\t'] = "\\t";
CONTROL_CHAR_ESCAPE_TABLE['\b'] = "\\b";
CONTROL_CHAR_ESCAPE_TABLE['\f'] = "\\f";
}
private static @Nullable String toUnicodeEscape(char c) {
if (c >= CONTROL_CHAR_ESCAPE_TABLE.length) {
return null;
}
return CONTROL_CHAR_ESCAPE_TABLE[c];
}
}
private JsonFormatterBack() {
}
}