MessageFormatter improvements

Motivation:

`FormattingTuple.getArgArray()` is never used.
In the `MessageFormatter` it is possible to make
some improvements, e.g. replace `StringBuffer`
with `StringBuilder`, avoid redundant allocations, etc.

Modifications:

- Remove `argArray` field from the `FormattingTuple`.
- In `MessageFormatter`:
  - replace `StringBuffer` with `StringBuilder`,
  - replace `HashMap` with `HashSet` and make it lazy initialized.
  - avoid redundant allocations (`substring()`, etc.)
  - use appropriate StringBuilder's methods for the some `Number` values.
- Porting unit tests from `slf4j`.

Result:

Less GC load on logging with internal `MessageFormatter`.
This commit is contained in:
Nikolay Fedorovskikh 2017-05-15 02:07:16 +05:00 committed by Scott Mitchell
parent ba5d1880bc
commit d768c5e628
3 changed files with 473 additions and 206 deletions

View File

@ -42,46 +42,20 @@ package io.netty.util.internal.logging;
/**
* Holds the results of formatting done by {@link MessageFormatter}.
*/
class FormattingTuple {
static final FormattingTuple NULL = new FormattingTuple(null);
final class FormattingTuple {
private final String message;
private final Throwable throwable;
private final Object[] argArray;
FormattingTuple(String message) {
this(message, null, null);
}
FormattingTuple(String message, Object[] argArray, Throwable throwable) {
FormattingTuple(String message, Throwable throwable) {
this.message = message;
this.throwable = throwable;
if (throwable == null) {
this.argArray = argArray;
} else {
this.argArray = trimmedCopy(argArray);
}
}
static Object[] trimmedCopy(Object[] argArray) {
if (argArray == null || argArray.length == 0) {
throw new IllegalStateException("nonsensical empty or null argument array");
}
final int trimmedLen = argArray.length - 1;
Object[] trimmed = new Object[trimmedLen];
System.arraycopy(argArray, 0, trimmed, 0, trimmedLen);
return trimmed;
}
public String getMessage() {
return message;
}
public Object[] getArgArray() {
return argArray;
}
public Throwable getThrowable() {
return throwable;
}

View File

@ -40,8 +40,8 @@
package io.netty.util.internal.logging;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.HashSet;
import java.util.Set;
// contributors: lizongbo: proposed special treatment of array parameter values
// Joern Huxhorn: pointed out double[] omission, suggested deep array copy
@ -109,9 +109,7 @@ import java.util.Map;
* {@link #arrayFormat(String, Object[])} methods for more details.
*/
final class MessageFormatter {
static final char DELIM_START = '{';
static final char DELIM_STOP = '}';
static final String DELIM_STR = "{}";
private static final String DELIM_STR = "{}";
private static final char ESCAPE_CHAR = '\\';
/**
@ -159,18 +157,6 @@ final class MessageFormatter {
return arrayFormat(messagePattern, new Object[]{argA, argB});
}
static Throwable getThrowableCandidate(Object[] argArray) {
if (argArray == null || argArray.length == 0) {
return null;
}
final Object lastEntry = argArray[argArray.length - 1];
if (lastEntry instanceof Throwable) {
return (Throwable) lastEntry;
}
return null;
}
/**
* Same principle as the {@link #format(String, Object)} and
* {@link #format(String, Object, Object)} methods except that any number of
@ -183,118 +169,109 @@ final class MessageFormatter {
*/
static FormattingTuple arrayFormat(final String messagePattern,
final Object[] argArray) {
if (argArray == null || argArray.length == 0) {
return new FormattingTuple(messagePattern, null);
}
Throwable throwableCandidate = getThrowableCandidate(argArray);
int lastArrIdx = argArray.length - 1;
Object lastEntry = argArray[lastArrIdx];
Throwable throwable = lastEntry instanceof Throwable? (Throwable) lastEntry : null;
if (messagePattern == null) {
return new FormattingTuple(null, argArray, throwableCandidate);
return new FormattingTuple(null, throwable);
}
if (argArray == null) {
return new FormattingTuple(messagePattern);
int j = messagePattern.indexOf(DELIM_STR);
if (j == -1) {
// this is a simple string
return new FormattingTuple(messagePattern, throwable);
}
StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);
int i = 0;
int j;
StringBuffer sbuf = new StringBuffer(messagePattern.length() + 50);
int L = 0;
do {
boolean notEscaped = j == 0 || messagePattern.charAt(j - 1) != ESCAPE_CHAR;
if (notEscaped) {
// normal case
sbuf.append(messagePattern, i, j);
} else {
sbuf.append(messagePattern, i, j - 1);
// check that escape char is not is escaped: "abc x:\\{}"
notEscaped = j >= 2 && messagePattern.charAt(j - 2) == ESCAPE_CHAR;
}
int L;
for (L = 0; L < argArray.length; L++) {
j = messagePattern.indexOf(DELIM_STR, i);
if (j == -1) {
// no more variables
if (i == 0) { // this is a simple string
return new FormattingTuple(messagePattern, argArray,
throwableCandidate);
} else { // add the tail string which contains no variables and return
// the result.
sbuf.append(messagePattern.substring(i, messagePattern.length()));
return new FormattingTuple(sbuf.toString(), argArray,
throwableCandidate);
i = j + 2;
if (notEscaped) {
deeplyAppendParameter(sbuf, argArray[L], null);
L++;
if (L > lastArrIdx) {
break;
}
} else {
if (isEscapedDelimeter(messagePattern, j)) {
if (!isDoubleEscaped(messagePattern, j)) {
L--; // DELIM_START was escaped, thus should not be incremented
sbuf.append(messagePattern.substring(i, j - 1));
sbuf.append(DELIM_START);
i = j + 1;
} else {
// The escape character preceding the delimiter start is
// itself escaped: "abc x:\\{}"
// we have to consume one backward slash
sbuf.append(messagePattern.substring(i, j - 1));
deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Void>());
i = j + 2;
}
} else {
// normal case
sbuf.append(messagePattern.substring(i, j));
deeplyAppendParameter(sbuf, argArray[L], new HashMap<Object[], Void>());
i = j + 2;
}
sbuf.append(DELIM_STR);
}
}
j = messagePattern.indexOf(DELIM_STR, i);
} while (j != -1);
// append the characters following the last {} pair.
sbuf.append(messagePattern.substring(i, messagePattern.length()));
if (L < argArray.length - 1) {
return new FormattingTuple(sbuf.toString(), argArray, throwableCandidate);
} else {
return new FormattingTuple(sbuf.toString(), argArray, null);
}
}
static boolean isEscapedDelimeter(String messagePattern,
int delimeterStartIndex) {
if (delimeterStartIndex == 0) {
return false;
}
return messagePattern.charAt(delimeterStartIndex - 1) == ESCAPE_CHAR;
}
static boolean isDoubleEscaped(String messagePattern,
int delimeterStartIndex) {
return delimeterStartIndex >= 2 && messagePattern.charAt(delimeterStartIndex - 2) == ESCAPE_CHAR;
sbuf.append(messagePattern, i, messagePattern.length());
return new FormattingTuple(sbuf.toString(), L <= lastArrIdx? throwable : null);
}
// special treatment of array values was suggested by 'lizongbo'
private static void deeplyAppendParameter(StringBuffer sbuf, Object o,
Map<Object[], Void> seenMap) {
private static void deeplyAppendParameter(StringBuilder sbuf, Object o,
Set<Object[]> seenSet) {
if (o == null) {
sbuf.append("null");
return;
}
if (!o.getClass().isArray()) {
safeObjectAppend(sbuf, o);
Class<?> objClass = o.getClass();
if (!objClass.isArray()) {
if (Number.class.isAssignableFrom(objClass)) {
// Prevent String instantiation for some number types
if (objClass == Long.class) {
sbuf.append(((Long) o).longValue());
} else if (objClass == Integer.class || objClass == Short.class || objClass == Byte.class) {
sbuf.append(((Number) o).intValue());
} else if (objClass == Double.class) {
sbuf.append(((Double) o).doubleValue());
} else if (objClass == Float.class) {
sbuf.append(((Float) o).floatValue());
} else {
safeObjectAppend(sbuf, o);
}
} else {
safeObjectAppend(sbuf, o);
}
} else {
// check for primitive array types because they
// unfortunately cannot be cast to Object[]
if (o instanceof boolean[]) {
sbuf.append('[');
if (objClass == boolean[].class) {
booleanArrayAppend(sbuf, (boolean[]) o);
} else if (o instanceof byte[]) {
} else if (objClass == byte[].class) {
byteArrayAppend(sbuf, (byte[]) o);
} else if (o instanceof char[]) {
} else if (objClass == char[].class) {
charArrayAppend(sbuf, (char[]) o);
} else if (o instanceof short[]) {
} else if (objClass == short[].class) {
shortArrayAppend(sbuf, (short[]) o);
} else if (o instanceof int[]) {
} else if (objClass == int[].class) {
intArrayAppend(sbuf, (int[]) o);
} else if (o instanceof long[]) {
} else if (objClass == long[].class) {
longArrayAppend(sbuf, (long[]) o);
} else if (o instanceof float[]) {
} else if (objClass == float[].class) {
floatArrayAppend(sbuf, (float[]) o);
} else if (o instanceof double[]) {
} else if (objClass == double[].class) {
doubleArrayAppend(sbuf, (double[]) o);
} else {
objectArrayAppend(sbuf, (Object[]) o, seenMap);
objectArrayAppend(sbuf, (Object[]) o, seenSet);
}
sbuf.append(']');
}
}
private static void safeObjectAppend(StringBuffer sbuf, Object o) {
private static void safeObjectAppend(StringBuilder sbuf, Object o) {
try {
String oAsString = o.toString();
sbuf.append(oAsString);
@ -307,120 +284,112 @@ final class MessageFormatter {
}
}
private static void objectArrayAppend(StringBuffer sbuf, Object[] a,
Map<Object[], Void> seenMap) {
sbuf.append('[');
if (!seenMap.containsKey(a)) {
seenMap.put(a, null);
final int len = a.length;
for (int i = 0; i < len; i++) {
deeplyAppendParameter(sbuf, a[i], seenMap);
if (i != len - 1) {
sbuf.append(", ");
}
private static void objectArrayAppend(StringBuilder sbuf, Object[] a, Set<Object[]> seenSet) {
if (a.length == 0) {
return;
}
if (seenSet == null) {
seenSet = new HashSet<Object[]>(a.length);
}
if (seenSet.add(a)) {
deeplyAppendParameter(sbuf, a[0], seenSet);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
deeplyAppendParameter(sbuf, a[i], seenSet);
}
// allow repeats in siblings
seenMap.remove(a);
seenSet.remove(a);
} else {
sbuf.append("...");
}
sbuf.append(']');
}
private static void booleanArrayAppend(StringBuffer sbuf, boolean[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void booleanArrayAppend(StringBuilder sbuf, boolean[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void byteArrayAppend(StringBuffer sbuf, byte[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void byteArrayAppend(StringBuilder sbuf, byte[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void charArrayAppend(StringBuffer sbuf, char[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void charArrayAppend(StringBuilder sbuf, char[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void shortArrayAppend(StringBuffer sbuf, short[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void shortArrayAppend(StringBuilder sbuf, short[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void intArrayAppend(StringBuffer sbuf, int[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void intArrayAppend(StringBuilder sbuf, int[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void longArrayAppend(StringBuffer sbuf, long[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void longArrayAppend(StringBuilder sbuf, long[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void floatArrayAppend(StringBuffer sbuf, float[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void floatArrayAppend(StringBuilder sbuf, float[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private static void doubleArrayAppend(StringBuffer sbuf, double[] a) {
sbuf.append('[');
final int len = a.length;
for (int i = 0; i < len; i++) {
sbuf.append(a[i]);
if (i != len - 1) {
sbuf.append(", ");
}
private static void doubleArrayAppend(StringBuilder sbuf, double[] a) {
if (a.length == 0) {
return;
}
sbuf.append(a[0]);
for (int i = 1; i < a.length; i++) {
sbuf.append(", ");
sbuf.append(a[i]);
}
sbuf.append(']');
}
private MessageFormatter() {

View File

@ -0,0 +1,324 @@
/*
* Copyright 2017 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Copyright (c) 2004-2011 QOS.ch
* All rights reserved.
* <p>
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* <p>
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package io.netty.util.internal.logging;
import org.junit.Test;
import static org.junit.Assert.*;
public class MessageFormatterTest {
@Test
public void testNull() {
String result = MessageFormatter.format(null, 1).getMessage();
assertNull(result);
}
@Test
public void nullParametersShouldBeHandledWithoutBarfing() {
String result = MessageFormatter.format("Value is {}.", null).getMessage();
assertEquals("Value is null.", result);
result = MessageFormatter.format("Val1 is {}, val2 is {}.", null, null).getMessage();
assertEquals("Val1 is null, val2 is null.", result);
result = MessageFormatter.format("Val1 is {}, val2 is {}.", 1, null).getMessage();
assertEquals("Val1 is 1, val2 is null.", result);
result = MessageFormatter.format("Val1 is {}, val2 is {}.", null, 2).getMessage();
assertEquals("Val1 is null, val2 is 2.", result);
result = MessageFormatter.arrayFormat(
"Val1 is {}, val2 is {}, val3 is {}", new Integer[] { null, null, null }).getMessage();
assertEquals("Val1 is null, val2 is null, val3 is null", result);
result = MessageFormatter.arrayFormat(
"Val1 is {}, val2 is {}, val3 is {}", new Integer[] { null, 2, 3 }).getMessage();
assertEquals("Val1 is null, val2 is 2, val3 is 3", result);
result = MessageFormatter.arrayFormat(
"Val1 is {}, val2 is {}, val3 is {}", new Integer[] { null, null, 3 }).getMessage();
assertEquals("Val1 is null, val2 is null, val3 is 3", result);
}
@Test
public void verifyOneParameterIsHandledCorrectly() {
String result = MessageFormatter.format("Value is {}.", 3).getMessage();
assertEquals("Value is 3.", result);
result = MessageFormatter.format("Value is {", 3).getMessage();
assertEquals("Value is {", result);
result = MessageFormatter.format("{} is larger than 2.", 3).getMessage();
assertEquals("3 is larger than 2.", result);
result = MessageFormatter.format("No subst", 3).getMessage();
assertEquals("No subst", result);
result = MessageFormatter.format("Incorrect {subst", 3).getMessage();
assertEquals("Incorrect {subst", result);
result = MessageFormatter.format("Value is {bla} {}", 3).getMessage();
assertEquals("Value is {bla} 3", result);
result = MessageFormatter.format("Escaped \\{} subst", 3).getMessage();
assertEquals("Escaped {} subst", result);
result = MessageFormatter.format("{Escaped", 3).getMessage();
assertEquals("{Escaped", result);
result = MessageFormatter.format("\\{}Escaped", 3).getMessage();
assertEquals("{}Escaped", result);
result = MessageFormatter.format("File name is {{}}.", "App folder.zip").getMessage();
assertEquals("File name is {App folder.zip}.", result);
// escaping the escape character
result = MessageFormatter.format("File name is C:\\\\{}.", "App folder.zip").getMessage();
assertEquals("File name is C:\\App folder.zip.", result);
}
@Test
public void testTwoParameters() {
String result = MessageFormatter.format("Value {} is smaller than {}.", 1, 2).getMessage();
assertEquals("Value 1 is smaller than 2.", result);
result = MessageFormatter.format("Value {} is smaller than {}", 1, 2).getMessage();
assertEquals("Value 1 is smaller than 2", result);
result = MessageFormatter.format("{}{}", 1, 2).getMessage();
assertEquals("12", result);
result = MessageFormatter.format("Val1={}, Val2={", 1, 2).getMessage();
assertEquals("Val1=1, Val2={", result);
result = MessageFormatter.format("Value {} is smaller than \\{}", 1, 2).getMessage();
assertEquals("Value 1 is smaller than {}", result);
result = MessageFormatter.format("Value {} is smaller than \\{} tail", 1, 2).getMessage();
assertEquals("Value 1 is smaller than {} tail", result);
result = MessageFormatter.format("Value {} is smaller than \\{", 1, 2).getMessage();
assertEquals("Value 1 is smaller than \\{", result);
result = MessageFormatter.format("Value {} is smaller than {tail", 1, 2).getMessage();
assertEquals("Value 1 is smaller than {tail", result);
result = MessageFormatter.format("Value \\{} is smaller than {}", 1, 2).getMessage();
assertEquals("Value {} is smaller than 1", result);
}
@Test
public void testExceptionIn_toString() {
Object o = new Object() {
@Override
public String toString() {
throw new IllegalStateException("a");
}
};
String result = MessageFormatter.format("Troublesome object {}", o).getMessage();
assertEquals("Troublesome object [FAILED toString()]", result);
}
@Test
public void testNullArray() {
String msg0 = "msg0";
String msg1 = "msg1 {}";
String msg2 = "msg2 {} {}";
String msg3 = "msg3 {} {} {}";
Object[] args = null;
String result = MessageFormatter.arrayFormat(msg0, args).getMessage();
assertEquals(msg0, result);
result = MessageFormatter.arrayFormat(msg1, args).getMessage();
assertEquals(msg1, result);
result = MessageFormatter.arrayFormat(msg2, args).getMessage();
assertEquals(msg2, result);
result = MessageFormatter.arrayFormat(msg3, args).getMessage();
assertEquals(msg3, result);
}
// tests the case when the parameters are supplied in a single array
@Test
public void testArrayFormat() {
Integer[] ia0 = { 1, 2, 3 };
String result = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia0).getMessage();
assertEquals("Value 1 is smaller than 2 and 3.", result);
result = MessageFormatter.arrayFormat("{}{}{}", ia0).getMessage();
assertEquals("123", result);
result = MessageFormatter.arrayFormat("Value {} is smaller than {}.", ia0).getMessage();
assertEquals("Value 1 is smaller than 2.", result);
result = MessageFormatter.arrayFormat("Value {} is smaller than {}", ia0).getMessage();
assertEquals("Value 1 is smaller than 2", result);
result = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia0).getMessage();
assertEquals("Val=1, {, Val=2", result);
result = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia0).getMessage();
assertEquals("Val=1, {, Val=2", result);
result = MessageFormatter.arrayFormat("Val1={}, Val2={", ia0).getMessage();
assertEquals("Val1=1, Val2={", result);
}
@Test
public void testArrayValues() {
Integer[] p1 = { 2, 3 };
String result = MessageFormatter.format("{}{}", 1, p1).getMessage();
assertEquals("1[2, 3]", result);
// Integer[]
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", p1 }).getMessage();
assertEquals("a[2, 3]", result);
// byte[]
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", new byte[] { 1, 2 } }).getMessage();
assertEquals("a[1, 2]", result);
// int[]
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", new int[] { 1, 2 } }).getMessage();
assertEquals("a[1, 2]", result);
// float[]
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", new float[] { 1, 2 } }).getMessage();
assertEquals("a[1.0, 2.0]", result);
// double[]
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", new double[] { 1, 2 } }).getMessage();
assertEquals("a[1.0, 2.0]", result);
}
@Test
public void testMultiDimensionalArrayValues() {
Integer[] ia0 = { 1, 2, 3 };
Integer[] ia1 = { 10, 20, 30 };
Integer[][] multiIntegerA = { ia0, ia1 };
String result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", multiIntegerA }).getMessage();
assertEquals("a[[1, 2, 3], [10, 20, 30]]", result);
int[][] multiIntA = { { 1, 2 }, { 10, 20 } };
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", multiIntA }).getMessage();
assertEquals("a[[1, 2], [10, 20]]", result);
float[][] multiFloatA = { { 1, 2 }, { 10, 20 } };
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", multiFloatA }).getMessage();
assertEquals("a[[1.0, 2.0], [10.0, 20.0]]", result);
Object[][] multiOA = { ia0, ia1 };
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", multiOA }).getMessage();
assertEquals("a[[1, 2, 3], [10, 20, 30]]", result);
Object[][][] _3DOA = { multiOA, multiOA };
result = MessageFormatter.arrayFormat("{}{}", new Object[] { "a", _3DOA }).getMessage();
assertEquals("a[[[1, 2, 3], [10, 20, 30]], [[1, 2, 3], [10, 20, 30]]]", result);
Byte[] ba0 = { 0, Byte.MAX_VALUE, Byte.MIN_VALUE };
Short[] sa0 = { 0, Short.MIN_VALUE, Short.MAX_VALUE };
result = MessageFormatter.arrayFormat("{}\\{}{}", new Object[] { new Object[] { ba0, sa0 }, ia1 }).getMessage();
assertEquals("[[0, 127, -128], [0, -32768, 32767]]{}[10, 20, 30]", result);
}
@Test
public void testCyclicArrays() {
Object[] cyclicA = new Object[1];
cyclicA[0] = cyclicA;
assertEquals("[[...]]", MessageFormatter.arrayFormat("{}", cyclicA).getMessage());
Object[] a = new Object[2];
a[0] = 1;
Object[] c = { 3, a };
Object[] b = { 2, c };
a[1] = b;
assertEquals("1[2, [3, [1, [...]]]]",
MessageFormatter.arrayFormat("{}{}", a).getMessage());
}
@Test
public void testArrayThrowable() {
FormattingTuple ft;
Throwable t = new Throwable();
Object[] ia = { 1, 2, 3, t };
ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia);
assertEquals("Value 1 is smaller than 2 and 3.", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("{}{}{}", ia);
assertEquals("123", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Value {} is smaller than {}.", ia);
assertEquals("Value 1 is smaller than 2.", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Value {} is smaller than {}", ia);
assertEquals("Value 1 is smaller than 2", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Val={}, {, Val={}", ia);
assertEquals("Val=1, {, Val=2", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Val={}, \\{, Val={}", ia);
assertEquals("Val=1, \\{, Val=2", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Val1={}, Val2={", ia);
assertEquals("Val1=1, Val2={", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("Value {} is smaller than {} and {}.", ia);
assertEquals("Value 1 is smaller than 2 and 3.", ft.getMessage());
assertEquals(t, ft.getThrowable());
ft = MessageFormatter.arrayFormat("{}{}{}{}", ia);
assertEquals("123java.lang.Throwable", ft.getMessage());
assertNull(ft.getThrowable());
}
}