Merge changes from topic "oct16b"
* changes: Recommend efficient String operations. Simple alternative to String.format().
This commit is contained in:
committed by
Android (Google) Code Review
commit
eac0321ff3
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package android.text;
|
||||
|
||||
import android.perftests.utils.BenchmarkState;
|
||||
import android.perftests.utils.PerfStatusReporter;
|
||||
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@LargeTest
|
||||
public class TextUtilsPerfTest {
|
||||
@Rule
|
||||
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
|
||||
|
||||
public static final String TEMPLATE = "Template that combines %s and %d together";
|
||||
|
||||
public String mVar1 = "example";
|
||||
public int mVar2 = 42;
|
||||
|
||||
/**
|
||||
* Measure overhead of formatting a string via {@link String#format}.
|
||||
*/
|
||||
@Test
|
||||
public void timeFormatUpstream() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
String res = String.format(TEMPLATE, mVar1, mVar2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure overhead of formatting a string via
|
||||
* {@link TextUtils#formatSimple}.
|
||||
*/
|
||||
@Test
|
||||
public void timeFormatLocal() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
String res = TextUtils.formatSimple(TEMPLATE, mVar1, mVar2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure overhead of formatting a string inline.
|
||||
*/
|
||||
@Test
|
||||
public void timeFormatInline() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
String res = "Template that combines " + mVar1 + " and " + mVar2 + " together";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure overhead of a passing null-check that uses a lambda to
|
||||
* communicate a custom error message.
|
||||
*/
|
||||
@Test
|
||||
public void timeFormat_Skip_Lambda() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
requireNonNull(this, () -> {
|
||||
return String.format(TEMPLATE, mVar1, mVar2);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure overhead of a passing null-check that uses varargs to communicate
|
||||
* a custom error message.
|
||||
*/
|
||||
@Test
|
||||
public void timeFormat_Skip_Varargs() {
|
||||
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
|
||||
while (state.keepRunning()) {
|
||||
requireNonNull(this, TEMPLATE, mVar1, mVar2);
|
||||
}
|
||||
}
|
||||
|
||||
private static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
private static <T> T requireNonNull(T obj, String format, Object... args) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
@@ -2079,6 +2079,94 @@ public class TextUtils {
|
||||
return Resources.getSystem().getQuantityString(R.plurals.selected_count, count, count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple alternative to {@link String#format} which purposefully supports
|
||||
* only a small handful of substitutions to improve execution speed.
|
||||
* Benchmarking reveals this optimized alternative performs 6.5x faster for
|
||||
* a typical format string.
|
||||
* <p>
|
||||
* Below is a summary of the limited grammar supported by this method; if
|
||||
* you need advanced features, please continue using {@link String#format}.
|
||||
* <ul>
|
||||
* <li>{@code %b} for {@code boolean}
|
||||
* <li>{@code %c} for {@code char}
|
||||
* <li>{@code %d} for {@code int} or {@code long}
|
||||
* <li>{@code %f} for {@code float} or {@code double}
|
||||
* <li>{@code %s} for {@code String}
|
||||
* <li>{@code %x} for hex representation of {@code int} or {@code long}
|
||||
* <li>{@code %%} for literal {@code %}
|
||||
* </ul>
|
||||
*
|
||||
* @throws IllegalArgumentException if the format string or arguments don't
|
||||
* match the supported grammar described above.
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull String formatSimple(@NonNull String format, Object... args) {
|
||||
final StringBuilder sb = new StringBuilder(format);
|
||||
int j = 0;
|
||||
for (int i = 0; i < sb.length(); ) {
|
||||
if (sb.charAt(i) == '%') {
|
||||
final String repl;
|
||||
final char code = sb.charAt(i + 1);
|
||||
switch (code) {
|
||||
case 'b': {
|
||||
if (j == args.length) {
|
||||
throw new IllegalArgumentException("Too few arguments");
|
||||
}
|
||||
final Object arg = args[j++];
|
||||
if (arg instanceof Boolean) {
|
||||
repl = Boolean.toString((boolean) arg);
|
||||
} else {
|
||||
repl = Boolean.toString(arg != null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'f':
|
||||
case 's': {
|
||||
if (j == args.length) {
|
||||
throw new IllegalArgumentException("Too few arguments");
|
||||
}
|
||||
final Object arg = args[j++];
|
||||
repl = String.valueOf(arg);
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
if (j == args.length) {
|
||||
throw new IllegalArgumentException("Too few arguments");
|
||||
}
|
||||
final Object arg = args[j++];
|
||||
if (arg instanceof Integer) {
|
||||
repl = Integer.toHexString((int) arg);
|
||||
} else if (arg instanceof Long) {
|
||||
repl = Long.toHexString((long) arg);
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"Unsupported hex type " + arg.getClass());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '%': {
|
||||
repl = "%";
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unsupported format code " + code);
|
||||
}
|
||||
}
|
||||
sb.replace(i, i + 2, repl);
|
||||
i += repl.length();
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
if (j != args.length) {
|
||||
throw new IllegalArgumentException("Too many arguments");
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the specified spanned text has a style span.
|
||||
* @hide
|
||||
|
||||
@@ -31,6 +31,12 @@ import java.util.Objects;
|
||||
*/
|
||||
public class Preconditions {
|
||||
|
||||
/**
|
||||
* Ensures that an expression checking an argument is true.
|
||||
*
|
||||
* @param expression the expression to check
|
||||
* @throws IllegalArgumentException if {@code expression} is false
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static void checkArgument(boolean expression) {
|
||||
if (!expression) {
|
||||
@@ -62,8 +68,9 @@ public class Preconditions {
|
||||
* @param messageArgs arguments for {@code messageTemplate}
|
||||
* @throws IllegalArgumentException if {@code expression} is false
|
||||
*/
|
||||
public static void checkArgument(boolean expression,
|
||||
final String messageTemplate,
|
||||
public static void checkArgument(
|
||||
final boolean expression,
|
||||
final @NonNull String messageTemplate,
|
||||
final Object... messageArgs) {
|
||||
if (!expression) {
|
||||
throw new IllegalArgumentException(String.format(messageTemplate, messageArgs));
|
||||
@@ -114,7 +121,9 @@ public class Preconditions {
|
||||
* @throws IllegalArgumentException if {@code string} is empty
|
||||
*/
|
||||
public static @NonNull <T extends CharSequence> T checkStringNotEmpty(
|
||||
final T string, final String messageTemplate, final Object... messageArgs) {
|
||||
final T string,
|
||||
final @NonNull String messageTemplate,
|
||||
final Object... messageArgs) {
|
||||
if (TextUtils.isEmpty(string)) {
|
||||
throw new IllegalArgumentException(String.format(messageTemplate, messageArgs));
|
||||
}
|
||||
@@ -160,18 +169,22 @@ public class Preconditions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the truth of an expression involving the state of the calling
|
||||
* instance, but not involving any parameters to the calling method.
|
||||
* Ensures that an object reference passed as a parameter to the calling
|
||||
* method is not null.
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @param message exception message
|
||||
* @throws IllegalStateException if {@code expression} is false
|
||||
* @param messageTemplate a printf-style message template to use if the check fails; will
|
||||
* be converted to a string using {@link String#format(String, Object...)}
|
||||
* @param messageArgs arguments for {@code messageTemplate}
|
||||
* @throws NullPointerException if {@code reference} is null
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static void checkState(final boolean expression, String message) {
|
||||
if (!expression) {
|
||||
throw new IllegalStateException(message);
|
||||
public static @NonNull <T> T checkNotNull(
|
||||
final T reference,
|
||||
final @NonNull String messageTemplate,
|
||||
final Object... messageArgs) {
|
||||
if (reference == null) {
|
||||
throw new NullPointerException(String.format(messageTemplate, messageArgs));
|
||||
}
|
||||
return reference;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,6 +199,41 @@ public class Preconditions {
|
||||
checkState(expression, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the truth of an expression involving the state of the calling
|
||||
* instance, but not involving any parameters to the calling method.
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @param errorMessage the exception message to use if the check fails; will
|
||||
* be converted to a string using {@link String#valueOf(Object)}
|
||||
* @throws IllegalStateException if {@code expression} is false
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static void checkState(final boolean expression, String errorMessage) {
|
||||
if (!expression) {
|
||||
throw new IllegalStateException(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the truth of an expression involving the state of the calling
|
||||
* instance, but not involving any parameters to the calling method.
|
||||
*
|
||||
* @param expression a boolean expression
|
||||
* @param messageTemplate a printf-style message template to use if the check fails; will
|
||||
* be converted to a string using {@link String#format(String, Object...)}
|
||||
* @param messageArgs arguments for {@code messageTemplate}
|
||||
* @throws IllegalStateException if {@code expression} is false
|
||||
*/
|
||||
public static void checkState(
|
||||
final boolean expression,
|
||||
final @NonNull String messageTemplate,
|
||||
final Object... messageArgs) {
|
||||
if (!expression) {
|
||||
throw new IllegalStateException(String.format(messageTemplate, messageArgs));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the truth of an expression involving whether the calling identity is authorized to
|
||||
* call the calling method.
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.text;
|
||||
|
||||
import static android.text.TextUtils.formatSimple;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@@ -794,4 +796,53 @@ public class TextUtilsTest {
|
||||
assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3));
|
||||
assertNull(TextUtils.trimToLengthWithEllipsis(null, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSimple_Types() {
|
||||
assertEquals("true", formatSimple("%b", true));
|
||||
assertEquals("false", formatSimple("%b", false));
|
||||
assertEquals("true", formatSimple("%b", this));
|
||||
assertEquals("false", formatSimple("%b", new Object[] { null }));
|
||||
|
||||
assertEquals("!", formatSimple("%c", '!'));
|
||||
|
||||
assertEquals("42", formatSimple("%d", 42));
|
||||
assertEquals("281474976710656", formatSimple("%d", 281474976710656L));
|
||||
|
||||
assertEquals("3.14159", formatSimple("%f", 3.14159));
|
||||
assertEquals("NaN", formatSimple("%f", Float.NaN));
|
||||
|
||||
assertEquals("example", formatSimple("%s", "example"));
|
||||
assertEquals("null", formatSimple("%s", new Object[] { null }));
|
||||
|
||||
assertEquals("2a", formatSimple("%x", 42));
|
||||
assertEquals("1000000000000", formatSimple("%x", 281474976710656L));
|
||||
|
||||
assertEquals("%", formatSimple("%%"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSimple_Empty() {
|
||||
assertEquals("", formatSimple(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSimple_Typical() {
|
||||
assertEquals("String foobar and %% number -42 together",
|
||||
formatSimple("String %s%s and %%%% number %d%d together", "foo", "bar", -4, 2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatSimple_Mismatch() {
|
||||
try {
|
||||
formatSimple("%s");
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
try {
|
||||
formatSimple("%s", "foo", "bar");
|
||||
fail();
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package com.google.errorprone.bugpatterns.android;
|
||||
|
||||
import static com.google.errorprone.BugPattern.SeverityLevel.WARNING;
|
||||
import static com.google.errorprone.matchers.Matchers.allOf;
|
||||
import static com.google.errorprone.matchers.Matchers.contains;
|
||||
import static com.google.errorprone.matchers.Matchers.kindIs;
|
||||
import static com.google.errorprone.matchers.Matchers.methodInvocation;
|
||||
import static com.google.errorprone.matchers.Matchers.not;
|
||||
import static com.google.errorprone.matchers.Matchers.staticMethod;
|
||||
|
||||
import com.google.auto.service.AutoService;
|
||||
import com.google.errorprone.BugPattern;
|
||||
import com.google.errorprone.VisitorState;
|
||||
import com.google.errorprone.bugpatterns.BugChecker;
|
||||
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
|
||||
import com.google.errorprone.matchers.Description;
|
||||
import com.google.errorprone.matchers.Matcher;
|
||||
import com.google.errorprone.predicates.TypePredicate;
|
||||
import com.google.errorprone.util.ASTHelpers;
|
||||
import com.sun.source.tree.ExpressionTree;
|
||||
import com.sun.source.tree.LiteralTree;
|
||||
import com.sun.source.tree.MethodInvocationTree;
|
||||
import com.sun.source.tree.Tree.Kind;
|
||||
import com.sun.tools.javac.code.Symbol.VarSymbol;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Android offers several efficient alternatives to some upstream {@link String}
|
||||
* operations.
|
||||
*/
|
||||
@AutoService(BugChecker.class)
|
||||
@BugPattern(
|
||||
name = "AndroidFrameworkEfficientStrings",
|
||||
summary = "Verifies efficient Strings best-practices",
|
||||
severity = WARNING)
|
||||
public final class EfficientStringsChecker extends BugChecker
|
||||
implements MethodInvocationTreeMatcher {
|
||||
|
||||
private static final Matcher<ExpressionTree> FORMAT_CALL = methodInvocation(
|
||||
staticMethod().onClass("java.lang.String").named("format"));
|
||||
private static final Matcher<ExpressionTree> PRECONDITIONS_CALL = methodInvocation(
|
||||
staticMethod().onClass(withSimpleName("Preconditions")).withAnyName());
|
||||
private static final Matcher<ExpressionTree> OBJECTS_CALL = methodInvocation(
|
||||
staticMethod().onClass("java.util.Objects").named("requireNonNull"));
|
||||
|
||||
/**
|
||||
* Match an expression which combines both string literals any other dynamic
|
||||
* values, since these allocate a transparent StringBuilder.
|
||||
* <p>
|
||||
* This won't match a single isolated string literal, or a chain consisting
|
||||
* of only string literals, since those don't require dynamic construction.
|
||||
*/
|
||||
private static final Matcher<ExpressionTree> CONTAINS_DYNAMIC_STRING = allOf(
|
||||
contains(ExpressionTree.class, kindIs(Kind.STRING_LITERAL)),
|
||||
contains(ExpressionTree.class, not(kindIs(Kind.STRING_LITERAL))));
|
||||
|
||||
@Override
|
||||
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
|
||||
if (FORMAT_CALL.matches(tree, state)) {
|
||||
// Skip over possible locale to find format string
|
||||
final List<? extends ExpressionTree> args = tree.getArguments();
|
||||
final ExpressionTree formatArg;
|
||||
final List<VarSymbol> vars = ASTHelpers.getSymbol(tree).params();
|
||||
if (vars.get(0).type.toString().equals("java.util.Locale")) {
|
||||
formatArg = args.get(1);
|
||||
} else {
|
||||
formatArg = args.get(0);
|
||||
}
|
||||
|
||||
// Determine if format string is "simple" enough to replace
|
||||
if (formatArg.getKind() == Kind.STRING_LITERAL) {
|
||||
final String format = String.valueOf(((LiteralTree) formatArg).getValue());
|
||||
if (isSimple(format)) {
|
||||
return buildDescription(formatArg)
|
||||
.setMessage("Simple format strings can be replaced with "
|
||||
+ "TextUtils.formatSimple() for a 6x performance improvement")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
} else if (PRECONDITIONS_CALL.matches(tree, state)
|
||||
|| OBJECTS_CALL.matches(tree, state)) {
|
||||
final List<? extends ExpressionTree> args = tree.getArguments();
|
||||
for (int i = 1 ; i < args.size(); i++) {
|
||||
final ExpressionTree arg = args.get(i);
|
||||
if (CONTAINS_DYNAMIC_STRING.matches(arg, state)) {
|
||||
return buildDescription(arg)
|
||||
.setMessage("Building dynamic messages is discouraged, since they "
|
||||
+ "always allocate a transparent StringBuilder, even in "
|
||||
+ "the successful case")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Description.NO_MATCH;
|
||||
}
|
||||
|
||||
static boolean isSimple(String format) {
|
||||
for (int i = 0; i < format.length(); i++) {
|
||||
char c = format.charAt(i);
|
||||
if (c == '%') {
|
||||
i++;
|
||||
c = format.charAt(i);
|
||||
switch (c) {
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'f':
|
||||
case 's':
|
||||
case 'x':
|
||||
case '%':
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static TypePredicate withSimpleName(final String filter) {
|
||||
return new TypePredicate() {
|
||||
@Override
|
||||
public boolean apply(Type type, VisitorState state) {
|
||||
return type.tsym.getSimpleName().toString().equals(filter);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package com.google.errorprone.bugpatterns.android;
|
||||
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
|
||||
import com.google.errorprone.CompilationTestHelper;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.JUnit4;
|
||||
|
||||
@RunWith(JUnit4.class)
|
||||
public class EfficientStringsCheckerTest {
|
||||
private CompilationTestHelper compilationHelper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
compilationHelper = CompilationTestHelper.newInstance(
|
||||
EfficientStringsChecker.class, getClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimple() {
|
||||
assertTrue(EfficientStringsChecker.isSimple(""));
|
||||
assertTrue(EfficientStringsChecker.isSimple("%s"));
|
||||
assertTrue(EfficientStringsChecker.isSimple("String %s%s and %%%% number %d%d together"));
|
||||
|
||||
assertFalse(EfficientStringsChecker.isSimple("%04d"));
|
||||
assertFalse(EfficientStringsChecker.isSimple("%02x:%02x:%02x"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormat() {
|
||||
compilationHelper
|
||||
.addSourceLines("Example.java",
|
||||
"import java.util.Locale;",
|
||||
"public class Example {",
|
||||
" public void example(String str) {",
|
||||
" String.format(str, str);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" String.format(\"foo %s bar\", str);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" String.format(\"foo %d bar\", 42);",
|
||||
" String.format(\"foo %04d bar\", 42);",
|
||||
" }",
|
||||
" public void exampleLocale(String str) {",
|
||||
" String.format(Locale.US, str, str);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" String.format(Locale.US, \"foo %s bar\", str);",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" String.format(Locale.US, \"foo %d bar\", 42);",
|
||||
" String.format(Locale.US, \"foo %04d bar\", 42);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPreconditions() {
|
||||
compilationHelper
|
||||
.addSourceFile("/android/util/Preconditions.java")
|
||||
.addSourceLines("Example.java",
|
||||
"import android.util.Preconditions;",
|
||||
"import java.util.Objects;",
|
||||
"public class Example {",
|
||||
" String str;",
|
||||
" public void checkState(boolean val) {",
|
||||
" Preconditions.checkState(val);",
|
||||
" Preconditions.checkState(val, str);",
|
||||
" Preconditions.checkState(val, \"foo\");",
|
||||
" Preconditions.checkState(val, \"foo\" + \"bar\");",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Preconditions.checkState(val, \"foo \" + val);",
|
||||
" }",
|
||||
" public void checkArgument(boolean val) {",
|
||||
" Preconditions.checkArgument(val);",
|
||||
" Preconditions.checkArgument(val, str);",
|
||||
" Preconditions.checkArgument(val, \"foo\");",
|
||||
" Preconditions.checkArgument(val, \"foo\" + \"bar\");",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Preconditions.checkArgument(val, \"foo \" + val);",
|
||||
" }",
|
||||
" public void checkNotNull(Object val) {",
|
||||
" Preconditions.checkNotNull(val);",
|
||||
" Preconditions.checkNotNull(val, str);",
|
||||
" Preconditions.checkNotNull(val, \"foo\");",
|
||||
" Preconditions.checkNotNull(val, \"foo\" + \"bar\");",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Preconditions.checkNotNull(val, \"foo \" + val);",
|
||||
" }",
|
||||
" public void requireNonNull(Object val) {",
|
||||
" Objects.requireNonNull(val);",
|
||||
" Objects.requireNonNull(val, str);",
|
||||
" Objects.requireNonNull(val, \"foo\");",
|
||||
" Objects.requireNonNull(val, \"foo\" + \"bar\");",
|
||||
" // BUG: Diagnostic contains:",
|
||||
" Objects.requireNonNull(val, \"foo \" + val);",
|
||||
" }",
|
||||
"}")
|
||||
.doTest();
|
||||
}
|
||||
}
|
||||
43
errorprone/tests/res/android/util/Preconditions.java
Normal file
43
errorprone/tests/res/android/util/Preconditions.java
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
package android.util;
|
||||
|
||||
public class Preconditions {
|
||||
public static void checkArgument(boolean expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static void checkArgument(boolean expression, Object errorMessage) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static <T> T checkNotNull(T reference) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static <T> T checkNotNull(T reference, Object errorMessage) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static void checkState(boolean expression) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public static void checkState(boolean expression, String message) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user