@@ -33,7 +33,6 @@ public final class Version implements Comparable<Version> {
3333 // Groups: 1=major, 2=minor, 3=patch, 4=build (4th numeric part), 5=fifth part, 6=qualifier suffix
3434 private static final Pattern RELEASE_PATTERN = Pattern .compile (
3535 "(\\ d+)(?:\\ .(\\ d+))?(?:\\ .(\\ d+))?(?:\\ .(\\ d+))?(?:\\ .(\\ d+))?([-.+].*)?" );
36- private static final String [] RELEASE_SUFFIXES = { ".final" , ".ga" , ".release" };
3736 private static final int QUALIFIER_GROUP = 6 ;
3837
3938 private static final Pattern QUALIFIER_TYPE_PATTERN =
@@ -81,7 +80,9 @@ public boolean isSnapshot() {
8180 /** Qualifier text without its leading separator (e.g. {@code "M1"}, {@code "SNAPSHOT"}), or {@code null}. */
8281 private final String qualifier ;
8382 private final ReleaseType releaseType ;
84- /** Original parsed string — used for display and comparison. */
83+ /** Numeric suffix of the qualifier (e.g. {@code 1} for {@code M1} or {@code RC2→2}); 0 if absent. */
84+ private final int qualifierNumber ;
85+ /** Original parsed string — used for display. */
8586 private final String versionString ;
8687
8788 private Version (int major , int minor , int patch , int build , String qualifier , String originalString ) {
@@ -91,6 +92,7 @@ private Version(int major, int minor, int patch, int build, String qualifier, St
9192 this .build = build ;
9293 this .qualifier = qualifier ;
9394 this .releaseType = toReleaseType (qualifier );
95+ this .qualifierNumber = toQualifierNumber (qualifier );
9496 this .versionString = originalString ;
9597 }
9698
@@ -123,6 +125,11 @@ public ReleaseType getReleaseType() {
123125 return releaseType ;
124126 }
125127
128+ /** Numeric suffix of the qualifier; 0 when absent (e.g. {@code SNAPSHOT→0}, {@code M1→1}, {@code RC2→2}). */
129+ public int getQualifierNumber () {
130+ return qualifierNumber ;
131+ }
132+
126133 /** True for GA releases and service packs. */
127134 public boolean isRelease () {
128135 return releaseType == ReleaseType .RELEASE || releaseType == ReleaseType .SERVICE_PACK ;
@@ -160,7 +167,13 @@ public String toString() {
160167 */
161168 @ Override
162169 public int compareTo (Version o ) {
163- return compareVersionStrings (this .versionString , o .versionString );
170+ int d ;
171+ if ((d = Integer .compare (major , o .major )) != 0 ) return d ;
172+ if ((d = Integer .compare (minor , o .minor )) != 0 ) return d ;
173+ if ((d = Integer .compare (patch , o .patch )) != 0 ) return d ;
174+ if ((d = Integer .compare (build , o .build )) != 0 ) return d ;
175+ if ((d = Integer .compare (releaseType .getPriority (), o .releaseType .getPriority ())) != 0 ) return d ;
176+ return Integer .compare (qualifierNumber , o .qualifierNumber );
164177 }
165178
166179 @ Override
@@ -214,120 +227,6 @@ public static Version parse(String versionStr) {
214227 }
215228 }
216229
217- // ---- comparison logic (derived from org.openrewrite.semver.LatestRelease, Apache 2.0) ----
218-
219- private static int compareVersionStrings (String v1 , String v2 ) {
220- if (v1 .equalsIgnoreCase (v2 )) {
221- return 0 ;
222- }
223-
224- String nv1 = normalizeVersion (v1 );
225- String nv2 = normalizeVersion (v2 );
226-
227- int vp1 = countVersionParts (nv1 );
228- int vp2 = countVersionParts (nv2 );
229-
230- // Pad the shorter version with ".0" segments so both have the same number of parts
231- if (vp1 > vp2 ) {
232- StringBuilder sb = new StringBuilder (nv2 );
233- for (int i = vp2 ; i < vp1 ; i ++) sb .append (".0" );
234- nv2 = sb .toString ();
235- } else if (vp2 > vp1 ) {
236- StringBuilder sb = new StringBuilder (nv1 );
237- for (int i = vp1 ; i < vp2 ; i ++) sb .append (".0" );
238- nv1 = sb .toString ();
239- }
240-
241- Matcher m1 = RELEASE_PATTERN .matcher (nv1 );
242- Matcher m2 = RELEASE_PATTERN .matcher (nv2 );
243- m1 .find ();
244- m2 .find ();
245-
246- int maxParts = Math .max (vp1 , vp2 );
247- for (int i = 1 ; i <= maxParts ; i ++) {
248- String p1 = m1 .group (i );
249- String p2 = m2 .group (i );
250- if (p1 == null ) {
251- return p2 == null ? nv1 .compareTo (nv2 ) : -1 ;
252- } else if (p2 == null ) {
253- return 1 ;
254- }
255- long diff = Long .parseLong (p1 ) - Long .parseLong (p2 );
256- if (diff != 0 ) {
257- return diff > 0 ? 1 : -1 ;
258- }
259- }
260-
261- // All numeric parts equal — compare by qualifier priority
262- int prio1 = qualifierPriority (m1 .group (QUALIFIER_GROUP ));
263- int prio2 = qualifierPriority (m2 .group (QUALIFIER_GROUP ));
264- if (prio1 != prio2 ) {
265- return Integer .compare (prio1 , prio2 );
266- }
267- return nv1 .compareTo (nv2 );
268- }
269-
270- /** Strips trailing {@code .final} / {@code .ga} / {@code .release} and pads to at least 3 parts. */
271- private static String normalizeVersion (String version ) {
272- int lastDotIdx = version .lastIndexOf ('.' );
273- for (String suffix : RELEASE_SUFFIXES ) {
274- if (version .regionMatches (true , lastDotIdx , suffix , 0 , suffix .length ())) {
275- version = version .substring (0 , lastDotIdx );
276- break ;
277- }
278- }
279- int parts = countVersionParts (version );
280- if (parts <= 2 ) {
281- String [] split = version .split ("(?=[-+])" );
282- for (; parts <= 2 ; parts ++) {
283- split [0 ] += ".0" ;
284- }
285- version = split .length > 1 ? split [0 ] + split [1 ] : split [0 ];
286- }
287- return version ;
288- }
289-
290- private static int countVersionParts (String version ) {
291- int count = 0 ;
292- int len = version .length ();
293- int lastSepIdx = -1 ;
294- for (int i = 0 ; i < len ; i ++) {
295- char c = version .charAt (i );
296- if (c == '.' || c == '-' || c == '$' ) {
297- if (lastSepIdx == i - 1 ) return count ;
298- lastSepIdx = i ;
299- } else if (lastSepIdx == i - 1 ) {
300- if (!Character .isDigit (c )) break ;
301- count ++;
302- }
303- }
304- return count ;
305- }
306-
307- private static int qualifierPriority (String suffix ) {
308- switch (extractQualifier (suffix )) {
309- case "alpha" : case "a" : return 1 ;
310- case "beta" : case "b" : return 2 ;
311- case "milestone" : case "m" : return 3 ;
312- case "rc" : case "cr" : return 4 ;
313- case "snapshot" : return 5 ;
314- case "" : case "ga" : case "final" : case "release" : return 6 ;
315- case "sp" : return 7 ;
316- default : return 8 ;
317- }
318- }
319-
320- private static String extractQualifier (String suffix ) {
321- if (suffix == null ) return "" ;
322- StringBuilder sb = new StringBuilder ();
323- for (int i = 1 ; i < suffix .length (); i ++) {
324- char c = suffix .charAt (i );
325- if (Character .isLetter (c )) sb .append (Character .toLowerCase (c ));
326- else break ;
327- }
328- return sb .toString ();
329- }
330-
331230 // ---- parsing helpers ----
332231
333232 private static int parseGroup (Matcher m , int group ) {
@@ -347,6 +246,18 @@ private static String stripSeparator(String qualifierWithSep) {
347246 return qualifierWithSep ;
348247 }
349248
249+ private static int toQualifierNumber (String qualifier ) {
250+ if (qualifier == null || qualifier .isEmpty ()) {
251+ return 0 ;
252+ }
253+ Matcher m = QUALIFIER_TYPE_PATTERN .matcher (qualifier );
254+ if (m .matches ()) {
255+ String num = m .group (2 );
256+ return num .isEmpty () ? 0 : Integer .parseInt (num );
257+ }
258+ return 0 ;
259+ }
260+
350261 private static ReleaseType toReleaseType (String qualifier ) {
351262 if (qualifier == null || qualifier .isEmpty ()) {
352263 return ReleaseType .RELEASE ;
@@ -355,6 +266,9 @@ private static ReleaseType toReleaseType(String qualifier) {
355266 if (lower .equals ("release" ) || lower .equals ("ga" ) || lower .equals ("final" )) {
356267 return ReleaseType .RELEASE ;
357268 }
269+ if (lower .startsWith ("snapshot" )) {
270+ return ReleaseType .SNAPSHOT ;
271+ }
358272 if (lower .startsWith ("sp" )) {
359273 return ReleaseType .SERVICE_PACK ;
360274 }
0 commit comments