Skip to content

Support arbitrary Java versions with JRE conditions #3931

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

sbrannen
Copy link
Member

@sbrannen sbrannen commented Aug 20, 2024

Overview

This PR introduces support for arbitrary Java versions in the JRE enum, @EnabledOnJre, @DisabledOnJre, @EnabledForJreRange, and @DisabledForJreRange.

Closes: #3930

Related Issues


Definition of Done

@sbrannen

This comment was marked as outdated.

@sbrannen
Copy link
Member Author

As can be inferred from the demo tests in the documentation module, the following are supported with the current proof of concept.

  • @EnabledOnJre(featureVersions = 24)

  • @EnabledOnJre(featureVersions = {23, 24})

  • @EnabledOnJre(value = JAVA_21, featureVersions = {22, 23})

  • @EnabledForJreRange(minFeatureVersion = 25)

  • @EnabledForJreRange(minFeatureVersion = 20, maxFeatureVersion = 25)

  • @EnabledForJreRange(min = JAVA_21, maxFeatureVersion = 24)

Copy link

@wilkinsona wilkinsona left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extensibility of this looks good to me, @sbrannen. Thanks for looking at it.

@sbrannen
Copy link
Member Author

The extensibility of this looks good to me, @sbrannen.

Glad you like it, and thanks for reviewing it, @wilkinsona! 👍

@sbrannen sbrannen changed the title Support arbitrary Java feature numbers with JRE conditions Support arbitrary Java feature versions with JRE conditions Aug 20, 2024
@marcphilipp marcphilipp linked an issue Jan 27, 2025 that may be closed by this pull request
13 tasks
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch 3 times, most recently from fee1aa0 to 1f8cf87 Compare February 1, 2025 12:45
@sbrannen sbrannen changed the title Support arbitrary Java feature versions with JRE conditions Support arbitrary Java versions with JRE conditions Feb 1, 2025
@sbrannen sbrannen changed the title Support arbitrary Java versions with JRE conditions Support arbitrary Java versions with JRE conditions Feb 1, 2025
@sbrannen sbrannen marked this pull request as ready for review February 1, 2025 12:56
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from 1f8cf87 to c386d4c Compare February 1, 2025 13:20
@marcphilipp marcphilipp self-requested a review February 1, 2025 17:20
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch 2 times, most recently from a6b9b54 to 9b3c212 Compare February 2, 2025 15:58
@sbrannen
Copy link
Member Author

sbrannen commented Feb 2, 2025

With the latest force pushed changes for this PR, the generated JRE source now looks like this.

/*
 * Copyright 2015-2025 the original author or authors.
 *
 * All rights reserved. This program and the accompanying materials are
 * made available under the terms of the Eclipse Public License v2.0 which
 * accompanies this distribution and is available at
 *
 * https://www.eclipse.org/legal/epl-v20.html
 */

package org.junit.jupiter.api.condition;

import static org.apiguardian.api.API.Status.DEPRECATED;
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
import static org.apiguardian.api.API.Status.STABLE;

import java.lang.reflect.Method;

import org.apiguardian.api.API;
import org.junit.platform.commons.logging.Logger;
import org.junit.platform.commons.logging.LoggerFactory;
import org.junit.platform.commons.support.ReflectionSupport;
import org.junit.platform.commons.util.StringUtils;

/**
 * Enumeration of Java Runtime Environment (JRE) versions.
 *
 * <p>If the current JRE version cannot be detected &mdash; for example, if the
 * {@code java.version} JVM system property is undefined &mdash; then none of
 * the constants defined in this enum will be considered to be the
 * {@linkplain #isCurrentVersion current JRE version}.
 *
 * @since 5.1
 * @see #JAVA_8
 * @see #JAVA_9
 * @see #JAVA_10
 * @see #JAVA_11
 * @see #JAVA_12
 * @see #JAVA_13
 * @see #JAVA_14
 * @see #JAVA_15
 * @see #JAVA_16
 * @see #JAVA_17
 * @see #JAVA_18
 * @see #JAVA_19
 * @see #JAVA_20
 * @see #JAVA_21
 * @see #JAVA_22
 * @see #JAVA_23
 * @see #JAVA_24
 * @see #JAVA_25
 * @see #OTHER
 * @see EnabledOnJre
 * @see DisabledOnJre
 * @see EnabledForJreRange
 * @see DisabledForJreRange
 */
