From 60e69c8986018dd4702a14de7b91fee142c3d0d1 Mon Sep 17 00:00:00 2001 From: mizosoft Date: Fri, 20 Dec 2024 13:18:59 +0200 Subject: [PATCH] Implement MediaType structured syntax suffix --- .../github/mizosoft/methanol/MediaType.java | 40 ++++++++----------- .../mizosoft/methanol/MediaTypeTest.java | 27 +++++++++++-- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/methanol/src/main/java/com/github/mizosoft/methanol/MediaType.java b/methanol/src/main/java/com/github/mizosoft/methanol/MediaType.java index 602b02d94..cab877ffc 100644 --- a/methanol/src/main/java/com/github/mizosoft/methanol/MediaType.java +++ b/methanol/src/main/java/com/github/mizosoft/methanol/MediaType.java @@ -187,7 +187,6 @@ public Optional charset() { * Returns either the value of the charset parameter or the given default charset if no such * parameter exists or if the charset has no support in this JVM. * - * @param defaultCharset the charset to fall back to * @throws IllegalCharsetNameException if a charset parameter exists the value of which is invalid */ public Charset charsetOrDefault(Charset defaultCharset) { @@ -199,13 +198,16 @@ public Charset charsetOrDefault(Charset defaultCharset) { } } - /** Equivalent to calling {@link #charsetOrDefault(Charset) charsetOrDefault(StandardCharsets.UTF_8)}. */ + /** + * Equivalent to calling {@link #charsetOrDefault(Charset) + * charsetOrDefault(StandardCharsets.UTF_8)}. + */ public Charset charsetOrUtf8() { return charsetOrDefault(UTF_8); } /** - * Return {@code true} if this media type is {@code *}{@code /*} or if it has a wildcard subtype. + * Returns {@code true} if this media type is {@code *}{@code /*} or if it has a wildcard subtype. */ public boolean hasWildcard() { return type.equals(WILDCARD) || subtype.equals(WILDCARD); @@ -216,24 +218,27 @@ public boolean hasWildcard() { * former's parameters is a subset of the latter's and either the former is a {@link * #hasWildcard() wildcard type} that includes the latter or both have equal concrete type and * subtype. - * - * @param other the other media type */ public boolean includes(MediaType other) { - return includesType(other.type, other.subtype) + return includes(other.type, other.subtype) && other.parameters.entrySet().containsAll(parameters.entrySet()); } - private boolean includesType(String otherType, String otherSubtype) { - return type.equals(WILDCARD) - || (type.equals(otherType) && (subtype.equals(WILDCARD) || subtype.equals(otherSubtype))); + private boolean includes(String otherType, String otherSubtype) { + return type.equals(WILDCARD) || (type.equals(otherType) && includesSubtype(otherSubtype)); + } + + private boolean includesSubtype(String otherSubtype) { + int structuredSuffixIndex; + return subtype.equals(WILDCARD) + || subtype.equals(otherSubtype) + || ((structuredSuffixIndex = otherSubtype.lastIndexOf('+')) != -1 + && otherSubtype.regionMatches(structuredSuffixIndex + 1, subtype, 0, subtype.length())); } /** * Returns whether this media type is compatible with the given one. Two media types are * compatible if either of them {@link #includes(MediaType) includes} the other. - * - * @param other the other media type */ public boolean isCompatibleWith(MediaType other) { return this.includes(other) || other.includes(this); @@ -242,8 +247,6 @@ public boolean isCompatibleWith(MediaType other) { /** * Returns a new {@code MediaType} with this instance's type, subtype and parameters but with the * name of the given charset as the value of the charset parameter. - * - * @param charset the new type's charset */ public MediaType withCharset(Charset charset) { requireNonNull(charset); @@ -257,8 +260,6 @@ public MediaType withCharset(Charset charset) { * Returns a new {@code MediaType} with this instance's type, subtype and parameters but with the * value of the parameter specified by the given name set to the given value. * - * @param name the parameter's name - * @param value the parameter's value * @throws IllegalArgumentException if the given name or value is invalid */ public MediaType withParameter(String name, String value) { @@ -269,7 +270,6 @@ public MediaType withParameter(String name, String value) { * Returns a new {@code MediaType} with this instance's type, subtype and parameters but with each * of the given parameters' names set to their corresponding values. * - * @param parameters the parameters to add or replace * @throws IllegalArgumentException if any of the given parameters is invalid */ public MediaType withParameters(Map parameters) { @@ -278,7 +278,7 @@ public MediaType withParameters(Map parameters) { /** * Tests the given object for equality with this instance. {@code true} is returned if the given - * object is a {@code MediaType} and both instances's type, subtype and parameters are equal. + * object is a {@code MediaType} and both instances' type, subtype and parameters are equal. * * @param obj the object to test for equality */ @@ -328,8 +328,6 @@ private String computeToString() { /** * Returns a new {@code MediaType} with the given type and subtype. * - * @param type the general type - * @param subtype the subtype * @throws IllegalArgumentException if the given type or subtype is invalid */ public static MediaType of(String type, String subtype) { @@ -339,9 +337,6 @@ public static MediaType of(String type, String subtype) { /** * Returns a new {@code MediaType} with the given type, subtype and parameters. * - * @param type the general type - * @param subtype the subtype - * @param parameters the parameters * @throws IllegalArgumentException if the given type, subtype or any of the given parameters is * invalid */ @@ -387,7 +382,6 @@ private static String validateAndNormalizeToken(String token) { /** * Parses the given string into a {@code MediaType} instance. * - * @param value the media type string * @throws IllegalArgumentException if the given string is an invalid media type */ public static MediaType parse(String value) { diff --git a/methanol/src/test/java/com/github/mizosoft/methanol/MediaTypeTest.java b/methanol/src/test/java/com/github/mizosoft/methanol/MediaTypeTest.java index a01ad85a0..a29f7bc57 100644 --- a/methanol/src/test/java/com/github/mizosoft/methanol/MediaTypeTest.java +++ b/methanol/src/test/java/com/github/mizosoft/methanol/MediaTypeTest.java @@ -263,9 +263,9 @@ void parseEmptyQuotedValue() { void parseQuotedValueWithQuotedPairs() { var escapedValue = "\\\"\\\\\\a"; // \"\\\a unescaped to -> "\a var unescapedValue = "\"\\a"; // "\a - var reespacedValue = "\\\"\\\\a"; // \"\\a (only backslash and quotes are re-quoted) + var reEscapedValue = "\\\"\\\\a"; // \"\\a (only backslash and quotes are re-quoted) assertThat(MediaType.parse("text/plain; a=\"" + escapedValue + "\"")) - .hasToString("text/plain; a=\"" + reespacedValue + "\"") + .hasToString("text/plain; a=\"" + reEscapedValue + "\"") .extracting(MediaType::parameters, MAP) .containsExactly(entry("a", unescapedValue)); } @@ -360,6 +360,27 @@ void inclusion() { assertThat(MediaType.parse("text/plain").includes(MediaType.parse("text/*"))).isFalse(); } + @Test + void inclusionWithStructuredSyntaxSuffix() { + assertInclusion(MediaType.parse("application/json"), MediaType.parse("application/x+json")); + assertInclusion( + MediaType.parse("application/json"), MediaType.parse("application/vnd.me+json")); + assertThat( + MediaType.parse("application/vnd.me+json") + .includes(MediaType.parse("application/json"))) + .isFalse(); + assertThat( + MediaType.parse("application/json").includes(MediaType.parse("application/vnd.me+xml"))) + .isFalse(); + assertThat( + MediaType.parse("application/vnd.me+xml").includes(MediaType.parse("application/json"))) + .isFalse(); + assertThat(MediaType.parse("application/json").includes(MediaType.parse("application/+"))) + .isFalse(); + assertThat(MediaType.parse("application/+").includes(MediaType.parse("application/json"))) + .isFalse(); + } + @Test void equalsAndHashcode() { assertThat(MediaType.parse("text/plain; charset=utf-8; a=pikachu")) @@ -379,7 +400,7 @@ void equalsAndHashcode() { Map.of( "a", "pikachu", "charset", "utf-8"))); - assertThat(MediaType.parse("text/plain").equals("text/plain")).isFalse(); + assertThat(MediaType.parse("text/plain")).isNotEqualTo("text/plain"); } // Exceptional behaviour