diff options
author | Jeff Sharkey <jsharkey@android.com> | 2020-10-16 23:35:01 -0600 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2020-10-20 21:54:26 -0600 |
commit | caf66d02fde298fcb010668e353a156bf2d49661 (patch) | |
tree | 15d4bc11576ed9a76226589517148e23dc6182dc | |
parent | 5c46da2a14e8ec8f0f39dc7802292581f7c4593e (diff) |
Expand formatSimple() to support widths.
One of the highest-traffic users of String.format() is in the
notification code, which uses argument widths. To support these
use-cases, this change adds argument width support, with tests.
Bug: 170978902
Test: atest error_prone_android_framework_test
Exempt-From-Owner-Approval: trivial additions
Change-Id: I8e36d4725a6d0cc896dedc5c457eb5f38486d7b6
4 files changed, 66 insertions, 7 deletions
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 72b35b9253eb..d0fd2b393633 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -2095,6 +2095,9 @@ public class TextUtils { * <li>{@code %s} for {@code String} * <li>{@code %x} for hex representation of {@code int} or {@code long} * <li>{@code %%} for literal {@code %} + * <li>{@code %04d} style grammar to specify the argument width, such as + * {@code %04d} to prefix an {@code int} with zeros or {@code %10b} to + * prefix a {@code boolean} with spaces * </ul> * * @throws IllegalArgumentException if the format string or arguments don't @@ -2106,8 +2109,23 @@ public class TextUtils { int j = 0; for (int i = 0; i < sb.length(); ) { if (sb.charAt(i) == '%') { + char code = sb.charAt(i + 1); + + // Decode any argument width request + char prefixChar = '\0'; + int prefixLen = 0; + int consume = 2; + while ('0' <= code && code <= '9') { + if (prefixChar == '\0') { + prefixChar = (code == '0') ? '0' : ' '; + } + prefixLen *= 10; + prefixLen += Character.digit(code, 10); + consume += 1; + code = sb.charAt(i + consume - 1); + } + final String repl; - final char code = sb.charAt(i + 1); switch (code) { case 'b': { if (j == args.length) { @@ -2155,8 +2173,15 @@ public class TextUtils { throw new IllegalArgumentException("Unsupported format code " + code); } } - sb.replace(i, i + 2, repl); - i += repl.length(); + + sb.replace(i, i + consume, repl); + + // Apply any argument width request + final int prefixInsert = (prefixChar == '0' && repl.charAt(0) == '-') ? 1 : 0; + for (int k = repl.length(); k < prefixLen; k++) { + sb.insert(i + prefixInsert, prefixChar); + } + i += Math.max(repl.length(), prefixLen); } else { i++; } diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java index 4e49118f438c..5362be337ede 100644 --- a/core/tests/coretests/src/android/text/TextUtilsTest.java +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -822,6 +822,25 @@ public class TextUtilsTest { } @Test + public void testFormatSimple_Width() { + assertEquals("42", formatSimple("%1d", 42)); + assertEquals("42", formatSimple("%2d", 42)); + assertEquals(" 42", formatSimple("%3d", 42)); + assertEquals(" 42", formatSimple("%4d", 42)); + assertEquals(" 42 42", formatSimple("%4d%4d", 42, 42)); + assertEquals(" -42", formatSimple("%4d", -42)); + assertEquals(" 42", formatSimple("%10d", 42)); + + assertEquals("42", formatSimple("%01d", 42)); + assertEquals("42", formatSimple("%02d", 42)); + assertEquals("042", formatSimple("%03d", 42)); + assertEquals("0042", formatSimple("%04d", 42)); + assertEquals("00420042", formatSimple("%04d%04d", 42, 42)); + assertEquals("-042", formatSimple("%04d", -42)); + assertEquals("0000000042", formatSimple("%010d", 42)); + } + + @Test public void testFormatSimple_Empty() { assertEquals("", formatSimple("")); } @@ -833,6 +852,13 @@ public class TextUtilsTest { } @Test + public void testFormatSimple_Advanced() { + assertEquals("crtcl=0x002a:intrsv=Y:grnk=0x0018:gsmry=A:example:rnk=0x0000", + formatSimple("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", + 42, 'Y', 24, 'A', "example", 0)); + } + + @Test public void testFormatSimple_Mismatch() { try { formatSimple("%s"); diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java index d2cb030faef6..3a0fbd33933f 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java @@ -180,8 +180,10 @@ public final class EfficientStringsChecker extends BugChecker for (int i = 0; i < format.length(); i++) { char c = format.charAt(i); if (c == '%') { - i++; - c = format.charAt(i); + c = format.charAt(++i); + while ('0' <= c && c <= '9') { + c = format.charAt(++i); + } switch (c) { case 'b': case 'c': diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java index ae9c316b8ca7..48e4ad11f9bf 100644 --- a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java +++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java @@ -41,9 +41,13 @@ public class EfficientStringsCheckerTest { assertTrue(EfficientStringsChecker.isSimple("")); assertTrue(EfficientStringsChecker.isSimple("%s")); assertTrue(EfficientStringsChecker.isSimple("String %s%s and %%%% number %d%d together")); + assertTrue(EfficientStringsChecker.isSimple("%04d")); + assertTrue(EfficientStringsChecker.isSimple("%02x:%02x:%02x")); + assertTrue(EfficientStringsChecker.isSimple("%10d")); - assertFalse(EfficientStringsChecker.isSimple("%04d")); - assertFalse(EfficientStringsChecker.isSimple("%02x:%02x:%02x")); + assertFalse(EfficientStringsChecker.isSimple("%0.4f")); + assertFalse(EfficientStringsChecker.isSimple("%t")); + assertFalse(EfficientStringsChecker.isSimple("%1$s")); } @Test @@ -58,6 +62,7 @@ public class EfficientStringsCheckerTest { " String.format(\"foo %s bar\", str);", " // BUG: Diagnostic contains:", " String.format(\"foo %d bar\", 42);", + " // BUG: Diagnostic contains:", " String.format(\"foo %04d bar\", 42);", " }", " public void exampleLocale(String str) {", @@ -66,6 +71,7 @@ public class EfficientStringsCheckerTest { " String.format(Locale.US, \"foo %s bar\", str);", " // BUG: Diagnostic contains:", " String.format(Locale.US, \"foo %d bar\", 42);", + " // BUG: Diagnostic contains:", " String.format(Locale.US, \"foo %04d bar\", 42);", " }", "}") |