1   /*
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v2.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  
15  package ch.qos.logback.core.util;
16  
17  import java.math.BigDecimal;
18  import java.nio.ByteBuffer;
19  import java.nio.charset.StandardCharsets;
20  
21  /**
22   * This is a utility class for writing json logs.
23   * It is imported from (and in collaboration with) penna.
24   *
25   * @author Henry John Kupty
26   * @see <a href="https://github.com/hkupty/penna">penna</a>
27   */
28  public final class DirectJson {
29      private static final int INITIAL_BUFFER_SIZE = 1024;
30      private static final byte QUOTE = '"';
31      private static final byte ENTRY_SEP = ':';
32      private static final byte KV_SEP = ',';
33      private static final byte DOT = '.';
34      private static final byte OPEN_OBJ = '{';
35      private static final byte CLOSE_OBJ = '}';
36      private static final byte OPEN_ARR = '[';
37      private static final byte CLOSE_ARR = ']';
38  
39      private static final byte[] NEWLINE = new byte[] {
40              '\\',
41              'n',
42      };
43      private static final byte[] ESCAPE = new byte[] {
44              '\\',
45              '\\',
46      };
47      private static final byte[] LINEBREAK = new byte[] {
48              '\\',
49              'r',
50      };
51      private static final byte[] TAB = new byte[] {
52              '\\',
53              't',
54      };
55      private static final byte[] TRUE = new byte[] {
56              't',
57              'r',
58              'u',
59              'e'
60      };
61      private static final byte[] FALSE = new byte[] {
62              'f',
63              'a',
64              'l',
65              's',
66              'e'
67      };
68      private static final byte[] NULL = new byte[] {
69              'n',
70              'u',
71              'l',
72              'l'
73      };
74  
75      private ByteBuffer buffer;
76  
77      public DirectJson() {
78          buffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
79      }
80  
81      public void openObject() { buffer.put(OPEN_OBJ); }
82      public void openArray() { buffer.put(OPEN_ARR); }
83  
84      public void openObject(String str) {
85          writeString(str);
86          writeEntrySep();
87          buffer.put(OPEN_OBJ);
88      }
89  
90      public void openArray(String str) {
91          writeString(str);
92          writeEntrySep();
93          buffer.put(OPEN_ARR);
94      }
95  
96      public void closeObject() {
97          var target = buffer.position() - 1;
98          if (',' == buffer.get(target)) {
99              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 }