1 /*
2 * This file is part of Waarp Project (named also Waarp or GG).
3 *
4 * Copyright (c) 2019, Waarp SAS, and individual contributors by the @author
5 * tags. See the COPYRIGHT.txt in the distribution for a full listing of
6 * individual contributors.
7 *
8 * All Waarp Project is free software: you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * Waarp is distributed in the hope that it will be useful, but WITHOUT ANY
14 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
15 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with
18 * Waarp . If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 /*
22 * Copyright 2013 The Netty Project
23 * The Netty Project licenses this file to you under the Apache License,
24 * version 2.0 (the "License"); you may not use this file except in compliance
25 * with the License. You may obtain a copy of the License at:
26 * http://www.apache.org/licenses/LICENSE-2.0
27 * Unless required by applicable law or agreed to in writing, software
28 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
29 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
30 * License for the specific language governing permissions and limitations
31 * under the License.
32 */
33 /**
34 * Copyright (c) 2004-2011 QOS.ch
35 * All rights reserved.
36 * <p>
37 * Permission is hereby granted, free of charge, to any person obtaining
38 * a copy of this software and associated documentation files (the
39 * "Software"), to deal in the Software without restriction, including
40 * without limitation the rights to use, copy, modify, merge, publish,
41 * distribute, sublicense, and/or sell copies of the Software, and to
42 * permit persons to whom the Software is furnished to do so, subject to
43 * the following conditions:
44 * <p>
45 * The above copyright notice and this permission notice shall be
46 * included in all copies or substantial portions of the Software.
47 * <p>
48 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
49 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
51 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
52 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
53 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
54 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
55 */
56 package org.waarp.common.logging;
57
58 import org.waarp.common.utility.ParametersChecker;
59
60 import java.text.MessageFormat;
61 import java.util.HashMap;
62 import java.util.Map;
63
64 // contributors: lizongbo: proposed special treatment of array parameter values
65 // Joern Huxhorn: pointed out double[] omission, suggested deep array copy
66
67 /**
68 * Formats messages according to very simple substitution rules. Substitutions
69 * can be made 1, 2 or more
70 * arguments.
71 * <p/>
72 * <p/>
73 * For example,
74 * <p/>
75 *
76 * <pre>
77 * MessageFormatter.format("Hi {}.", "there")
78 * </pre>
79 * <p/>
80 * will return the string "Hi there.".
81 * <p/>
82 * The {} pair is called the <em>formatting anchor</em>. It serves to designate
83 * the location where arguments
84 * need to be substituted within the message pattern.
85 * <p/>
86 * In case your message contains the '{' or the '}' character, you do not have
87 * to do anything special unless
88 * the '}' character immediately follows '{'. For example,
89 * <p/>
90 *
91 * <pre>
92 * MessageFormatter.format("Set {1,2,3} is not equal to {}.", "1,2");
93 * </pre>
94 * <p/>
95 * will return the string "Set {1,2,3} is not equal to 1,2.".
96 * <p/>
97 * <p/>
98 * If for whatever reason you need to place the string "{}" in the message
99 * without its <em>formatting
100 * anchor</em> meaning, then you need to escape the '{' character with '\',
101 * that
102 * is the backslash character.
103 * Only the '{' character should be escaped. There is no need to escape the '}'
104 * character. For example,
105 * <p/>
106 *
107 * <pre>
108 * MessageFormatter.format("Set \\{} is not equal to {}.", "1,2");
109 * </pre>
110 * <p/>
111 * will return the string "Set {} is not equal to 1,2.".
112 * <p/>
113 * <p/>
114 * The escaping behavior just described can be overridden by escaping the
115 * escape
116 * character '\'. Calling
117 * <p/>
118 *
119 * <pre>
120 * MessageFormatter.format("File name is C:\\\\{}.", "file.zip");
121 * </pre>
122 * <p/>
123 * will return the string "File name is C:\file.zip".
124 * <p/>
125 * <p/>
126 * The formatting conventions are different than those of {@link MessageFormat}
127 * which ships with the Java
128 * platform. This is justified by the fact that SLF4J's implementation is 10
129 * times faster than that of
130 * {@link MessageFormat}. This local performance difference is both measurable
131 * and significant in the larger
132 * context of the complete logging processing chain.
133 * <p/>
134 * <p/>
135 * See also {@link #format(String, Object)}, {@link #format(String, Object,
136 * Object)} and
137 * {@link #arrayFormat(String, Object[])} methods for more details.
138 */
139 final class MessageFormatter {
140
141 static final char DELIM_START = '{';
142 static final char DELIM_STOP = '}';
143 static final String DELIM_STR = "{}";
144 private static final char ESCAPE_CHAR = '\\';
145
146 /**
147 * Performs single argument substitution for the 'messagePattern' passed as
148 * parameter.
149 * <p/>
150 * For example,
151 * <p/>
152 *
153 * <pre>
154 * MessageFormatter.format("Hi {}.", "there");
155 * </pre>
156 * <p/>
157 * will return the string "Hi there.".
158 * <p/>
159 *
160 * @param messagePattern The message pattern which will be parsed
161 * and
162 * formatted
163 * @param arg The argument to be substituted in place of the
164 * formatting
165 * anchor
166 *
167 * @return The formatted message
168 */
169 static FormattingTuple format(final String messagePattern, final Object arg) {
170 return arrayFormat(messagePattern, new Object[] { arg });
171 }
172
173 /**
174 * Performs a two argument substitution for the 'messagePattern' passed as
175 * parameter.
176 * <p/>
177 * For example,
178 * <p/>
179 *
180 * <pre>
181 * MessageFormatter.format("Hi {}. My name is {}.", "Alice", "Bob");
182 * </pre>
183 * <p/>
184 * will return the string "Hi Alice. My name is Bob.".
185 *
186 * @param messagePattern The message pattern which will be parsed
187 * and
188 * formatted
189 * @param argA The argument to be substituted in place of the first
190 * formatting anchor
191 * @param argB The argument to be substituted in place of the second
192 * formatting anchor
193 *
194 * @return The formatted message
195 */
196 static FormattingTuple format(final String messagePattern, final Object argA,
197 final Object argB) {
198 return arrayFormat(messagePattern, new Object[] { argA, argB });
199 }
200
201 static Throwable getThrowableCandidate(final Object[] argArray) {
202 if (argArray == null || argArray.length == 0) {
203 return null;
204 }
205
206 final Object lastEntry = argArray[argArray.length - 1];
207 if (lastEntry instanceof Throwable) {
208 return (Throwable) lastEntry;
209 }
210 return null;
211 }
212
213 /**
214 * Same principle as the {@link #format(String, Object)} and {@link
215 * #format(String, Object, Object)} methods
216 * except that any number of arguments can be passed in an array.
217 *
218 * @param messagePattern The message pattern which will be parsed
219 * and
220 * formatted
221 * @param argArray An array of arguments to be substituted in place
222 * of
223 * formatting anchors
224 *
225 * @return The formatted message
226 */
227 static FormattingTuple arrayFormat(final String messagePattern,
228 final Object[] argArray) {
229
230 final Throwable throwableCandidate = getThrowableCandidate(argArray);
231
232 if (messagePattern == null) {
233 return new FormattingTuple(null, argArray, throwableCandidate);
234 }
235
236 if (argArray == null) {
237 return new FormattingTuple(messagePattern);
238 }
239
240 int i = 0;
241 int j;
242 final StringBuilder sbuild =
243 new StringBuilder(messagePattern.length() + 50);
244
245 int l;
246 for (l = 0; l < argArray.length; l++) {
247
248 j = messagePattern.indexOf(DELIM_STR, i);
249
250 if (j == -1) {
251 // no more variables
252 if (i == 0) {
253 // this is a simple string
254 return new FormattingTuple(messagePattern, argArray,
255 throwableCandidate);
256 } else {
257 // add the tail string which contains no variables and return the result.
258 sbuild.append(messagePattern.substring(i));
259 return new FormattingTuple(sbuild.toString(), argArray,
260 throwableCandidate);
261 }
262 } else {
263 if (isEscapedDelimeter(messagePattern, j)) {
264 if (!isDoubleEscaped(messagePattern, j)) {
265 l--;
266 // DELIM_START was escaped, thus should not be incremented
267 sbuild.append(messagePattern, i, j - 1).append(DELIM_START);
268 i = j + 1;
269 } else {
270 // The escape character preceding the delimiter start is
271 // itself escaped: "abc x:\\{}"
272 // we have to consume one backward slash
273 sbuild.append(messagePattern, i, j - 1);
274 deeplyAppendParameter(sbuild, argArray[l],
275 new HashMap<Object[], Void>());
276 i = j + 2;
277 }
278 } else {
279 // normal case
280 sbuild.append(messagePattern, i, j);
281 deeplyAppendParameter(sbuild, argArray[l],
282 new HashMap<Object[], Void>());
283 i = j + 2;
284 }
285 }
286 }
287 // append the characters following the last {} pair.
288 sbuild.append(messagePattern.substring(i));
289 if (l < argArray.length - 1) {
290 return new FormattingTuple(sbuild.toString(), argArray,
291 throwableCandidate);
292 } else {
293 return new FormattingTuple(sbuild.toString(), argArray, null);
294 }
295 }
296
297 static boolean isEscapedDelimeter(final String messagePattern,
298 final int delimeterStartIndex) {
299 ParametersChecker.checkParameterNullOnly("Must not be null",
300 messagePattern);
301 if (delimeterStartIndex == 0) {
302 return false;
303 }
304 return messagePattern.charAt(delimeterStartIndex - 1) == ESCAPE_CHAR;
305 }
306
307 static boolean isDoubleEscaped(final String messagePattern,
308 final int delimeterStartIndex) {
309 ParametersChecker.checkParameterNullOnly("Must not be null",
310 messagePattern);
311 return delimeterStartIndex >= 2 &&
312 delimeterStartIndex - 2 > messagePattern.length() &&
313 messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
314 }
315
316 // special treatment of array values was suggested by 'lizongbo'
317 static void deeplyAppendParameter(final StringBuilder sbuild, final Object o,
318 final Map<Object[], Void> seenMap) {
319 if (o == null) {
320 sbuild.append("null");
321 return;
322 }
323 if (!o.getClass().isArray()) {
324 safeObjectAppend(sbuild, o);
325 } else {
326 // check for primitive array types because they
327 // unfortunately cannot be cast to Object[]
328 if (o instanceof boolean[]) {
329 booleanArrayAppend(sbuild, (boolean[]) o);
330 } else if (o instanceof byte[]) {
331 byteArrayAppend(sbuild, (byte[]) o);
332 } else if (o instanceof char[]) {
333 charArrayAppend(sbuild, (char[]) o);
334 } else if (o instanceof short[]) {
335 shortArrayAppend(sbuild, (short[]) o);
336 } else if (o instanceof int[]) {
337 intArrayAppend(sbuild, (int[]) o);
338 } else if (o instanceof long[]) {
339 longArrayAppend(sbuild, (long[]) o);
340 } else if (o instanceof float[]) {
341 floatArrayAppend(sbuild, (float[]) o);
342 } else if (o instanceof double[]) {
343 doubleArrayAppend(sbuild, (double[]) o);
344 } else {
345 objectArrayAppend(sbuild, (Object[]) o, seenMap);
346 }
347 }
348 }
349
350 private static void safeObjectAppend(final StringBuilder sbuild,
351 final Object o) {
352 try {
353 final String oAsString = o.toString();
354 sbuild.append(oAsString);
355 } catch (final Exception t) {
356 SysErrLogger.FAKE_LOGGER.ignoreLog(t);
357 SysErrLogger.FAKE_LOGGER.syserr(
358 "SLF4J: Failed toString() invocation on an object of type [" +
359 o.getClass().getName() + ']' + t.getMessage());
360 sbuild.append("[FAILED toString()]");
361 }
362 }
363
364 private static void objectArrayAppend(final StringBuilder sbuild,
365 final Object[] a,
366 final Map<Object[], Void> seenMap) {
367 sbuild.append('[');
368 if (!seenMap.containsKey(a)) {
369 seenMap.put(a, null);
370 final int len = a.length;
371 for (int i = 0; i < len; i++) {
372 deeplyAppendParameter(sbuild, a[i], seenMap);
373 if (i != len - 1) {
374 sbuild.append(", ");
375 }
376 }
377 // allow repeats in siblings
378 seenMap.remove(a);
379 } else {
380 sbuild.append("...");
381 }
382 sbuild.append(']');
383 }
384
385 private static void booleanArrayAppend(final StringBuilder sbuild,
386 final boolean[] a) {
387 sbuild.append('[');
388 final int len = a.length;
389 for (int i = 0; i < len; i++) {
390 sbuild.append(a[i]);
391 if (i != len - 1) {
392 sbuild.append(", ");
393 }
394 }
395 sbuild.append(']');
396 }
397
398 private static void byteArrayAppend(final StringBuilder sbuild,
399 final byte[] a) {
400 sbuild.append('[');
401 final int len = a.length;
402 for (int i = 0; i < len; i++) {
403 sbuild.append(a[i]);
404 if (i != len - 1) {
405 sbuild.append(", ");
406 }
407 }
408 sbuild.append(']');
409 }
410
411 private static void charArrayAppend(final StringBuilder sbuild,
412 final char[] a) {
413 sbuild.append('[');
414 final int len = a.length;
415 for (int i = 0; i < len; i++) {
416 sbuild.append(a[i]);
417 if (i != len - 1) {
418 sbuild.append(", ");
419 }
420 }
421 sbuild.append(']');
422 }
423
424 private static void shortArrayAppend(final StringBuilder sbuild,
425 final short[] a) {
426 sbuild.append('[');
427 final int len = a.length;
428 for (int i = 0; i < len; i++) {
429 sbuild.append(a[i]);
430 if (i != len - 1) {
431 sbuild.append(", ");
432 }
433 }
434 sbuild.append(']');
435 }
436
437 private static void intArrayAppend(final StringBuilder sbuild,
438 final int[] a) {
439 sbuild.append('[');
440 final int len = a.length;
441 for (int i = 0; i < len; i++) {
442 sbuild.append(a[i]);
443 if (i != len - 1) {
444 sbuild.append(", ");
445 }
446 }
447 sbuild.append(']');
448 }
449
450 private static void longArrayAppend(final StringBuilder sbuild,
451 final long[] a) {
452 sbuild.append('[');
453 final int len = a.length;
454 for (int i = 0; i < len; i++) {
455 sbuild.append(a[i]);
456 if (i != len - 1) {
457 sbuild.append(", ");
458 }
459 }
460 sbuild.append(']');
461 }
462
463 private static void floatArrayAppend(final StringBuilder sbuild,
464 final float[] a) {
465 sbuild.append('[');
466 final int len = a.length;
467 for (int i = 0; i < len; i++) {
468 sbuild.append(a[i]);
469 if (i != len - 1) {
470 sbuild.append(", ");
471 }
472 }
473 sbuild.append(']');
474 }
475
476 private static void doubleArrayAppend(final StringBuilder sbuild,
477 final double[] a) {
478 sbuild.append('[');
479 final int len = a.length;
480 for (int i = 0; i < len; i++) {
481 sbuild.append(a[i]);
482 if (i != len - 1) {
483 sbuild.append(", ");
484 }
485 }
486 sbuild.append(']');
487 }
488
489 private MessageFormatter() {
490 }
491 }