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