Skip to content
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

Copy com.intellij.util.lang.JavaVersion #2286

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,334 @@
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.util.lang;

import com.intellij.ReviseWhenPortedToJDK;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* <b>NB: copied from intellij to workaround KT-73967.</b>
*
* <p>Can be removed as soon as Kotlin depends on intellij of at least 243.23654.3.</p>
*
*
*
* <p>A class representing a version of some Java platform - e.g. the runtime the class is loaded into, or some installed JRE.</p>
*
* <p>Based on <a href="http://openjdk.org/jeps/322">JEP 322 "Time-Based Release Versioning"</a> (Java 10+), but also supports JEP 223
* "New Version-String Scheme" (Java 9), as well as earlier version's formats.</p>
*
* <p>See {@link #parse(String)} for examples of supported version strings.</p>
*
* @implNote the class is used in bootstrap - please use only JDK API
*/
@SuppressWarnings("DuplicatedCode")
public final class JavaVersion implements Comparable<JavaVersion> {
/**
* The major version.
* Corresponds to the first number of the 9+ format (<b>9</b>.0.1) / the second number of the 1.x format (1.<b>8</b>.0_60).
*/
public final int feature;

/**
* The minor version.
* Corresponds to the second number of the 9+ format (9.<b>0</b>.1) / the third number of 1.x the format (1.8.<b>0</b>_60).
* Was used in version strings prior to 1.5, in newer strings is always {@code 0}.
*/
public final int minor;

/**
* The patch version.
* Corresponds to the third number of the 9+ format (9.0.<b>1</b>) / the number after an underscore of the 1.x format (1.8.0_<b>60</b>).
*/
public final int update;

/**
* The build number.
* Corresponds to a number prefixed by the "plus" sign in the 9+ format (9.0.1+<b>7</b>) /
* by "-b" string in the 1.x format (1.8.0_60-b<b>12</b>).
*/
public final int build;

/**
* {@code true} if the platform is an early access release, {@code false} otherwise (or when not known).
*/
public final boolean ea;

private JavaVersion(int feature, int minor, int update, int build, boolean ea) {
this.feature = feature;
this.minor = minor;
this.update = update;
this.build = build;
this.ea = ea;
}

@Override
public int compareTo(@NotNull JavaVersion o) {
int diff = feature - o.feature;
if (diff != 0) return diff;
diff = minor - o.minor;
if (diff != 0) return diff;
diff = update - o.update;
if (diff != 0) return diff;
diff = build - o.build;
if (diff != 0) return diff;
return (ea ? 0 : 1) - (o.ea ? 0 : 1);
}

public boolean isAtLeast(int feature) {
return this.feature >= feature;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof JavaVersion)) return false;
JavaVersion other = (JavaVersion)o;
return feature == other.feature &&
minor == other.minor &&
update == other.update &&
build == other.build &&
ea == other.ea;
}

@Override
public int hashCode() {
int hash = feature;
hash = 31 * hash + minor;
hash = 31 * hash + update;
hash = 31 * hash + build;
hash = 31 * hash + (ea ? 1231 : 1237);
return hash;
}

/**
* @return feature version string, e.g. <b>1.8</b> or <b>11</b>
*/
public @NotNull String toFeatureString() {
return formatVersionTo(true, true);
}

/**
* @return feature, minor and update components of the version string, e.g.
* <b>1.8.0_242</b> or <b>11.0.5</b>
*/
public @NotNull String toFeatureMinorUpdateString() {
return formatVersionTo(false, true);
}

@Override
public String toString() {
return formatVersionTo(false, false);
}

private String formatVersionTo(boolean upToFeature, boolean upToUpdate) {
StringBuilder sb = new StringBuilder();
if (feature > 8) {
sb.append(feature);
if (!upToFeature) {
if (minor > 0 || update > 0) sb.append('.').append(minor);
if (update > 0) sb.append('.').append(update);
if (!upToUpdate) {
if (ea) sb.append("-ea");
if (build > 0) sb.append('+').append(build);
}
}
}
else {
sb.append("1.").append(feature);
if (!upToFeature) {
if (minor > 0 || update > 0 || ea || build > 0) sb.append('.').append(minor);
if (update > 0) sb.append('_').append(update);
if (!upToUpdate) {
if (ea) sb.append("-ea");
if (build > 0) sb.append("-b").append(build);
}
}
}
return sb.toString();
}

/**
* Composes a version object out of given parameters.
*
* @throws IllegalArgumentException when any of the numbers is negative
*/
public static @NotNull JavaVersion compose(int feature, int minor, int update, int build, boolean ea) throws IllegalArgumentException {
if (feature < 0) throw new IllegalArgumentException();
if (minor < 0) throw new IllegalArgumentException();
if (update < 0) throw new IllegalArgumentException();
if (build < 0) throw new IllegalArgumentException();
return new JavaVersion(feature, minor, update, build, ea);
}

public static @NotNull JavaVersion compose(int feature) {
return compose(feature, 0, 0, 0, false);
}

private static JavaVersion current;

/**
* Returns the version of a Java runtime the class is loaded into.
* The method attempts to parse {@code "java.runtime.version"} system property first (usually, it is more complete),
* and falls back to {@code "java.version"} if the former is invalid or differs in {@link #feature} or {@link #minor} numbers.
*/
public static @NotNull JavaVersion current() {
if (current == null) {
JavaVersion fallback = parse(System.getProperty("java.version"));
JavaVersion rt = rtVersion();
if (rt == null) {
try { rt = parse(System.getProperty("java.runtime.version")); }
catch (Throwable ignored) { }
}
current = rt != null && rt.feature == fallback.feature && rt.minor == fallback.minor ? rt : fallback;
}
return current;
}

