Skip to content

Commit ee6d2cc

Browse files
committed
Add pre-release support to Version class #212
Enhanced the Version class to support pre-release identifiers in version strings (e.g., 1.2.3-alpha). Updated parsing, comparison, and string representation logic to handle pre-release values according to semantic versioning. Improved null checks and refactored version parsing to use StringUtils.split instead of StringTokenizer.
1 parent 61d453a commit ee6d2cc

2 files changed

Lines changed: 199 additions & 31 deletions

File tree

microsphere-java-core/src/main/java/io/microsphere/util/Version.java

Lines changed: 97 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,30 @@
1818

1919
import io.microsphere.annotation.Immutable;
2020
import io.microsphere.annotation.Nonnull;
21+
import io.microsphere.annotation.Nullable;
2122
import io.microsphere.lang.ClassDataRepository;
2223

2324
import java.io.Serializable;
2425
import java.net.URL;
2526
import java.util.Objects;
26-
import java.util.StringTokenizer;
2727
import java.util.function.BiPredicate;
2828

2929
import static io.microsphere.constants.SymbolConstants.COMMA_CHAR;
3030
import static io.microsphere.constants.SymbolConstants.DOT;
3131
import static io.microsphere.constants.SymbolConstants.EQUAL;
3232
import static io.microsphere.constants.SymbolConstants.GREATER_THAN;
3333
import static io.microsphere.constants.SymbolConstants.GREATER_THAN_OR_EQUAL_TO;
34+
import static io.microsphere.constants.SymbolConstants.HYPHEN;
3435
import static io.microsphere.constants.SymbolConstants.LESS_THAN;
3536
import static io.microsphere.constants.SymbolConstants.LESS_THAN_OR_EQUAL_TO;
3637
import static io.microsphere.constants.SymbolConstants.QUOTE_CHAR;
3738
import static io.microsphere.constants.SymbolConstants.SPACE_CHAR;
39+
import static io.microsphere.lang.ClassDataRepository.INSTANCE;
3840
import static io.microsphere.text.FormatUtils.format;
3941
import static io.microsphere.util.Assert.assertNotBlank;
4042
import static io.microsphere.util.Assert.assertNotNull;
43+
import static io.microsphere.util.Assert.assertTrue;
44+
import static io.microsphere.util.StringUtils.split;
4145
import static io.microsphere.util.Version.Operator.EQ;
4246
import static io.microsphere.util.Version.Operator.GE;
4347
import static io.microsphere.util.Version.Operator.GT;
@@ -51,6 +55,13 @@
5155
* <p>
5256
* This class provides methods to compare versions using standard comparison operators such as greater than,
5357
* less than, and equal to. It also supports parsing version strings in the format "major.minor.patch".
58+
* The supported version patterns :
59+
* <ul>
60+
* <li>major</li>
61+
* <li>major.minor</li>
62+
* <li>major.minor.patch</li>
63+
* <li>major.minor.patch-preRelease</li>
64+
* </ul>
5465
* </p>
5566
* See <a href="https://semver.org/">Semantic Versioning</a> for more details.
5667
*
@@ -77,14 +88,17 @@
7788
@Immutable
7889
public class Version implements Comparable<Version>, Serializable {
7990

80-
private static final long serialVersionUID = -6434504483466016691L;
91+
private static final long serialVersionUID = 609463905158722630L;
8192

8293
private final int major;
8394

8495
private final int minor;
8596

8697
private final int patch;
8798

99+
@Nullable
100+
private final String preRelease;
101+
88102
public Version(int major) {
89103
this(major, 0);
90104
}
@@ -94,9 +108,18 @@ public Version(int major, int minor) {
94108
}
95109

96110
public Version(int major, int minor, int patch) {
111+
this(major, minor, patch, null);
112+
}
113+
114+
public Version(int major, int minor, int patch, String preRelease) {
115+
assertTrue(major >= 0, "The 'major' version must not be a non-negative integer!");
116+
assertTrue(minor >= 0, "The 'minor' version must not be a non-negative integer!");
117+
assertTrue(patch >= 0, "The 'patch' version must not be a non-negative integer!");
118+
assertTrue(major + minor + patch > 0, "The 'major', 'minor' and 'patch' must not be null at the same time!");
97119
this.major = major;
98120
this.minor = minor;
99121
this.patch = patch;
122+
this.preRelease = preRelease;
100123
}
101124

102125
/**
@@ -126,6 +149,16 @@ public int getPatch() {
126149
return patch;
127150
}
128151

152+
/**
153+
* The pre-release
154+
*
155+
* @return pre-release
156+
*/
157+
@Nullable
158+
public String getPreRelease() {
159+
return preRelease;
160+
}
161+
129162
/**
130163
* Current {@link Version} is greater than that
131164
*
@@ -252,6 +285,9 @@ public int hashCode() {
252285
int result = major;
253286
result = 31 * result + minor;
254287
result = 31 * result + patch;
288+
if (preRelease != null) {
289+
result = 31 * result + preRelease.hashCode();
290+
}
255291
return result;
256292
}
257293

@@ -271,17 +307,35 @@ public int compareTo(Version that) {
271307

272308
result = compare(this.patch, that.patch);
273309

310+
if (result != 0) {
311+
return result;
312+
}
313+
314+
result = comparePreRelease(this.preRelease, that.preRelease);
315+
274316
return result;
275317
}
276318

319+
/**
320+
* Compare pre-release
321+
*
322+
* @param v1
323+
* @param v2
324+
* @return
325+
*/
326+
static int comparePreRelease(String v1, String v2) {
327+
if (v1 == null) {
328+
return v2 == null ? 0 : 1;
329+
}
330+
if (v2 == null) {
331+
return -1;
332+
}
333+
return v1.compareTo(v2);
334+
}
277335

278336
@Override
279337
public String toString() {
280-
return "Version{" +
281-
"major=" + major +
282-
", minor=" + minor +
283-
", patch=" + patch +
284-
'}';
338+
return major + DOT + minor + DOT + patch + (preRelease == null ? "" : "-" + preRelease);
285339
}
286340

287341
public static Version of(int major) {
@@ -296,6 +350,10 @@ public static Version of(int major, int minor, int patch) {
296350
return ofVersion(major, minor, patch);
297351
}
298352

353+
public static Version of(int major, int minor, int patch, String preRelease) {
354+
return ofVersion(major, minor, patch, preRelease);
355+
}
356+
299357
public static Version of(String version) {
300358
return ofVersion(version);
301359
}
@@ -312,19 +370,27 @@ public static Version ofVersion(int major, int minor, int patch) {
312370
return new Version(major, minor, patch);
313371
}
314372

373+
public static Version ofVersion(int major, int minor, int patch, String preRelease) {
374+
return new Version(major, minor, patch, preRelease);
375+
}
376+
315377
public static Version ofVersion(String version) {
316378
assertNotNull(version, () -> "The 'version' argument must not be null!");
317379
assertNotBlank(version, () -> "The 'version' argument must not be blank!");
318380

319-
StringTokenizer st = new StringTokenizer(version.trim(), DOT);
381+
String[] coreAndPreRelease = split(version.trim(), HYPHEN);
382+
String core = coreAndPreRelease[0];
383+
String preRelease = coreAndPreRelease.length > 1 ? coreAndPreRelease[1] : null;
320384

321-
int major = getValue(st);
322-
int minor = getValue(st);
323-
int patch = getValue(st);
385+
String[] majorAndMinorAndPatch = split(core, DOT);
386+
int size = majorAndMinorAndPatch.length;
324387

325-
return of(major, minor, patch);
326-
}
388+
int major = getValue(majorAndMinorAndPatch[0]);
389+
int minor = size > 1 ? getValue(majorAndMinorAndPatch[1]) : 0;
390+
int patch = size > 2 ? getValue(majorAndMinorAndPatch[2]) : 0;
327391

392+
return of(major, minor, patch, preRelease);
393+
}
328394

329395
/**
330396
* Class that exposes the version. Fetches the "Implementation-Version" manifest attribute from the jar file.
@@ -338,21 +404,14 @@ public static Version getVersion(Class<?> targetClass) throws IllegalArgumentExc
338404
Package targetPackage = targetClass.getPackage();
339405
String version = targetPackage.getImplementationVersion();
340406
if (version == null) {
341-
ClassDataRepository repository = ClassDataRepository.INSTANCE;
407+
ClassDataRepository repository = INSTANCE;
342408
URL classResource = repository.getCodeSourceLocation(targetClass);
343409
String errorMessage = format("The 'Implementation-Version' manifest attribute can't be fetched from the jar file[class resource : '{}'] by the target class[name :'{}']", classResource, targetClass.getName());
344410
throw new IllegalArgumentException(errorMessage);
345411
}
346412
return of(version);
347413
}
348414

349-
static int getValue(StringTokenizer st) {
350-
if (st.hasMoreTokens()) {
351-
return getValue(st.nextToken());
352-
}
353-
return 0;
354-
}
355-
356415
static int getValue(String part) {
357416
final int value;
358417
try {
@@ -374,10 +433,10 @@ public boolean test(Version v1, Version v2) {
374433
if (v1 == v2) {
375434
return true;
376435
}
377-
if (v2 == null) return false;
378-
if (v1.major != v2.major) return false;
379-
if (v1.minor != v2.minor) return false;
380-
return v1.patch == v2.patch;
436+
if (v1 == null || v2 == null) {
437+
return false;
438+
}
439+
return v1.compareTo(v2) == 0;
381440
}
382441
},
383442

@@ -390,7 +449,9 @@ public boolean test(Version v1, Version v2) {
390449
if (v1 == v2) {
391450
return false;
392451
}
393-
if (v2 == null) return false;
452+
if (v1 == null || v2 == null) {
453+
return false;
454+
}
394455
return v1.compareTo(v2) < 0;
395456
}
396457
},
@@ -404,7 +465,9 @@ public boolean test(Version v1, Version v2) {
404465
if (v1 == v2) {
405466
return true;
406467
}
407-
if (v2 == null) return false;
468+
if (v1 == null || v2 == null) {
469+
return false;
470+
}
408471
return v1.compareTo(v2) <= 0;
409472
}
410473
},
@@ -418,7 +481,9 @@ public boolean test(Version v1, Version v2) {
418481
if (v1 == v2) {
419482
return false;
420483
}
421-
if (v2 == null) return false;
484+
if (v1 == null || v2 == null) {
485+
return false;
486+
}
422487
return v1.compareTo(v2) > 0;
423488
}
424489
},
@@ -432,7 +497,9 @@ public boolean test(Version v1, Version v2) {
432497
if (v1 == v2) {
433498
return true;
434499
}
435-
if (v2 == null) return false;
500+
if (v1 == null || v2 == null) {
501+
return false;
502+
}
436503
return v1.compareTo(v2) >= 0;
437504
}
438505
};
@@ -474,6 +541,5 @@ public static Operator of(String symbol) {
474541

475542
throw new IllegalArgumentException(messageBuilder.toString());
476543
}
477-
478544
}
479-
}
545+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package io.microsphere.util;
19+
20+
import io.microsphere.util.Version.Operator;
21+
import org.junit.jupiter.api.Test;
22+
23+
import static io.microsphere.util.Version.Operator.EQ;
24+
import static io.microsphere.util.Version.Operator.GE;
25+
import static io.microsphere.util.Version.Operator.GT;
26+
import static io.microsphere.util.Version.Operator.LE;
27+
import static io.microsphere.util.Version.Operator.LT;
28+
import static io.microsphere.util.Version.Operator.of;
29+
import static io.microsphere.util.Version.ofVersion;
30+
import static org.junit.jupiter.api.Assertions.assertEquals;
31+
import static org.junit.jupiter.api.Assertions.assertFalse;
32+
import static org.junit.jupiter.api.Assertions.assertThrows;
33+
import static org.junit.jupiter.api.Assertions.assertTrue;
34+
35+
/**
36+
* The {@link Operator} Test
37+
*
38+
* @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
39+
* @see Operator
40+
* @since 1.0.0
41+
*/
42+
class VersionOperatorTest {
43+
44+
private static final Version TEST_VERSION = ofVersion("1.2.3");
45+
46+
@Test
47+
void testOf() {
48+
assertEquals(EQ, of("="));
49+
assertEquals(LT, of("<"));
50+
assertEquals(LE, of("<="));
51+
assertEquals(GT, of(">"));
52+
assertEquals(GE, of(">="));
53+
}
54+
55+
@Test
56+
void test() {
57+
assertTrue(EQ.test(TEST_VERSION, TEST_VERSION));
58+
assertFalse(EQ.test(TEST_VERSION, ofVersion("1.2.4")));
59+
60+
assertTrue(LT.test(TEST_VERSION, ofVersion("1.2.4")));
61+
assertFalse(LT.test(TEST_VERSION, TEST_VERSION));
62+
63+
assertTrue(LE.test(TEST_VERSION, ofVersion("1.2.4")));
64+
assertTrue(LE.test(TEST_VERSION, TEST_VERSION));
65+
66+
assertFalse(GT.test(TEST_VERSION, ofVersion("1.2.4")));
67+
assertTrue(GT.test(TEST_VERSION, ofVersion("1.2.2")));
68+
69+
assertFalse(GE.test(TEST_VERSION, ofVersion("1.2.4")));
70+
assertTrue(GE.test(TEST_VERSION, TEST_VERSION));
71+
}
72+
73+
@Test
74+
void testWithNull() {
75+
assertTrue(EQ.test(null, null));
76+
assertFalse(EQ.test(TEST_VERSION, null));
77+
assertFalse(EQ.test(null, TEST_VERSION));
78+
79+
assertFalse(LT.test(null, null));
80+
assertFalse(LT.test(TEST_VERSION, null));
81+
assertFalse(LT.test(null, TEST_VERSION));
82+
83+
assertTrue(LE.test(null, null));
84+
assertFalse(LE.test(TEST_VERSION, null));
85+
assertFalse(LE.test(null, TEST_VERSION));
86+
87+
assertFalse(GT.test(null, null));
88+
assertFalse(GT.test(TEST_VERSION, null));
89+
assertFalse(GT.test(null, TEST_VERSION));
90+
91+
assertTrue(GE.test(null, null));
92+
assertFalse(GE.test(TEST_VERSION, null));
93+
assertFalse(GE.test(null, TEST_VERSION));
94+
}
95+
96+
@Test
97+
void testOnIllegalArgumentException() {
98+
assertThrows(IllegalArgumentException.class, () -> of(""));
99+
assertThrows(IllegalArgumentException.class, () -> of("@"));
100+
assertThrows(IllegalArgumentException.class, () -> of("-"));
101+
}
102+
}

0 commit comments

Comments
 (0)