001package ch.qos.logback.core.util;
002
003import java.math.BigDecimal;
004import java.nio.ByteBuffer;
005import java.nio.charset.StandardCharsets;
006
007/**
008 * This is a utility class for writing json logs.
009 * It is imported from (and in collaboration with) penna.
010 *
011 * @author Henry John Kupty
012 * @see <a href="https://github.com/hkupty/penna">penna</a>
013 */
014public final class DirectJson {
015    private static final int INITIAL_BUFFER_SIZE = 1024;
016    private static final byte QUOTE = '"';
017    private static final byte ENTRY_SEP = ':';
018    private static final byte KV_SEP = ',';
019    private static final byte DOT = '.';
020    private static final byte OPEN_OBJ = '{';
021    private static final byte CLOSE_OBJ = '}';
022    private static final byte OPEN_ARR = '[';
023    private static final byte CLOSE_ARR = ']';
024
025    private static final byte[] NEWLINE = new byte[] {
026            '\\',
027            'n',
028    };
029    private static final byte[] ESCAPE = new byte[] {
030            '\\',
031            '\\',
032    };
033    private static final byte[] LINEBREAK = new byte[] {
034            '\\',
035            'r',
036    };
037    private static final byte[] TAB = new byte[] {
038            '\\',
039            't',
040    };
041    private static final byte[] TRUE = new byte[] {
042            't',
043            'r',
044            'u',
045            'e'
046    };
047    private static final byte[] FALSE = new byte[] {
048            'f',
049            'a',
050            'l',
051            's',
052            'e'
053    };
054    private static final byte[] NULL = new byte[] {
055            'n',
056            'u',
057            'l',
058            'l'
059    };
060
061    private ByteBuffer buffer;
062
063    public DirectJson() {
064        buffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
065    }
066
067    public void openObject() { buffer.put(OPEN_OBJ); }
068    public void openArray() { buffer.put(OPEN_ARR); }
069
070    public void openObject(String str) {
071        writeString(str);
072        writeEntrySep();
073        buffer.put(OPEN_OBJ);
074    }
075
076    public void openArray(String str) {
077        writeString(str);
078        writeEntrySep();
079        buffer.put(OPEN_ARR);
080    }
081
082    public void closeObject() {
083        var target = buffer.position() - 1;
084        if (',' == buffer.get(target)) {
085            buffer.put(target, CLOSE_OBJ);
086        } else {
087            buffer.put(CLOSE_OBJ);
088        }
089    }
090
091    public void closeArray() {
092        var target = buffer.position() - 1;
093        if (',' == buffer.get(target)) {
094            buffer.put(target, CLOSE_ARR);
095        } else {
096            buffer.put(CLOSE_ARR);
097        }
098    }
099
100    public void writeRaw(String str) {
101        for(int i = 0; i < str.length(); i++ ){
102            var chr = str.codePointAt(i);
103            switch (chr) {
104                case '\\':
105                    buffer.put(ESCAPE);
106                    break;
107                case '\n':
108                    buffer.put(NEWLINE);
109                    break;
110                case '\r':
111                    buffer.put(LINEBREAK);
112                    break;
113                case '\t':
114                    buffer.put(TAB);
115                    break;
116                default:
117                    if (chr >= 0x80 && chr <= 0x10FFFF) {
118                        buffer.put(String.valueOf(str.charAt(i)).getBytes());
119                    } else if (chr > 0x1F) buffer.put((byte) chr);
120            }
121
122        }
123    }
124
125    public void writeRaw(char chr) { buffer.put((byte) chr); }
126    public void writeRaw(byte[] chr) { buffer.put(chr); }
127
128    public void writeQuote() { buffer.put(QUOTE); }
129    public void writeString(String str) {
130        checkSpace(str.length() + 3);
131        buffer.put(QUOTE);
132        writeRaw(str);
133        buffer.put(QUOTE);
134        buffer.put(KV_SEP);
135    }
136    public void writeSep() { buffer.put(KV_SEP); }
137
138    public void writeNumberRaw(final long data) {
139        final int pos = buffer.position();
140        final int sz = (int) Math.log10(data) + 1;
141        long dataPointer = data;
142
143        for (int i = sz - 1; i >= 0; i--) {
144            byte chr = (byte) (dataPointer % 10);
145            dataPointer = dataPointer / 10;
146            chr += 48;
147            buffer.put(pos + i, chr);
148        }
149
150        buffer.position(pos + sz);
151    }
152
153    public void writeNumber(final long data) {
154        final int pos = buffer.position();
155        final int sz = data == 0 ? 1 : (int) Math.log10(data) + 1;
156        long dataPointer = data;
157
158        for (int i = sz - 1; i >= 0; i--) {
159            byte chr = (byte) (dataPointer % 10);
160            dataPointer = dataPointer / 10;
161            chr += 48;
162            buffer.put(pos + i, chr);
163        }
164
165        buffer.position(pos + sz);
166        buffer.put(KV_SEP);
167    }
168
169    public void writeNumber(final double data) {
170        int pos = buffer.position();
171        long whole = (long) data;
172        final int sz = (int) Math.log10(whole) + 1;
173
174        for (int i = sz - 1; i >= 0; i--) {
175            byte chr = (byte) (whole % 10);
176            whole = whole / 10;
177            chr += 48;
178            buffer.put(pos + i, chr);
179        }
180        buffer.position(pos + sz);
181        buffer.put(DOT);
182        pos = buffer.position();
183        BigDecimal fractional = BigDecimal.valueOf(data).remainder(BigDecimal.ONE);
184        int decs = 0;
185        while (!fractional.equals(BigDecimal.ZERO)) {
186            fractional = fractional.movePointRight(1);
187            byte chr = (byte) (fractional.intValue() + 48);
188            fractional = fractional.remainder(BigDecimal.ONE);
189            decs += 1;
190            buffer.put(chr);
191        }
192
193        buffer.position(pos + decs);
194        buffer.put(KV_SEP);
195    }
196
197    public void writeEntrySep() { buffer.put(buffer.position() - 1, ENTRY_SEP); }
198
199    public void writeStringValue(String key, String value) {
200        writeString(key);
201        writeEntrySep();
202        writeString(value);
203    }
204
205    public void writeNumberValue(String key, long value) {
206        writeString(key);
207        writeEntrySep();
208        writeNumber(value);
209    }
210
211    public void writeNumberValue(String key, double value) {
212        writeString(key);
213        writeEntrySep();
214        writeNumber(value);
215    }
216
217    public void writeBoolean(boolean value) {
218        buffer.put(value ? TRUE : FALSE);
219        buffer.put(KV_SEP);
220    }
221
222    public void writeNull() {
223        buffer.put(NULL);
224        buffer.put(KV_SEP);
225    }
226
227    public void checkSpace(int size) {
228        if (buffer.position() + size >= buffer.capacity()) {
229            var newSize = (buffer.capacity() + size) * 2;
230            ByteBuffer newBuffer = ByteBuffer.allocateDirect(newSize);
231            buffer.flip();
232            newBuffer.put(buffer);
233            buffer = newBuffer;
234        }
235    }
236
237    public byte[] flush() {
238        byte[] result = new byte[buffer.position()];
239        buffer.flip();
240        buffer.get(result);
241        buffer.clear();
242
243        return result;
244    }
245}