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