Skip to content

Commit

Permalink
Polish "Add ability to exclude ProjectGenerationConfiguration"
Browse files Browse the repository at this point in the history
  • Loading branch information
mhalbritter committed Nov 19, 2024
1 parent 2b83654 commit 2945eb1
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,65 @@

package io.spring.initializr.generator.project;

import java.util.Arrays;
import java.util.Set;
import java.util.function.Predicate;

/**
* Allows to filter {@link ProjectGenerationConfiguration}.
*
* @author Simon Zambrovski
* @author Moritz Halbritter
*/
public interface ProjectGenerationConfigurationTypeFilter {
public interface ProjectGenerationConfigurationTypeFilter extends Predicate<Class<?>> {

/**
* Creates a {@link ProjectGenerationConfigurationTypeFilter} which includes the given
* types.
* @param types the types to include
* @return a {@link ProjectGenerationConfigurationTypeFilter}
*/
static ProjectGenerationConfigurationTypeFilter include(Class<?>... types) {
Set<Class<?>> classes = Set.of(types);
return classes::contains;
}

/**
* Creates a {@link ProjectGenerationConfigurationTypeFilter} which excludes the given
* types.
* @param types the types to exclude
* @return a {@link ProjectGenerationConfigurationTypeFilter}
*/
static ProjectGenerationConfigurationTypeFilter exclude(Class<?>... types) {
Set<Class<?>> classes = Set.of(types);
return (clazz) -> !classes.contains(clazz);
}

/**
* Creates a {@link ProjectGenerationConfigurationTypeFilter} from multiple filters
* which must all match.
* @param filters the filters
* @return a combined {@link ProjectGenerationConfigurationTypeFilter}
*/
static ProjectGenerationConfigurationTypeFilter allMatch(ProjectGenerationConfigurationTypeFilter... filters) {
return allMatch(Arrays.asList(filters));
}

/**
* Determines if the provided class matches the filter.
* @param type type to match.
* @return true, if the type matches.
* Creates a {@link ProjectGenerationConfigurationTypeFilter} from multiple filters
* which must all match.
* @param filters the filters
* @return a combined {@link ProjectGenerationConfigurationTypeFilter}
*/
boolean match(Class<?> type);
static ProjectGenerationConfigurationTypeFilter allMatch(
Iterable<? extends ProjectGenerationConfigurationTypeFilter> filters) {
return (clazz) -> {
boolean match = true;
for (ProjectGenerationConfigurationTypeFilter filter : filters) {
match &= filter.test(clazz);
}
return match;
};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Simon Zambrovski
* @author Moritz Halbritter
*/
public class ProjectGenerator {

Expand Down Expand Up @@ -119,8 +121,9 @@ public <T> T generate(ProjectDescription description, ProjectAssetGenerator<T> p
* Return the {@link ProjectGenerationConfiguration} class names that should be
* considered. By default, this method will load candidates using
* {@link SpringFactoriesLoader} with {@link ProjectGenerationConfiguration} and
* exclude those which are matched by the
* {@link ProjectGenerationConfigurationTypeFilter}
* exclude those which are not matched by the
* {@link ProjectGenerationConfigurationTypeFilter}, also loaded by
* {@link SpringFactoriesLoader}.
* @param description the description of the project to generate
* @return a list of candidate configurations
*/
Expand All @@ -129,8 +132,8 @@ protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescri
List<String> candidates = getProjectGenerationConfigurationFactoryNames();
ProjectGenerationConfigurationTypeFilter filter = getProjectGenerationConfigurationExclusionFilter();
return candidates.stream().filter((candidate) -> {
Class<?> type = this.resolveClass(candidate);
return type != null && !filter.match(type);
Class<?> type = resolveClass(candidate);
return type != null && filter.test(type);
}).toList();
}

Expand All @@ -152,14 +155,7 @@ List<String> getProjectGenerationConfigurationFactoryNames() {
ProjectGenerationConfigurationTypeFilter getProjectGenerationConfigurationExclusionFilter() {
List<ProjectGenerationConfigurationTypeFilter> filters = SpringFactoriesLoader
.loadFactories(ProjectGenerationConfigurationTypeFilter.class, getClass().getClassLoader());
// Build a composite filter, combining results with a logical OR
return (configurationClass) -> {
boolean excluded = false;
for (ProjectGenerationConfigurationTypeFilter filter : filters) {
excluded = excluded || filter.match(configurationClass);
}
return excluded;
};
return ProjectGenerationConfigurationTypeFilter.allMatch(filters);
}

private void registerProjectDescription(ProjectGenerationContext context, ProjectDescription description) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2012-2024 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 io.spring.initializr.generator.project;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link ProjectGenerationConfigurationTypeFilter}.
*
* @author Moritz Halbritter
*/
class ProjectGenerationConfigurationTypeFilterTests {

@Test
void include() {
ProjectGenerationConfigurationTypeFilter filter = ProjectGenerationConfigurationTypeFilter.include(A.class,
B.class);
assertThat(filter).accepts(A.class, B.class);
assertThat(filter).rejects(C.class);
}

@Test
void exclude() {
ProjectGenerationConfigurationTypeFilter filter = ProjectGenerationConfigurationTypeFilter.exclude(A.class,
B.class);
assertThat(filter).rejects(A.class, B.class);
assertThat(filter).accepts(C.class);
}

@Test
void allMatch() {
ProjectGenerationConfigurationTypeFilter filterA = (clazz) -> clazz.equals(A.class);
ProjectGenerationConfigurationTypeFilter filterAorB = (clazz) -> clazz.equals(A.class) || clazz.equals(B.class);
ProjectGenerationConfigurationTypeFilter filterNotC = (clazz) -> !clazz.equals(C.class);
ProjectGenerationConfigurationTypeFilter combined = ProjectGenerationConfigurationTypeFilter.allMatch(filterA,
filterAorB, filterNotC);
assertThat(combined).accepts(A.class);
assertThat(combined).rejects(B.class, C.class);
}

private static final class A {

}

private static final class B {

}

private static final class C {

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,13 @@ void loadAndConstructProjectGenerationTypeExclusionFilter() {
ProjectGenerator generator = new ProjectGenerator(mockContextInitializr());
ProjectGenerationConfigurationTypeFilter filter = generator.getProjectGenerationConfigurationExclusionFilter();
assertThat(filter).isNotNull();
assertThat(filter.match(TestProjectGenerationConfiguration.class)).isTrue();
assertThat(filter.match(TestProjectGenerationConfiguration2.class)).isTrue();
assertThat(filter.match(Integer.class)).isFalse();
assertThat(filter.test(TestProjectGenerationConfiguration.class)).isFalse();
assertThat(filter.test(TestProjectGenerationConfiguration2.class)).isFalse();
assertThat(filter.test(Integer.class)).isTrue();
}

@Test
void filterProjectContributorsCorrectly(@TempDir Path projectDir) {
void filterProjectContributorsCorrectly() {
ProjectDescription description = mock(ProjectDescription.class);
given(description.getArtifactId()).willReturn("test-custom-contributor");
given(description.getBuildSystem()).willReturn(new MavenBuildSystem());
Expand All @@ -222,7 +222,7 @@ List<String> getProjectGenerationConfigurationFactoryNames() {

@Override
ProjectGenerationConfigurationTypeFilter getProjectGenerationConfigurationExclusionFilter() {
return TestProjectGenerationConfiguration2.class::equals;
return (clazz) -> !TestProjectGenerationConfiguration2.class.equals(clazz);
}
};
List<String> candidates = generator.getCandidateProjectGenerationConfigurations(description);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@
public class TestProjectGenerationConfiguration2ExcludingTypeFilter
implements ProjectGenerationConfigurationTypeFilter {

private final ProjectGenerationConfigurationTypeFilter delegate = ProjectGenerationConfigurationTypeFilter
.exclude(TestProjectGenerationConfiguration2.class);

@Override
public boolean match(Class<?> type) {
return TestProjectGenerationConfiguration2.class.equals(type);
public boolean test(Class<?> type) {
return this.delegate.test(type);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@

public class TestProjectGenerationConfigurationExcludingTypeFilter implements ProjectGenerationConfigurationTypeFilter {

private final ProjectGenerationConfigurationTypeFilter delegate = ProjectGenerationConfigurationTypeFilter
.exclude(TestProjectGenerationConfiguration.class);

@Override
public boolean match(Class<?> type) {
return TestProjectGenerationConfiguration.class.equals(type);
public boolean test(Class<?> type) {
return this.delegate.test(type);
}

}

0 comments on commit 2945eb1

Please sign in to comment.