Browse Source

Improve testing of architecture checks

See gh-48139

Signed-off-by: Scott Frederick <scottyfred@gmail.com>
3.5.x
Scott Frederick 1 month ago committed by Stéphane Nicoll
parent
commit
8e65122576
  1. 36
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java
  2. 59
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotations.java
  3. 77
      buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java
  4. 145
      buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java
  5. 46
      buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnMissingBean.java
  6. 42
      buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationProperties.java
  7. 28
      buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesBinding.java
  8. 29
      buildSrc/src/test/java/org/springframework/boot/build/architecture/collectors/toList/CollectorsToList.java
  9. 30
      buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/valueonly/TypeSameAsMethodReturnType.java
  10. 30
      buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withname/WithNameAttribute.java
  11. 30
      buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withtype/WithTypeAttribute.java
  12. 32
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/bindingnonstatic/BindingMethodNonStatic.java
  13. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java
  14. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixonly/ConfigurationPropertiesWithPrefixOnly.java
  15. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classvalueonly/ConfigurationPropertiesWithValueOnly.java
  16. 2
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/deprecatedsince/DeprecatedConfigurationPropertySince.java
  17. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java
  18. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixonly/ConfigurationPropertiesWithPrefixOnly.java
  19. 34
      buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodvalueonly/ConfigurationPropertiesWithValueOnly.java
  20. 28
      buildSrc/src/test/java/org/springframework/boot/build/architecture/url/decode/UrlDecodeWithStringEncoding.java
  21. 28
      buildSrc/src/test/java/org/springframework/boot/build/architecture/url/encode/UrlEncodeWithStringEncoding.java

36
buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheck.java

