Skip to content

Commit

Permalink
Copy com.intellij.util.lang.JavaVersion
Browse files Browse the repository at this point in the history
from upstream Kotlin repo. See KT-73967 for details.
  https://youtrack.jetbrains.com/issue/KT-73967

Verified by examining the artifacts locally.
  • Loading branch information
ting-yuan committed Jan 3, 2025
1 parent 5392d86 commit 4aa67c3
Showing 1 changed file with 334 additions and 0 deletions.
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;
}
}

0 comments on commit 4aa67c3

Please sign in to comment.