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}