Skip to content

Commit

Permalink
Proof of concept for preconditions on declarative recipes
Browse files Browse the repository at this point in the history
  • Loading branch information
sambsnyd committed Oct 23, 2023
1 parent e3521a1 commit ebcb1f3
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.NonFinal;
import org.intellij.lang.annotations.Language;
import org.openrewrite.Contributor;
import org.openrewrite.Maintainer;
import org.openrewrite.Recipe;
import org.openrewrite.Validated;
import org.openrewrite.*;
import org.openrewrite.internal.lang.Nullable;
import org.openrewrite.marker.SearchResult;

import java.net.URI;
import java.time.Duration;
Expand Down Expand Up @@ -67,6 +66,11 @@ public boolean causesAnotherCycle() {

private final List<Recipe> uninitializedRecipes = new ArrayList<>();
private final List<Recipe> recipeList = new ArrayList<>();
private final List<Recipe> preconditions = new ArrayList<>();

public void addPrecondition(Recipe recipe) {
preconditions.add(recipe);
}

@JsonIgnore
private Validated<Object> validation = Validated.test("initialization",
Expand Down Expand Up @@ -111,9 +115,78 @@ public void initialize(Collection<Recipe> availableRecipes, Map<String, List<Con
uninitializedRecipes.clear();
}

@Value
@EqualsAndHashCode(callSuper = true)
@RequiredArgsConstructor
static class PreconditionBellwether extends Recipe {

@Override
public String getDisplayName() {
return "Precondition bellwether";
}

@Override
public String getDescription() {
return "Evaluates a precondition and makes that result available to the preconditions of other recipes.";
}

TreeVisitor<?, ExecutionContext> precondition;

@NonFinal
transient boolean preconditionApplicable;

/**
* Returns a visitor, suitable for being used as a precondition, that returns as its result whatever this
* bellwether evaluated to.
*/
public TreeVisitor<?, ExecutionContext> getFollower() {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
if(preconditionApplicable) {
return SearchResult.found(tree);
}
return tree;
}
};
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return new TreeVisitor<Tree, ExecutionContext>() {
@Override
public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) {
Tree t = precondition.visit(tree, ctx);
preconditionApplicable = t != tree;
return tree;
}
};
}
}


@Override
public List<Recipe> getRecipeList() {
return recipeList;
if(preconditions.isEmpty()) {
return recipeList;
}

//noinspection unchecked
TreeVisitor<?, ExecutionContext> andPreconditions = Preconditions.and(
preconditions.stream().map(Recipe::getVisitor).toArray(TreeVisitor[]::new));
PreconditionBellwether bellwether = new PreconditionBellwether(andPreconditions);
TreeVisitor<?, ExecutionContext> bellwetherFollower = bellwether.getFollower();
List<Recipe> recipeListWithBellwether = new ArrayList<>(recipeList.size() + 1);
recipeListWithBellwether.add(bellwether);
for (Recipe recipe : recipeList) {
if(recipe instanceof ScanningRecipe) {
recipeListWithBellwether.add(new PreconditionDecoratedScanningRecipe<>(bellwetherFollower, (ScanningRecipe<?>) recipe));
} else {
recipeListWithBellwether.add(new PreconditionDecoratedRecipe(bellwetherFollower, recipe));
}
}

return recipeListWithBellwether;
}

public void addUninitialized(Recipe recipe) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.openrewrite.config;

import org.openrewrite.Recipe;

public class PreconditionBellwether extends Recipe {
@Override
public String getDisplayName() {
return "Precondition bellwether";
}

@Override
public String getDescription() {
return "Evaluates a precondition as an implementation detail";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.openrewrite.config;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;

@EqualsAndHashCode(callSuper = true)
@Value
public class PreconditionDecoratedRecipe extends Recipe {

TreeVisitor<?, ExecutionContext> precondition;
Recipe delegate;

@Override
public String getName() {
return delegate.getName();
}

@Override
public String getDisplayName() {
return delegate.getDisplayName();
}

@Override
public String getDescription() {
return delegate.getDescription();
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
return Preconditions.check(precondition, delegate.getVisitor());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.openrewrite.config;

import lombok.EqualsAndHashCode;
import lombok.Value;
import org.openrewrite.*;

@Value
@EqualsAndHashCode(callSuper = true)
public class PreconditionDecoratedScanningRecipe<T> extends ScanningRecipe<T> {

TreeVisitor<?, ExecutionContext> precondition;
ScanningRecipe<T> delegate;

@Override
public String getName() {
return delegate.getName();
}

@Override
public String getDisplayName() {
return delegate.getDisplayName();
}

@Override
public String getDescription() {
return delegate.getDescription();
}

@Override
public T getInitialValue(ExecutionContext ctx) {
return delegate.getInitialValue(ctx);
}

@Override
public TreeVisitor<?, ExecutionContext> getScanner(T acc) {
return delegate.getScanner(acc);
}

@Override
public TreeVisitor<?, ExecutionContext> getVisitor(T acc) {
return Preconditions.check(precondition, delegate.getVisitor(acc));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package org.openrewrite.config;

import org.junit.jupiter.api.Test;
import org.openrewrite.ExecutionContext;
import org.openrewrite.marker.SearchResult;
import org.openrewrite.test.RewriteTest;
import org.openrewrite.text.ChangeText;
import org.openrewrite.text.PlainText;
import org.openrewrite.text.PlainTextVisitor;

import java.util.List;
import java.util.Map;

import static org.openrewrite.test.SourceSpecs.text;
import static org.openrewrite.test.RewriteTest.toRecipe;

public class DeclarativeRecipeTest implements RewriteTest {

@Test
void precondition() {
rewriteRun(
spec -> {
spec.validateRecipeSerialization(false);
DeclarativeRecipe dr = new DeclarativeRecipe("test", "test", "test", null,
null, null, true, null);
dr.addPrecondition(
toRecipe(() -> new PlainTextVisitor<>() {
@Override
public PlainText visitText(PlainText text, ExecutionContext executionContext) {
if("1".equals(text.getText())) {
return SearchResult.found(text);
}
return text;
}
})
);
dr.addUninitialized(
new ChangeText("2")
);
dr.addUninitialized(
new ChangeText("3")
);
dr.initialize(List.of(), Map.of());
spec.recipe(dr);
},
text("1","3"),
text("2")
);
}
}

0 comments on commit ebcb1f3

Please sign in to comment.