Skip to content

Commit e9d4fe4

Browse files
committed
Relocate MediaType to jupiter.api package and deprecate existing class
Work In Progress See #4886
1 parent ba56322 commit e9d4fe4

File tree

17 files changed

+411
-82
lines changed

17 files changed

+411
-82
lines changed

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

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

Lines changed: 51 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,32 @@ 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.condition(Files.exists(file), () -> "file must exist: " + file);
109+
Preconditions.condition(Files.isRegularFile(file), () -> "file must be a regular file: " + file);
110+
publishFile(file, MediaType.parse(mediaType.toString()));
111+
}
112+
113+
/**
114+
* Publish the supplied file and attach it to the current test or container.
115+
*
116+
* <p>The file will be copied to the report output directory replacing any
117+
* potentially existing file with the same name.
118+
*
119+
* @param file the file to be published; never {@code null} or blank
120+
* @param mediaType the media type of the file; never {@code null}; use
121+
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
122+
* @since 6.0
123+
*/
124+
@API(status = MAINTAINED, since = "6.0")
103125
default void publishFile(Path file, MediaType mediaType) {
104126
Preconditions.notNull(file, "file must not be null");
105127
Preconditions.notNull(mediaType, "mediaType must not be null");
@@ -153,11 +175,35 @@ default void publishDirectory(Path directory) {
153175
* @param name the name of the file to be published; never {@code null} or
154176
* blank and must not contain any path separators
155177
* @param mediaType the media type of the file; never {@code null}; use
156-
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
178+
* {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM}
179+
* if unknown
157180
* @param action the action to be executed to write the file; never {@code null}
158181
* @since 5.12
182+
* @deprecated Use {@link #publishFile(String, MediaType, ThrowingConsumer)} instead.
159183
*/
160-
@API(status = MAINTAINED, since = "5.13.3")
184+
@Deprecated(since = "6.0", forRemoval = true)
185+
@API(status = DEPRECATED, since = "6.0")
186+
@SuppressWarnings("removal")
187+
default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType,
188+
ThrowingConsumer<Path> action) {
189+
publishFile(name, MediaType.parse(mediaType.toString()), action);
190+
}
191+
192+
/**
193+
* Publish a file with the supplied name and media type written by the supplied
194+
* action and attach it to the current test or container.
195+
*
196+
* <p>The {@link Path} passed to the supplied action will be relative to the
197+
* report output directory, but it is up to the action to write the file.
198+
*
199+
* @param name the name of the file to be published; never {@code null} or
200+
* blank and must not contain any path separators
201+
* @param mediaType the media type of the file; never {@code null}; use
202+
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
203+
* @param action the action to be executed to write the file; never {@code null}
204+
* @since 6.0
205+
*/
206+
@API(status = MAINTAINED, since = "6.0")
161207
default void publishFile(String name, MediaType mediaType, ThrowingConsumer<Path> action) {
162208
throw new UnsupportedOperationException();
163209
}

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

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -409,13 +409,42 @@ default void publishReportEntry(String value) {
409409
* @param name the name of the file to be published; never {@code null} or
410410
* blank and must not contain any path separators
411411
* @param mediaType the media type of the file; never {@code null}; use
412-
* {@link MediaType#APPLICATION_OCTET_STREAM} if unknown
412+
* {@link org.junit.jupiter.api.extension.MediaType#APPLICATION_OCTET_STREAM}
413+
* if unknown
413414
* @param action the action to be executed to write the file; never {@code null}
414415
* @since 5.12
415416
* @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished
417+
* @deprecated Use
418+
* {@link #publishFile(String, org.junit.jupiter.api.MediaType, ThrowingConsumer)}
419+
* instead.
416420
*/
417-
@API(status = MAINTAINED, since = "5.13.3")
418-
void publishFile(String name, MediaType mediaType, ThrowingConsumer<Path> action);
421+
@Deprecated(since = "6.0", forRemoval = true)
422+
@API(status = DEPRECATED, since = "6.0")
423+
@SuppressWarnings("removal")
424+
default void publishFile(String name, org.junit.jupiter.api.extension.MediaType mediaType,
425+
ThrowingConsumer<Path> action) {
426+
427+
Preconditions.notNull(mediaType, "mediaType must not be null");
428+
publishFile(name, org.junit.jupiter.api.MediaType.parse(mediaType.toString()), action);
429+
}
430+
431+
/**
432+
* Publish a file with the supplied name written by the supplied action and
433+
* attach it to the current test or container.
434+
*
435+
* <p>The file will be resolved in the report output directory prior to
436+
* invoking the supplied action.
437+
*
438+
* @param name the name of the file to be attached; never {@code null} or
439+
* blank and must not contain any path separators
440+
* @param mediaType the media type of the file; never {@code null}; use
441+
* {@link org.junit.jupiter.api.MediaType#APPLICATION_OCTET_STREAM} if unknown
442+
* @param action the action to be executed to write the file; never {@code null}
443+
* @since 6.0
444+
* @see org.junit.platform.engine.EngineExecutionListener#fileEntryPublished
445+
*/
446+
@API(status = MAINTAINED, since = "6.0")
447+
void publishFile(String name, org.junit.jupiter.api.MediaType mediaType, ThrowingConsumer<Path> action);
419448

420449
/**
421450
* Publish a directory with the supplied name written by the supplied action

0 commit comments

Comments
 (0)