summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2020-10-16 23:35:01 -0600
committerJeff Sharkey <jsharkey@android.com>2020-10-20 21:54:26 -0600
commitcaf66d02fde298fcb010668e353a156bf2d49661 (patch)
tree15d4bc11576ed9a76226589517148e23dc6182dc
parent5c46da2a14e8ec8f0f39dc7802292581f7c4593e (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
-rw-r--r--core/java/android/text/TextUtils.java31
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java26
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java6
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java10
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);",
" }",
"}")