diff --git a/framework-annotation-processor/framework-annotation-processor.gradle b/framework-annotation-processor/framework-annotation-processor.gradle new file mode 100644 index 0000000000..d68d69e097 --- /dev/null +++ b/framework-annotation-processor/framework-annotation-processor.gradle @@ -0,0 +1,3 @@ +plugins { + id("java") +} diff --git a/framework-annotation-processor/src/main/java/org/springframework/annotation/processor/NonProcessedAnnotationClaimer.java b/framework-annotation-processor/src/main/java/org/springframework/annotation/processor/NonProcessedAnnotationClaimer.java new file mode 100644 index 0000000000..049bae3173 --- /dev/null +++ b/framework-annotation-processor/src/main/java/org/springframework/annotation/processor/NonProcessedAnnotationClaimer.java @@ -0,0 +1,41 @@ +package org.springframework.annotation.processor; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import java.util.Set; + +/** + * This annotation processor claims all the annotations that are not processed at compile time at all. + * Otherwise, the compiler would emit a warning that + * {@code No processor claimed any of these annotations}. Adding this to the compiler arg option {@code -Werror}, + * would fail the build. + */ +@SupportedAnnotationTypes({ + "org.springframework.core.annotation.AliasFor", + "javax.annotation.Nonnull", + "org.jspecify.annotations.NullMarked", + "com.oracle.svm.core.annotate.Alias", + "org.springframework.lang.Contract", + "jdk.jfr/jdk.jfr.Registered", + "org.springframework.aot.hint.annotation.Reflective", + "jdk.jfr/jdk.jfr.Category", + "javax.annotation.CheckForNull", + "com.oracle.svm.core.annotate.Substitute", + "jdk.jfr/jdk.jfr.Enabled", + "jdk.jfr/jdk.jfr.Label", + "org.springframework.aot.hint.annotation.RegisterReflection", + "com.oracle.svm.core.annotate.TargetClass", + "jdk.jfr/jdk.jfr.StackTrace", + "jdk.jfr/jdk.jfr.Description", + "javax.annotation.meta.TypeQualifierNickname", + "javax.annotation.meta.TypeQualifierDefault", + "javax.annotation.Generated" +}) +@SupportedSourceVersion(SourceVersion.RELEASE_21) +public class NonProcessedAnnotationClaimer extends AbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + return true; + } +} diff --git a/framework-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/framework-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000000..a8be7ed8a8 --- /dev/null +++ b/framework-annotation-processor/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +org.springframework.annotation.processor.NonProcessedAnnotationClaimer \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index e9fe33a314..840cb12871 100644 --- a/settings.gradle +++ b/settings.gradle @@ -29,6 +29,7 @@ include "framework-api" include "framework-bom" include "framework-docs" include "framework-platform" +include 'framework-annotation-processor' include "integration-tests" rootProject.name = "spring" @@ -51,3 +52,4 @@ settings.gradle.projectsLoaded { } } } + diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 5ac17dd889..828bc04f81 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -25,6 +25,9 @@ configurations { objenesis graalvm } +tasks.compileJava { + options.errorprone.excludedPaths = ".*/build/generated/.*" +} tasks.register('javapoetRepackJar', ShadowJar) { archiveBaseName = 'spring-javapoet-repack' @@ -67,6 +70,7 @@ tasks.register('objenesisSourceJar', Jar) { } dependencies { + annotationProcessor project(":framework-annotation-processor") javapoet("com.squareup:javapoet:${javapoetVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") api(files(javapoetRepackJar)) @@ -76,6 +80,9 @@ dependencies { compileOnly("com.google.code.findbugs:jsr305") compileOnly("io.projectreactor.tools:blockhound") compileOnly("org.graalvm.sdk:graal-sdk") + compileOnly("guru.mocker.annotation:mixin-annotation:1.1.0") + annotationProcessor("guru.mocker.annotation:mixin-annotation-processor:1.1.0") + implementation 'javax.annotation:javax.annotation-api:1.3.2' optional("io.micrometer:context-propagation") optional("io.netty:netty-buffer") optional("io.projectreactor:reactor-core") diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java index 97207b8f00..67777823d0 100644 --- a/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java +++ b/spring-core/src/main/java/org/springframework/util/MultiValueMapAdapter.java @@ -18,12 +18,10 @@ package org.springframework.util; import java.io.Serializable; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.function.BiConsumer; +import guru.mocker.annotation.mixin.Mixin; import org.jspecify.annotations.Nullable; /** @@ -38,18 +36,16 @@ import org.jspecify.annotations.Nullable; * @see LinkedMultiValueMap */ @SuppressWarnings("serial") -public class MultiValueMapAdapter implements MultiValueMap, Serializable { - - private final Map> targetMap; - +@Mixin +public class MultiValueMapAdapter extends MapForwarder implements MultiValueMap, Serializable { /** * Wrap the given target {@link Map} as a {@link MultiValueMap} adapter. - * @param targetMap the plain target {@code Map} + * @param mapForwarder the plain target {@code Map} */ - public MultiValueMapAdapter(Map> targetMap) { - Assert.notNull(targetMap, "'targetMap' must not be null"); - this.targetMap = targetMap; + public MultiValueMapAdapter(Map> mapForwarder) { + super(mapForwarder); + Assert.notNull(mapForwarder, "'targetMap' must not be null"); } @@ -57,19 +53,19 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ @Override public @Nullable V getFirst(K key) { - List values = this.targetMap.get(key); + List values = this.mapForwarder.get(key); return (!CollectionUtils.isEmpty(values) ? values.get(0) : null); } @Override public void add(K key, @Nullable V value) { - List values = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(1)); + List values = this.mapForwarder.computeIfAbsent(key, k -> new ArrayList<>(1)); values.add(value); } @Override public void addAll(K key, List values) { - List currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size())); + List currentValues = this.mapForwarder.computeIfAbsent(key, k -> new ArrayList<>(values.size())); currentValues.addAll(values); } @@ -82,7 +78,7 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ public void set(K key, @Nullable V value) { List values = new ArrayList<>(1); values.add(value); - this.targetMap.put(key, values); + this.mapForwarder.put(key, values); } @Override @@ -92,8 +88,8 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ @Override public Map toSingleValueMap() { - Map singleValueMap = CollectionUtils.newLinkedHashMap(this.targetMap.size()); - this.targetMap.forEach((key, values) -> { + Map singleValueMap = CollectionUtils.newLinkedHashMap(this.mapForwarder.size()); + this.mapForwarder.forEach((key, values) -> { if (!CollectionUtils.isEmpty(values)) { singleValueMap.put(key, values.get(0)); } @@ -104,89 +100,13 @@ public class MultiValueMapAdapter implements MultiValueMap, Serializ // Map implementation - @Override - public int size() { - return this.targetMap.size(); - } - - @Override - public boolean isEmpty() { - return this.targetMap.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.targetMap.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.targetMap.containsValue(value); - } - - @Override - public @Nullable List get(Object key) { - return this.targetMap.get(key); - } - - @Override - public @Nullable List put(K key, List value) { - return this.targetMap.put(key, value); - } - - @Override - public @Nullable List putIfAbsent(K key, List value) { - return this.targetMap.putIfAbsent(key, value); - } - - @Override - public @Nullable List remove(Object key) { - return this.targetMap.remove(key); - } - - @Override - public void putAll(Map> map) { - this.targetMap.putAll(map); - } - - @Override - public void clear() { - this.targetMap.clear(); - } - - @Override - public Set keySet() { - return this.targetMap.keySet(); - } - - @Override - public Collection> values() { - return this.targetMap.values(); - } - - @Override - public Set>> entrySet() { - return this.targetMap.entrySet(); - } - - @Override - public void forEach(BiConsumer> action) { - this.targetMap.forEach(action); - } - @Override public boolean equals(@Nullable Object other) { - return (this == other || this.targetMap.equals(other)); + return (this == other || this.mapForwarder.equals(other)); } @Override public int hashCode() { - return this.targetMap.hashCode(); + return this.mapForwarder.hashCode(); } - - @Override - public String toString() { - return this.targetMap.toString(); - } - }