All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.moon.core.json.JSONStringer Maven / Gradle / Ivy

package com.moon.core.json;

import com.moon.core.beans.BeanInfoUtil;
import com.moon.core.enums.Const;
import com.moon.core.lang.Joiner;
import com.moon.core.lang.ref.BooleanAccessor;
import com.moon.core.lang.ref.DoubleAccessor;
import com.moon.core.lang.ref.IntAccessor;
import com.moon.core.lang.ref.LongAccessor;
import com.moon.core.time.DateTimeUtil;
import com.moon.core.time.DateUtil;
import com.moon.core.time.DateTime;
import com.moon.core.util.SetUtil;
import com.moon.core.util.function.TableConsumer;
import com.moon.core.util.interfaces.Stringify;
import org.joda.time.MutableDateTime;
import org.joda.time.ReadableInstant;
import org.joda.time.ReadablePartial;
import org.joda.time.format.DateTimeFormat;
import sun.util.BuddhistCalendar;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.CharBuffer;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.*;
import java.util.*;
import java.util.concurrent.atomic.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate;

import static com.moon.core.time.CalendarUtil.isImportedJodaTime;

/**
 * @author moonsky
 */
class JSONStringer implements Stringify {

    private final static Map STRINGIFIER_MAP = new HashMap<>();

    final static JSONStringer STRINGER = new JSONStringer();

    static {
        for (DefaultStringifiers value : DefaultStringifiers.values()) {
            Set classes = value.getSupportsCls();
            for (Class cls : classes) {
                STRINGIFIER_MAP.put(cls, value);
            }
        }

        if (isImportedJodaTime()) {
            importJodaTime();
        }
    }

    @Override
    public String stringify(Object obj) {
        return stringify(obj, StringifySettings.EMPTY);
    }

    public String stringify(Object data, StringifySettings settings) {
        return stringify(new StringBuilder(), data, settings).toString();
    }

    public StringBuilder stringify(StringBuilder container, Object data, StringifySettings settings) {
        settings.initialize();
        doStringify(container, data, settings);
        settings.destroy();
        return container;
    }

    private JSONStringer() { }

    private static StringBuilder doStringify(StringBuilder builder, Object obj) {
        return doStringify(builder, obj, StringifySettings.EMPTY);
    }

    private static StringBuilder doStringify(StringBuilder builder, Object obj, StringifySettings settings) {
        if (obj == null) {
            return addUnwrapped(builder, settings, "null");
        } else {
            // 用 Map 的方式比逐个 instanceof 效率快
            Stringifier stringifier = STRINGIFIER_MAP.get(obj.getClass());
            if (stringifier == null) {
                if (obj instanceof Map) {
                    stringifyMap(builder, (Map) obj, settings);
                } else if (obj instanceof Iterable) {
                    stringifyCollect(builder, (Iterable) obj, settings);
                } else if (obj instanceof Object[]) {
                    stringifyObjects(builder, (Object[]) obj, settings);
                } else if (obj instanceof Iterator) {
                    stringifyIterator(builder, (Iterator) obj, settings);
                } else {
                    stringifyOfJavaBean(builder, obj, settings);
                }
            } else {
                stringifier.accept(builder, obj, settings);
            }
        }
        return builder;
    }

    private static void stringifyOfJavaBean(StringBuilder stringer, Object obj, StringifySettings settings) {
        doAppendAroundObject(stringer, obj, settings, (builder, setting, data) -> {
            BeanInfoUtil.getFieldDescriptorsMap(data.getClass()).forEach((name, desc) -> {
                beforeAppend(builder, settings);
                builder.append('"').append(name).append('"').append(':');
                doStringify(builder, desc.getValue(data)).append(',');
                afterAppended(builder, settings);
            });
        });
    }

    private static void stringifyIterator(
        StringBuilder builder, Iterator iterator, StringifySettings stringifySettings
    ) {
        doAppendAroundArray(builder, iterator, stringifySettings, (str, settings, values) -> {
            while (values.hasNext()) {
                beforeAppend(str, settings);
                doStringify(builder, values.next(), settings).append(',');
                afterAppended(str, settings);
            }
        });
    }

