summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeHugger Robot <treehugger-gerrit@google.com>2020-10-16 22:27:50 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2020-10-16 22:27:50 +0000
commiteac0321ff317049f020a9f48d07742b8cce99be2 (patch)
treeb0ecb1872fa22168eaba5d69df1755f3874ad996
parentfe82ad21fe6bac1075e5e4a32278a4039d7cdcaa (diff)
parenta52266b3e352e33bae1ce77cfef7b16219f4817e (diff)
Merge changes from topic "oct16b"
* changes: Recommend efficient String operations. Simple alternative to String.format().
-rw-r--r--apct-tests/perftests/core/src/android/text/TextUtilsPerfTest.java109
-rw-r--r--core/java/android/text/TextUtils.java88
-rw-r--r--core/java/com/android/internal/util/Preconditions.java66
-rw-r--r--core/tests/coretests/src/android/text/TextUtilsTest.java51
-rw-r--r--errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java146
-rw-r--r--errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java119
-rw-r--r--errorprone/tests/res/android/util/Preconditions.java43
7 files changed, 613 insertions, 9 deletions
diff --git a/apct-tests/perftests/core/src/android/text/TextUtilsPerfTest.java b/apct-tests/perftests/core/src/android/text/TextUtilsPerfTest.java
new file mode 100644
index 000000000000..c62269eb2978
--- /dev/null
+++ b/apct-tests/perftests/core/src/android/text/TextUtilsPerfTest.java
@@ -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;
+ }
+}
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index d441e6bbf8cb..72b35b9253eb 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2080,6 +2080,94 @@ public class TextUtils {
}
/**
+ * 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
*/
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index dae649a903d5..e80e5454f40e 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -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,17 +169,49 @@ public class Preconditions {
}
/**
+ * Ensures that an object reference passed as a parameter to the calling
+ * method is not null.
+ *
+ * @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
+ */
+ 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;
+ }
+
+ /**
* 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 message exception message
* @throws IllegalStateException if {@code expression} is false
*/
@UnsupportedAppUsage
- public static void checkState(final boolean expression, String message) {
+ public static void checkState(final boolean expression) {
+ 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(message);
+ throw new IllegalStateException(errorMessage);
}
}
@@ -179,11 +220,18 @@ public class Preconditions {
* 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
*/
- @UnsupportedAppUsage
- public static void checkState(final boolean expression) {
- checkState(expression, null);
+ public static void checkState(
+ final boolean expression,
+ final @NonNull String messageTemplate,
+ final Object... messageArgs) {
+ if (!expression) {
+ throw new IllegalStateException(String.format(messageTemplate, messageArgs));
+ }
}
/**
diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java
index 285ad9fae13c..4e49118f438c 100644
--- a/core/tests/coretests/src/android/text/TextUtilsTest.java
+++ b/core/tests/coretests/src/android/text/TextUtilsTest.java
@@ -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) {
+ }
+ }
}
diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java
new file mode 100644
index 000000000000..3fbd51deec65
--- /dev/null
+++ b/errorprone/java/com/google/errorprone/bugpatterns/android/EfficientStringsChecker.java
@@ -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);
+ }
+ };
+ }
+}
diff --git a/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java
new file mode 100644
index 000000000000..a755564d52dd
--- /dev/null
+++ b/errorprone/tests/java/com/google/errorprone/bugpatterns/android/EfficientStringsCheckerTest.java
@@ -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();
+ }
+}
diff --git a/errorprone/tests/res/android/util/Preconditions.java b/errorprone/tests/res/android/util/Preconditions.java
new file mode 100644
index 000000000000..558cdaf483a4
--- /dev/null
+++ b/errorprone/tests/res/android/util/Preconditions.java
@@ -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();
+ }
+}