1110 * cannot be represented as an {@link Integer} |
1110 * cannot be represented as an {@link Integer} |
1111 * |
1111 * |
1112 * @return The Version of the given string |
1112 * @return The Version of the given string |
1113 */ |
1113 */ |
1114 public static Version parse(String s) { |
1114 public static Version parse(String s) { |
1115 return VersionBuilder.parse(s); |
1115 if (s == null) |
|
1116 throw new NullPointerException(); |
|
1117 |
|
1118 // Shortcut to avoid initializing VersionPattern when creating |
|
1119 // major version constants during startup |
|
1120 if (isSimpleNumber(s)) { |
|
1121 return new Version(List.of(Integer.parseInt(s)), |
|
1122 Optional.empty(), Optional.empty(), Optional.empty()); |
|
1123 } |
|
1124 Matcher m = VersionPattern.VSTR_PATTERN.matcher(s); |
|
1125 if (!m.matches()) |
|
1126 throw new IllegalArgumentException("Invalid version string: '" |
|
1127 + s + "'"); |
|
1128 |
|
1129 // $VNUM is a dot-separated list of integers of arbitrary length |
|
1130 List<Integer> version = new ArrayList<>(); |
|
1131 for (String i : m.group(VersionPattern.VNUM_GROUP).split("\\.")) |
|
1132 version.add(Integer.parseInt(i)); |
|
1133 |
|
1134 Optional<String> pre = Optional.ofNullable( |
|
1135 m.group(VersionPattern.PRE_GROUP)); |
|
1136 |
|
1137 String b = m.group(VersionPattern.BUILD_GROUP); |
|
1138 // $BUILD is an integer |
|
1139 Optional<Integer> build = (b == null) |
|
1140 ? Optional.empty() |
|
1141 : Optional.of(Integer.parseInt(b)); |
|
1142 |
|
1143 Optional<String> optional = Optional.ofNullable( |
|
1144 m.group(VersionPattern.OPT_GROUP)); |
|
1145 |
|
1146 // empty '+' |
|
1147 if ((m.group(VersionPattern.PLUS_GROUP) != null) |
|
1148 && !build.isPresent()) { |
|
1149 if (optional.isPresent()) { |
|
1150 if (pre.isPresent()) |
|
1151 throw new IllegalArgumentException("'+' found with" |
|
1152 + " pre-release and optional components:'" + s |
|
1153 + "'"); |
|
1154 } else { |
|
1155 throw new IllegalArgumentException("'+' found with neither" |
|
1156 + " build or optional components: '" + s + "'"); |
|
1157 } |
|
1158 } |
|
1159 return new Version(version, pre, build, optional); |
|
1160 } |
|
1161 |
|
1162 private static boolean isSimpleNumber(String s) { |
|
1163 for (int i = 0; i < s.length(); i++) { |
|
1164 char c = s.charAt(i); |
|
1165 char lowerBound = (i > 0) ? '0' : '1'; |
|
1166 if (c < lowerBound || c > '9') { |
|
1167 return false; |
|
1168 } |
|
1169 } |
|
1170 return true; |
1116 } |
1171 } |
1117 |
1172 |
1118 /** |
1173 /** |
1119 * Returns the <a href="#major">major</a> version number. |
1174 * Returns the <a href="#major">major</a> version number. |
1120 * |
1175 * |
1439 |
1494 |
1440 return h; |
1495 return h; |
1441 } |
1496 } |
1442 } |
1497 } |
1443 |
1498 |
1444 private static class VersionBuilder { |
1499 private static class VersionPattern { |
1445 // $VNUM(-$PRE)?(\+($BUILD)?(\-$OPT)?)? |
1500 // $VNUM(-$PRE)?(\+($BUILD)?(\-$OPT)?)? |
1446 // RE limits the format of version strings |
1501 // RE limits the format of version strings |
1447 // ([1-9][0-9]*(?:(?:\.0)*\.[1-9][0-9]*)*)(?:-([a-zA-Z0-9]+))?(?:(\+)(0|[1-9][0-9]*)?)?(?:-([-a-zA-Z0-9.]+))? |
1502 // ([1-9][0-9]*(?:(?:\.0)*\.[1-9][0-9]*)*)(?:-([a-zA-Z0-9]+))?(?:(\+)(0|[1-9][0-9]*)?)?(?:-([-a-zA-Z0-9.]+))? |
1448 |
1503 |
1449 private static final String VNUM |
1504 private static final String VNUM |
1450 = "(?<VNUM>[1-9][0-9]*(?:(?:\\.0)*\\.[1-9][0-9]*)*)"; |
1505 = "(?<VNUM>[1-9][0-9]*(?:(?:\\.0)*\\.[1-9][0-9]*)*)"; |
1451 private static final String VNUM_GROUP = "VNUM"; |
|
1452 |
|
1453 private static final String PRE = "(?:-(?<PRE>[a-zA-Z0-9]+))?"; |
1506 private static final String PRE = "(?:-(?<PRE>[a-zA-Z0-9]+))?"; |
1454 private static final String PRE_GROUP = "PRE"; |
|
1455 |
|
1456 private static final String BUILD |
1507 private static final String BUILD |
1457 = "(?:(?<PLUS>\\+)(?<BUILD>0|[1-9][0-9]*)?)?"; |
1508 = "(?:(?<PLUS>\\+)(?<BUILD>0|[1-9][0-9]*)?)?"; |
1458 private static final String PLUS_GROUP = "PLUS"; |
|
1459 private static final String BUILD_GROUP = "BUILD"; |
|
1460 |
|
1461 private static final String OPT = "(?:-(?<OPT>[-a-zA-Z0-9.]+))?"; |
1509 private static final String OPT = "(?:-(?<OPT>[-a-zA-Z0-9.]+))?"; |
1462 private static final String OPT_GROUP = "OPT"; |
|
1463 |
|
1464 private static final String VSTR_FORMAT |
1510 private static final String VSTR_FORMAT |
1465 = "^" + VNUM + PRE + BUILD + OPT + "$"; |
1511 = "^" + VNUM + PRE + BUILD + OPT + "$"; |
1466 private static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT); |
1512 |
1467 |
1513 static final Pattern VSTR_PATTERN = Pattern.compile(VSTR_FORMAT); |
1468 /** |
1514 |
1469 * Constructs a valid <a href="verStr">version string</a> containing |
1515 static final String VNUM_GROUP = "VNUM"; |
1470 * a <a href="#verNum">version number</a> followed by pre-release and |
1516 static final String PRE_GROUP = "PRE"; |
1471 * build information. |
1517 static final String PLUS_GROUP = "PLUS"; |
1472 * |
1518 static final String BUILD_GROUP = "BUILD"; |
1473 * @param s |
1519 static final String OPT_GROUP = "OPT"; |
1474 * A string to be interpreted as a version |
|
1475 * |
|
1476 * @throws IllegalArgumentException |
|
1477 * If the given string cannot be interpreted as a valid |
|
1478 * version |
|
1479 * |
|
1480 * @throws NullPointerException |
|
1481 * If {@code s} is {@code null} |
|
1482 * |
|
1483 * @throws NumberFormatException |
|
1484 * If an element of the version number or the build number |
|
1485 * cannot be represented as an {@link Integer} |
|
1486 */ |
|
1487 static Version parse(String s) { |
|
1488 if (s == null) |
|
1489 throw new NullPointerException(); |
|
1490 |
|
1491 Matcher m = VSTR_PATTERN.matcher(s); |
|
1492 if (!m.matches()) |
|
1493 throw new IllegalArgumentException("Invalid version string: '" |
|
1494 + s + "'"); |
|
1495 |
|
1496 // $VNUM is a dot-separated list of integers of arbitrary length |
|
1497 List<Integer> version = new ArrayList<>(); |
|
1498 for (String i : m.group(VNUM_GROUP).split("\\.")) |
|
1499 version.add(Integer.parseInt(i)); |
|
1500 |
|
1501 Optional<String> pre = Optional.ofNullable(m.group(PRE_GROUP)); |
|
1502 |
|
1503 String b = m.group(BUILD_GROUP); |
|
1504 // $BUILD is an integer |
|
1505 Optional<Integer> build = (b == null) |
|
1506 ? Optional.empty() |
|
1507 : Optional.of(Integer.parseInt(b)); |
|
1508 |
|
1509 Optional<String> optional = Optional.ofNullable(m.group(OPT_GROUP)); |
|
1510 |
|
1511 // empty '+' |
|
1512 if ((m.group(PLUS_GROUP) != null) && !build.isPresent()) { |
|
1513 if (optional.isPresent()) { |
|
1514 if (pre.isPresent()) |
|
1515 throw new IllegalArgumentException("'+' found with" |
|
1516 + " pre-release and optional components:'" + s |
|
1517 + "'"); |
|
1518 } else { |
|
1519 throw new IllegalArgumentException("'+' found with neither" |
|
1520 + " build or optional components: '" + s + "'"); |
|
1521 } |
|
1522 } |
|
1523 return new Version(version, pre, build, optional); |
|
1524 } |
|
1525 } |
1520 } |
1526 } |
1521 } |