@API(status = STABLE, since = "5.1")
public enum JRE {

	/**
	 * An undefined JRE version.
	 *
	 * <p>This constant is used by JUnit as a default configuration value but is
	 * not intended to be used by users.
	 *
	 * <p>This constant returns {@code -1} for its {@link #version() version}.
	 *
	 * @since 5.12
	 */
	@API(status = EXPERIMENTAL, since = "5.12")
	UNDEFINED(-1),

	/**
	 * Java 8.
	 */
	JAVA_8(8),

	/**
	 * Java 9.
	 */
	JAVA_9(9),

	/**
	 * Java 10.
	 */
	JAVA_10(10),

	/**
	 * Java 11.
	 */
	JAVA_11(11),

	/**
	 * Java 12.
	 *
	 * @since 5.4
	 */
	@API(status = STABLE, since = "5.4")
	JAVA_12(12),

	/**
	 * Java 13.
	 *
	 * @since 5.4
	 */
	@API(status = STABLE, since = "5.4")
	JAVA_13(13),

	/**
	 * Java 14.
	 *
	 * @since 5.5
	 */
	@API(status = STABLE, since = "5.5")
	JAVA_14(14),

	/**
	 * Java 15.
	 *
	 * @since 5.6
	 */
	@API(status = STABLE, since = "5.6")
	JAVA_15(15),

	/**
	 * Java 16.
	 *
	 * @since 5.7
	 */
	@API(status = STABLE, since = "5.7")
	JAVA_16(16),

	/**
	 * Java 17.
	 *
	 * @since 5.7.1
	 */
	@API(status = STABLE, since = "5.7.1")
	JAVA_17(17),

	/**
	 * Java 18.
	 *
	 * @since 5.8.1
	 */
	@API(status = STABLE, since = "5.8.1")
	JAVA_18(18),

	/**
	 * Java 19.
	 *
	 * @since 5.9
	 */
	@API(status = STABLE, since = "5.9")
	JAVA_19(19),

	/**
	 * Java 20.
	 *
	 * @since 5.9
	 */
	@API(status = STABLE, since = "5.9")
	JAVA_20(20),

	/**
	 * Java 21.
	 *
	 * @since 5.9.2
	 */
	@API(status = STABLE, since = "5.9.2")
	JAVA_21(21),

	/**
	 * Java 22.
	 *
	 * @since 5.10
	 */
	@API(status = STABLE, since = "5.10")
	JAVA_22(22),

	/**
	 * Java 23.
	 *
	 * @since 5.11
	 */
	@API(status = STABLE, since = "5.11")
	JAVA_23(23),

	/**
	 * Java 24.
	 *
	 * @since 5.11
	 */
	@API(status = STABLE, since = "5.11")
	JAVA_24(24),

	/**
	 * Java 25.
	 *
	 * @since 5.11.4
	 */
	@API(status = STABLE, since = "5.11.4")
	JAVA_25(25),

	/**
	 * A JRE version other than {@link #JAVA_8}, {@link #JAVA_9},
	 * {@link #JAVA_10}, {@link #JAVA_11}, {@link #JAVA_12},
	 * {@link #JAVA_13}, {@link #JAVA_14}, {@link #JAVA_15},
	 * {@link #JAVA_16}, {@link #JAVA_17}, {@link #JAVA_18},
	 * {@link #JAVA_19}, {@link #JAVA_20}, {@link #JAVA_21},
	 * {@link #JAVA_22}, {@link #JAVA_23}, {@link #JAVA_24},
	 * or {@link #JAVA_25}.
	 *
	 * <p>This constant returns {@link Integer#MAX_VALUE} for its
	 * {@link #version() version}.
	 */
	OTHER(Integer.MAX_VALUE);

