Skip to content
This repository was archived by the owner on May 12, 2024. It is now read-only.

Commit b57fe0a

Browse files
committed
Add Runtime.version() and Runtime.Version
1 parent ed4c3b2 commit b57fe0a

5 files changed

Lines changed: 402 additions & 26 deletions

File tree

RandomNextLongTest.java

Lines changed: 0 additions & 25 deletions
This file was deleted.
Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
/*
2+
* This file is part of JavaDowngrader - https://github.com/RaphiMC/JavaDowngrader
3+
* Copyright (C) 2023 RK_01/RaphiMC and contributors
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package net.raphimc.javadowngrader.runtime.java.lang;
19+
20+
import java.math.BigInteger;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Optional;
25+
import java.util.regex.Matcher;
26+
import java.util.regex.Pattern;
27+
import java.util.stream.Collectors;
28+
29+
public class Runtime {
30+
private static Version version;
31+
32+
public static Version version() {
33+
Version v = version;
34+
if (v == null) {
35+
final String strVersion = System.getProperty("java.runtime.version");
36+
try {
37+
v = Version.parse(strVersion);
38+
} catch (Exception e) {
39+
if (!strVersion.startsWith("1.8")) {
40+
throw e;
41+
}
42+
final int underscoreIndex = strVersion.indexOf('_');
43+
final String versionNumber = strVersion.substring(0, underscoreIndex);
44+
final int dashIndex = strVersion.indexOf('-', underscoreIndex + 1);
45+
final String build;
46+
final Optional<String> opt;
47+
if (dashIndex >= 0) {
48+
build = strVersion.substring(underscoreIndex + 1, dashIndex);
49+
opt = Optional.of(strVersion.substring(dashIndex + 1));
50+
} else {
51+
build = strVersion.substring(underscoreIndex + 1);
52+
opt = Optional.empty();
53+
}
54+
final String[] split = versionNumber.split("\\.");
55+
final Integer[] versionI = new Integer[split.length];
56+
for (int i = 0; i < split.length; i++) {
57+
versionI[i] = Integer.parseInt(split[i]);
58+
}
59+
int n = versionI.length;
60+
while (n > 1 && versionI[n - 1] == 0) n--;
61+
v = new Version(
62+
Collections.unmodifiableList(Arrays.asList(Arrays.copyOf(versionI, n))),
63+
Optional.empty(), Optional.of(Integer.parseInt(build)), opt
64+
);
65+
}
66+
version = v;
67+
}
68+
return v;
69+
}
70+
71+
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
72+
public static class Version implements Comparable<Version> {
73+
private final List<Integer> version;
74+
private final Optional<String> pre;
75+
private final Optional<Integer> build;
76+
private final Optional<String> optional;
77+
78+
private Version(List<Integer> unmodifiableListOfVersions, Optional<String> pre, Optional<Integer> build, Optional<String> optional) {
79+
this.version = unmodifiableListOfVersions;
80+
this.pre = pre;
81+
this.build = build;
82+
this.optional = optional;
83+
}
84+
85+
public static Version parse(String s) {
86+
if (s == null) {
87+
throw new NullPointerException();
88+
}
89+
90+
if (isSimpleNumber(s)) {
91+
return new Version(Collections.singletonList(Integer.parseInt(s)), Optional.empty(), Optional.empty(), Optional.empty());
92+
}
93+
final Matcher m = VersionPattern.VSTR_PATTERN.matcher(s);
94+
if (!m.matches()) {
95+
throw new IllegalArgumentException("Invalid version string: '" + s + "'");
96+
}
97+
98+
final String[] split = m.group(VersionPattern.VNUM_GROUP).split("\\.");
99+
final Integer[] version = new Integer[split.length];
100+
for (int i = 0; i < split.length; i++) {
101+
version[i] = Integer.parseInt(split[i]);
102+
}
103+
104+
final Optional<String> pre = Optional.ofNullable(m.group(VersionPattern.PRE_GROUP));
105+
106+
final String b = m.group(VersionPattern.BUILD_GROUP);
107+
final Optional<Integer> build = b == null ? Optional.empty() : Optional.of(Integer.parseInt(b));
108+
109+
final Optional<String> optional = Optional.ofNullable(m.group(VersionPattern.OPT_GROUP));
110+
111+
if (!build.isPresent()) {
112+
if (m.group(VersionPattern.PLUS_GROUP) != null) {
113+
if (optional.isPresent()) {
114+
if (pre.isPresent()) {
115+
throw new IllegalArgumentException("'+' found with pre-release and optional components:'" + s + "'");
116+
}
117+
} else {
118+
throw new IllegalArgumentException("'+' found with neither build or optional components: '" + s + "'");
119+
}
120+
} else {
121+
if (optional.isPresent() && !pre.isPresent()) {
122+
throw new IllegalArgumentException("optional component must be preceded by a pre-release component or '+': '" + s + "'");
123+
}
124+
}
125+
}
126+
return new Version(Collections.unmodifiableList(Arrays.asList(version)), pre, build, optional);
127+
}
128+
129+
private static boolean isSimpleNumber(String s) {
130+
for (int i = 0; i < s.length(); i++) {
131+
final char c = s.charAt(i);
132+
final char lowerBound = i > 0 ? '0' : '1';
133+
if (c < lowerBound || c > '9') {
134+
return false;
135+
}
136+
}
137+
return true;
138+
}
139+
140+
public int feature() {
141+
return version.get(0);
142+
}
143+
144+
public int interim() {
145+
return version.size() > 1 ? version.get(1) : 0;
146+
}
147+
148+
public int update() {
149+
return version.size() > 2 ? version.get(2) : 0;
150+
}
151+
152+
public int patch() {
153+
return version.size() > 3 ? version.get(3) : 0;
154+
}
155+
156+
public int major() {
157+
return feature();
158+
}
159+
160+
public int minor() {
161+
return interim();
162+
}
163+
164+
public int security() {
165+
return update();
166+
}
167+
168+
public List<Integer> version() {
169+
return version;
170+
}
171+
172+
public Optional<String> pre() {
173+
return pre;
174+
}
175+
176+
public Optional<Integer> build() {
177+
return build;
178+
}
179+
180+
public Optional<String> optional() {
181+
return optional;
182+
}
183+
184+
@Override
185+
public int compareTo(@SuppressWarnings("NullableProblems") Version obj) {
186+
return compare(obj, false);
187+
}
188+
189+
public int compareToIgnoreOptional(Version obj) {
190+
return compare(obj, true);
191+
}
192+
193+
private int compare(Version obj, boolean ignoreOpt) {
194+
if (obj == null) {
195+
throw new NullPointerException();
196+
}
197+
198+
int ret = compareVersion(obj);
199+
if (ret != 0) {
200+
return ret;
201+
}
202+
203+
ret = comparePre(obj);
204+
if (ret != 0) {
205+
return ret;
206+
}
207+
208+
ret = compareBuild(obj);
209+
if (ret != 0) {
210+
return ret;
211+
}
212+
213+
return !ignoreOpt ? compareOptional(obj) : 0;
214+
}
215+
216+
private int compareVersion(Version obj) {
217+
final int size = version.size();
218+
final int oSize = obj.version().size();
219+
final int min = Math.min(size, oSize);
220+
for (int i = 0; i < min; i++) {
221+
final int val = version.get(i);
222+
final int oVal = obj.version().get(i);
223+
if (val != oVal) {
224+
return val - oVal;
225+
}
226+
}
227+
return size - oSize;
228+
}
229+
230+
private int comparePre(Version obj) {
231+
final Optional<String> oPre = obj.pre();
232+
if (!pre.isPresent()) {
233+
if (oPre.isPresent()) {
234+
return 1;
235+
}
236+
} else {
237+
if (!oPre.isPresent()) {
238+
return -1;
239+
}
240+
final String val = pre.get();
241+
final String oVal = oPre.get();
242+
if (val.matches("\\d+")) {
243+
return oVal.matches("\\d+") ? new BigInteger(val).compareTo(new BigInteger(oVal)) : -1;
244+
} else {
245+
return oVal.matches("\\d+") ? 1 : val.compareTo(oVal);
246+
}
247+
}
248+
return 0;
249+
}
250+
251+
private int compareBuild(Version obj) {
252+
final Optional<Integer> oBuild = obj.build();
253+
if (oBuild.isPresent()) {
254+
//noinspection OptionalIsPresent
255+
return build.isPresent() ? build.get().compareTo(oBuild.get()) : -1;
256+
} else if (build.isPresent()) {
257+
return 1;
258+
}
259+
return 0;
260+
}
261+
262+
private int compareOptional(Version obj) {
263+
final Optional<String> oOpt = obj.optional();
264+
if (!optional.isPresent()) {
265+
if (oOpt.isPresent()) {
266+
return -1;
267+
}
268+
} else {
269+
//noinspection OptionalIsPresent
270+
if (!oOpt.isPresent()) {
271+
return 1;
272+
}
273+
return optional.get().compareTo(oOpt.get());
274+
}
275+
return 0;
276+
}
277+
278+
@Override
279+
public String toString() {
280+
final StringBuilder sb = new StringBuilder(version.stream().map(Object::toString).collect(Collectors.joining(".")));
281+
282+
pre.ifPresent(v -> sb.append("-").append(v));
283+
284+
if (build.isPresent()) {
285+
sb.append("+").append(build.get());
286+
//noinspection OptionalIsPresent
287+
if (optional.isPresent()) {
288+
sb.append("-").append(optional.get());
289+
}
290+
} else if (optional.isPresent()) {
291+
sb.append(pre.isPresent() ? "-" : "+-");
292+
sb.append(optional.get());
293+
}
294+
295+
return sb.toString();
296+
}
297+
298+
@Override
299+
@SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
300+
public boolean equals(Object obj) {
301+
final boolean ret = equalsIgnoreOptional(obj);
302+
if (!ret) {
303+
return false;
304+
}
305+
306+
final Version that = (Version)obj;
307+
return this.optional().equals(that.optional());
308+
}
309+
310+
public boolean equalsIgnoreOptional(Object obj) {
311+
if (this == obj) {
312+
return true;
313+
}
314+
if ((obj instanceof Version)) {
315+
final Version that = (Version)obj;
316+
return this.version().equals(that.version) && this.pre().equals(that.pre()) && this.build().equals(that.build());
317+
}
318+
return false;
319+
}
320+
321+
@Override
322+
public int hashCode() {
323+
int h = 1;
324+
final int p = 17;
325+
326+
h = p * h + version.hashCode();
327+
h = p * h + pre.hashCode();
328+
h = p * h + build.hashCode();
329+
h = p * h + optional.hashCode();
330+
331+
return h;
332+
}
333+
}
334+
335+
private static class VersionPattern {
336+
private static final String VNUM = "(?<VNUM>[1-9][0-9]*(?:(?:\\.0)*\\.[1-9][0-9]*)*)";
337+
private static final String PRE = "(?:-(?<PRE>[a-zA-Z0-9]+))?";
338+
private static final String BUILD = "(?:(?<PLUS>\\+)(?<BUILD>0|[1-9][0-9]*)?)?";
339+
private static final String OPT = "(?:-(?<OPT>[-a-zA-Z0-9.]+))?";
340+
private static final String VSTR_FORMAT = VNUM + PRE + BUILD + OPT;
341+
342+
static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT);
343+
344+
static final String VNUM_GROUP = "VNUM";
345+
static final String PRE_GROUP = "PRE";
346+
static final String PLUS_GROUP = "PLUS";
347+
static final String BUILD_GROUP = "BUILD";
348+
static final String OPT_GROUP = "OPT";
349+
}
350+
}

src/main/java/net/raphimc/javadowngrader/transformer/j8/Java9ToJava8.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ public Java9ToJava8() {
7575
this.addMethodCallReplacer(Opcodes.INVOKEVIRTUAL, "java/util/OptionalInt", "stream", new OptionalStreamMCR("Int", "I"));
7676
this.addMethodCallReplacer(Opcodes.INVOKEVIRTUAL, "java/util/OptionalLong", "stream", new OptionalStreamMCR("Long", "J"));
7777
this.addMethodCallReplacer(Opcodes.INVOKEVIRTUAL, "java/util/OptionalDouble", "stream", new OptionalStreamMCR("Double", "D"));
78+
79+
this.addMethodCallReplacer(Opcodes.INVOKESTATIC, "java/lang/Runtime", "version", new RuntimeVersionMCR());
80+
this.addClassReplacement("java/lang/Runtime$Version");
7881
}
7982

8083
@Override

0 commit comments

Comments
 (0)