Skip to content

Commit 9990349

Browse files
committed
Relocate MediaType to jupiter.api package and deprecate existing class
org.junit.jupiter.api.extension.MediaType currently causes cycles between the `api` and `api.extension` packages in junit-jupiter-api. Since the junit-jupiter-api artifact constitutes the public API for JUnit Jupiter, we have traditionally not allowed package cycles within that artifact. Thus, in order to reduce package cycles and improve the quality of the code base, this commit introduces a new MediaType in the org.junit.jupiter.api package. Note that the existing MediaType now extends the new MediaType. To encourage users to migrate to the new supported MediaType this commit also introduces the following deprecations. * `org.junit.jupiter.api.extension.MediaType` is now deprecated in favor of the new `org.junit.jupiter.api.MediaType`. * The `publishFile(...)` methods in `TestReporter` which accept an `org.junit.jupiter.api.extension.MediaType` are now deprecated in favor of new variants which accept an `org.junit.jupiter.api.MediaType`. * The `publishFile(...)` method in `ExtensionContext` which accepts an `org.junit.jupiter.api.extension.MediaType` is now deprecated in favor of a new variant which accepts an `org.junit.jupiter.api.MediaType`. Furthermore, we plan to remove the deprecated MediaType in JUnit 6.2. See #4876 Closes #4886
1 parent 060198c commit 9990349

File tree

20 files changed

+490
-80
lines changed

20 files changed

+490
-80
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.0.0-RC3.adoc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ JUnit repository on GitHub.
5757
* `MediaType.APPLICATION_JSON_UTF_8` is now deprecated in favor of using
5858
`MediaType.APPLICATION_JSON`, since the industry considers UTF-8 to be the implicit
5959
default encoding for the `application/json` media type.
60+
* `org.junit.jupiter.api.extension.MediaType` is now deprecated in favor of the new
61+
`org.junit.jupiter.api.MediaType`.
62+
* The `publishFile(...)` methods in `TestReporter` which accept an
63+
`org.junit.jupiter.api.extension.MediaType` are now deprecated in favor of new variants
64+
which accept an `org.junit.jupiter.api.MediaType`.
65+
* The `publishFile(...)` method in `ExtensionContext` which accepts an
66+
`org.junit.jupiter.api.extension.MediaType` is now deprecated in favor of a new variant
67+
which accepts an `org.junit.jupiter.api.MediaType`.
6068
* `org.junit.jupiter.params.support.ParameterInfo` is now deprecated in favor of the new
6169
`org.junit.jupiter.params.ParameterInfo`.
6270

documentation/src/test/java/example/TestReporterDemo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
import java.util.List;
1717
import java.util.Map;
1818

19+
import org.junit.jupiter.api.MediaType;
1920
import org.junit.jupiter.api.Test;
2021
import org.junit.jupiter.api.TestReporter;
21-
import org.junit.jupiter.api.extension.MediaType;
2222
import org.junit.jupiter.api.io.TempDir;
2323
import org.junit.jupiter.api.parallel.Execution;
2424
import org.junit.jupiter.api.parallel.ExecutionMode;

