Skip to content

Commit

Permalink
Merge pull request #1584 from zambrovski
Browse files Browse the repository at this point in the history
* pr/1584:
  Polish "Add ability to exclude ProjectGenerationConfiguration"
  Add ability to exclude ProjectGenerationConfiguration

Closes gh-1584
  • Loading branch information
mhalbritter committed Nov 19, 2024
2 parents 0db7ed4 + 2945eb1 commit 445a1ce
Show file tree
Hide file tree
Showing 8 changed files with 313 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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 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 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));
}

/**
* Creates a {@link ProjectGenerationConfigurationTypeFilter} from multiple filters
* which must all match.
* @param filters the filters
* @return a combined {@link ProjectGenerationConfigurationTypeFilter}
*/
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 @@ -24,6 +24,7 @@
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.ClassUtils;

/**
* Main entry point for project generation that processes a {@link ProjectDescription} by
Expand All @@ -34,6 +35,8 @@
*
* @author Andy Wilkinson
* @author Stephane Nicoll
* @author Simon Zambrovski
* @author Moritz Halbritter
*/
public class ProjectGenerator {

Expand Down Expand Up @@ -116,17 +119,45 @@ 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}.
* considered. By default, this method will load candidates using
* {@link SpringFactoriesLoader} with {@link ProjectGenerationConfiguration} and
* 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
*/
@SuppressWarnings("deprecation")

protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
List<String> candidates = getProjectGenerationConfigurationFactoryNames();
ProjectGenerationConfigurationTypeFilter filter = getProjectGenerationConfigurationExclusionFilter();
return candidates.stream().filter((candidate) -> {
Class<?> type = resolveClass(candidate);
return type != null && filter.test(type);
}).toList();
}

private Class<?> resolveClass(String candidate) {
try {
return ClassUtils.forName(candidate, getClass().getClassLoader());
}
catch (ClassNotFoundException ex) {
return null;
}
}

@SuppressWarnings("deprecation")
List<String> getProjectGenerationConfigurationFactoryNames() {
return SpringFactoriesLoader.loadFactoryNames(ProjectGenerationConfiguration.class,
getClass().getClassLoader());
}

ProjectGenerationConfigurationTypeFilter getProjectGenerationConfigurationExclusionFilter() {
List<ProjectGenerationConfigurationTypeFilter> filters = SpringFactoriesLoader
.loadFactories(ProjectGenerationConfigurationTypeFilter.class, getClass().getClassLoader());
return ProjectGenerationConfigurationTypeFilter.allMatch(filters);
}

private void registerProjectDescription(ProjectGenerationContext context, ProjectDescription description) {
context.registerBean(ProjectDescription.class, resolve(description, context));
}
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 @@ -25,6 +25,8 @@

import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
import io.spring.initializr.generator.project.contributor.TestProjectGenerationConfiguration;
import io.spring.initializr.generator.project.contributor.TestProjectGenerationConfiguration2;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InOrder;
Expand Down Expand Up @@ -181,8 +183,9 @@ void generateCanBeExtendedToFilterProjectContributors(@TempDir Path projectDir)
given(description.getBuildSystem()).willReturn(new MavenBuildSystem());
ProjectGenerator generator = new ProjectGenerator(mockContextInitializr()) {
@Override
protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescription description) {
assertThat(description).isSameAs(description);
protected List<String> getCandidateProjectGenerationConfigurations(
ProjectDescription generatorDescription) {
assertThat(description).isSameAs(generatorDescription);
return Collections.singletonList(TestProjectGenerationConfiguration.class.getName());
}
};
Expand All @@ -195,6 +198,37 @@ protected List<String> getCandidateProjectGenerationConfigurations(ProjectDescri
verify(description).getBuildSystem();
}

@Test
void loadAndConstructProjectGenerationTypeExclusionFilter() {
ProjectGenerator generator = new ProjectGenerator(mockContextInitializr());
ProjectGenerationConfigurationTypeFilter filter = generator.getProjectGenerationConfigurationExclusionFilter();
assertThat(filter).isNotNull();
assertThat(filter.test(TestProjectGenerationConfiguration.class)).isFalse();
assertThat(filter.test(TestProjectGenerationConfiguration2.class)).isFalse();
assertThat(filter.test(Integer.class)).isTrue();
}

@Test
void filterProjectContributorsCorrectly() {
ProjectDescription description = mock(ProjectDescription.class);
given(description.getArtifactId()).willReturn("test-custom-contributor");
given(description.getBuildSystem()).willReturn(new MavenBuildSystem());
ProjectGenerator generator = new ProjectGenerator(mockContextInitializr()) {
@Override
List<String> getProjectGenerationConfigurationFactoryNames() {
return Lists.list(TestProjectGenerationConfiguration.class.getName(),
TestProjectGenerationConfiguration2.class.getName());
}

@Override
ProjectGenerationConfigurationTypeFilter getProjectGenerationConfigurationExclusionFilter() {
return (clazz) -> !TestProjectGenerationConfiguration2.class.equals(clazz);
}
};
List<String> candidates = generator.getCandidateProjectGenerationConfigurations(description);
assertThat(candidates).containsOnly(TestProjectGenerationConfiguration.class.getCanonicalName());
}

@SuppressWarnings("unchecked")
private Consumer<ProjectGenerationContext> mockContextInitializr() {
return mock(Consumer.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Copyright 2012-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 io.spring.initializr.generator.project.contributor;

import io.spring.initializr.generator.project.ProjectGenerationConfiguration;

/**
* Test contributor.
*/
@ProjectGenerationConfiguration
public class TestProjectGenerationConfiguration2 {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2012-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 io.spring.initializr.generator.project.contributor;

import io.spring.initializr.generator.project.ProjectGenerationConfigurationTypeFilter;

public class TestProjectGenerationConfiguration2ExcludingTypeFilter
implements ProjectGenerationConfigurationTypeFilter {

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2012-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 io.spring.initializr.generator.project.contributor;

import io.spring.initializr.generator.project.ProjectGenerationConfigurationTypeFilter;

public class TestProjectGenerationConfigurationExcludingTypeFilter implements ProjectGenerationConfigurationTypeFilter {

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

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
io.spring.initializr.generator.project.ProjectGenerationConfigurationTypeFilter=\
io.spring.initializr.generator.project.contributor.TestProjectGenerationConfigurationExcludingTypeFilter,\
io.spring.initializr.generator.project.contributor.TestProjectGenerationConfiguration2ExcludingTypeFilter

0 comments on commit 445a1ce

Please sign in to comment.