In addition to patching your code using the checks built-in to Error Prone, we’ve developed a mechanism to refactor your code using before-and-after templates (we call them Refaster templates). Once you write these templates, you compile them into .refaster files, then use the Error Prone compiler to refactor your code according to those rules.
Refaster is described in more detail in a research paper presented by Louis Wasserman at the Workshop for Refactoring Tools.
Explaining how to write refaster rules is best done by example:
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.AlsoNegation;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
public class StringIsEmpty {
@BeforeTemplate
boolean equalsEmptyString(String string) {
return string.equals("");
}
@BeforeTemplate
boolean lengthEquals0(String string) {
return string.length() == 0;
}
@AfterTemplate
@AlsoNegation
boolean optimizedMethod(String string) {
return string.isEmpty();
}
}
Refaster templates are any class with multiple methods with the same return type and list of arguments with the same name. One of the methods should be annotated @AfterTemplate
, and every other method should be annotated with @BeforeTemplate
. With this template, any code calling String#equals
passing in the empty string literal, or calling String#length
and comparing it to 0 will be replaced by a call to String#isEmpty
. Notably, no matter how the String expression is generated, Refaster will do the replacement:
boolean b = someChained().methodCall().returningAString().length() == 0;
becomes
boolean b = someChained().methodCall().returningAString().isEmpty();
while
if (this.someStringField.equals(""))
becomes
if (this.someStringField.isEmpty())
There are other annotations in the refaster.annotations package that allow you to express more complex before-and-after refactorings, with examples about how to use them in their javadoc. See also the advanced features section of this document.
In the above example, @AlsoNegation
is used to signal that the rule can also match the logical negation of the @BeforeTemplate
bodies (string.length() != 0
becomes !string.isEmpty()
);
TIP: These instructions are valid as of Error Prone 2.3.1, and are subject to change.
Use the Error Prone javac JAR and the Error Prone Refaster JAR to compile the Refaster template, using JDK 9 or newer:
wget http://repo1.maven.org/maven2/com/google/errorprone/error_prone_refaster/2.3.1/error_prone_refaster-2.3.1.jar
javac \
-cp error_prone_refaster-2.3.1.jar \
"-Xplugin:RefasterRuleCompiler --out ${PWD}/myrule.refaster" \
StringIsEmpty.java
You should see a file named myrule.refaster
in your current directory. To use
this to refactor your code, add the following flags to the Error Prone compiler
(this is similar to patching):
-XepPatchChecks:refaster:/full/path/to/myrule.refaster
-XepPatchLocation:/full/path/to/your/source/root
Example:
class Demo {
boolean isEmpty(String s) {
return s.length() == 0;
}
}
javac \
-J-Xbootclasspath/p:javac-9+181-r4173-1.jar \
-XDcompilePolicy=byfile \
--should-stop=ifError=FLOW \
-processorpath error_prone_core-2.3.2-with-dependencies.jar \
"-Xplugin:ErrorProne -XepPatchChecks:refaster:${PWD}/myrule.refaster -XepPatchLocation:${PWD}" \
Demo.java
This will generate a unified diff file named error-prone.patch
that you can
apply similarly to how you would apply other patches:
$ cat error-prone.patch
...
class Demo {
boolean isEmpty(String s) {
- return s.length() == 0;
+ return s.isEmpty();
}
}
$ patch -p0 -u -i error-prone.patch
The idea of Refaster is, more or less, to take the pseudocode you’d write to explain a refactoring you wanted to perform, and make that into real code that performs a real refactoring.
Let’s take apart an example rule.
class Utf8Length { // A name for the refactoring
@BeforeTemplate // This is what the code looks like before the refactoring
int toUtf8Length( // the method name is unimportant
String string /* the string parameter stands in for any expression of type String */) {
return /* this is here just to make the compiler happy */
string.getBytes(StandardCharsets.UTF_8).length;
// this is what the code looks like before the refactoring
}
@AfterTemplate // replace code with this pattern
int optimizedMethod(
String string /* substitute in the original String expression */) {
return Utf8.encodedLength(string);
}
}
This refactoring rewrites expressions of the form someString.getBytes(UTF_8).length
to Utf8.encodedLength(string)
, a method from Guava that avoids allocating an entire byte array just to get its length, no matter where that String
comes from – it can be the result of a more complicated expression, or just a simple variable. Additionally, this will match even if the int
result of the expression is being passed to a method, added to something else, assigned to a variable, whatever.
This is called an expression template because it rewrites expressions. Let’s look at a block template:
class ListSwap<T> {
// T is a type parameter for the entire rule, which will be inferred from the match
@BeforeTemplate
void manualSwap(
List<T> list, int i, int j) {
// the list, i, and j can be any expressions of appropriate type
T tmp = list.get(i);
// tmp doesn't actually have to be the variable name used in the code being matched
list.set(i, list.get(j));
list.set(j, tmp);
}
@AfterTemplate
void swap(List<T> list, int i, int j) {
Collections.swap(list, i, j);
}
}
This rule matches three consecutive lines that “look like” the body of the @BeforeTemplate
, and replaces them with the single line of the @AfterTemplate
.
Refaster was originally built for the Java Libraries Team at Google, around the slogan of “make simple refactorings simple.” We tended to focus on library migrations, method renamings, and the like; refactorings that could be clearly described by just describing what the code should look like before and after the change. We use Refaster for simple refactorings like this, and combine it with more sophisticated analyses written with the Error Prone API for refactorings Refaster can’t express.
Refaster excels at refactorings like:
We have also found Refaster good for expressing code simplification patterns to improve efficiency or code readability. For example, here is a simplification rule for Java 8 streams that Refaster expresses neatly:
class SortedFirst<T> {
@BeforeTemplate
Optional<T> before(Stream<T> stream, Comparator<? super T> comparator) {
return stream.sorted(comparator).findFirst();
}
@AfterTemplate
Optional<T> after(Stream<T> stream, Comparator<? super T> comparator) {
return stream.min(comparator);
}
}
At Google, we have a tool that uses thousands of these simplification patterns to automatically comment on code reviews, suggesting improvements to code. We hope to release many of these soon.
Refaster.anyOf
A particularly commonly used method is Refaster.anyOf
, a “magic” method for
use in your @BeforeTemplate
which indicates that any of the specified
expressions are allowed to match. For example:
class AddAllArrayToBuilder<E> {
@BeforeTemplate
ImmutableCollection.Builder<E> addAllAsList(
ImmutableCollection.Builder<E> builder, E[] elements) {
return builder.addAll(Refaster.anyOf(
Arrays.asList(elements),
ImmutableList.copyOf(elements),
Lists.newArrayList(elements)));
}
@AfterTemplate
ImmutableCollection.Builder<E> addAll(
ImmutableCollection.Builder<E> builder, E[] elements) {
builder.add(elements);
}
}
is equivalent to, but much shorter than,
class AddAllArrayToBuilder {
@BeforeTemplate
ImmutableCollection.Builder<E> addAllArraysAsList(
ImmutableCollection.Builder<E> builder, E[] elements) {
return builder.addAll(Arrays.asList(elements));
}
@BeforeTemplate
ImmutableCollection.Builder<E> addAllImmutableListCopyOf(
ImmutableCollection.Builder<E> builder, E[] elements) {
return builder.addAll(ImmutableList.copyOf(elements));
}
@BeforeTemplate
ImmutableCollection.Builder<E> addAllNewArrayList(
ImmutableCollection.Builder<E> builder, E[] elements) {
return builder.addAll(Lists.newArrayList(elements));
}
@AfterTemplate
ImmutableCollection.Builder<E> addAll(
ImmutableCollection.Builder<E> builder, E[] elements) {
builder.add(elements);
}
}
Refaster.clazz()
and other methodsConsider the following refactoring, where calling X.class.cast(o) for any X is replaced with a simple cast to X.
class ClassCast<T> {
@BeforeTemplate
T cast(Object o) {
return Refaster.<T>clazz().cast(o);
}
@AfterTemplate
T cast(Object o) {
return (T) o;
}
}
Here, Refaster.<T>clazz()
is a “magic incantation” to substitute for the
impossible-to-compile code you would want to write here, T.class
. There are
a variety of these “magic incantations” in the Refaster
class
for code patterns that you might wish to write in a @BeforeTemplate
but don’t
technically compile as Java code.
@UseImportPolicy
Refaster attempts to automatically infer how to import newly used classes and methods into refactored code, but the approach it uses to do so can be configured with the @UseImportPolicy
annotation on the @AfterTemplate
method. (Note that Refaster will not pay attention to how the class is imported or qualified in the original Refaster template!) The ImportPolicy
enum offers the following options:
IMPORT_TOP_LEVEL
, the default: imports the top-level class and explicitly qualifies references to nested classes. For example, to refer to java.util.Map.Entry
, Refaster will import java.util.Map;
and refer to the type as Map.Entry
.IMPORT_CLASS_DIRECTLY
imports classes directly, whether nested in other classes or not: e.g. import java.util.Map.Entry;
STATIC_IMPORT_ALWAYS
static imports methods explicitly mentioned in the Refaster template, and refers to types themselves as in IMPORT_TOP_LEVEL
.Placeholder methods are a particularly powerful feature of Refaster, allowing you to match arbitrary chunks of code in terms of arguments, not just expressions of a given type. Here is a basic example usage of a placeholder:
abstract class ComputeIfAbsent<K, V> {
/*
* Represents an arbitrary expression in terms of an input, key.
*/
@Placeholder
abstract V function(K key);
@BeforeTemplate
void before(Map<K, V> map, K key) {
if (!map.containsKey(key)) {
map.put(key, function(key));
}
}
@AfterTemplate
void after(Map<K, V> map, K key) {
map.computeIfAbsent(key, (K k) -> function(k));
}
}
We annotate an abstract
method in the Refaster template class to represent
“some function in terms of the specified input.” By default, arguments to the
placeholder must be used for the placeholder to match. For example, this
pattern would not match
if (!map.containsKey(k)) {
map.put(k, 0);
}
because the expression 0
does not refer to k
. To change this behavior, you
may annotate the arguments to the placeholder method with @MayOptionallyUse
:
@Placeholder
abstract V function(@MayOptionallyUse K key);
Note also that the code matched by the placeholder method cannot refer to
variables in the @BeforeTemplate
that are not explicitly passed in. So
if (!map.containsKey(k)) {
map.put(k, map.get(k - 1));
}
would not be matched, because the expression in the put
refers to map
,
which is not passed into the placeholder method. The match can refer to
variables that aren’t explicitly mentioned in the Refaster pattern, e.g.
if (!map.containsKey(k)) {
map.put(k, k + ":" + suffixString);
}
because suffixString
is not a variable in the @BeforeTemplate
.
By default, placeholder methods are not permitted to simply pass on one of their
arguments unchanged. This behavior can be overridden with the annotation:
@Placeholder(allowsIdentity = true)
.
The above example used placeholders to match single expressions in terms of other expressions, but not multiple lines of code. These, too, are supported. Consider the following refactoring:
abstract class IfSetAdd<E> {
@Placeholder
abstract void doAfterAdd(E element);
@BeforeTemplate
void ifNotContainsThenAdd(Set<E> set, E elem) {
if (!set.contains(elem)) {
set.add(elem);
doAfterAdd(elem);
}
}
@AfterTemplate
void ifAdd(Set<E> set, E elem) {
if (set.add(elem)) {
doAfterAdd(elem);
}
}
}
…which would e.g. rewrite
if (!mySet.contains(e)) {
mySet.add(e);
log("added %s to set", e);
}
to
if (mySet.add(e)) {
log("added %s to set", e);
}
There is also some limited magic supported here with block versus expression lambdas. For example, consider the refactoring
abstract class MapEntryLoop<K, V> {
@Placeholder
abstract void doSomething(K k, V v);
@BeforeTemplate
void entrySetLoop(Map<K, V> map) {
for (Map.Entry<K, V> entry : map.entrySet()) {
doSomething(entry.getKey(), entry.getValue());
}
}
@AfterTemplate
void mapForEach(Map<K, V> map) {
map.forEach((K key, V value) -> doSomething(key, value));
}
}
would rewrite
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + ":" + e.getValue());
}
for (Map.Entry<String, Integer> e : map.entrySet()) {
String str = e.getKey() + ":" + e.getValue();
System.out.println(str);
}
to
map.forEach(
(String key, Integer value) ->
System.out.println(key + ":" + value));
map.forEach(
(String key, Integer value) -> { // multiple lines!
String str = key + ":" + value;
System.out.println(str);
});
…that is, it will automatically bracket the lambda body, or not, as appropriate to the placeholder body actually matched.