    private static void stringifyCollect(
        StringBuilder builder, Iterable iterable, StringifySettings stringifySettings
    ) {
        doAppendAroundArray(builder, iterable, stringifySettings, (str, settings, values) -> {
            for (Object value : values) {
                beforeAppend(str, settings);
                doStringify(str, value, settings).append(',');
                afterAppended(str, settings);
            }
        });
    }

    private static void stringifyMap(StringBuilder builder, Map inputMap, StringifySettings stringifySettings) {
        doAppendAroundObject(builder, inputMap, stringifySettings, (str, settings, values) -> {
            for (Object o : values.entrySet()) {
                Map.Entry entry = (Map.Entry) o;
                beforeAppend(str, settings);
                builder.append('"').append(entry.getKey()).append('"').append(':');
                doStringify(builder, entry.getValue(), settings).append(',');
                afterAppended(str, settings);
            }
        });
    }

    private static void stringifyObjects(StringBuilder builder, Object[] arr, StringifySettings stringifySettings) {
        doAppendAroundArray(builder, arr, stringifySettings, (str, settings, values) -> {
            for (Object v : values) {
                if (v == null) {
                    beforeAppend(str, settings);
                    str.append("null").append(',');
                    afterAppended(str, settings);
                } else {
                    doStringify(str, v, settings).append(',');
                }
            }
        });
    }

    interface Stringifier extends TableConsumer, Predicate {

        @Override
        void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings);

        Set getSupportsCls();