/**
* Attempts to use Runtime.version() method available since Java 9.
*/
@ReviseWhenPortedToJDK("9")
private static @Nullable JavaVersion rtVersion() {
try {
Object version = Runtime.class.getMethod("version").invoke(null);
int major = (Integer)version.getClass().getMethod("major").invoke(version);
int minor = (Integer)version.getClass().getMethod("minor").invoke(version);
int security = (Integer)version.getClass().getMethod("security").invoke(version);
Object buildOpt = version.getClass().getMethod("build").invoke(version);
int build = (Integer)buildOpt.getClass().getMethod("orElse", Object.class).invoke(buildOpt, Integer.valueOf(0));
Object preOpt = version.getClass().getMethod("pre").invoke(version);
boolean ea = (Boolean)preOpt.getClass().getMethod("isPresent").invoke(preOpt);
return new JavaVersion(major, minor, security, build, ea);
}
catch (Throwable ignored) {
return null;
}
}

private static final int MAX_ACCEPTED_VERSION = 50; // sanity check

/**
* <p>Parses a Java version string.</p>
*
* <p>Supports various sources, including (but not limited to):<br>
* - {@code "java.*version"} system properties (a version number without any decoration)<br>
* - values of Java compiler -source/-target/--release options ("$MAJOR", "1.$MAJOR")<br>
* - output of "{@code java -version}" (usually "java version \"$VERSION\"")<br>
* - a second line of the above command (something like to "Java(TM) SE Runtime Environment (build $VERSION)")<br>
* - output of "{@code java --full-version}" ("java $VERSION")<br>
* - a line of "release" file ("JAVA_VERSION=\"$VERSION\"")</p>
*
* <p>See com.intellij.util.lang.JavaVersionTest for examples.</p>
*
* @throws IllegalArgumentException if failed to recognize the number.
*/
public static @NotNull JavaVersion parse(@NotNull String versionString) throws IllegalArgumentException {
// trimming
String str = versionString.trim();
Map<String, String> trimmingMap = new HashMap<>(); // "substring to detect" to "substring from which to trim"
trimmingMap.put("Runtime Environment", "(build ");
trimmingMap.put("OpenJ9", "version ");
trimmingMap.put("GraalVM", "Java ");
for (String keyToDetect : trimmingMap.keySet()) {
if (str.contains(keyToDetect)) {
int p = str.indexOf(trimmingMap.get(keyToDetect));
if (p > 0) str = str.substring(p);
}
}

// partitioning
List<String> numbers = new ArrayList<>(), separators = new ArrayList<>();
int length = str.length(), p = 0;
boolean number = false;
while (p < length) {
int start = p;
while (p < length && Character.isDigit(str.charAt(p)) == number) p++;
String part = str.substring(start, p);
(number ? numbers : separators).add(part);
number = !number;
}

// parsing
if (!numbers.isEmpty() && !separators.isEmpty()) {
try {
int feature = Integer.parseInt(numbers.get(0)), minor = 0, update = 0, build = 0;
boolean ea = false;

if (feature >= 5 && feature < MAX_ACCEPTED_VERSION) {
// Java 9+; Java 5+ (short format)
p = 1;
while (p < separators.size() && ".".equals(separators.get(p))) p++;
if (p > 1 && numbers.size() > 2) {
minor = Integer.parseInt(numbers.get(1));
update = Integer.parseInt(numbers.get(2));
}
if (p < separators.size()) {
String s = separators.get(p);
if (s != null && !s.isEmpty() && s.charAt(0) == '-') {
ea = startsWithWord(s, "-ea") || startsWithWord(s, "-internal");
if (p < numbers.size() && s.charAt(s.length() - 1) == '+') {
build = Integer.parseInt(numbers.get(p));
}
p++;
}
if (build == 0 && p < separators.size() && p < numbers.size() && "+".equals(separators.get(p))) {
build = Integer.parseInt(numbers.get(p));
}
}
return new JavaVersion(feature, minor, update, build, ea);
}
else if (feature == 1 && numbers.size() > 1 && separators.size() > 1 && ".".equals(separators.get(1))) {
// Java 1.0 .. 1.4; Java 5+ (prefixed format)
feature = Integer.parseInt(numbers.get(1));
if (feature <= MAX_ACCEPTED_VERSION) {
if (numbers.size() > 2 && separators.size() > 2 && ".".equals(separators.get(2))) {
minor = Integer.parseInt(numbers.get(2));
if (numbers.size() > 3 && separators.size() > 3 && "_".equals(separators.get(3))) {
update = Integer.parseInt(numbers.get(3));
if (separators.size() > 4) {
String s = separators.get(4);
if (s != null && !s.isEmpty() && s.charAt(0) == '-') {
ea = startsWithWord(s, "-ea") || startsWithWord(s, "-internal");
}
p = 4;
while (p < separators.size() && !separators.get(p).endsWith("-b")) p++;
if (p < numbers.size()) {
build = Integer.parseInt(numbers.get(p));
}
}
}
}
return new JavaVersion(feature, minor, update, build, ea);
}
}
}
catch (NumberFormatException ignored) { }
}

throw new IllegalArgumentException(versionString);
}

private static boolean startsWithWord(String s, String word) {
return s.startsWith(word) && (s.length() == word.length() || !Character.isLetterOrDigit(s.charAt(word.length())));
}

/**
* A safe version of {@link #parse(String)} - returns {@code null} when unable to parse a version string.
*/
public static @Nullable JavaVersion tryParse(String versionString) {
if (versionString != null) {
try {
return parse(versionString);
}
catch (IllegalArgumentException ignored) { }
}

return null;
}
}
Loading