1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 package org.spf4j.base;
33
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
35 import gnu.trove.set.hash.THashSet;
36 import java.io.IOException;
37 import java.io.UncheckedIOException;
38 import java.util.Arrays;
39 import java.util.Set;
40 import javax.annotation.Nonnull;
41 import org.spf4j.io.ObjectAppenderSupplier;
42
43
44
45
46
47
48
49
50
51
52
53
54 public final class Slf4jMessageFormatter {
55
56 private static final char DELIM_START = '{';
57 private static final String DELIM_STR = "{}";
58 private static final char ESCAPE_CHAR = '\\';
59
60
61 public interface ErrorHandler {
62 void accept(Object obj, Appendable sbuf, Throwable t) throws IOException;
63 }
64
65
66
67 private Slf4jMessageFormatter() {
68 }
69
70
71 @SuppressWarnings("checkstyle:regexp")
72 public static void exHandle(final Object obj, final Appendable sbuf, final Throwable t) throws IOException {
73 String className = obj.getClass().getName();
74 synchronized (System.err) {
75 System.err.print("SPF4J: Failed toString() invocation on an object of type [");
76 System.err.print(className);
77 System.err.println(']');
78 }
79 Throwables.writeTo(t, System.err, Throwables.PackageDetail.SHORT);
80 sbuf.append("[FAILED toString() for ");
81 sbuf.append(className);
82 sbuf.append(']');
83 }
84
85 public static String toString(@Nonnull final String messagePattern,
86 final Object... argArray) {
87 StringBuilder sb = new StringBuilder(messagePattern.length() + argArray.length * 8);
88 try {
89 int nrUsed = format(sb, messagePattern, argArray);
90 if (nrUsed != argArray.length) {
91 throw new IllegalArgumentException("Invalid format "
92 + messagePattern + ", params " + Arrays.toString(argArray));
93 }
94 } catch (IOException ex) {
95 throw new UncheckedIOException(ex);
96 }
97 return sb.toString();
98 }
99
100
101
102
103
104
105
106
107
108
109
110 public static int format(@Nonnull final Appendable to, @Nonnull final String messagePattern,
111 final Object... argArray) throws IOException {
112 return format(to, messagePattern, ObjectAppenderSupplier.TO_STRINGER, argArray);
113 }
114
115
116
117
118
119
120
121
122
123
124
125 public static int format(@Nonnull final Appendable to,
126 @Nonnull final ObjectAppenderSupplier appSupplier, @Nonnull final String messagePattern,
127 final Object... argArray) throws IOException {
128 return format(to, messagePattern, appSupplier, argArray);
129 }
130
131
132
133
134
135
136
137
138
139
140
141 public static int format(@Nonnull final Appendable to, @Nonnull final String messagePattern,
142 @Nonnull final ObjectAppenderSupplier appSupplier, final Object... argArray) throws IOException {
143 return format(0, to, messagePattern, appSupplier, argArray);
144 }
145
146
147
148
149
150
151
152
153
154
155
156
157 public static int format(final int firstArgIdx, @Nonnull final Appendable to, @Nonnull final String messagePattern,
158 @Nonnull final ObjectAppenderSupplier appSupplier, final Object... argArray) throws IOException {
159 return format(Slf4jMessageFormatter::exHandle, firstArgIdx, to, messagePattern, appSupplier, argArray);
160 }
161
162
163 public static int getFormatParameterNumber(@Nonnull final String messagePattern) {
164 int nrParams = 0;
165 int i = 0;
166 int j;
167 while ((j = messagePattern.indexOf(DELIM_STR, i)) >= 0) {
168 if (isEscapedDelimeter(messagePattern, j)) {
169 if (isDoubleEscaped(messagePattern, j)) {
170
171
172
173 nrParams++;
174 i = j + 2;
175 } else {
176 i = j + 1;
177 }
178 } else {
179
180 nrParams++;
181 i = j + 2;
182 }
183 }
184 return nrParams;
185 }
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203 public static int format(final ErrorHandler exHandler, final int firstArgIdx,
204 @Nonnull final Appendable to, @Nonnull final String messagePattern,
205 @Nonnull final ObjectAppenderSupplier appSupplier, final Object... argArray)
206 throws IOException {
207 int i = 0;
208 final int len = argArray.length;
209 int k = firstArgIdx;
210 for (; k < len; k++) {
211 int j = messagePattern.indexOf(DELIM_STR, i);
212 if (j == -1) {
213
214 break;
215 } else {
216 if (isEscapedDelimeter(messagePattern, j)) {
217 if (isDoubleEscaped(messagePattern, j)) {
218
219
220
221 to.append(messagePattern, i, j - 1);
222 deeplyAppendParameter(exHandler, to, argArray[k], new THashSet<>(), appSupplier);
223 i = j + 2;
224 } else {
225 k--;
226 to.append(messagePattern, i, j - 1);
227 to.append(DELIM_START);
228 i = j + 1;
229 }
230 } else {
231
232 to.append(messagePattern, i, j);
233 deeplyAppendParameter(exHandler, to, argArray[k], new THashSet<>(), appSupplier);
234 i = j + 2;
235 }
236 }
237 }
238
239 to.append(messagePattern, i, messagePattern.length());
240 return k;
241 }
242
243 private static boolean isEscapedDelimeter(final String messagePattern, final int delimeterStartIndex) {
244 if (delimeterStartIndex == 0) {
245 return false;
246 }
247 return messagePattern.charAt(delimeterStartIndex - 1) == ESCAPE_CHAR;
248 }
249
250 private static boolean isDoubleEscaped(final String messagePattern, final int delimeterStartIndex) {
251 return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
252 }
253
254
255 @SuppressFBWarnings("ITC_INHERITANCE_TYPE_CHECKING")
256 private static void deeplyAppendParameter(final ErrorHandler exHandler, final Appendable sbuf, final Object o,
257 final Set<Object[]> seen, final ObjectAppenderSupplier appSupplier) throws IOException {
258 if (o == null) {
259 sbuf.append("null");
260 return;
261 }
262 if (!o.getClass().isArray()) {
263 safeObjectAppend(exHandler, sbuf, o, appSupplier);
264 } else {
265
266
267 if (o instanceof boolean[]) {
268 booleanArrayAppend(sbuf, (boolean[]) o);
269 } else if (o instanceof byte[]) {
270 byteArrayAppend(sbuf, (byte[]) o);
271 } else if (o instanceof char[]) {
272 charArrayAppend(sbuf, (char[]) o);
273 } else if (o instanceof short[]) {
274 shortArrayAppend(sbuf, (short[]) o);
275 } else if (o instanceof int[]) {
276 intArrayAppend(sbuf, (int[]) o);
277 } else if (o instanceof long[]) {
278 longArrayAppend(sbuf, (long[]) o);
279 } else if (o instanceof float[]) {
280 floatArrayAppend(sbuf, (float[]) o);
281 } else if (o instanceof double[]) {
282 doubleArrayAppend(sbuf, (double[]) o);
283 } else {
284 objectArrayAppend(exHandler, sbuf, (Object[]) o, seen, appSupplier);
285 }
286 }
287 }
288
289 @SuppressWarnings("unchecked")
290 public static void safeObjectAppend(final ErrorHandler exHandler, final Appendable sbuf, final Object obj,
291 final ObjectAppenderSupplier appSupplier) throws IOException {
292 try {
293 appSupplier.get((Class) obj.getClass()).append(obj, sbuf, appSupplier);
294 } catch (IOException | RuntimeException | StackOverflowError t) {
295 exHandler.accept(obj, sbuf, t);
296 }
297
298 }
299
300 @SuppressFBWarnings("ABC_ARRAY_BASED_COLLECTIONS")
301 private static void objectArrayAppend(final ErrorHandler exHandler, final Appendable sbuf,
302 final Object[] a, final Set<Object[]> seen,
303 final ObjectAppenderSupplier appSupplier) throws IOException {
304 sbuf.append('[');
305 if (seen.add(a)) {
306 final int len = a.length;
307 if (len > 0) {
308 deeplyAppendParameter(exHandler, sbuf, a[0], seen, appSupplier);
309 for (int i = 1; i < len; i++) {
310 sbuf.append(", ");
311 deeplyAppendParameter(exHandler, sbuf, a[i], seen, appSupplier);
312 }
313 }
314
315 seen.remove(a);
316 } else {
317 sbuf.append("...");
318 }
319 sbuf.append(']');
320 }
321
322 private static void booleanArrayAppend(final Appendable sbuf, final boolean[] a) throws IOException {
323 sbuf.append('[');
324 final int len = a.length;
325 if (len > 0) {
326 sbuf.append(Boolean.toString(a[0]));
327 for (int i = 1; i < len; i++) {
328 sbuf.append(", ");
329 sbuf.append(Boolean.toString(a[i]));
330 }
331 }
332 sbuf.append(']');
333 }
334
335 private static void byteArrayAppend(final Appendable sbuf, final byte[] a) throws IOException {
336 sbuf.append('[');
337 final int len = a.length;
338 if (len > 0) {
339 sbuf.append(Byte.toString(a[0]));
340 for (int i = 1; i < len; i++) {
341 sbuf.append(", ");
342 sbuf.append(Byte.toString(a[i]));
343 }
344 }
345 sbuf.append(']');
346 }
347
348 private static void charArrayAppend(final Appendable sbuf, final char[] a) throws IOException {
349 sbuf.append('[');
350 final int len = a.length;
351 if (len > 0) {
352 sbuf.append(a[0]);
353 for (int i = 1; i < len; i++) {
354 sbuf.append(", ");
355 sbuf.append(a[i]);
356 }
357 }
358 sbuf.append(']');
359 }
360
361 private static void shortArrayAppend(final Appendable sbuf, final short[] a) throws IOException {
362 sbuf.append('[');
363 final int len = a.length;
364 if (len > 0) {
365 sbuf.append(Short.toString(a[0]));
366 for (int i = 1; i < len; i++) {
367 sbuf.append(", ");
368 sbuf.append(Short.toString(a[i]));
369 }
370 }
371 sbuf.append(']');
372 }
373
374 private static void intArrayAppend(final Appendable sbuf, final int[] a) throws IOException {
375 sbuf.append('[');
376 final int len = a.length;
377 if (len > 0) {
378 sbuf.append(Integer.toString(a[0]));
379 for (int i = 1; i < len; i++) {
380 sbuf.append(", ");
381 sbuf.append(Integer.toString(a[i]));
382 }
383 }
384 sbuf.append(']');
385 }
386
387 private static void longArrayAppend(final Appendable sbuf, final long[] a) throws IOException {
388 sbuf.append('[');
389 final int len = a.length;
390 if (len > 0) {
391 sbuf.append(Long.toString(a[0]));
392 for (int i = 1; i < len; i++) {
393 sbuf.append(", ");
394 sbuf.append(Long.toString(a[i]));
395 }
396 }
397 sbuf.append(']');
398 }
399
400 private static void floatArrayAppend(final Appendable sbuf, final float[] a) throws IOException {
401 sbuf.append('[');
402 final int len = a.length;
403 if (len > 0) {
404 sbuf.append(Float.toString(a[0]));
405 for (int i = 1; i < len; i++) {
406 sbuf.append(", ");
407 sbuf.append(Float.toString(a[i]));
408 }
409 }
410 sbuf.append(']');
411 }
412
413 private static void doubleArrayAppend(final Appendable sbuf, final double[] a) throws IOException {
414 sbuf.append('[');
415 final int len = a.length;
416 if (len > 0) {
417 sbuf.append(Double.toString(a[0]));
418 for (int i = 1; i < len; i++) {
419 sbuf.append(", ");
420 sbuf.append(Double.toString(a[i]));
421 }
422 }
423 sbuf.append(']');
424 }
425
426 }