Refactor MimeType/MediaType specificity

This commit makes several changes to MimeType and MediaType
related to the topic of specificity.

This commit deprecates the MimeType and MediaType Comparators.
Comparators require a transitive relationship, and the desired order for
these types is not transitive (see #27488).

Instead, this commit introduces two new MimeType methods: isMoreSpecific
and isLessSpecific, both of which return booleans. MediaType overrides
these methods to include the quality factor (q) in the comparison.

All MediaType sorting methods have been deprecated in favor of
MimeTypeUtils::sortBySpecificity.  This sorting method now uses
MimeType::isLessSpecific in combination a bubble sort algorithm (which
does not require a transitive compare function).

Closes gh-27580
This commit is contained in:
Arjen Poutsma
2021-10-19 18:40:41 +02:00
committed by Arjen Poutsma
parent a7789db067
commit 6d9136013e
5 changed files with 342 additions and 40 deletions

View File

@@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.testfixture.io.SerializationTestUtils;
import org.springframework.util.MimeTypeUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -240,6 +241,44 @@ public class MediaTypeTests {
}
@Test
void isMoreSpecific() {
MediaType audio = new MediaType("audio");
MediaType audioBasic = new MediaType("audio", "basic");
MediaType audioBasic07 = new MediaType("audio", "basic", 0.7);
MediaType audioBasic03 = new MediaType("audio", "basic", 0.3);
assertThat(audioBasic.isMoreSpecific(audio)).isTrue();
assertThat(audio.isMoreSpecific(audioBasic)).isFalse();
assertThat(audio.isMoreSpecific(audioBasic07)).isTrue();
assertThat(audioBasic07.isMoreSpecific(audio)).isFalse();
assertThat(audioBasic07.isMoreSpecific(audioBasic03)).isTrue();
assertThat(audioBasic03.isMoreSpecific(audioBasic07)).isFalse();
assertThat(audioBasic.isMoreSpecific(MediaType.TEXT_HTML)).isFalse();
}
@Test
void isLessSpecific() {
MediaType audio = new MediaType("audio");
MediaType audioBasic = new MediaType("audio", "basic");
MediaType audioBasic07 = new MediaType("audio", "basic", 0.7);
MediaType audioBasic03 = new MediaType("audio", "basic", 0.3);
assertThat(audioBasic.isLessSpecific(audio)).isFalse();
assertThat(audio.isLessSpecific(audioBasic)).isTrue();
assertThat(audio.isLessSpecific(audioBasic07)).isFalse();
assertThat(audioBasic07.isLessSpecific(audio)).isTrue();
assertThat(audioBasic07.isLessSpecific(audioBasic03)).isFalse();
assertThat(audioBasic03.isLessSpecific(audioBasic07)).isTrue();
assertThat(audioBasic.isLessSpecific(MediaType.TEXT_HTML)).isFalse();
}
@Test
public void specificityComparator() throws Exception {
MediaType audioBasic = new MediaType("audio", "basic");
@@ -470,4 +509,33 @@ public class MediaTypeTests {
assertThat(original).isEqualTo(deserialized);
}
@Test
public void sortBySpecificity() {
MediaType audioBasic = new MediaType("audio", "basic");
MediaType audio = new MediaType("audio");
MediaType audio03 = new MediaType("audio", "*", 0.3);
MediaType audio07 = new MediaType("audio", "*", 0.7);
MediaType audioBasicLevel = new MediaType("audio", "basic", Collections.singletonMap("level", "1"));
MediaType all = MediaType.ALL;
List<MediaType> expected = new ArrayList<>();
expected.add(audioBasicLevel);
expected.add(audioBasic);
expected.add(audio);
expected.add(all);
expected.add(audio07);
expected.add(audio03);
List<MediaType> result = new ArrayList<>(expected);
Random rnd = new Random();
// shuffle & sort 10 times
for (int i = 0; i < 10; i++) {
Collections.shuffle(result, rnd);
MimeTypeUtils.sortBySpecificity(result);
assertThat(result).containsExactlyElementsOf(expected);
}
}
}