	static final int UNDEFINED_VERSION = -1;

	private static final Logger logger = LoggerFactory.getLogger(JRE.class);

	private static final int CURRENT_VERSION = determineCurrentVersion();

	private static final JRE CURRENT_JRE = determineCurrentJre(CURRENT_VERSION);

	private static int determineCurrentVersion() {
		String javaVersion = System.getProperty("java.version");
		boolean javaVersionIsBlank = StringUtils.isBlank(javaVersion);

		if (javaVersionIsBlank) {
			logger.debug(
				() -> "JVM system property 'java.version' is undefined. It is therefore not possible to detect Java 8.");
		}

		if (!javaVersionIsBlank && javaVersion.startsWith("1.8")) {
			return 8;
		}

		try {
			// java.lang.Runtime.version() is a static method available on Java 9+
			// that returns an instance of java.lang.Runtime.Version which has the
			// following method: public int major()
			Method versionMethod = Runtime.class.getMethod("version");
			Object version = ReflectionSupport.invokeMethod(versionMethod, null);
			Method majorMethod = version.getClass().getMethod("major");
			return (int) ReflectionSupport.invokeMethod(majorMethod, version);
		}
		catch (Exception ex) {
			logger.debug(ex, () -> "Failed to determine the current JRE version via java.lang.Runtime.Version.");
		}

		return UNDEFINED_VERSION;
	}

	private static JRE determineCurrentJre(int currentVersion) {
		switch (currentVersion) {
			case UNDEFINED_VERSION:
				// null signals that the current JRE version is undefined.
				return null;
			case 8:
				return JAVA_8;
			case 9:
				return JAVA_9;
			case 10:
				return JAVA_10;
			case 11:
				return JAVA_11;
			case 12:
				return JAVA_12;
			case 13:
				return JAVA_13;
			case 14:
				return JAVA_14;
			case 15:
				return JAVA_15;
			case 16:
				return JAVA_16;
			case 17:
				return JAVA_17;
			case 18:
				return JAVA_18;
			case 19:
				return JAVA_19;
			case 20:
				return JAVA_20;
			case 21:
				return JAVA_21;
			case 22:
				return JAVA_22;
			case 23:
				return JAVA_23;
			case 24:
				return JAVA_24;
			case 25:
				return JAVA_25;
			default:
				return OTHER;
		}
	}

	private final int version;

	private JRE(int version) {
		this.version = version;
	}

	/**
	 * Get the version of <em>this</em> {@code JRE}.
	 *
	 * <p>If this {@code JRE} is {@link #UNDEFINED}, this method returns
	 * {@code -1}. If this {@code JRE} is {@link #OTHER}, this method returns
	 * {@link Integer#MAX_VALUE}.
	 *
	 * @return the version of this {@code JRE}
	 * @since 5.12
	 * @see Runtime.Version#feature()
	 * @see #currentVersionNumber()
	 */
	@API(status = EXPERIMENTAL, since = "5.12")
	public int version() {
		return this.version;
	}

	/**
	 * @return {@code true} if <em>this</em> {@code JRE} is known to be the
	 * Java Runtime Environment version for the currently executing JVM or if
	 * the version is {@link #OTHER}
	 *
	 * @see #currentJre()
	 * @see #currentVersionNumber()
	 */
	public boolean isCurrentVersion() {
		return this == CURRENT_JRE;
	}

	/**
	 * @return the {@link JRE} for the currently executing JVM, potentially
	 * {@link #OTHER}
	 *
	 * @since 5.7
	 * @see #currentVersionNumber()
	 * @deprecated in favor of {@link #currentJre()}
	 */
	@API(status = DEPRECATED, since = "5.12")
	@Deprecated
	public static JRE currentVersion() {
		return currentJre();
	}