        @Override
        default boolean test(Object o) {
            if (o == null) {
                return false;
            }
            Set classes = getSupportsCls();
            if (o instanceof Class) {
                return classes.contains(o);
            }
            return classes.contains(o.getClass());
        }
    }

    @SuppressWarnings("all")
    enum DefaultStringifiers implements Stringifier {
        OPTIONAL_AT_UTIL {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                Object value = ((Optional) o).get();
                beforeAppend(builder, stringifySettings);
                doStringify(builder, value, stringifySettings);
                afterAppended(builder, stringifySettings);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(Optional.class); }
        },
        OPTIONAL_DOUBLE {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                double value = ((OptionalDouble) o).getAsDouble();
                beforeAppend(builder, stringifySettings);
                builder.append(value);
                afterAppended(builder, stringifySettings);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(OptionalDouble.class); }
        },
        OPTIONAL_LONG {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                long value = ((OptionalLong) o).getAsLong();
                beforeAppend(builder, stringifySettings);
                builder.append(value);
                afterAppended(builder, stringifySettings);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(OptionalLong.class); }
        },
        OPTIONAL_INT {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                int value = ((OptionalInt) o).getAsInt();
                beforeAppend(builder, stringifySettings);
                builder.append(value);
                afterAppended(builder, stringifySettings);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(OptionalInt.class); }
        },
        CALENDAR {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                String value = DateUtil.format((Calendar) o);
                addString(builder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(Calendar.class, GregorianCalendar.class, BuddhistCalendar.class);
            }
        },
        DATE {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                String value = DateUtil.format((Date) o);
                addString(builder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(Date.class, Time.class, java.sql.Date.class, Timestamp.class, DateTime.class);
            }
        },
        JDK8_DATE {
            @Override
            public void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings) {
                String value = DateTimeUtil.format((LocalDate) o);
                addString(stringBuilder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(LocalDate.class);
            }
        },
        JDK8_TIME {
            @Override
            public void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings) {
                String value = DateTimeUtil.format((LocalTime) o);
                addString(stringBuilder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(LocalTime.class);
            }
        },
        JDK8_DATE_TIME {
            @Override
            public void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings) {
                String value = DateTimeUtil.format((LocalDateTime) o);
                addString(stringBuilder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(LocalDateTime.class);
            }
        },
        JDK8_DATE_OFFSET {
            @Override
            public void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings) {
                String value = DateTimeUtil.format(((OffsetDateTime) o).toLocalDateTime());
                addString(stringBuilder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(OffsetDateTime.class); }
        },
        JDK8_DATE_ZONED {
            @Override
            public void accept(StringBuilder stringBuilder, Object o, StringifySettings stringifySettings) {
                String value = DateTimeUtil.format(((ZonedDateTime) o).toLocalDateTime());
                addString(stringBuilder, stringifySettings, value);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(ZonedDateTime.class); }
        },
        WRAPPED {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                addString(builder, stringifySettings, o);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(CharSequence.class,
                    java.util.StringJoiner.class,
                    StringBuilder.class,
                    StringBuffer.class,
                    Joiner.class,
                    JSONString.class,
                    CharBuffer.class,
                    String.class,
                    // wrapped
                    Character.class,
                    char.class);
            }
        },
        UNWRAPPED {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                addUnwrapped(builder, stringifySettings, o);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(Number.class,
                    byte.class,
                    short.class,
                    int.class,
                    long.class,
                    float.class,
                    double.class,
                    Byte.class,
                    Short.class,
                    Integer.class,
                    Long.class,
                    Float.class,
                    Double.class,
                    BigDecimal.class,
                    BigInteger.class,
                    AtomicInteger.class,
                    AtomicLong.class,
                    LongAdder.class,
                    DoubleAdder.class,
                    IntAccessor.class,
                    LongAccessor.class,
                    DoubleAccessor.class,
                    LongAccumulator.class,
                    DoubleAccumulator.class,
                    JSONNumber.class,
                    Boolean.class,
                    // unwrapped
                    boolean.class,
                    JSONBoolean.class,
                    AtomicBoolean.class,
                    BooleanAccessor.class);
            }
        },
        /**
         * 数组
         */
        CHARSEQUENCES {
            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(CharSequence[].class,
                    java.util.StringJoiner[].class,
                    StringBuilder[].class,
                    StringBuffer[].class,
                    Joiner[].class,
                    String[].class);
            }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                stringifyObjects(builder, (Object[]) values, stringifySettings);
            }
        },
        CHARS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(char[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (char[]) values, stringifySettings, (str, settings, vs) -> {
                    for (char v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        BOOLEANS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(boolean[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (boolean[]) values, stringifySettings, (str, settings, vs) -> {
                    for (boolean v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        INTS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(int[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (int[]) values, stringifySettings, (str, settings, vs) -> {
                    for (int v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        BYTES {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(byte[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (byte[]) values, stringifySettings, (str, settings, vs) -> {
                    for (byte v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        SHORTS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(short[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (short[]) values, stringifySettings, (str, settings, vs) -> {
                    for (short v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        LONGS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(long[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (long[]) values, stringifySettings, (str, settings, vs) -> {
                    for (long v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        FLOATS {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(float[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (float[]) values, stringifySettings, (str, settings, vs) -> {
                    for (float v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        },
        DOUBLES {
            @Override
            public Set getSupportsCls() { return SetUtil.newSet(double[].class); }

            @Override
            public void accept(StringBuilder builder, Object values, StringifySettings stringifySettings) {
                doAppendAroundArray(builder, (double[]) values, stringifySettings, (str, settings, vs) -> {
                    for (double v : vs) {
                        afterAppended(beforeAppend(str, settings).append(v).append(','), settings);
                    }
                });
            }
        };
    }

    private static  void doAppendAroundObject(
        StringBuilder builder, T value, StringifySettings settings,//
        TableConsumer consumer
    ) { doAppendAround(builder, value, settings, '{', '}', consumer); }

    private static  void doAppendAroundArray(
        StringBuilder builder, T value, StringifySettings settings,//
        TableConsumer consumer
    ) { doAppendAround(builder, value, settings, '[', ']', consumer); }

    private static  void doAppendAround(
        StringBuilder builder, T value, StringifySettings settings, char open, char close,//
        TableConsumer consumer
    ) {
        startAppend(builder, open, settings);
        consumer.accept(builder, settings, value);
        endAppended(builder, close, settings);
    }

    private static StringBuilder addString(StringBuilder sb, StringifySettings settings, Object value) {
        return addAround(sb, settings, value, (builder, str) -> builder.append('"').append(str).append('"'));
    }

    private static StringBuilder addUnwrapped(StringBuilder sb, StringifySettings settings, Object value) {
        return addAround(sb, settings, value, (builder, v) -> builder.append(v));
    }

    private static  StringBuilder addAround(
        StringBuilder builder, StringifySettings settings, T data, BiConsumer appender
    ) {
        appender.accept(beforeAppend(builder, settings), data);
        return afterAppended(builder, settings);
    }

    private static StringBuilder close(StringBuilder builder, char value) {
        builder.setCharAt(builder.length() - 1, value);
        return builder;
    }

    private static StringBuilder startAppend(StringBuilder builder, char open, StringifySettings settings) {
        addUnwrapped(builder, settings, String.valueOf(open));
        settings.open();
        return builder;
    }

    private static StringBuilder endAppended(StringBuilder builder, char close, StringifySettings settings) {
        if (settings.getIndentWhitespaces() != null) {
            // close(builder, '\n');
            builder.deleteCharAt(builder.length() - 1);
            settings.close();
            beforeAppend(builder, settings);
            builder.append(close);
        } else {
            close(builder, close);
        }
        return builder;
    }

    private static StringBuilder beforeAppend(StringBuilder builder, StringifySettings settings) {
        if (builder.length() > 0) {
            char last = builder.charAt(builder.length() - 1);
            if (last == ':') {
                builder.append(' ');
            } else if (last != '\n') {
                String indent = settings.getIndentWhitespaces();
                if (indent != null) {
                    builder.append('\n');
                    builder.append(indent);
                }
            }
        }
        return builder;
    }

    private static StringBuilder afterAppended(StringBuilder builder, StringifySettings settings) {
        return builder;
    }

    /*
     ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     ~ ~ joda date ~~~~~~~~~~~~~~~~~~~~~~
     ~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     */

    private static void importJodaTime() {
        for (Stringifier value : JodaStringifier.values()) {
            Set classes = value.getSupportsCls();
            for (Class cls : classes) {
                STRINGIFIER_MAP.put(cls, value);
            }
        }
    }

    @SuppressWarnings("all")
    enum JodaStringifier implements Stringifier {
        DATE {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                org.joda.time.LocalDate date = (org.joda.time.LocalDate) o;
                String formatted = DateTimeFormat.forPattern(Const.PATTERN_DATE).print(date);
                addString(builder, stringifySettings, formatted);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(org.joda.time.LocalDate.class); }
        },
        TIME {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                org.joda.time.LocalTime time = (org.joda.time.LocalTime) o;
                String formatted = DateTimeFormat.forPattern(Const.PATTERN_TIME).print(time);
                addString(builder, stringifySettings, formatted);
            }

            @Override
            public Set getSupportsCls() { return SetUtil.newSet(org.joda.time.LocalTime.class); }
        },
        DATE_TIME {
            @Override
            public void accept(StringBuilder builder, Object o, StringifySettings stringifySettings) {
                String formatted;
                if (o instanceof ReadableInstant) {
                    ReadableInstant instant = (ReadableInstant) o;
                    formatted = DateTimeFormat.forPattern(Const.PATTERN).print(instant);
                } else if (o instanceof ReadablePartial) {
                    ReadablePartial partial = (ReadablePartial) o;
                    formatted = DateTimeFormat.forPattern(Const.PATTERN).print(partial);
                } else {
                    throw new IllegalArgumentException("Invalid datetime value of: " + o);
                }
                addString(builder, stringifySettings, formatted);
            }

            @Override
            public Set getSupportsCls() {
                return SetUtil.newSet(org.joda.time.DateTime.class,
                    org.joda.time.Instant.class,
                    org.joda.time.LocalDateTime.class,
                    MutableDateTime.class);
            }
        };
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy