Skip to content

Commit

Permalink
Introduce Strings.lenientFormat(), copied from Preconditions.format()…
Browse files Browse the repository at this point in the history
…. Rewrote documentation but the method body remains unchanged.

RELNOTES=Introduce `Strings.lenientFormat()`, copied from `Preconditions.format()`.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=196586088
  • Loading branch information
dimo414 authored and ronshapiro committed May 15, 2018
1 parent 9b4a9db commit 7fe1702
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 0 deletions.
19 changes: 19 additions & 0 deletions android/guava-tests/test/com/google/common/base/StringsTest.java
Expand Up @@ -211,6 +211,25 @@ public void testValidSurrogatePairAt() {
assertFalse(Strings.validSurrogatePairAt("\uD8ABx", 0));
}

public void testLenientFormat() {
assertEquals("%s", Strings.lenientFormat("%s"));
assertEquals("5", Strings.lenientFormat("%s", 5));
assertEquals("foo [5]", Strings.lenientFormat("foo", 5));
assertEquals("foo [5, 6, 7]", Strings.lenientFormat("foo", 5, 6, 7));
assertEquals("%s 1 2", Strings.lenientFormat("%s %s %s", "%s", 1, 2));
assertEquals(" [5, 6]", Strings.lenientFormat("", 5, 6));
assertEquals("123", Strings.lenientFormat("%s%s%s", 1, 2, 3));
assertEquals("1%s%s", Strings.lenientFormat("%s%s%s", 1));
assertEquals("5 + 6 = 11", Strings.lenientFormat("%s + 6 = 11", 5));
assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + %s = 11", 6));
assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + 6 = %s", 11));
assertEquals("5 + 6 = 11", Strings.lenientFormat("%s + %s = %s", 5, 6, 11));
assertEquals("null [null, null]", Strings.lenientFormat("%s", null, null, null));
assertEquals("null [5, 6]", Strings.lenientFormat(null, 5, 6));
assertEquals("null", Strings.lenientFormat("%s", (Object) null));
assertEquals("(Object[])null", Strings.lenientFormat("%s", (Object[]) null));
}

@GwtIncompatible // NullPointerTester
public void testNullPointers() {
NullPointerTester tester = new NullPointerTester();
Expand Down
66 changes: 66 additions & 0 deletions android/guava/src/com/google/common/base/Strings.java
Expand Up @@ -210,6 +210,72 @@ public static String commonSuffix(CharSequence a, CharSequence b) {
return a.subSequence(a.length() - s, a.length()).toString();
}

/**
* Returns the given {@code template} string with each occurrence of {@code "%s"} replaced with
* the corresponding argument value from {@code args}; or, if the placeholder and argument counts
* do not match, returns a best-effort form of that string. Will not throw an exception under any
* circumstances (as long as all arguments' {@code toString} methods successfully return).
*
* <p><b>Note:</b> For most string-formatting needs, use {@link String#format}, {@link
* PrintWriter#format}, and related methods. These support the full range of {@linkplain
* Formatter#syntax format specifiers}, and alert you to usage errors by throwing {@link
* InvalidFormatException}.
*
* <p>In certain cases, such as outputting debugging information or constructing a message to be
* used for another unchecked exception, an exception during string formatting would serve little
* purpose except to supplant the real information you were trying to provide. These are the cases
* this method is made for; it instead generates a best-effort string with all supplied argument
* values present. This method is also useful in environments such as GWT where {@code
* String.format} is not available. As an example, method implementations of the {@link
* Preconditions} class use this formatter, for both of the reasons just discussed.
*
* <p><b>Warning:</b> Only the exact two-character placeholder sequence {@code "%s"} is
* recognized.
*
* @param template a string containing zero or more {@code "%s"} placeholder sequences. {@code
* null} is treated as the four-character string {@code "null"}.
* @param args the arguments to be substituted into the message template. The first argument
* specified is substituted for the first occurrence of {@code "%s"} in the template, and so
* forth. A {@code null} argument is converted to the four-character string {@code "null"};
* non-null values are converted to strings using {@link Object#toString()}.
* @since NEXT
*/
// TODO(diamondm) consider using Arrays.toString() for array parameters
// TODO(diamondm) capture exceptions thrown from arguments' toString methods
public static String lenientFormat(@NullableDecl String template, @NullableDecl Object... args) {
template = String.valueOf(template); // null -> "null"

args = args == null ? new Object[] {"(Object[])null"} : args;

// start substituting the arguments into the '%s' placeholders
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i = 0;
while (i < args.length) {
int placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template, templateStart, placeholderStart);
builder.append(args[i++]);
templateStart = placeholderStart + 2;
}
builder.append(template, templateStart, template.length());

// if we run out of placeholders, append the extra args in square braces
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while (i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}

return builder.toString();
}

/**
* True when a valid surrogate pair starts at the given {@code index} in the given {@code string}.
* Out-of-range indexes return false.
Expand Down
5 changes: 5 additions & 0 deletions guava-gwt/test/com/google/common/base/StringsTest_gwt.java
Expand Up @@ -38,6 +38,11 @@ public void testIsNullOrEmpty() throws Exception {
testCase.testIsNullOrEmpty();
}

public void testLenientFormat() throws Exception {
com.google.common.base.StringsTest testCase = new com.google.common.base.StringsTest();
testCase.testLenientFormat();
}

public void testNullToEmpty() throws Exception {
com.google.common.base.StringsTest testCase = new com.google.common.base.StringsTest();
testCase.testNullToEmpty();
Expand Down
19 changes: 19 additions & 0 deletions guava-tests/test/com/google/common/base/StringsTest.java
Expand Up @@ -211,6 +211,25 @@ public void testValidSurrogatePairAt() {
assertFalse(Strings.validSurrogatePairAt("\uD8ABx", 0));
}

public void testLenientFormat() {
assertEquals("%s", Strings.lenientFormat("%s"));
assertEquals("5", Strings.lenientFormat("%s", 5));
assertEquals("foo [5]", Strings.lenientFormat("foo", 5));
assertEquals("foo [5, 6, 7]", Strings.lenientFormat("foo", 5, 6, 7));
assertEquals("%s 1 2", Strings.lenientFormat("%s %s %s", "%s", 1, 2));
assertEquals(" [5, 6]", Strings.lenientFormat("", 5, 6));
assertEquals("123", Strings.lenientFormat("%s%s%s", 1, 2, 3));
assertEquals("1%s%s", Strings.lenientFormat("%s%s%s", 1));
assertEquals("5 + 6 = 11", Strings.lenientFormat("%s + 6 = 11", 5));
assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + %s = 11", 6));
assertEquals("5 + 6 = 11", Strings.lenientFormat("5 + 6 = %s", 11));
assertEquals("5 + 6 = 11", Strings.lenientFormat("%s + %s = %s", 5, 6, 11));
assertEquals("null [null, null]", Strings.lenientFormat("%s", null, null, null));
assertEquals("null [5, 6]", Strings.lenientFormat(null, 5, 6));
assertEquals("null", Strings.lenientFormat("%s", (Object) null));
assertEquals("(Object[])null", Strings.lenientFormat("%s", (Object[]) null));
}

@GwtIncompatible // NullPointerTester
public void testNullPointers() {
NullPointerTester tester = new NullPointerTester();
Expand Down
67 changes: 67 additions & 0 deletions guava/src/com/google/common/base/Strings.java
Expand Up @@ -209,6 +209,73 @@ public static String commonSuffix(CharSequence a, CharSequence b) {
return a.subSequence(a.length() - s, a.length()).toString();
}

/**
* Returns the given {@code template} string with each occurrence of {@code "%s"} replaced with
* the corresponding argument value from {@code args}; or, if the placeholder and argument counts
* do not match, returns a best-effort form of that string. Will not throw an exception under any
* circumstances (as long as all arguments' {@code toString} methods successfully return).
*
* <p><b>Note:</b> For most string-formatting needs, use {@link String#format}, {@link
* PrintWriter#format}, and related methods. These support the full range of {@linkplain
* Formatter#syntax format specifiers}, and alert you to usage errors by throwing {@link
* InvalidFormatException}.
*
* <p>In certain cases, such as outputting debugging information or constructing a message to be
* used for another unchecked exception, an exception during string formatting would serve little
* purpose except to supplant the real information you were trying to provide. These are the cases
* this method is made for; it instead generates a best-effort string with all supplied argument
* values present. This method is also useful in environments such as GWT where {@code
* String.format} is not available. As an example, method implementations of the {@link
* Preconditions} class use this formatter, for both of the reasons just discussed.
*
* <p><b>Warning:</b> Only the exact two-character placeholder sequence {@code "%s"} is
* recognized.
*
* @param template a string containing zero or more {@code "%s"} placeholder sequences. {@code
* null} is treated as the four-character string {@code "null"}.
* @param args the arguments to be substituted into the message template. The first argument
* specified is substituted for the first occurrence of {@code "%s"} in the template, and so
* forth. A {@code null} argument is converted to the four-character string {@code "null"};
* non-null values are converted to strings using {@link Object#toString()}.
* @since NEXT
*/
// TODO(diamondm) consider using Arrays.toString() for array parameters
// TODO(diamondm) capture exceptions thrown from arguments' toString methods
public static String lenientFormat(
@Nullable String template, @Nullable Object @Nullable... args) {
template = String.valueOf(template); // null -> "null"

args = args == null ? new Object[] {"(Object[])null"} : args;

// start substituting the arguments into the '%s' placeholders
StringBuilder builder = new StringBuilder(template.length() + 16 * args.length);
int templateStart = 0;
int i = 0;
while (i < args.length) {
int placeholderStart = template.indexOf("%s", templateStart);
if (placeholderStart == -1) {
break;
}
builder.append(template, templateStart, placeholderStart);
builder.append(args[i++]);
templateStart = placeholderStart + 2;
}
builder.append(template, templateStart, template.length());

// if we run out of placeholders, append the extra args in square braces
if (i < args.length) {
builder.append(" [");
builder.append(args[i++]);
while (i < args.length) {
builder.append(", ");
builder.append(args[i++]);
}
builder.append(']');
}

return builder.toString();
}

/**
* True when a valid surrogate pair starts at the given {@code index} in the given {@code string}.
* Out-of-range indexes return false.
Expand Down

0 comments on commit 7fe1702

Please sign in to comment.