@@ -67,6 +67,21 @@ public enum Type {
6767 (?:\\ +(?<local>[a-zA-Z0-9]+(?:[-_.][a-zA-Z0-9]+)*))?\
6868 $""" ,
6969 Pattern .CASE_INSENSITIVE );
70+ private static final Pattern NORM_PATTERN_LEADING_V = Pattern .compile ("^[vV]" );
71+ private static final Pattern NORM_PATTERN_EPOCH = Pattern .compile ("^([0-9]+)!" );
72+ private static final Pattern NORM_PATTERN_ALPHA = Pattern .compile ("[-_.]?(alpha)" );
73+ private static final Pattern NORM_PATTERN_BETA = Pattern .compile ("[-_.]?(beta)" );
74+ private static final Pattern NORM_PATTERN_RC = Pattern .compile ("[-_.]?(c|pre|preview)" );
75+ private static final Pattern NORM_PATTERN_PRE_NUM = Pattern .compile ("(a|b|rc)[-_.]?([0-9]+)" );
76+ private static final Pattern NORM_PATTERN_PRE_IMPLICIT_NUM = Pattern .compile ("(a|b|rc)(?![0-9])" );
77+ private static final Pattern NORM_PATTERN_POST = Pattern .compile ("[-_.]?(post|rev|r)[-_.]?([0-9]+)" );
78+ private static final Pattern NORM_PATTERN_POST_IMPLICIT = Pattern .compile ("-([0-9]+)(?!.*post)" );
79+ private static final Pattern NORM_PATTERN_DEV = Pattern .compile ("[-_.]?dev[-_.]?([0-9]+)" );
80+ private static final Pattern NORM_PATTERN_DEV_IMPLICIT_NUM = Pattern .compile ("[-_.]?dev(?![0-9])" );
81+ private static final Pattern NORM_PATTERN_LOCAL_SEP = Pattern .compile ("\\ +([a-zA-Z0-9]+)[-_.]" );
82+ private static final Pattern LOCAL_SEGMENT_SEPARATOR_PATTERN = Pattern .compile ("\\ ." );
83+ private static final Pattern LOCAL_NORMALIZE_PATTERN = Pattern .compile ("[-_]" );
84+ private static final Pattern ASCII_DIGITS_PATTERN = Pattern .compile ("\\ d+" );
7085
7186 private final int epoch ;
7287 private final List <Integer > release ;
@@ -187,36 +202,56 @@ private static String normalize(final String versionStr) {
187202 // https://peps.python.org/pep-0440/#leading-and-trailing-whitespace
188203 String normalized = versionStr .strip ();
189204
205+ // Avoid the heavy regex machinery when the version is simple, e.g. `666` or `6.6.6`.
206+ if (isSimpleNumericVersion (normalized )) {
207+ return normalized ;
208+ }
209+
190210 // https://peps.python.org/pep-0440/#preceding-v-character
191- normalized = normalized .replaceFirst ("^[vV]" , "" );
211+ normalized = NORM_PATTERN_LEADING_V . matcher ( normalized ) .replaceFirst ("" );
192212
193213 // https://peps.python.org/pep-0440/#integer-normalization
194- normalized = normalized .replaceAll ("^([0-9]+)!" , "$1!" );
214+ normalized = NORM_PATTERN_EPOCH . matcher ( normalized ) .replaceAll ("$1!" );
195215
196216 // https://peps.python.org/pep-0440/#pre-release-separators
197217 // https://peps.python.org/pep-0440/#pre-release-spelling
198- normalized = normalized .replaceAll ("[-_.]?(alpha)" , "a" );
199- normalized = normalized .replaceAll ("[-_.]?(beta)" , "b" );
200- normalized = normalized .replaceAll ("[-_.]?(c|pre|preview)" , "rc" );
201- normalized = normalized . replaceAll ( "(a|b|rc)[-_.]?([0-9]+)" , "$1$2" );
202- normalized = normalized .replaceAll ("(a|b|rc)(?![0-9])" , "$10" );
218+ normalized = NORM_PATTERN_ALPHA . matcher ( normalized ) .replaceAll ("a" );
219+ normalized = NORM_PATTERN_BETA . matcher ( normalized ) .replaceAll ("b" );
220+ normalized = NORM_PATTERN_RC . matcher ( normalized ) .replaceAll ("rc" );
221+ normalized = NORM_PATTERN_PRE_NUM . matcher ( normalized ). replaceAll ( "$1$2" );
222+ normalized = NORM_PATTERN_PRE_IMPLICIT_NUM . matcher ( normalized ) .replaceAll ("$10" );
203223
204224 // https://peps.python.org/pep-0440/#post-release-separators
205225 // https://peps.python.org/pep-0440/#post-release-spelling
206- normalized = normalized . replaceAll ( "[-_.]?(post|rev|r)[-_.]?([0-9]+)" , ".post$2" );
207- normalized = normalized . replaceAll ( "-([0-9]+)(?!.*post)" , ".post$1" );
226+ normalized = NORM_PATTERN_POST . matcher ( normalized ). replaceAll ( ".post$2" );
227+ normalized = NORM_PATTERN_POST_IMPLICIT . matcher ( normalized ). replaceAll ( ".post$1" );
208228
209229 // https://peps.python.org/pep-0440/#development-release-separators
210- normalized = normalized .replaceAll ("[-_.]?dev[-_.]?([0-9]+)" , ".dev$1" );
211- normalized = normalized .replaceAll ("[-_.]?dev(?![0-9])" , ".dev0" );
230+ normalized = NORM_PATTERN_DEV . matcher ( normalized ) .replaceAll (".dev$1" );
231+ normalized = NORM_PATTERN_DEV_IMPLICIT_NUM . matcher ( normalized ) .replaceAll (".dev0" );
212232
213233 // https://peps.python.org/pep-0440/#local-version-segments
214- normalized = normalized . replaceAll ( " \\ +([a-zA-Z0-9]+)[-_.]" , "+$1." );
234+ normalized = NORM_PATTERN_LOCAL_SEP . matcher ( normalized ). replaceAll ( "+$1." );
215235
216236 // https://peps.python.org/pep-0440/#case-sensitivity
217237 return normalized .toLowerCase (Locale .ROOT );
218238 }
219239
240+ private static boolean isSimpleNumericVersion (String version ) {
241+ if (version .isEmpty ()) {
242+ return false ;
243+ }
244+
245+ for (int i = 0 ; i < version .length (); i ++) {
246+ final char c = version .charAt (i );
247+ if ((c < '0' || c > '9' ) && c != '.' ) {
248+ return false ;
249+ }
250+ }
251+
252+ return true ;
253+ }
254+
220255 private static int parseEpoch (final String epochStr ) {
221256 if (epochStr == null || epochStr .isBlank ()) {
222257 return 0 ;
@@ -288,7 +323,7 @@ private static String parseLocal(final String localStr) {
288323 return null ;
289324 }
290325
291- return localStr .toLowerCase (Locale .ROOT ).replaceAll ("[-_]" , "." );
326+ return LOCAL_NORMALIZE_PATTERN . matcher ( localStr .toLowerCase (Locale .ROOT )) .replaceAll ("." );
292327 }
293328
294329 private static int compareRelease (final List <Integer > release1 , final List <Integer > release2 ) {
@@ -390,16 +425,16 @@ private static int compareLocal(final String local1, final String local2) {
390425 return 1 ;
391426 }
392427
393- final String [] parts1 = local1 .split (" \\ ." );
394- final String [] parts2 = local2 .split (" \\ ." );
428+ final String [] parts1 = LOCAL_SEGMENT_SEPARATOR_PATTERN .split (local1 );
429+ final String [] parts2 = LOCAL_SEGMENT_SEPARATOR_PATTERN .split (local2 );
395430 final int maxLen = Math .max (parts1 .length , parts2 .length );
396431
397432 for (int i = 0 ; i < maxLen ; i ++) {
398433 final String p1 = i < parts1 .length ? parts1 [i ] : "" ;
399434 final String p2 = i < parts2 .length ? parts2 [i ] : "" ;
400435
401436 final int result ;
402- if (p1 .matches (" \\ d+" ) && p2 .matches (" \\ d+" )) {
437+ if (ASCII_DIGITS_PATTERN . matcher ( p1 ) .matches () && ASCII_DIGITS_PATTERN . matcher ( p2 ) .matches ()) {
403438 result = Integer .compare (Integer .parseInt (p1 ), Integer .parseInt (p2 ));
404439 } else {
405440 result = p1 .compareTo (p2 );
0 commit comments