All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openrewrite.config.DeclarativeRecipe Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2020 the original author or authors.
 * 

* 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 *

* https://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 org.openrewrite.config; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.*; import lombok.experimental.NonFinal; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.Nullable; import org.openrewrite.*; import java.net.URI; import java.time.Duration; import java.util.*; import java.util.function.Supplier; import static java.util.Collections.emptyList; import static org.openrewrite.Validated.invalid; @RequiredArgsConstructor public class DeclarativeRecipe extends Recipe { @Getter private final String name; @Getter @Language("markdown") private final String displayName; @Getter @Language("markdown") private final String description; @Getter private final Set tags; @Nullable private final Duration estimatedEffortPerOccurrence; private final URI source; private final boolean causesAnotherCycle; @Override public boolean causesAnotherCycle() { return causesAnotherCycle || super.causesAnotherCycle(); } @Getter private final List maintainers; private final List uninitializedRecipes = new ArrayList<>(); @Setter private List recipeList = new ArrayList<>(); private final List uninitializedPreconditions = new ArrayList<>(); @Setter private List preconditions = new ArrayList<>(); public void addPrecondition(Recipe recipe) { uninitializedPreconditions.add(recipe); } @JsonIgnore private Validated validation = Validated.none(); @JsonIgnore private Validated initValidation = null; @Override public Duration getEstimatedEffortPerOccurrence() { return estimatedEffortPerOccurrence == null ? Duration.ofMinutes(0) : estimatedEffortPerOccurrence; } public void initialize(Collection availableRecipes, Map> recipeToContributors) { initValidation = Validated.none(); initialize(uninitializedRecipes, recipeList, availableRecipes, recipeToContributors); initialize(uninitializedPreconditions, preconditions, availableRecipes, recipeToContributors); } private void initialize(List uninitialized, List initialized, Collection availableRecipes, Map> recipeToContributors) { initialized.clear(); for (int i = 0; i < uninitialized.size(); i++) { Recipe recipe = uninitialized.get(i); if (recipe instanceof LazyLoadedRecipe) { String recipeFqn = ((LazyLoadedRecipe) recipe).getRecipeFqn(); Optional next = availableRecipes.stream() .filter(r -> recipeFqn.equals(r.getName())).findAny(); if (next.isPresent()) { Recipe subRecipe = next.get(); if (subRecipe instanceof DeclarativeRecipe) { ((DeclarativeRecipe) subRecipe).initialize(availableRecipes, recipeToContributors); } initialized.add(subRecipe); } else { initValidation = initValidation.and( invalid(name + ".recipeList" + "[" + i + "] (in " + source + ")", recipeFqn, "recipe '" + recipeFqn + "' does not exist.", null)); } } else { recipe.setContributors(recipeToContributors.getOrDefault(recipe.getName(), emptyList())); if (recipe instanceof DeclarativeRecipe) { ((DeclarativeRecipe) recipe).initialize(availableRecipes, recipeToContributors); } initialized.add(recipe); } } } @Value @EqualsAndHashCode(callSuper = false) @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. " + "\"bellwether\", noun - One that serves as a leader or as a leading indicator of future trends. "; } Supplier> precondition; @NonFinal transient boolean preconditionApplicable; @Override public TreeVisitor getVisitor() { return new TreeVisitor() { TreeVisitor p = precondition.get(); @Override public boolean isAcceptable(SourceFile sourceFile, ExecutionContext ctx) { return p.isAcceptable(sourceFile, ctx); } @Override public @Nullable Tree visit(@Nullable Tree tree, ExecutionContext ctx) { Tree t = p.visit(tree, ctx); preconditionApplicable = t != tree; return tree; } }; } } @EqualsAndHashCode(callSuper = false) @Value static class BellwetherDecoratedRecipe extends Recipe implements DelegatingRecipe { DeclarativeRecipe.PreconditionBellwether bellwether; 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 getVisitor() { return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor()); } @Override public List getRecipeList() { return decorateWithPreconditionBellwether(bellwether, delegate.getRecipeList()); } @Override public boolean causesAnotherCycle() { return delegate.causesAnotherCycle(); } } @Value @EqualsAndHashCode(callSuper = false) static class BellwetherDecoratedScanningRecipe extends ScanningRecipe implements DelegatingRecipe { DeclarativeRecipe.PreconditionBellwether bellwether; ScanningRecipe 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 getScanner(T acc) { return delegate.getScanner(acc); } @Override public Collection generate(T acc, ExecutionContext ctx) { return delegate.generate(acc, ctx); } @Override public TreeVisitor getVisitor(T acc) { return Preconditions.check(bellwether.isPreconditionApplicable(), delegate.getVisitor(acc)); } @Override public boolean causesAnotherCycle() { return delegate.causesAnotherCycle(); } } @Override public final List getRecipeList() { if (preconditions.isEmpty()) { return recipeList; } List>> andPreconditions = new ArrayList<>(); for (Recipe precondition : preconditions) { if (isScanningRecipe(precondition)) { throw new IllegalArgumentException( getName() + " declares the ScanningRecipe " + precondition.getName() + " as a precondition." + "ScanningRecipe cannot be used as Preconditions."); } andPreconditions.add(() -> orVisitors(precondition)); } PreconditionBellwether bellwether = new PreconditionBellwether(Preconditions.and(andPreconditions.toArray(new Supplier[]{}))); List recipeListWithBellwether = new ArrayList<>(recipeList.size() + 1); recipeListWithBellwether.add(bellwether); recipeListWithBellwether.addAll(decorateWithPreconditionBellwether(bellwether, recipeList)); return recipeListWithBellwether; } private static TreeVisitor orVisitors(Recipe recipe) { if (recipe.getRecipeList().isEmpty()) { return recipe.getVisitor(); } List> conditions = new ArrayList<>(); conditions.add(recipe.getVisitor()); for (Recipe r : recipe.getRecipeList()) { conditions.add(orVisitors(r)); } //noinspection unchecked return Preconditions.or(conditions.toArray(new TreeVisitor[0])); } private static boolean isScanningRecipe(Recipe recipe) { if (recipe instanceof ScanningRecipe) { return true; } for (Recipe r : recipe.getRecipeList()) { if (isScanningRecipe(r)) { return true; } } return false; } private static List decorateWithPreconditionBellwether(PreconditionBellwether bellwether, List recipeList) { List mappedRecipeList = new ArrayList<>(recipeList.size()); for (Recipe recipe : recipeList) { if (recipe instanceof ScanningRecipe) { mappedRecipeList.add(new BellwetherDecoratedScanningRecipe<>(bellwether, (ScanningRecipe) recipe)); } else { mappedRecipeList.add(new BellwetherDecoratedRecipe(bellwether, recipe)); } } return mappedRecipeList; } public void addUninitialized(Recipe recipe) { uninitializedRecipes.add(recipe); } public void addUninitialized(String recipeName) { uninitializedRecipes.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); } public void addUninitializedPrecondition(Recipe recipe) { uninitializedPreconditions.add(recipe); } public void addUninitializedPrecondition(String recipeName) { uninitializedPreconditions.add(new DeclarativeRecipe.LazyLoadedRecipe(recipeName)); } public void addValidation(Validated validated) { validation = validation.and(validated); } @Override public Validated validate() { Validated validated = Validated.none(); if (!uninitializedRecipes.isEmpty() && uninitializedRecipes.size() != recipeList.size()) { validated = validated.and(Validated.invalid("initialization", recipeList, "DeclarativeRecipe must not contain uninitialized recipes. Be sure to call .initialize() on DeclarativeRecipe.")); } if (!uninitializedPreconditions.isEmpty() && uninitializedPreconditions.size() != preconditions.size()) { validated = validated.and(Validated.invalid("initialization", preconditions, "DeclarativeRecipe must not contain uninitialized preconditions. Be sure to call .initialize() on DeclarativeRecipe.")); } return validated.and(validation) .and(initValidation == null ? Validated.none() : initValidation); } @Value @EqualsAndHashCode(callSuper = false) private static class LazyLoadedRecipe extends Recipe { String recipeFqn; @Override public String getDisplayName() { return "Lazy loaded recipe"; } @Override public String getDescription() { return "Recipe that is loaded lazily."; } } @Override protected RecipeDescriptor createRecipeDescriptor() { List recipeList = new ArrayList<>(); for (Recipe childRecipe : getRecipeList()) { recipeList.add(childRecipe.getDescriptor()); } return new RecipeDescriptor(getName(), getDisplayName(), getDescription(), getTags(), getEstimatedEffortPerOccurrence(), emptyList(), recipeList, getDataTableDescriptors(), getMaintainers(), getContributors(), getExamples(), source); } @Value private static class NameEmail { String name; String email; } @Override public List getContributors() { if (contributors == null) { Map contributorToLineCount = new HashMap<>(); contributors = new ArrayList<>(); for (Recipe recipe : getRecipeList()) { for (Contributor contributor : recipe.getContributors()) { NameEmail nameEmail = new NameEmail(contributor.getName(), contributor.getEmail()); contributorToLineCount.put(nameEmail, contributorToLineCount.getOrDefault(nameEmail, 0) + contributor.getLineCount()); } } for (Map.Entry contributorEntry : contributorToLineCount.entrySet()) { contributors.add(new Contributor(contributorEntry.getKey().getName(), contributorEntry.getKey().getEmail(), contributorEntry.getValue())); } contributors.sort(Comparator.comparing(Contributor::getLineCount).reversed()); } return contributors; } }