@ -26,7 +26,6 @@ import java.nio.file.StandardOpenOption; @@ -26,7 +26,6 @@ import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import java.util.stream.Stream;
@ -60,6 +59,8 @@ import org.gradle.api.tasks.SourceSet; @@ -60,6 +59,8 @@ import org.gradle.api.tasks.SourceSet;
import org.gradle.api.tasks.TaskAction;
import org.gradle.api.tasks.VerificationException;
import org.springframework.boot.build.architecture.ArchitectureCheckAnnotations.Annotation;
/**
* {@link Task} that checks for architecture problems.
*
@ -72,27 +73,26 @@ import org.gradle.api.tasks.VerificationException; @@ -72,27 +73,26 @@ import org.gradle.api.tasks.VerificationException;
*/
public abstract class ArchitectureCheck extends DefaultTask {
static final String CONDITIONAL_ON_CLASS = "ConditionalOnClass";
static final String DEPRECATED_CONFIGURATION_PROPERTY = "DeprecatedConfigurationProperty";
private static final String CONDITIONAL_ON_CLASS_ANNOTATION = "org.springframework.boot.autoconfigure.condition.ConditionalOnClass";
private static final String DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION = "org.springframework.boot.context.properties.DeprecatedConfigurationProperty";
private FileCollection classes;
public ArchitectureCheck() {
getOutputDirectory().convention(getProject().getLayout().getBuildDirectory().dir(getName()));
getAnnotationClasses().convention(Map.of(CONDITIONAL_ON_CLASS, CONDITIONAL_ON_CLASS_ANNOTATION,
DEPRECATED_CONFIGURATION_PROPERTY, DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION));
getAnnotationClasses().convention(ArchitectureCheckAnnotations.asMap());
getRules().addAll(getProhibitObjectsRequireNonNull().convention(true)
.map(whenTrue(ArchitectureRules::noClassesShouldCallObjectsRequireNonNull)));
getRules().addAll(ArchitectureRules.standard());
getRules().addAll(whenMainSources(() -> ArchitectureRules
.beanMethods(annotationClassFor(CONDITIONAL_ON_CLASS, CONDITIONAL_ON_CLASS_ANNOTATION))));
getRules().addAll(whenMainSources(() -> ArchitectureRules.configurationProperties(
annotationClassFor(DEPRECATED_CONFIGURATION_PROPERTY, DEPRECATED_CONFIGURATION_PROPERTY_ANNOTATION))));
getRules().addAll(whenMainSources(() -> ArchitectureRules.beanMethods(
ArchitectureCheckAnnotations.classFor(getAnnotationClasses().get(), Annotation.CONDITIONAL_ON_CLASS))));
getRules().addAll(whenMainSources(() -> ArchitectureRules.conditionalOnMissingBean(ArchitectureCheckAnnotations
.classFor(getAnnotationClasses().get(), Annotation.CONDITIONAL_ON_MISSING_BEAN))));
getRules().addAll(whenMainSources(() -> ArchitectureRules.configurationProperties(ArchitectureCheckAnnotations
.classFor(getAnnotationClasses().get(), Annotation.CONFIGURATION_PROPERTIES))));
getRules()
.addAll(whenMainSources(() -> ArchitectureRules.configurationPropertiesBinding(ArchitectureCheckAnnotations
.classFor(getAnnotationClasses().get(), Annotation.CONFIGURATION_PROPERTIES_BINDING))));
getRules().addAll(
whenMainSources(() -> ArchitectureRules.configurationPropertiesDeprecation(ArchitectureCheckAnnotations
.classFor(getAnnotationClasses().get(), Annotation.DEPRECATED_CONFIGURATION_PROPERTY))));
getRuleDescriptions().set(getRules().map(this::asDescriptions));
}
@ -110,10 +110,6 @@ public abstract class ArchitectureCheck extends DefaultTask { @@ -110,10 +110,6 @@ public abstract class ArchitectureCheck extends DefaultTask {
return rules.stream().map(ArchRule::getDescription).toList();
}
private String annotationClassFor(String name, String defaultValue) {
return getAnnotationClasses().get().getOrDefault(name, defaultValue);
}
@TaskAction
void checkArchitecture() throws Exception {
withCompileClasspath(() -> {
@ -204,7 +200,7 @@ public abstract class ArchitectureCheck extends DefaultTask { @@ -204,7 +200,7 @@ public abstract class ArchitectureCheck extends DefaultTask {
@Input // Use descriptions as input since rules aren't serializable
abstract ListProperty<String> getRuleDescriptions();
@Input
@Internal
abstract MapProperty<String, String> getAnnotationClasses();
}

59
buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureCheckAnnotations.java

@ -0,0 +1,59 @@ @@ -0,0 +1,59 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture;
import java.util.Map;
/**
* Annotations used in architecture checks, which can be overridden in architecture rule
* tests.
*
* @author Scott Frederick
*/
public final class ArchitectureCheckAnnotations {
enum Annotation {
CONDITIONAL_ON_CLASS, CONDITIONAL_ON_MISSING_BEAN, CONFIGURATION_PROPERTIES, DEPRECATED_CONFIGURATION_PROPERTY,
CONFIGURATION_PROPERTIES_BINDING
}
private ArchitectureCheckAnnotations() {
}
private static final Map<String, String> annotationNameToClassName = Map.of(Annotation.CONDITIONAL_ON_CLASS.name(),
"org.springframework.boot.autoconfigure.condition.ConditionalOnClass",
Annotation.CONDITIONAL_ON_MISSING_BEAN.name(),
"org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean",
Annotation.CONFIGURATION_PROPERTIES.name(),
"org.springframework.boot.context.properties.ConfigurationProperties",
Annotation.DEPRECATED_CONFIGURATION_PROPERTY.name(),
"org.springframework.boot.context.properties.DeprecatedConfigurationProperty",
Annotation.CONFIGURATION_PROPERTIES_BINDING.name(),
"org.springframework.boot.context.properties.ConfigurationPropertiesBinding");
static Map<String, String> asMap() {
return annotationNameToClassName;
}
static String classFor(Map<String, String> annotationProperty, Annotation annotation) {
String name = annotation.name();
return annotationProperty.getOrDefault(name, asMap().get(name));
}
}

77
buildSrc/src/main/java/org/springframework/boot/build/architecture/ArchitectureRules.java

@ -92,22 +92,32 @@ final class ArchitectureRules { @@ -92,22 +92,32 @@ final class ArchitectureRules {
rules.add(noClassesShouldLoadResourcesUsingResourceUtils());
rules.add(noClassesShouldCallStringToUpperCaseWithoutLocale());
rules.add(noClassesShouldCallStringToLowerCaseWithoutLocale());
rules.add(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType());
rules.add(enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter());
rules.add(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute());
rules.add(methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute());
rules.add(conditionsShouldNotBePublic());
rules.add(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic());
return List.copyOf(rules);
}
static List<ArchRule> beanMethods(String annotationName) {
static List<ArchRule> beanMethods(String annotationClass) {
return List.of(allBeanMethodsShouldReturnNonPrivateType(),
allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(annotationName));
allBeanMethodsShouldNotHaveConditionalOnClassAnnotation(annotationClass));
}
static List<ArchRule> configurationProperties(String annotationName) {
return List.of(allDeprecatedConfigurationPropertiesShouldIncludeSince(annotationName));
static List<ArchRule> conditionalOnMissingBean(String annotationClass) {
return List
.of(conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType(annotationClass));
}
static List<ArchRule> configurationProperties(String annotationClass) {
return List.of(classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass),
methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(annotationClass));
}
static List<ArchRule> configurationPropertiesBinding(String annotationClass) {
return List.of(allConfigurationPropertiesBindingBeanMethodsShouldBeStatic(annotationClass));
}
static List<ArchRule> configurationPropertiesDeprecation(String annotationClass) {
return List.of(allDeprecatedConfigurationPropertiesShouldIncludeSince(annotationClass));
}
private static ArchRule allBeanMethodsShouldReturnNonPrivateType() {
@ -247,16 +257,17 @@ final class ArchitectureRules { @@ -247,16 +257,17 @@ final class ArchitectureRules {
.because(shouldUse("String.toLowerCase(Locale.ROOT)"));
}
private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType() {
return methodsThatAreAnnotatedWith("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean")
.should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType())
private static ArchRule conditionalOnMissingBeanShouldNotSpecifyOnlyATypeThatIsTheSameAsMethodReturnType(
String annotation) {
return methodsThatAreAnnotatedWith(annotation)
.should(notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType(annotation))
.allowEmptyShould(true);
}
private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType() {
private static ArchCondition<? super JavaMethod> notSpecifyOnlyATypeThatIsTheSameAsTheMethodReturnType(
String annotation) {
return check("not specify only a type that is the same as the method's return type", (item, events) -> {
JavaAnnotation<JavaMethod> conditionalAnnotation = item
.getAnnotationOfType("org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean");
JavaAnnotation<JavaMethod> conditionalAnnotation = item.getAnnotationOfType(annotation);
Map<String, Object> properties = conditionalAnnotation.getProperties();
if (!hasProperty("type", properties) && !hasProperty("name", properties)) {
conditionalAnnotation.get("value").ifPresent((value) -> {
@ -274,7 +285,7 @@ final class ArchitectureRules { @@ -274,7 +285,7 @@ final class ArchitectureRules {
if (property == null) {
return false;
}
return !property.getClass().isArray() || ((Object[]) property).length > 0;
return (property.getClass().isArray()) ? ((Object[]) property).length > 0 : !property.toString().isEmpty();
}
private static ArchRule enumSourceShouldNotHaveValueThatIsTheSameAsTypeOfMethodsFirstParameter() {
@ -303,33 +314,37 @@ final class ArchitectureRules { @@ -303,33 +314,37 @@ final class ArchitectureRules {
});
}
private static ArchRule classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute() {
private static ArchRule classLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(
String annotationClass) {
return ArchRuleDefinition.classes()
.that()
.areAnnotatedWith("org.springframework.boot.context.properties.ConfigurationProperties")
.should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties())
.areAnnotatedWith(annotationClass)
.should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass))
.allowEmptyShould(true);
}
private static ArchRule methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute() {
private static ArchRule methodLevelConfigurationPropertiesShouldNotSpecifyOnlyPrefixAttribute(
String annotationClass) {
return ArchRuleDefinition.methods()
.that()
.areAnnotatedWith("org.springframework.boot.context.properties.ConfigurationProperties")
.should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties())
.areAnnotatedWith(annotationClass)
.should(notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass))
.allowEmptyShould(true);
}
private static ArchCondition<? super HasAnnotations<?>> notSpecifyOnlyPrefixAttributeOfConfigurationProperties() {
return check("not specify only prefix attribute of @ConfigurationProperties",
ArchitectureRules::notSpecifyOnlyPrefixAttributeOfConfigurationProperties);
private static ArchCondition<? super HasAnnotations<?>> notSpecifyOnlyPrefixAttributeOfConfigurationProperties(
String annotationClass) {
return check("not specify only prefix attribute of @ConfigurationProperties", (item,
events) -> notSpecifyOnlyPrefixAttributeOfConfigurationProperties(annotationClass, item, events));
}
private static void notSpecifyOnlyPrefixAttributeOfConfigurationProperties(HasAnnotations<?> item,
ConditionEvents events) {
JavaAnnotation<?> configurationPropertiesAnnotation = item
.getAnnotationOfType("org.springframework.boot.context.properties.ConfigurationProperties");
private static void notSpecifyOnlyPrefixAttributeOfConfigurationProperties(String annotationClass,
HasAnnotations<?> item, ConditionEvents events) {
JavaAnnotation<?> configurationPropertiesAnnotation = item.getAnnotationOfType(annotationClass);
Map<String, Object> properties = configurationPropertiesAnnotation.getProperties();
if (properties.size() == 1 && properties.containsKey("prefix")) {
if (hasProperty("prefix", properties) && !hasProperty("value", properties)
&& properties.get("ignoreInvalidFields").equals(false)
&& properties.get("ignoreUnknownFields").equals(true)) {
addViolation(events, item, configurationPropertiesAnnotation.getDescription()
+ " should specify implicit 'value' attribute other than explicit 'prefix' attribute");
}
@ -349,9 +364,9 @@ final class ArchitectureRules { @@ -349,9 +364,9 @@ final class ArchitectureRules {
.allowEmptyShould(true);
}
private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic() {
private static ArchRule allConfigurationPropertiesBindingBeanMethodsShouldBeStatic(String annotationClass) {
return methodsThatAreAnnotatedWith("org.springframework.context.annotation.Bean").and()
.areAnnotatedWith("org.springframework.boot.context.properties.ConfigurationPropertiesBinding")
.areAnnotatedWith(annotationClass)
.should()
.beStatic()
.allowEmptyShould(true);

145
buildSrc/src/test/java/org/springframework/boot/build/architecture/ArchitectureCheckTests.java

@ -43,7 +43,11 @@ import org.junit.jupiter.api.io.TempDir; @@ -43,7 +43,11 @@ import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.springframework.boot.build.architecture.ArchitectureCheckAnnotations.Annotation;
import org.springframework.boot.build.architecture.annotations.TestConditionalOnClass;
import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesBinding;
import org.springframework.boot.build.architecture.annotations.TestDeprecatedConfigurationProperty;
import org.springframework.util.ClassUtils;
import org.springframework.util.FileSystemUtils;
@ -204,6 +208,29 @@ class ArchitectureCheckTests { @@ -204,6 +208,29 @@ class ArchitectureCheckTests {
build(this.gradleBuild.withProhibitObjectsRequireNonNull(false), task);
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsCollectorsToListShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "collectors/toList");
buildAndFail(this.gradleBuild, task, "because java.util.stream.Stream.toList() should be used instead");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsUrlEncoderWithStringEncodingShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "url/encode");
buildAndFail(this.gradleBuild, task,
"because java.net.URLEncoder.encode(String s, Charset charset) should be used instead");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsUrlDecoderWithStringEncodingShouldFailAndWriteReport(Task task) throws IOException {
prepareTask(task, "url/decode");
buildAndFail(this.gradleBuild, task,
"because java.net.URLDecoder.decode(String s, Charset charset) should be used instead");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenClassCallsStringToUpperCaseWithoutLocaleShouldFailAndWriteReport(Task task) throws IOException {
@ -232,6 +259,81 @@ class ArchitectureCheckTests { @@ -232,6 +259,81 @@ class ArchitectureCheckTests {
build(this.gradleBuild, task);
}
@Test
void whenConditionalOnMissingBeanWithTypeSameAsMethodReturnTypeShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonmissingbean/valueonly", "annotations");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnMissingBeanAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN,
"should not specify only a value that is the same as the method's return type");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenConditionalOnMissingBeanWithTypeAttributeShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "conditionalonmissingbean/withtype", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenConditionalOnMissingBeanWithNameAttributeShouldSucceedAndWriteEmptyReport(Task task) throws IOException {
prepareTask(task, "conditionalonmissingbean/withname", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT), task);
}
@Test
void whenClassLevelConfigurationPropertiesContainsOnlyPrefixShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classprefixonly", "annotations");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN,
"should specify implicit 'value' attribute other than explicit 'prefix' attribute");
}
@Test
void whenClassLevelConfigurationPropertiesContainsPrefixAndIgnoreShouldSucceedAndWriteEmptyReport()
throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classprefixandignore", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN);
}
@Test
void whenClassLevelConfigurationPropertiesContainsOnlyValueShouldSucceedAndWriteEmptyReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/classvalueonly", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN);
}
@Test
void whenMethodLevelConfigurationPropertiesContainsOnlyPrefixShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodprefixonly", "annotations");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN,
"should specify implicit 'value' attribute other than explicit 'prefix' attribute");
}
@Test
void whenMethodLevelConfigurationPropertiesContainsPrefixAndIgnoreShouldSucceedAndWriteEmptyReport()
throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodprefixandignore", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN);
}
@Test
void whenMethodLevelConfigurationPropertiesContainsOnlyValueShouldSucceedAndWriteEmptyReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/methodvalueonly", "annotations");
build(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN);
}
@Test
void whenConfigurationPropertiesBindingBeanMethodIsNotStaticShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/bindingnonstatic", "annotations");
buildAndFail(this.gradleBuild.withDependencies(SPRING_CONTEXT).withConfigurationPropertiesBindingAnnotation(),
Task.CHECK_ARCHITECTURE_MAIN, "does not have modifier STATIC");
}
@ParameterizedTest(name = "{0}")
@EnumSource(Task.class)
void whenBeanPostProcessorBeanMethodIsNotStaticWithExternalClassShouldFailAndWriteReport(Task task)
@ -304,8 +406,7 @@ class ArchitectureCheckTests { @@ -304,8 +406,7 @@ class ArchitectureCheckTests {
@Test
void whenConditionalOnClassUsedOnBeanMethodsWithMainSourcesShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "conditionalonclass", "annotations");
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT)
.withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName());
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnClassAnnotation();
buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN,
"because @ConditionalOnClass on @Bean methods is ineffective - it doesn't prevent"
+ " the method signature from being loaded. Such condition need to be placed"
@ -315,16 +416,15 @@ class ArchitectureCheckTests { @@ -315,16 +416,15 @@ class ArchitectureCheckTests {
@Test
void whenConditionalOnClassUsedOnBeanMethodsWithTestSourcesShouldSucceedAndWriteEmptyReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_TEST, "conditionalonclass", "annotations");
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT)
.withConditionalOnClassAnnotation(TestConditionalOnClass.class.getName());
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT).withConditionalOnClassAnnotation();
build(gradleBuild, Task.CHECK_ARCHITECTURE_TEST);
}
@Test
void whenDeprecatedConfigurationPropertyIsMissingSinceShouldFailAndWriteReport() throws IOException {
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties", "annotations");
prepareTask(Task.CHECK_ARCHITECTURE_MAIN, "configurationproperties/deprecatedsince", "annotations");
GradleBuild gradleBuild = this.gradleBuild.withDependencies(SPRING_CONTEXT)
.withDeprecatedConfigurationPropertyAnnotation(TestDeprecatedConfigurationProperty.class.getName());
.withDeprecatedConfigurationPropertyAnnotation();
buildAndFail(gradleBuild, Task.CHECK_ARCHITECTURE_MAIN,
"should include a non-empty 'since' attribute of @DeprecatedConfigurationProperty",
"DeprecatedConfigurationPropertySince.getProperty");
@ -429,22 +529,39 @@ class ArchitectureCheckTests { @@ -429,22 +529,39 @@ class ArchitectureCheckTests {
return this;
}
GradleBuild withConditionalOnClassAnnotation(String annotationClass) {
for (Task task : Task.values()) {
configureTask(task, (configuration) -> configuration
.withAnnotation(ArchitectureCheck.CONDITIONAL_ON_CLASS, annotationClass));
GradleBuild withConditionalOnClassAnnotation() {
configureTasks(Annotation.CONDITIONAL_ON_CLASS.name(), TestConditionalOnClass.class.getName());
return this;
}
GradleBuild withConditionalOnMissingBeanAnnotation() {
configureTasks(Annotation.CONDITIONAL_ON_MISSING_BEAN.name(), TestConditionalOnMissingBean.class.getName());
return this;
}
GradleBuild withDeprecatedConfigurationPropertyAnnotation(String annotationClass) {
for (Task task : Task.values()) {
configureTask(task, (configuration) -> configuration
.withAnnotation(ArchitectureCheck.DEPRECATED_CONFIGURATION_PROPERTY, annotationClass));
GradleBuild withConfigurationPropertiesAnnotation() {
configureTasks(Annotation.CONFIGURATION_PROPERTIES.name(), TestConfigurationProperties.class.getName());
return this;
}
GradleBuild withConfigurationPropertiesBindingAnnotation() {
configureTasks(Annotation.CONFIGURATION_PROPERTIES_BINDING.name(),
TestConfigurationPropertiesBinding.class.getName());
return this;
}
GradleBuild withDeprecatedConfigurationPropertyAnnotation() {
configureTasks(Annotation.DEPRECATED_CONFIGURATION_PROPERTY.name(),
TestDeprecatedConfigurationProperty.class.getName());
return this;
}
private void configureTasks(String annotationName, String annotationClass) {
for (Task task : Task.values()) {
configureTask(task, (configuration) -> configuration.withAnnotation(annotationName, annotationClass));
}
}
private void configureTask(Task task, UnaryOperator<TaskConfiguration> configurer) {
this.taskConfigurations.computeIfAbsent(task, (key) -> new TaskConfiguration(null, null));
this.taskConfigurations.compute(task, (key, value) -> configurer.apply(value));

46
buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConditionalOnMissingBean.java

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.annotations;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@code @ConditionalOnMissingBean} analogue for architecture checks.
*/
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestConditionalOnMissingBean {
Class<?>[] value() default {};
String[] type() default {};
Class<?>[] ignored() default {};
String[] ignoredType() default {};
Class<? extends Annotation>[] annotation() default {};
String[] name() default {};
Class<?>[] parameterizedContainer() default {};
}

42
buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationProperties.java

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Indexed;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Indexed
public @interface TestConfigurationProperties {
@AliasFor("prefix")
String value() default "";
@AliasFor("value")
String prefix() default "";
boolean ignoreInvalidFields() default false;
boolean ignoreUnknownFields() default true;
}

28
buildSrc/src/test/java/org/springframework/boot/build/architecture/annotations/TestConfigurationPropertiesBinding.java

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestConfigurationPropertiesBinding {
}

29
buildSrc/src/test/java/org/springframework/boot/build/architecture/collectors/toList/CollectorsToList.java

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.collectors.toList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
class CollectorsToList {
void exampleMethod() {
List<String> strings = Stream.of("a", "b", "c").collect(Collectors.toList());
}
}

30
buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/valueonly/TypeSameAsMethodReturnType.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.conditionalonmissingbean.valueonly;
import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
class TypeSameAsMethodReturnType {
@Bean
@TestConditionalOnMissingBean(String.class)
String helloWorld() {
return "Hello World";
}
}

30
buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withname/WithNameAttribute.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.conditionalonmissingbean.withname;
import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
class WithNameAttribute {
@Bean
@TestConditionalOnMissingBean(name = "myBean")
String helloWorld() {
return "Hello World";
}
}

30
buildSrc/src/test/java/org/springframework/boot/build/architecture/conditionalonmissingbean/withtype/WithTypeAttribute.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.conditionalonmissingbean.withtype;
import org.springframework.boot.build.architecture.annotations.TestConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
class WithTypeAttribute {
@Bean
@TestConditionalOnMissingBean(type = "String")
String helloWorld() {
return "Hello World";
}
}

32
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/bindingnonstatic/BindingMethodNonStatic.java

@ -0,0 +1,32 @@ @@ -0,0 +1,32 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.bindingnonstatic;
import java.util.List;
import org.springframework.boot.build.architecture.annotations.TestConfigurationPropertiesBinding;
import org.springframework.context.annotation.Bean;
public class BindingMethodNonStatic {
@Bean
@TestConfigurationPropertiesBinding
public List<String> binder() {
return List.of("hello", "world");
}
}

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.classprefixandignore;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
@TestConfigurationProperties(prefix = "testing", ignoreUnknownFields = false)
public class ConfigurationPropertiesWithPrefixAndIgnore {
private String property;
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classprefixonly/ConfigurationPropertiesWithPrefixOnly.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.classprefixonly;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
@TestConfigurationProperties(prefix = "testing")
public class ConfigurationPropertiesWithPrefixOnly {
private String property;
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/classvalueonly/ConfigurationPropertiesWithValueOnly.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.classvalueonly;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
@TestConfigurationProperties("testing")
public class ConfigurationPropertiesWithValueOnly {
private String property;
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

2
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/DeprecatedConfigurationPropertySince.java → buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/deprecatedsince/DeprecatedConfigurationPropertySince.java

@ -14,7 +14,7 @@ @@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.boot.build.architecture.configurationproperties;
package org.springframework.boot.build.architecture.configurationproperties.deprecatedsince;
import org.springframework.boot.build.architecture.annotations.TestDeprecatedConfigurationProperty;

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixandignore/ConfigurationPropertiesWithPrefixAndIgnore.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.methodprefixandignore;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
public class ConfigurationPropertiesWithPrefixAndIgnore {
private String property;
@TestConfigurationProperties(prefix = "testing", ignoreInvalidFields = true)
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodprefixonly/ConfigurationPropertiesWithPrefixOnly.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.methodprefixonly;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
public class ConfigurationPropertiesWithPrefixOnly {
private String property;
@TestConfigurationProperties(prefix = "testing")
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

34
buildSrc/src/test/java/org/springframework/boot/build/architecture/configurationproperties/methodvalueonly/ConfigurationPropertiesWithValueOnly.java

@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.configurationproperties.methodvalueonly;
import org.springframework.boot.build.architecture.annotations.TestConfigurationProperties;
public class ConfigurationPropertiesWithValueOnly {
private String property;
@TestConfigurationProperties("testing")
public String getProperty() {
return this.property;
}
public void setProperty(String property) {
this.property = property;
}
}

28
buildSrc/src/test/java/org/springframework/boot/build/architecture/url/decode/UrlDecodeWithStringEncoding.java

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.url.decode;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
class UrlDecodeWithStringEncoding {
void exampleMethod() throws UnsupportedEncodingException {
URLDecoder.decode("https://example.com", "UTF-8");
}
}

28
buildSrc/src/test/java/org/springframework/boot/build/architecture/url/encode/UrlEncodeWithStringEncoding.java

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
/*
* Copyright 2012-present 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.springframework.boot.build.architecture.url.encode;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
class UrlEncodeWithStringEncoding {
void exampleMethod() throws UnsupportedEncodingException {
URLEncoder.encode("https://example.com", "UTF-8");
}
}
Loading…
Cancel
Save