gradle/config/japicmp/accepted-breaking-changes.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
org.junit.jupiter.api.AssertionsKt
2+
org.junit.jupiter.api.extension.ExtensionContext#publishFile
23
org.junit.jupiter.api.extension.ExtensionContext#getConfigurationParameter
34
org.junit.jupiter.api.extension.ExtensionContext$Namespace
45
org.junit.jupiter.api.extension.ExtensionContext$Store#computeIfAbsent
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api;
12+
13+
import static java.nio.charset.StandardCharsets.UTF_8;
14+
import static org.apiguardian.api.API.Status.MAINTAINED;
15+
16+
import java.nio.charset.Charset;
17+
import java.nio.file.Path;
18+
import java.util.Objects;
19+
import java.util.regex.Matcher;
20+
import java.util.regex.Pattern;
21+
22+
import org.apiguardian.api.API;
23+
import org.jspecify.annotations.Nullable;
24+
import org.junit.platform.commons.util.Preconditions;
25+
26+
/**
27+
* Represents a media type as defined by
28+
* <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>.
29+
*
30+
* <p><strong>WARNING</strong>: This type should not be extended by third parties.
31+
*
32+
* @since 6.0
33+
* @see TestReporter#publishFile(Path, MediaType)
34+
* @see TestReporter#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer)
35+
* @see org.junit.jupiter.api.extension.ExtensionContext#publishFile(String, MediaType, org.junit.jupiter.api.function.ThrowingConsumer)
36+
*/
37+
@API(status = MAINTAINED, since = "6.0")
38+
public class MediaType {
39+
40+
private static final Pattern PATTERN;
41+
42+
static {
43+
// https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
44+
String whitespace = "[ \t]*";
45+
String token = "[0-9A-Za-z!#$%&'*+.^_`|~-]+";
46+
String quotedString = "\"(?:[^\"\\\\]|\\.)*\"";
47+
String parameter = ";" + whitespace + token + "=" + "(?:" + token + "|" + quotedString + ")";
48+
PATTERN = Pattern.compile(token + "/" + token + "(?:" + whitespace + parameter + ")*");
49+
}
50+
51+
/**
52+
* The {@code text/plain} media type.
53+
*/
54+
public static final MediaType TEXT_PLAIN = create("text", "plain");
55+
56+
/**
57+
* The {@code text/plain; charset=UTF-8} media type.
58+
*/
59+
public static final MediaType TEXT_PLAIN_UTF_8 = create("text", "plain", UTF_8);
60+
61+
/**
62+
* The {@code application/json} media type.
63+
*/
64+
public static final MediaType APPLICATION_JSON = create("application", "json");
65+
66+
/**
67+
* The {@code application/octet-stream} media type.
68+
*/
69+
public static final MediaType APPLICATION_OCTET_STREAM = create("application", "octet-stream");
70+
71+
/**
72+
* The {@code image/jpeg} media type.
73+
*/
74+
public static final MediaType IMAGE_JPEG = create("image", "jpeg");
75+
76+
/**
77+
* The {@code image/png} media type.
78+
*/
79+
public static final MediaType IMAGE_PNG = create("image", "png");
80+
81+
private final String value;
82+
83+
/**
84+
* Parse the given media type value.
85+
*
86+
* <p>Must be valid according to
87+
* <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>.
88+
*
89+
* @param value the media type value to parse; never {@code null} or blank
90+
* @return the parsed media type
91+
*/
92+
public static MediaType parse(String value) {
93+
return new MediaType(value);
94+
}
95+
96+
/**
97+
* Create a media type with the given type and subtype.
98+
*
99+
* @param type the type; never {@code null} or blank
100+
* @param subtype the subtype; never {@code null} or blank
101+
* @return the media type
102+
*/
103+
public static MediaType create(String type, String subtype) {
104+
return new MediaType(type, subtype, null);
105+
}
106+
107+
/**
108+
* Create a media type with the given type, subtype, and charset.
109+
*
110+
* @param type the type; never {@code null} or blank
111+
* @param subtype the subtype; never {@code null} or blank
112+
* @param charset the charset; never {@code null}
113+
* @return the media type
114+
*/
115+
public static MediaType create(String type, String subtype, Charset charset) {
116+
Preconditions.notNull(charset, "charset must not be null");
117+
return new MediaType(type, subtype, charset);
118+
}
119+
120+
protected MediaType(String type, String subtype, @Nullable Charset charset) {
121+
this("%s/%s%s".formatted(//
122+
Preconditions.notBlank(type, "type must not be null or blank").strip(),
123+
Preconditions.notBlank(subtype, "subtype must not be null or blank").strip(),
124+
(charset != null ? ("; charset=" + charset.name()) : "")));
125+
}
126+
127+
protected MediaType(String value) {
128+
// Mimic sealed types, permitting only this class and api.extension.MediaType.
129+
if (getClass() != MediaType.class
130+
&& !getClass().getName().equals("org.junit.jupiter.api.extension.MediaType")) {
131+
throw new IllegalStateException(
132+
"Type '%s' is not permitted to extend MediaType".formatted(getClass().getName()));
133+
}
134+
135+
String strippedValue = Preconditions.notBlank(value, "value must not be null or blank").strip();
136+
Matcher matcher = PATTERN.matcher(strippedValue);
137+
Preconditions.condition(matcher.matches(), () -> "Invalid media type: '" + strippedValue + "'");
138+
this.value = strippedValue;
139+
}
140+
141+
/**
142+
* {@return a string representation of this media type}
143+
*/
144+
@Override
145+
public final String toString() {
146+
return this.value;
147+
}
148+
149+
@Override
150+
public final boolean equals(Object obj) {
151+
return this == obj || (obj instanceof MediaType that && this.value.equals(that.value));
152+
}
153+
154+
@Override
155+
public final int hashCode() {
156+
return Objects.hashCode(this.value);
157+
}
158+
159+
}

junit-jupiter-api/src/main/java/org/junit/jupiter/api/TestReporter.java

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
package org.junit.jupiter.api;
1212

1313
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
14+
import static org.apiguardian.api.API.Status.DEPRECATED;
1415
import static org.apiguardian.api.API.Status.MAINTAINED;
1516
import static org.apiguardian.api.API.Status.STABLE;
1617

@@ -22,7 +23,6 @@
2223
import java.util.stream.Stream;
2324

2425
import org.apiguardian.api.API;
25-
import org.junit.jupiter.api.extension.MediaType;
2626
import org.junit.jupiter.api.function.ThrowingConsumer;
2727
import org.junit.platform.commons.util.Preconditions;
2828

