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}