	/**
	 * @return the {@link JRE} for the currently executing JVM, potentially
	 * {@link #OTHER}
	 *
	 * @since 5.12
	 * @see #currentVersionNumber()
	 */
	@API(status = EXPERIMENTAL, since = "5.12")
	public static JRE currentJre() {
		return CURRENT_JRE;
	}

	/**
	 * @return the version number for the currently executing JVM, or {@code -1}
	 * to signal that the version is undefined
	 *
	 * @since 5.12
	 * @see Runtime.Version#feature()
	 * @see #currentJre()
	 */
	@API(status = EXPERIMENTAL, since = "5.12")
	public static int currentVersionNumber() {
		return CURRENT_VERSION;
	}

	/**
	 * @return {@code true} if the supplied version number is known to be the
	 * Java Runtime Environment version for the currently executing JVM or if
	 * the supplied version number is {@code -1} and the version of the current
	 * JVM is unknown
	 *
	 * @since 5.12
	 * @see Runtime.Version#feature()
	 */
	@API(status = EXPERIMENTAL, since = "5.12")
	public static boolean isCurrentVersion(int version) {
		return version == CURRENT_VERSION;
	}

	static boolean isCurrentVersionWithinRange(int min, int max) {
		return CURRENT_VERSION >= min && CURRENT_VERSION <= max;
	}

}

Note the introduction of the JRE.UNDEFINED enum constant as well as the departure from using the "feature" terminology for the current numerical Java version, favoring just "version" instead.

In addition, currentVersion() has been deprecated in favor of a new currentJre() utility method.

sbrannen added a commit to sbrannen/junit-framework that referenced this pull request Feb 2, 2025
This commit introduces support for arbitrary Java versions in the JRE
enum, @⁠EnabledOnJre, @⁠DisabledOnJre, @⁠EnabledForJreRange, and
@⁠DisabledForJreRange.

Closes: junit-team#3930
Closes: junit-team#3931
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from 9b3c212 to 4c4593f Compare February 2, 2025 17:53
sbrannen added a commit to sbrannen/junit-framework that referenced this pull request Feb 2, 2025
This commit introduces support for arbitrary Java versions in the JRE
enum, @⁠EnabledOnJre, @⁠DisabledOnJre, @⁠EnabledForJreRange, and
@⁠DisabledForJreRange.

Closes: junit-team#3930
Closes: junit-team#3931
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from 4c4593f to d197f5a Compare February 2, 2025 17:56
sbrannen added a commit to sbrannen/junit-framework that referenced this pull request Feb 3, 2025
This commit introduces support for arbitrary Java versions in the JRE
enum, @⁠EnabledOnJre, @⁠DisabledOnJre, @⁠EnabledForJreRange, and
@⁠DisabledForJreRange.

Closes: junit-team#3930
Closes: junit-team#3931
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from d197f5a to cc6a514 Compare February 3, 2025 08:13
This commit introduces support for arbitrary Java versions in the JRE
enum, @⁠EnabledOnJre, @⁠DisabledOnJre, @⁠EnabledForJreRange, and
@⁠DisabledForJreRange.

Closes: junit-team#3930
Closes: junit-team#3931
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from cc6a514 to 699af22 Compare February 3, 2025 08:44
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from 67f73f9 to d292998 Compare February 3, 2025 10:42
@sbrannen sbrannen force-pushed the issues/3930-arbitrary-java-feature-numbers branch from 5f40351 to fddfeee Compare February 3, 2025 12:43
sbrannen

This comment was marked as outdated.

@sbrannen sbrannen closed this in 2d9a10e Feb 3, 2025
sbrannen added a commit that referenced this pull request Feb 3, 2025
See: #3931

Co-authored-by: Marc Philipp <mail@marcphilipp.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support arbitrary Java versions with JRE conditions
3 participants