Refaster templates

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.

Building Refaster Templates

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());

Running the Refaster refactoring

TIP: These instructions are valid as of the most recent snapshot at HEAD, and are subject to change

Use the error prone javac jar and the error_prone_refaster jar to compile the refaster template:

wget http://repo1.maven.org/maven2/com/google/errorprone/javac/9-dev-r3297-4/javac-9-dev-r3297-4.jar
wget http://repo1.maven.org/maven2/com/google/errorprone/error_prone_refaster/2.0.18/error_prone_refaster-2.0.18.jar

java -cp javac-9-dev-r3297-4.jar:error_prone_refaster-2.0.18.jar \
  com.google.errorprone.refaster.RefasterRuleCompiler \
  StringIsEmpty.java --out `pwd`/myrule.refaster

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

This will generate a unified diff file named error-prone.patch that you can apply similarly to how you would apply other patches:

cd /full/path/to/your/source/root
patch -p0 -u -i error-prone.patch

Advanced features

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 methods

Consider 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

Expression placeholders

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.

Matching the identity

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).

Block placeholders

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.