Markers
Add arbitrary metadata to ASTs
Markers are used to annotate AST elements with metadata. Visitors can attach any type implementing the Marker interface to any AST element having Markers. Markers can be used to identify search results or to communicate between Recipes during OpenRewrite execution. When an AST is printed back to source code only SearchResult Markers are printed.

Build Markers

Style Markers

Java Markers

  • JavaProject - Name and publication coordinates (groupId, artifactId, version) of the source files associated project
  • JavaSourceSet - Name (e.g. main or test) and list of fully qualified types representing the classpath
  • JavaVersion - Java version including source and target compatibility of a source file
  • JavaVarKeyword - An AST marker for inferred type variable declarations

Maven Markers

  • MavenResolutionResult - Rich data model of a pom.xml, including full dependency resolution information

Recipe Markers

  • RecipesThatMadeChanges - Provides a collection of Recipes having made changes to the AST during recipe execution

Search Makers

  • SearchResult - A printable marker added to an AST identifiying a search result

Usage

Adding Markers to an AST Element

AST implementations providing Markers have at last two methods to add Markers. AST elements are immutable, and that includes their metadata, so these methods return a copy of the AST element with the specified Markers.
// Returns the existing Markers
Markers getMarkers();
// Fully replaces any existing Markers
<M extends Markable> M withMarkers(Markers markers);

Reading Markers from an AST Element

The Markers class provides several convenience methods.
Markers.java
// Returns the first Marker of the specified type.
<M extends Marker> Optional<M> findFirst(Class<M> markerType)
// Returns all Markers of the specified type
<M extends Marker> List<M> findAll(Class<M> markerType)
// Add a new marker or update some existing marker via the remappingFunction
// The existence of "identity" is determined based on equality
<M extends Marker> Markers compute(M identity, BinaryOperator<M> remappingFunction)
// Add a new marker or update some existing marker via the remappingFunction
// The existence of "identity" is determined based on type equality
<M extends Marker> Markers computeByType(M identity, BinaryOperator<M> remappingFunction)

SearchResult

The most common form of Marker in a typical Recipe is a SearchResult. Any Recipe which adds SearchResult markers to an AST can be described as a search recipe. RecipeSearchResults include which specific recipe instance created it. So recipes are able to differentiate between search results left by an instance of the search visitor they're interested in and results left by a different instance of the same type of visitor. RecipeSearchResults can optionally include a text description.

Adding a Search Result to an AST

In this example the search recipe FindAnnotations adds a RecipeSearchResult indicating that it found a matching Annotation.
FindAnnotations.java
public J.Annotation visitAnnotation(J.Annotation annotation, ExecutionContext ctx) {
J.Annotation a = super.visitAnnotation(annotation, ctx);
if (annotationMatcher.matches(annotation)) {
aa = a.withMarkers(a.getMarkers().searchResult());
}
return a;
}
FindMissingTypes.java
public J.Identifier visitIdentifier(J.Identifier identifier, ExecutionContext ctx) {
J.Identifier ident = super.visitIdentifier(identifier, ctx);
if (isNullType(ident.getType()) && !isAllowedToHaveNullType(ident)) {
ident = ident.withMarkers(ident.getMarkers().searchResult("Identifier type is null"));
}
return ident;
}
SearchResult Markers are written back to source code as comments where the description is the content
So for example if the FindMethods("A singleArg(String)") recipe were applied to this Java source file:
class Test {
void test() {
new java.util.ArrayList<String>().forEach(new A()::singleArg);
}
}
The search result would be visualized like so:
class Test {
void test() {
new java.util.ArrayList<String>().forEach(new A()::/*~~>*/singleArg);
}
}