@@ -96,10 +96,31 @@ default void publishEntry(String value) {
9696
*
9797
* @param file the file to be published; never {@code null}
9898
* @param mediaType the media type of the file; never {@code null}; use
99-
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
99+
* {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM}
100+
* if unknown
100101
* @since 5.12
102+
* @deprecated Use {@link #publishFile(Path, MediaType)} instead.
101103
*/
102-
@API(status = MAINTAINED, since = "5.13.3")
104+
@Deprecated(since = "6.0", forRemoval = true)
105+
@API(status = DEPRECATED, since = "6.0")
106+
@SuppressWarnings("removal")
107+
default void publishFile(Path file, org.junit.jupiter.api.extension.MediaType mediaType) {
108+
Preconditions.notNull(mediaType, "mediaType must not be null");
109+
publishFile(file, MediaType.parse(mediaType.toString()));
110+
}
111+
112+
/**
113+
* Publish the supplied file and attach it to the current test or container.
114+
*
115+
* <p>The file will be copied to the report output directory replacing any
116+
* potentially existing file with the same name.
117+
*
118+
* @param file the file to be published; never {@code null}
119+
* @param mediaType the media type of the file; never {@code null}; use
120+
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
121+
* @since 6.0
122+
*/
123+
@API(status = MAINTAINED, since = "6.0")
103124
default void publishFile(Path file, MediaType mediaType) {
104125
Preconditions.notNull(file, "file must not be null");
105126
Preconditions.notNull(mediaType, "mediaType must not be null");
@@ -153,11 +174,37 @@ default void publishDirectory(Path directory) {
153174
* @param name the name of the file to be published; never {@code null} or
154175
* blank and must not contain any path separators
155176
* @param mediaType the media type of the file; never {@code null}; use
156-
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
177+
* {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM}
178+
* if unknown
157179
* @param action the action to be executed to write the file; never {@code null}
158180
* @since 5.12
181+
* @deprecated Use {@link #publishFile(String, MediaType, ThrowingConsumer)} instead.
159182
*/
160-
@API(status = MAINTAINED, since = "5.13.3")
183+
@Deprecated(since = "6.0", forRemoval = true)
184+
@API(status = DEPRECATED, since = "6.0")
185+
@SuppressWarnings("removal")
186+
default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType,
187+
ThrowingConsumer<Path> action) {
188+
189+
Preconditions.notNull(mediaType, "mediaType must not be null");
190+
publishFile(name, MediaType.parse(mediaType.toString()), action);
191+
}
192+
193+
/**
194+
* Publish a file with the supplied name and media type written by the supplied
195+
* action and attach it to the current test or container.
196+
*
197+
* <p>The {@link Path} passed to the supplied action will be relative to the
198+
* report output directory, but it is up to the action to write the file.
199+
*
200+
* @param name the name of the file to be published; never {@code null} or
201+
* blank and must not contain any path separators
202+
* @param mediaType the media type of the file; never {@code null}; use
203+
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
204+
* @param action the action to be executed to write the file; never {@code null}
205+
* @since 6.0
206+
*/
207+
@API(status = MAINTAINED, since = "6.0")
161208
default void publishFile(String name, MediaType mediaType, ThrowingConsumer<Path> action) {
162209
throw new UnsupportedOperationException();
163210
}

junit-jupiter-api/src/main/java/org/junit/jupiter/api/extension/ExtensionContext.java

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import org.apiguardian.api.API;
3131
import org.jspecify.annotations.Nullable;
32+
import org.junit.jupiter.api.MediaType;
3233
import org.junit.jupiter.api.TestInstance.Lifecycle;
3334
import org.junit.jupiter.api.function.ThrowingConsumer;
3435
import org.junit.jupiter.api.parallel.ExecutionMode;
@@ -409,12 +410,41 @@ default void publishReportEntry(String value) {
409410
* @param name the name of the file to be published; never {@code null} or
410411
* blank and must not contain any path separators
411412
* @param mediaType the media type of the file; never {@code null}; use
412-
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
413+
* {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM}
414+
* if unknown
413415
* @param action the action to be executed to write the file; never {@code null}
414416
* @since 5.12
415417
* @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished
418+
* @deprecated Use
419+
* {@link #publishFile(String, MediaType, ThrowingConsumer)}
420+
* instead.
416421
*/
417-
@API(status = MAINTAINED, since = "5.13.3")
422+
@Deprecated(since = "6.0", forRemoval = true)
423+
@API(status = DEPRECATED, since = "6.0")
424+
@SuppressWarnings("removal")
425+
default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType,
426+
ThrowingConsumer<Path> action) {
427+
428+
Preconditions.notNull(mediaType, "mediaType must not be null");
429+
publishFile(name, MediaType.parse(mediaType.toString()), action);
430+
}
431+
432+
/**
433+
* Publish a file with the supplied name written by the supplied action and
434+
* attach it to the current test or container.
435+
*
436+
* <p>The file will be resolved in the report output directory prior to
437+
* invoking the supplied action.
438+
*
439+
* @param name the name of the file to be attached; never {@code null} or
440+
* blank and must not contain any path separators
441+
* @param mediaType the media type of the file; never {@code null}; use
442+
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
443+
* @param action the action to be executed to write the file; never {@code null}
444+
* @since 6.0
445+
* @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished
446+
*/
447+
@API(status = MAINTAINED, since = "6.0")
418448
void publishFile(String name, MediaType mediaType, ThrowingConsumer<Path> action);
419449

420450
/**

0 commit comments

Comments
 (0)