() {
113 |
114 | @Override
115 | public int compare(VersionRegEx o1, VersionRegEx o2) {
116 | return compareWithBuildMetaData(o1, o2);
117 | }
118 | };
119 |
120 | private static final Pattern PRE_RELEASE = Pattern.compile("" +
121 | "(?:(?:[0-9]+[a-zA-Z-][\\w-]*)|(?:[a-zA-Z][\\w-]*)|(?:[1-9]\\d*)|0)" +
122 | "(?:\\.(?:(?:[0-9]+[a-zA-Z-][\\w-]*)|(?:[a-zA-Z][\\w-]*)|(?:[1-9]\\d*)|0))*");
123 |
124 | private static final Pattern BUILD_MD = Pattern.compile("[\\w-]+(\\.[\\w-]+)*");
125 | private static final Pattern VERSION_PATTERN = Pattern.compile(""
126 | + "(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)"
127 | + "(?:-((?:(?:[0-9]+[a-zA-Z-][\\w-]*)|(?:[a-zA-Z][\\w-]*)|(?:[1-9]\\d*)|0)"
128 | + "(?:\\.(?:(?:[0-9]+[a-zA-Z-][\\w-]*)|(?:[a-zA-Z][\\w-]*)|(?:[1-9]\\d*)|0))*))?"
129 | + "(?:\\+([\\w-]+(\\.[\\w-]+)*))?");
130 |
131 | // Match result group indices
132 | private static final int MAJOR_GROUP = 1;
133 | private static final int MINOR_GROUP = 2;
134 | private static final int PATCH_GROUP = 3;
135 | private static final int PRE_RELEASE_GROUP = 4;
136 | private static final int BUILD_MD_GROUP = 5;
137 |
138 | private static final int TO_STRING_ESTIMATE = 12;
139 | private static final int HASH_PRIME = 31;
140 |
141 | private final int major;
142 | private final int minor;
143 | private final int patch;
144 | private final String preRelease;
145 | private final String buildMetaData;
146 |
147 | // store hash code once it has been calculated
148 | private volatile int hash;
149 |
150 | private VersionRegEx(int major, int minor, int patch, String preRelease, String buildMd) {
151 | this.major = major;
152 | this.minor = minor;
153 | this.patch = patch;
154 | this.preRelease = preRelease;
155 | this.buildMetaData = buildMd;
156 | }
157 |
158 | /**
159 | * Tries to parse the given String as a semantic version and returns whether the
160 | * String is properly formatted according to the semantic version specification.
161 | *
162 | *
163 | * Note: this method does not throw an exception upon null
input, but
164 | * returns false
instead.
165 | *
166 | *
167 | * @param version The String to check.
168 | * @return Whether the given String is formatted as a semantic version.
169 | * @since 0.5.0
170 | */
171 | public static boolean isValidVersion(String version) {
172 | if (version == null || version.isEmpty()) {
173 | return false;
174 | }
175 | return VERSION_PATTERN.matcher(version).matches();
176 | }
177 |
178 | /**
179 | * Returns whether the given String is a valid pre-release identifier. That is, this
180 | * method returns true
if, and only if the {@code preRelease} parameter
181 | * is either the empty string or properly formatted as a pre-release identifier
182 | * according to the semantic version specification.
183 | *
184 | *
185 | * Note: this method does not throw an exception upon null
input, but
186 | * returns false
instead.
187 | *
188 | *
189 | * @param preRelease The String to check.
190 | * @return Whether the given String is a valid pre-release identifier.
191 | * @since 0.5.0
192 | */
193 | public static boolean isValidPreRelease(String preRelease) {
194 | return preRelease != null &&
195 | (preRelease.isEmpty() || PRE_RELEASE.matcher(preRelease).matches());
196 | }
197 |
198 | /**
199 | * Returns whether the given String is a valid build meta data identifier. That is,
200 | * this method returns true
if, and only if the {@code buildMetaData}
201 | * parameter is either the empty string or properly formatted as a build meta data
202 | * identifier according to the semantic version specification.
203 | *
204 | *
205 | * Note: this method does not throw an exception upon null
input, but
206 | * returns false
instead.
207 | *
208 | *
209 | * @param buildMetaData The String to check.
210 | * @return Whether the given String is a valid build meta data identifier.
211 | * @since 0.5.0
212 | */
213 | public static boolean isValidBuildMetaData(String buildMetaData) {
214 | return buildMetaData != null &&
215 | (buildMetaData.isEmpty() || BUILD_MD.matcher(buildMetaData).matches());
216 | }
217 |
218 | /**
219 | * Returns the greater of the two given versions by comparing them using their natural
220 | * ordering. If both versions are equal, then the first argument is returned.
221 | *
222 | * @param v1 The first version.
223 | * @param v2 The second version.
224 | * @return The greater version.
225 | * @throws IllegalArgumentException If either argument is null
.
226 | * @since 0.4.0
227 | */
228 | public static VersionRegEx max(VersionRegEx v1, VersionRegEx v2) {
229 | require(v1 != null, "v1 is null");
230 | require(v2 != null, "v2 is null");
231 | return compare(v1, v2, false) < 0
232 | ? v2
233 | : v1;
234 | }
235 |
236 | /**
237 | * Returns the lower of the two given versions by comparing them using their natural
238 | * ordering. If both versions are equal, then the first argument is returned.
239 | *
240 | * @param v1 The first version.
241 | * @param v2 The second version.
242 | * @return The lower version.
243 | * @throws IllegalArgumentException If either argument is null
.
244 | * @since 0.4.0
245 | */
246 | public static VersionRegEx min(VersionRegEx v1, VersionRegEx v2) {
247 | require(v1 != null, "v1 is null");
248 | require(v2 != null, "v2 is null");
249 | return compare(v1, v2, false) <= 0
250 | ? v1
251 | : v2;
252 | }
253 |
254 | /**
255 | * Compares two versions, following the semantic version specification. Here
256 | * is a quote from semantic version 2.0.0
257 | * specification:
258 | *
259 | *
260 | * Precedence refers to how versions are compared to each other when ordered.
261 | * Precedence MUST be calculated by separating the version into major, minor, patch
262 | * and pre-release identifiers in that order (Build metadata does not figure into
263 | * precedence). Precedence is determined by the first difference when comparing each
264 | * of these identifiers from left to right as follows: Major, minor, and patch
265 | * versions are always compared numerically. Example: 1.0.0 < 2.0.0 < 2.1.0 <
266 | * 2.1.1. When major, minor, and patch are equal, a pre-release version has lower
267 | * precedence than a normal version. Example: 1.0.0-alpha < 1.0.0. Precedence for
268 | * two pre-release versions with the same major, minor, and patch version MUST be
269 | * determined by comparing each dot separated identifier from left to right until a
270 | * difference is found as follows: identifiers consisting of only digits are compared
271 | * numerically and identifiers with letters or hyphens are compared lexically in ASCII
272 | * sort order. Numeric identifiers always have lower precedence than non-numeric
273 | * identifiers. A larger set of pre-release fields has a higher precedence than a
274 | * smaller set, if all of the preceding identifiers are equal. Example: 1.0.0-alpha
275 | * < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 <
276 | * 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
277 | *
278 | *
279 | *
280 | * This method fulfills the general contract for Java's {@link Comparator Comparators}
281 | * and {@link Comparable Comparables}.
282 | *
283 | *
284 | * @param v1 The first version for comparison.
285 | * @param v2 The second version for comparison.
286 | * @return A value below 0 iff {@code v1 < v2}, a value above 0 iff
287 | * {@code v1 > v2 and 0 iff v1 = v2}.
288 | * @throws NullPointerException If either parameter is null.
289 | * @since 0.2.0
290 | */
291 | public static int compare(VersionRegEx v1, VersionRegEx v2) {
292 | // throw NPE to comply with Comparable specification
293 | if (v1 == null) {
294 | throw new NullPointerException("v1 is null");
295 | } else if (v2 == null) {
296 | throw new NullPointerException("v2 is null");
297 | }
298 | return compare(v1, v2, false);
299 | }
300 |
301 | /**
302 | * Compares two Versions with additionally considering the build meta data field if
303 | * all other parts are equal. Note: This is not part of the semantic version
304 | * specification.
305 | *
306 | *
307 | * Comparison of the build meta data parts happens exactly as for pre release
308 | * identifiers. Considering of build meta data first kicks in if both versions are
309 | * equal when using their natural order.
310 | *
311 | *
312 | *
313 | * This method fulfills the general contract for Java's {@link Comparator Comparators}
314 | * and {@link Comparable Comparables}.
315 | *
316 | *
317 | * @param v1 The first version for comparison.
318 | * @param v2 The second version for comparison.
319 | * @return A value below 0 iff {@code v1 < v2}, a value above 0 iff
320 | * {@code v1 > v2 and 0 iff v1 = v2}.
321 | * @throws NullPointerException If either parameter is null.
322 | * @since 0.3.0
323 | */
324 | public static int compareWithBuildMetaData(VersionRegEx v1, VersionRegEx v2) {
325 | // throw NPE to comply with Comparable specification
326 | if (v1 == null) {
327 | throw new NullPointerException("v1 is null");
328 | } else if (v2 == null) {
329 | throw new NullPointerException("v2 is null");
330 | }
331 | return compare(v1, v2, true);
332 | }
333 |
334 | private static int compare(VersionRegEx v1, VersionRegEx v2, boolean withBuildMetaData) {
335 | int result = 0;
336 | if (v1 != v2) {
337 | final int mc, mm, mp, pr, md;
338 | if ((mc = compareInt(v1.major, v2.major)) != 0) {
339 | result = mc;
340 | } else if ((mm = compareInt(v1.minor, v2.minor)) != 0) {
341 | result = mm;
342 | } else if ((mp = compareInt(v1.patch, v2.patch)) != 0) {
343 | result = mp;
344 | } else if ((pr = comparePreRelease(v1, v2)) != 0) {
345 | result = pr;
346 | } else if (withBuildMetaData && ((md = compareBuildMetaData(v1, v2)) != 0)) {
347 | result = md;
348 | }
349 | }
350 | return result;
351 | }
352 |
353 | private static int compareInt(int a, int b) {
354 | return a - b;
355 | }
356 |
357 | private static int comparePreRelease(VersionRegEx v1, VersionRegEx v2) {
358 | return compareLiterals(v1.getPreRelease(), v2.getPreRelease());
359 | }
360 |
361 | private static int compareBuildMetaData(VersionRegEx v1, VersionRegEx v2) {
362 | return compareLiterals(v1.getBuildMetaData(), v2.getBuildMetaData());
363 | }
364 |
365 | private static int compareLiterals(String v1Literal, String v2Literal) {
366 | int result = 0;
367 | if (!v1Literal.isEmpty() && !v2Literal.isEmpty()) {
368 | // compare pre release parts
369 | result = compareIdentifiers(v1Literal.split("\\."), v2Literal.split("\\."));
370 | } else if (!v1Literal.isEmpty()) {
371 | // other is greater, because it is no pre release
372 | result = -1;
373 | } else if (!v2Literal.isEmpty()) {
374 | // this is greater because other is no pre release
375 | result = 1;
376 | }
377 | return result;
378 | }
379 |
380 | private static int compareIdentifiers(String[] parts1, String[] parts2) {
381 | final int min = Math.min(parts1.length, parts2.length);
382 | for (int i = 0; i < min; ++i) {
383 | final int r = compareIdentifierParts(parts1[i], parts2[i]);
384 | if (r != 0) {
385 | // versions differ in part i
386 | return r;
387 | }
388 | }
389 |
390 | // all id's are equal, so compare amount of id's
391 | return compareInt(parts1.length, parts2.length);
392 | }
393 |
394 | private static int compareIdentifierParts(String p1, String p2) {
395 | final int num1 = isNumeric(p1);
396 | final int num2 = isNumeric(p2);
397 |
398 | final int result;
399 | if (num1 < 0 && num2 < 0) {
400 | // both are not numerical -> compare lexically
401 | result = p1.compareTo(p2);
402 | } else if (num1 >= 0 && num2 >= 0) {
403 | // both are numerical
404 | result = compareInt(num1, num2);
405 | } else if (num1 >= 0) {
406 | // only part1 is numerical -> p2 is greater
407 | result = -1;
408 | } else {
409 | // only part2 is numerical -> p1 is greater
410 | result = 1;
411 | }
412 | return result;
413 | }
414 |
415 | /**
416 | * Determines whether s is a positive number. If it is, the number is returned,
417 | * otherwise the result is -1.
418 | *
419 | * @param s The String to check.
420 | * @return The positive number (incl. 0) if s a number, or -1 if it is not.
421 | */
422 | private static int isNumeric(String s) {
423 | try {
424 | return Integer.parseInt(s);
425 | } catch (final NumberFormatException e) {
426 | return -1;
427 | }
428 | }
429 |
430 | /**
431 | * Creates a new Version from the provided components. Neither value of
432 | * {@code major, minor} or {@code patch} must be lower than 0 and at least one must be
433 | * greater than zero. {@code preRelease} or {@code buildMetaData} may be the empty
434 | * String. In this case, the created {@code Version} will have no pre release resp.
435 | * build meta data field. If those parameters are not empty, they must conform to the
436 | * semantic version specification.
437 | *
438 | * @param major The major version.
439 | * @param minor The minor version.
440 | * @param patch The patch version.
441 | * @param preRelease The pre release version or the empty string.
442 | * @param buildMetaData The build meta data field or the empty string.
443 | * @return The version instance.
444 | * @throws VersionFormatException If {@code preRelease} or {@code buildMetaData} does
445 | * not conform to the semantic version specification.
446 | */
447 | public static final VersionRegEx create(int major, int minor, int patch,
448 | String preRelease,
449 | String buildMetaData) {
450 | checkParams(major, minor, patch);
451 | require(preRelease != null, "preRelease is null");
452 | require(buildMetaData != null, "buildMetaData is null");
453 |
454 | if (!isValidPreRelease(preRelease)) {
455 | throw new VersionFormatException(
456 | String.format("Illegal pre-release part: %s", preRelease));
457 | } else if (!isValidBuildMetaData(buildMetaData)) {
458 | throw new VersionFormatException(
459 | String.format("Illegal build-meta-data part: %s", buildMetaData));
460 | }
461 | return new VersionRegEx(major, minor, patch, preRelease, buildMetaData);
462 | }
463 |
464 | /**
465 | * Creates a new Version from the provided components. The version's build meta data
466 | * field will be empty. Neither value of {@code major, minor} or {@code patch} must be
467 | * lower than 0 and at least one must be greater than zero. {@code preRelease} may be
468 | * the empty String. In this case, the created version will have no pre release field.
469 | * If it is not empty, it must conform to the specifications of the semantic version.
470 | *
471 | * @param major The major version.
472 | * @param minor The minor version.
473 | * @param patch The patch version.
474 | * @param preRelease The pre release version or the empty string.
475 | * @return The version instance.
476 | * @throws VersionFormatException If {@code preRelease} is not empty and does not
477 | * conform to the semantic versioning specification
478 | */
479 | public static final VersionRegEx create(int major, int minor, int patch,
480 | String preRelease) {
481 | checkParams(major, minor, patch);
482 | require(preRelease != null, "preRelease is null");
483 |
484 | if (!preRelease.isEmpty() && !PRE_RELEASE.matcher(preRelease).matches()) {
485 | throw new VersionFormatException(preRelease);
486 | }
487 | return new VersionRegEx(major, minor, patch, preRelease, "");
488 | }
489 |
490 | /**
491 | * Creates a new Version from the three provided components. The version's pre release
492 | * and build meta data fields will be empty. Neither value must be lower than 0 and at
493 | * least one must be greater than zero
494 | *
495 | * @param major The major version.
496 | * @param minor The minor version.
497 | * @param patch The patch version.
498 | * @return The version instance.
499 | */
500 | public static final VersionRegEx create(int major, int minor, int patch) {
501 | checkParams(major, minor, patch);
502 | return new VersionRegEx(major, minor, patch, "", "");
503 | }
504 |
505 | private static void checkParams(int major, int minor, int patch) {
506 | require(major >= 0, "major < 0");
507 | require(minor >= 0, "minor < 0");
508 | require(patch >= 0, "patch < 0");
509 | require(major != 0 || minor != 0 || patch != 0, "all parts are 0");
510 | }
511 |
512 | private static void require(boolean condition, String message) {
513 | if (!condition) {
514 | throw new IllegalArgumentException(message);
515 | }
516 | }
517 |
518 | /**
519 | * Tries to parse the provided String as a semantic version. If the string does not
520 | * conform to the semantic version specification, a {@link VersionFormatException}
521 | * will be thrown.
522 | *
523 | * @param versionString The String to parse.
524 | * @return The parsed version.
525 | * @throws VersionFormatException If the String is no valid version
526 | * @throws IllegalArgumentException If {@code versionString} is null
.
527 | */
528 | public static final VersionRegEx parseVersion(String versionString) {
529 | require(versionString != null, "versionString is null");
530 | final Matcher m = VERSION_PATTERN.matcher(versionString);
531 | if (!m.matches()) {
532 | throw new VersionFormatException(versionString);
533 | }
534 |
535 | final int major = Integer.parseInt(m.group(MAJOR_GROUP));
536 | final int minor = Integer.parseInt(m.group(MINOR_GROUP));
537 | final int patch = Integer.parseInt(m.group(PATCH_GROUP));
538 |
539 | checkParams(major, minor, patch);
540 |
541 | final String preRelease;
542 | if (m.group(PRE_RELEASE_GROUP) != null) {
543 | preRelease = m.group(PRE_RELEASE_GROUP);
544 | } else {
545 | preRelease = "";
546 | }
547 |
548 | final String buildMD;
549 | if (m.group(BUILD_MD_GROUP) != null) {
550 | buildMD = m.group(BUILD_MD_GROUP);
551 | } else {
552 | buildMD = "";
553 | }
554 |
555 | return new VersionRegEx(major, minor, patch, preRelease, buildMD);
556 | }
557 |
558 | /**
559 | * Tries to parse the provided String as a semantic version. If
560 | * {@code allowPreRelease} is false
, the String must have neither a
561 | * pre-release nor a build meta data part. Thus the given String must have the format
562 | * {@code X.Y.Z} where at least one part must be greater than zero.
563 | *
564 | *
565 | * If {@code allowPreRelease} is true
, the String is parsed according to
566 | * the normal semantic version specification.
567 | *
568 | *
569 | * @param versionString The String to parse.
570 | * @param allowPreRelease Whether pre-release and build meta data field are allowed.
571 | * @return The parsed version.
572 | * @throws VersionFormatException If the String is no valid version
573 | * @since 0.4.0
574 | */
575 | public static VersionRegEx parseVersion(String versionString, boolean allowPreRelease) {
576 | final VersionRegEx version = parseVersion(versionString);
577 | if (!allowPreRelease && (version.isPreRelease() || version.hasBuildMetaData())) {
578 | throw new VersionFormatException(String.format(
579 | "Version is expected to have no pre-release or build meta data part"));
580 | }
581 | return version;
582 | }
583 |
584 | /**
585 | * Returns the lower of this version and the given version according to its natural
586 | * ordering. If versions are equal, {@code this} is returned.
587 | *
588 | * @param other The version to compare with.
589 | * @return The lower version.
590 | * @throws IllegalArgumentException If {@code other} is null
.
591 | * @since 0.5.0
592 | * @see #min(VersionRegEx, VersionRegEx)
593 | */
594 | public VersionRegEx min(VersionRegEx other) {
595 | return min(this, other);
596 | }
597 |
598 | /**
599 | * Returns the greater of this version and the given version according to its natural
600 | * ordering. If versions are equal, {@code this} is returned.
601 | *
602 | * @param other The version to compare with.
603 | * @return The greater version.
604 | * @throws IllegalArgumentException If {@code other} is null
.
605 | * @since 0.5.0
606 | * @see #max(VersionRegEx, VersionRegEx)
607 | */
608 | public VersionRegEx max(VersionRegEx other) {
609 | return max(this, other);
610 | }
611 |
612 | /**
613 | * Gets this version's major number.
614 | *
615 | * @return The major version.
616 | */
617 | public int getMajor() {
618 | return this.major;
619 | }
620 |
621 | /**
622 | * Gets this version's minor number.
623 | *
624 | * @return The minor version.
625 | */
626 | public int getMinor() {
627 | return this.minor;
628 | }
629 |
630 | /**
631 | * Gets this version's path number.
632 | *
633 | * @return The patch number.
634 | */
635 | public int getPatch() {
636 | return this.patch;
637 | }
638 |
639 | /**
640 | * Gets the pre release parts of this version as array by splitting the pre result
641 | * version string at the dots.
642 | *
643 | * @return Pre release version as array. Array is empty if this version has no pre
644 | * release part.
645 | */
646 | public String[] getPreReleaseParts() {
647 | return this.preRelease.isEmpty() ? EMPTY_ARRAY : this.preRelease.split("\\.");
648 | }
649 |
650 | /**
651 | * Gets the pre release identifier of this version. If this version has no such
652 | * identifier, an empty string is returned.
653 | *
654 | * @return This version's pre release identifier or an empty String if this version
655 | * has no such identifier.
656 | */
657 | public String getPreRelease() {
658 | return this.preRelease;
659 | }
660 |
661 | /**
662 | * Gets this version's build meta data. If this version has no build meta data, the
663 | * returned string is empty.
664 | *
665 | * @return The build meta data or an empty String if this version has no build meta
666 | * data.
667 | */
668 | public String getBuildMetaData() {
669 | return this.buildMetaData;
670 | }
671 |
672 | /**
673 | * Gets this version's build meta data as array by splitting the meta data at dots. If
674 | * this version has no build meta data, the result is an empty array.
675 | *
676 | * @return The build meta data as array.
677 | */
678 | public String[] getBuildMetaDataParts() {
679 | return this.buildMetaData.isEmpty() ? EMPTY_ARRAY
680 | : this.buildMetaData.split("\\.");
681 | }
682 |
683 | /**
684 | * Determines whether this version is still under initial development.
685 | *
686 | * @return true
iff this version's major part is zero.
687 | */
688 | public boolean isInitialDevelopment() {
689 | return this.major == 0;
690 | }
691 |
692 | /**
693 | * Determines whether this is a pre release version.
694 | *
695 | * @return true
iff {@link #getPreRelease()} is not empty.
696 | */
697 | public boolean isPreRelease() {
698 | return !this.preRelease.isEmpty();
699 | }
700 |
701 | /**
702 | * Determines whether this version has a build meta data field.
703 | *
704 | * @return true
iff {@link #getBuildMetaData()} is not empty.
705 | */
706 | public boolean hasBuildMetaData() {
707 | return !this.buildMetaData.isEmpty();
708 | }
709 |
710 | /**
711 | * Creates a String representation of this version by joining its parts together as by
712 | * the semantic version specification.
713 | *
714 | * @return The version as a String.
715 | */
716 | @Override
717 | public String toString() {
718 | final StringBuilder b = new StringBuilder(this.preRelease.length()
719 | + this.buildMetaData.length() + TO_STRING_ESTIMATE);
720 | b.append(this.major).append(".").append(this.minor).append(".")
721 | .append(this.patch);
722 | if (!this.preRelease.isEmpty()) {
723 | b.append("-").append(this.preRelease);
724 | }
725 | if (!this.buildMetaData.isEmpty()) {
726 | b.append("+").append(this.buildMetaData);
727 | }
728 | return b.toString();
729 | }
730 |
731 | /**
732 | * The hash code for a version instance is computed from the fields {@link #getMajor()
733 | * major}, {@link #getMinor() minor}, {@link #getPatch() patch} and
734 | * {@link #getPreRelease() pre-release}.
735 | *
736 | * @return A hash code for this object.
737 | */
738 | @Override
739 | public int hashCode() {
740 | int h = this.hash;
741 | if (h == 0) {
742 | h = HASH_PRIME + this.major;
743 | h = HASH_PRIME * h + this.minor;
744 | h = HASH_PRIME * h + this.patch;
745 | h = HASH_PRIME * h + this.preRelease.hashCode();
746 | this.hash = h;
747 | }
748 | return this.hash;
749 | }
750 |
751 | /**
752 | * Determines whether this version is equal to the passed object. This is the case if
753 | * the passed object is an instance of Version and this version
754 | * {@link #compareTo(VersionRegEx) compared} to the provided one yields 0. Thus, this
755 | * method ignores the {@link #getBuildMetaData()} field.
756 | *
757 | * @param obj the object to compare with.
758 | * @return true
iff {@code obj} is an instance of {@code Version} and
759 | * {@code this.compareTo((Version) obj) == 0}
760 | * @see #compareTo(VersionRegEx)
761 | */
762 | @Override
763 | public boolean equals(Object obj) {
764 | return testEquality(obj, false);
765 | }
766 |
767 | /**
768 | * Determines whether this version is equal to the passed object (as determined by
769 | * {@link #equals(Object)} and additionally considers the build meta data part of both
770 | * versions for equality.
771 | *
772 | * @param obj The object to compare with.
773 | * @return true
iff {@code this.equals(obj)} and
774 | * {@code this.getBuildMetaData().equals(((Version) obj).getBuildMetaData())}
775 | * @since 0.4.0
776 | */
777 | public boolean equalsWithBuildMetaData(Object obj) {
778 | return testEquality(obj, true);
779 | }
780 |
781 | private boolean testEquality(Object obj, boolean includeBuildMd) {
782 | return obj == this || obj instanceof VersionRegEx
783 | && compare(this, (VersionRegEx) obj, includeBuildMd) == 0;
784 | }
785 |
786 | /**
787 | * Compares this version to the provided one, following the semantic
788 | * versioning specification. See {@link #compare(VersionRegEx, VersionRegEx)} for more
789 | * information.
790 | *
791 | * @param other The version to compare to.
792 | * @return A value lower than 0 if this < other, a value greater than 0 if this
793 | * > other and 0 if this == other. The absolute value of the result has no
794 | * semantical interpretation.
795 | */
796 | @Override
797 | public int compareTo(VersionRegEx other) {
798 | return compare(this, other);
799 | }
800 |
801 | /**
802 | * Compares this version to the provided one. Unlike the {@link #compareTo(VersionRegEx)}
803 | * method, this one additionally considers the build meta data field of both versions,
804 | * if all other parts are equal. Note: This is not part of the semantic
805 | * version specification.
806 | *
807 | *
808 | * Comparison of the build meta data parts happens exactly as for pre release
809 | * identifiers. Considering of build meta data first kicks in if both versions are
810 | * equal when using their natural order.
811 | *
812 | *
813 | * @param other The version to compare to.
814 | * @return A value lower than 0 if this < other, a value greater than 0 if this
815 | * > other and 0 if this == other. The absolute value of the result has no
816 | * semantical interpretation.
817 | * @since 0.3.0
818 | */
819 | public int compareToWithBuildMetaData(VersionRegEx other) {
820 | return compareWithBuildMetaData(this, other);
821 | }
822 | }
823 |
--------------------------------------------------------------------------------
/src/main/java/de/skuzzle/semantic/Version.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Simon Taddiken
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy
7 | * of this software and associated documentation files (the "Software"), to deal
8 | * in the Software without restriction, including without limitation the rights
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | * copies of the Software, and to permit persons to whom the Software is
11 | * furnished to do so, subject to the following conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in
14 | * all copies or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | * SOFTWARE.
23 | */
24 | package de.skuzzle.semantic;
25 |
26 | import java.io.ObjectStreamException;
27 | import java.io.Serializable;
28 | import java.util.ArrayList;
29 | import java.util.Arrays;
30 | import java.util.Comparator;
31 | import java.util.List;
32 | import java.util.Locale;
33 |
34 | /**
35 | * This class is an implementation of the full semantic version 2.0.0
36 | * specification. Instances can be obtained using the
37 | * static overloads of the create method or by {@link #parseVersion(String)
38 | * parsing} a String. This class implements {@link Comparable} to compare two versions by
39 | * following the specifications linked to above. The {@link #equals(Object)} method
40 | * conforms to the result of {@link #compareTo(Version)}, {@link #hashCode()} is
41 | * implemented appropriately. Neither method considers the {@link #getBuildMetaData()
42 | * build meta data} field for comparison.
43 | *
44 | *
45 | * Instances of this class are immutable and thus thread safe. This also means that all
46 | * methods taking an array or other kind of modifiable objects as input, will first make a
47 | * copy before using it as internal state.
48 | *
49 | *
50 | * Note that unless stated otherwise, none of the public methods of this class accept
51 | * null
values. Most methods will throw an {@link IllegalArgumentException}
52 | * when encountering a null
argument. However, to comply with the
53 | * {@link Comparable} contract, the comparison methods will throw a
54 | * {@link NullPointerException} instead.
55 | *
56 | * @author Simon Taddiken
57 | */
58 | public final class Version implements Comparable, Serializable {
59 |
60 | /** Conforms to all Version implementations since 0.6.0 */
61 | private static final long serialVersionUID = 6034927062401119911L;
62 |
63 | private static final String[] EMPTY_ARRAY = new String[0];
64 |
65 | /**
66 | * The minimum value '0.0.0' for valid versions where all parts are 0 or empty.
67 | *
68 | * @since 2.1.0
69 | */
70 | public static final Version ZERO = Version.create(0, 0, 0);
71 |
72 | /**
73 | * Semantic Version Specification to which this class complies.
74 | *
75 | * @since 0.2.0
76 | */
77 | public static final Version COMPLIANCE = Version.create(2, 0, 0);
78 |
79 | /**
80 | * This exception indicates that a version- or a part of a version string could not be
81 | * parsed according to the semantic version specification.
82 | *
83 | * @author Simon Taddiken
84 | */
85 | public static class VersionFormatException extends RuntimeException {
86 |
87 | private static final long serialVersionUID = 1L;
88 |
89 | /**
90 | * Creates a new VersionFormatException with the given message.
91 | *
92 | * @param message The exception message.
93 | */
94 | private VersionFormatException(String message) {
95 | super(message);
96 | }
97 | }
98 |
99 | /**
100 | * Comparator for natural version ordering. See {@link #compare(Version, Version)} for
101 | * more information.
102 | *
103 | * Instead of using this field, consider using a method reference like in
104 | * Version::compare
.
105 | *
106 | * @since 0.2.0
107 | */
108 | public static final Comparator NATURAL_ORDER = new Comparator() {
109 |
110 | @Override
111 | public int compare(Version o1, Version o2) {
112 | return o1.compareTo(o2);
113 | }
114 | };
115 |
116 | /**
117 | * Comparator for ordering versions with additionally considering the build meta data
118 | * field when comparing versions.
119 | *
120 | * Instead of using this field, consider using a method reference like in
121 | * Version::compareWithBuildMetaData
.
122 | *
123 | * Note: this comparator imposes orderings that are inconsistent with equals.
124 | *
125 | * @since 0.3.0
126 | */
127 | public static final Comparator WITH_BUILD_META_DATA_ORDER = new Comparator() {
128 |
129 | @Override
130 | public int compare(Version o1, Version o2) {
131 | return o1.compareToWithBuildMetaData(o2);
132 | }
133 | };
134 |
135 | private static final int TO_STRING_ESTIMATE = 16;
136 |
137 | // state machine states for parsing a version string
138 | private static final int STATE_MAJOR_INIT = 0;
139 | private static final int STATE_MAJOR_LEADING_ZERO = 1;
140 | private static final int STATE_MAJOR_DEFAULT = 2;
141 | private static final int STATE_MINOR_INIT = 3;
142 | private static final int STATE_MINOR_LEADING_ZERO = 4;
143 | private static final int STATE_MINOR_DEFAULT = 5;
144 | private static final int STATE_PATCH_INIT = 6;
145 | private static final int STATE_PATCH_LEADING_ZERO = 7;
146 | private static final int STATE_PATCH_DEFAULT = 8;
147 | private static final int STATE_PRERELEASE_INIT = 9;
148 | private static final int STATE_BUILDMD_INIT = 10;
149 |
150 | private static final int STATE_PART_INIT = 0;
151 | private static final int STATE_PART_LEADING_ZERO = 1;
152 | private static final int STATE_PART_NUMERIC = 2;
153 | private static final int STATE_PART_DEFAULT = 3;
154 |
155 | private static final int DECIMAL = 10;
156 |
157 | private static final int EOS = -1;
158 | private static final int FAILURE = -2;
159 |
160 | private final int major;
161 | private final int minor;
162 | private final int patch;
163 | private final String[] preReleaseParts;
164 | private final String[] buildMetaDataParts;
165 |
166 | // Since 1.1.0
167 | // these fields are only necessary for deserializing previous versions
168 | // see #readResolve method
169 | @Deprecated
170 | private String preRelease;
171 | @Deprecated
172 | private String buildMetaData;
173 |
174 | // store hash code once it has been calculated
175 | private static final int NOT_YET_CALCULATED = 2;
176 | private static final int HASH_PRIME = 31;
177 | private volatile int hash = NOT_YET_CALCULATED;
178 |
179 | private Version(int major, int minor, int patch, String[] preRelease,
180 | String[] buildMd) {
181 | checkParams(major, minor, patch);
182 | this.major = major;
183 | this.minor = minor;
184 | this.patch = patch;
185 | this.preReleaseParts = preRelease;
186 | this.buildMetaDataParts = buildMd;
187 | }
188 |
189 | private static Version parse(String s, boolean verifyOnly) {
190 | /*
191 | * Since 1.1.0:
192 | *
193 | * This huge and ugly inline parsing replaces the prior regex because it is way
194 | * faster. Besides that it also does provide better error messages in case a
195 | * String could not be parsed correctly. Condition and mutation coverage is
196 | * extremely high to ensure correctness.
197 | */
198 |
199 | // note: getting the char array once is faster than calling charAt multiple times
200 | final char[] stream = s.toCharArray();
201 | int major = 0;
202 | int minor = 0;
203 | int patch = 0;
204 | int state = STATE_MAJOR_INIT;
205 |
206 | List preRelease = null;
207 | List buildMd = null;
208 | loop: for (int i = 0; i <= stream.length; ++i) {
209 | final int c = i < stream.length ? stream[i] : EOS;
210 |
211 | switch (state) {
212 |
213 | // Parse major part
214 | case STATE_MAJOR_INIT:
215 | if (c == '0') {
216 | state = STATE_MAJOR_LEADING_ZERO;
217 | } else if (c >= '1' && c <= '9') {
218 | major = major * DECIMAL + Character.digit(c, DECIMAL);
219 | state = STATE_MAJOR_DEFAULT;
220 | } else if (verifyOnly) {
221 | return null;
222 | } else {
223 | throw unexpectedChar(s, c);
224 | }
225 | break;
226 | case STATE_MAJOR_LEADING_ZERO:
227 | if (c == '.') {
228 | // single 0 is allowed
229 | state = STATE_MINOR_INIT;
230 | } else if (c >= '0' && c <= '9') {
231 | if (verifyOnly) {
232 | return null;
233 | }
234 | throw illegalLeadingChar(s, '0', "major");
235 | } else if (verifyOnly) {
236 | return null;
237 | } else {
238 | throw unexpectedChar(s, c);
239 | }
240 | break;
241 | case STATE_MAJOR_DEFAULT:
242 | if (c >= '0' && c <= '9') {
243 | major = major * DECIMAL + Character.digit(c, DECIMAL);
244 | } else if (c == '.') {
245 | state = STATE_MINOR_INIT;
246 | } else if (verifyOnly) {
247 | return null;
248 | } else {
249 | throw unexpectedChar(s, c);
250 | }
251 | break;
252 |
253 | // parse minor part
254 | case STATE_MINOR_INIT:
255 | if (c == '0') {
256 | state = STATE_MINOR_LEADING_ZERO;
257 | } else if (c >= '1' && c <= '9') {
258 | minor = minor * DECIMAL + Character.digit(c, DECIMAL);
259 | state = STATE_MINOR_DEFAULT;
260 | } else if (verifyOnly) {
261 | return null;
262 | } else {
263 | throw unexpectedChar(s, c);
264 | }
265 | break;
266 | case STATE_MINOR_LEADING_ZERO:
267 | if (c == '.') {
268 | // single 0 is allowed
269 | state = STATE_PATCH_INIT;
270 | } else if (c >= '0' && c <= '9') {
271 | if (verifyOnly) {
272 | return null;
273 | }
274 | throw illegalLeadingChar(s, '0', "minor");
275 | } else if (verifyOnly) {
276 | return null;
277 | } else {
278 | throw unexpectedChar(s, c);
279 | }
280 | break;
281 | case STATE_MINOR_DEFAULT:
282 | if (c >= '0' && c <= '9') {
283 | minor = minor * DECIMAL + Character.digit(c, DECIMAL);
284 | } else if (c == '.') {
285 | state = STATE_PATCH_INIT;
286 | } else if (verifyOnly) {
287 | return null;
288 | } else {
289 | throw unexpectedChar(s, c);
290 | }
291 | break;
292 |
293 | // parse patch part
294 | case STATE_PATCH_INIT:
295 | if (c == '0') {
296 | state = STATE_PATCH_LEADING_ZERO;
297 | } else if (c >= '1' && c <= '9') {
298 | patch = patch * DECIMAL + Character.digit(c, DECIMAL);
299 | state = STATE_PATCH_DEFAULT;
300 | } else if (verifyOnly) {
301 | return null;
302 | } else {
303 | throw unexpectedChar(s, c);
304 | }
305 | break;
306 | case STATE_PATCH_LEADING_ZERO:
307 | if (c == '-') {
308 | // single 0 is allowed
309 | state = STATE_PRERELEASE_INIT;
310 | } else if (c == '+') {
311 | state = STATE_BUILDMD_INIT;
312 | } else if (c == EOS) {
313 | break loop;
314 | } else if (c >= '0' && c <= '9') {
315 | if (verifyOnly) {
316 | return null;
317 | }
318 | throw illegalLeadingChar(s, '0', "patch");
319 | } else if (verifyOnly) {
320 | return null;
321 | } else {
322 | throw unexpectedChar(s, c);
323 | }
324 | break;
325 | case STATE_PATCH_DEFAULT:
326 | if (c >= '0' && c <= '9') {
327 | patch = patch * DECIMAL + Character.digit(c, DECIMAL);
328 | } else if (c == '-') {
329 | state = STATE_PRERELEASE_INIT;
330 | } else if (c == '+') {
331 | state = STATE_BUILDMD_INIT;
332 | } else if (c != EOS) {
333 | // eos is allowed here
334 | if (verifyOnly) {
335 | return null;
336 | }
337 | throw unexpectedChar(s, c);
338 | }
339 | break;
340 | case STATE_PRERELEASE_INIT:
341 |
342 | preRelease = verifyOnly ? null : new ArrayList();
343 | i = parseID(stream, s, i, verifyOnly, false, true, preRelease,
344 | "pre-release");
345 | if (i == FAILURE) {
346 | // implies verifyOnly == true, otherwise exception would have been
347 | // thrown
348 | return null;
349 | }
350 | final int c1 = i < stream.length ? stream[i] : EOS;
351 |
352 | if (c1 == '+') {
353 | state = STATE_BUILDMD_INIT;
354 | } else {
355 | break loop;
356 | }
357 | break;
358 |
359 | case STATE_BUILDMD_INIT:
360 | buildMd = verifyOnly ? null : new ArrayList();
361 | i = parseID(stream, s, i, verifyOnly, true, false, buildMd,
362 | "build-meta-data");
363 | if (i == FAILURE) {
364 | // implies verifyOnly == true, otherwise exception would have been
365 | // thrown
366 | return null;
367 | }
368 |
369 | break loop;
370 | default:
371 | throw new IllegalStateException("Illegal state: " + state);
372 | }
373 | }
374 | final String[] prerelease = preRelease == null ? EMPTY_ARRAY
375 | : preRelease.toArray(new String[preRelease.size()]);
376 | final String[] buildmetadata = buildMd == null ? EMPTY_ARRAY
377 | : buildMd.toArray(new String[buildMd.size()]);
378 | return new Version(major, minor, patch, prerelease, buildmetadata);
379 | }
380 |
381 | private static int parseID(char[] stream, String full, int start, boolean verifyOnly,
382 | boolean allowLeading0, boolean preRelease, List parts,
383 | String partName) {
384 |
385 | assert verifyOnly || parts != null;
386 |
387 | final StringBuilder b = verifyOnly
388 | ? null
389 | : new StringBuilder(stream.length - start);
390 |
391 | int i = start;
392 | while (i <= stream.length) {
393 |
394 | i = parseIDPart(stream, full, i, verifyOnly, allowLeading0, preRelease, true,
395 | b, partName);
396 | if (i == FAILURE) {
397 | // implies verifyOnly == true, otherwise exception would have been thrown
398 | return FAILURE;
399 | } else if (!verifyOnly) {
400 | parts.add(b.toString());
401 | }
402 |
403 | final int c = i < stream.length ? stream[i] : EOS;
404 | if (c == '.') {
405 | // keep looping
406 | ++i;
407 | } else {
408 | // identifier is done (hit EOS or '+')
409 | return i;
410 | }
411 | }
412 | throw new IllegalStateException();
413 | }
414 |
415 | private static int parseIDPart(char[] stream, String full, int start,
416 | boolean verifyOnly,
417 | boolean allowLeading0, boolean preRelease, boolean allowDot,
418 | StringBuilder b, String partName) {
419 |
420 | if (b != null) {
421 | b.setLength(0);
422 | }
423 |
424 | int state = STATE_PART_INIT;
425 | for (int i = start; i <= stream.length; ++i) {
426 | final int c = i < stream.length ? stream[i] : EOS;
427 |
428 | switch (state) {
429 | case STATE_PART_INIT:
430 | if (c == '0' && !allowLeading0) {
431 | state = STATE_PART_LEADING_ZERO;
432 | if (b != null) {
433 | b.append('0');
434 | }
435 | } else if (c == '-' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
436 | || c >= '0' && c <= '9') {
437 | if (b != null) {
438 | b.appendCodePoint(c);
439 | }
440 | state = STATE_PART_DEFAULT;
441 | } else if (c == '.') {
442 | if (verifyOnly) {
443 | return FAILURE;
444 | }
445 | throw unexpectedChar(full, -1);
446 | } else {
447 | if (verifyOnly) {
448 | return FAILURE;
449 | }
450 | throw unexpectedChar(full, c);
451 | }
452 | break;
453 | case STATE_PART_LEADING_ZERO:
454 | // when in this state we consumed a single '0'
455 | if (c == '-' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
456 | if (b != null) {
457 | b.appendCodePoint(c);
458 | }
459 | state = STATE_PART_DEFAULT;
460 | } else if (c >= '0' && c <= '9') {
461 | if (b != null) {
462 | b.appendCodePoint(c);
463 | }
464 | state = STATE_PART_NUMERIC;
465 | } else if (c == '.' && allowDot || c == EOS || c == '+' && preRelease) {
466 | // if we are parsing a pre release part it can be terminated by a
467 | // '+' in case a build meta data follows
468 |
469 | // here, this part consist of a single '0'
470 | return i;
471 | } else if (verifyOnly) {
472 | return FAILURE;
473 | } else {
474 | throw unexpectedChar(full, c);
475 | }
476 | break;
477 | case STATE_PART_NUMERIC:
478 | // when in this state, the part began with a '0' and we only consumed
479 | // numeric chars so far
480 | if (c == '-' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
481 | if (b != null) {
482 | b.appendCodePoint(c);
483 | }
484 | state = STATE_PART_DEFAULT;
485 | } else if (c >= '0' && c <= '9') {
486 | if (b != null) {
487 | b.appendCodePoint(c);
488 | }
489 | } else if (c == '.' || c == EOS || c == '+' && preRelease) {
490 | // if we are parsing a pre release part it can be terminated by a
491 | // '+' in case a build meta data follows
492 |
493 | if (verifyOnly) {
494 | return FAILURE;
495 | }
496 | throw illegalLeadingChar(full, '0', partName);
497 | } else if (verifyOnly) {
498 | return FAILURE;
499 | } else {
500 | throw unexpectedChar(full, c);
501 | }
502 | break;
503 | case STATE_PART_DEFAULT:
504 | if (c == '-' || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z'
505 | || c >= '0' && c <= '9') {
506 | if (b != null) {
507 | b.appendCodePoint(c);
508 | }
509 | } else if (c == '.' && allowDot || c == EOS || c == '+' && preRelease) {
510 | // if we are parsing a pre release part it can be terminated by a
511 | // '+' in case a build meta data follows
512 | return i;
513 | } else if (verifyOnly) {
514 | return FAILURE;
515 | } else {
516 | throw unexpectedChar(full, c);
517 | }
518 | break;
519 | }
520 | }
521 |
522 | throw new IllegalStateException();
523 | }
524 |
525 | private static VersionFormatException illegalLeadingChar(String v, int c,
526 | String part) {
527 | return new VersionFormatException(
528 | String.format("Illegal leading char '%c' in %s part of %s", c, part, v));
529 | }
530 |
531 | private static VersionFormatException unexpectedChar(String v, int c) {
532 | if (c == EOS) {
533 | return new VersionFormatException(String.format(
534 | "Incomplete version part in %s", v));
535 | }
536 | return new VersionFormatException(
537 | String.format("Unexpected char in %s: %c", v, c));
538 | }
539 |
540 | /**
541 | * Creates a new Version from this one, replacing only the major part with the given
542 | * one. All other parts will remain the same as in this Version.
543 | *
544 | * @param newMajor The new major version.
545 | * @return A new Version.
546 | * @throws IllegalArgumentException If all fields in the resulting Version are 0.
547 | * @since 1.1.0
548 | */
549 | public Version withMajor(int newMajor) {
550 | return new Version(newMajor, this.minor, this.patch, this.preReleaseParts,
551 | this.buildMetaDataParts);
552 | }
553 |
554 | /**
555 | * Creates a new Version from this one, replacing only the minor part with the given
556 | * one. All other parts will remain the same as in this Version.
557 | *
558 | * @param newMinor The new minor version.
559 | * @return A new Version.
560 | * @throws IllegalArgumentException If all fields in the resulting Version are 0.
561 | * @since 1.1.0
562 | */
563 | public Version withMinor(int newMinor) {
564 | return new Version(this.major, newMinor, this.patch, this.preReleaseParts,
565 | this.buildMetaDataParts);
566 | }
567 |
568 | /**
569 | * Creates a new Version from this one, replacing only the patch part with the given
570 | * one. All other parts will remain the same as in this Version.
571 | *
572 | * @param newPatch The new patch version.
573 | * @return A new Version.
574 | * @throws IllegalArgumentException If all fields in the resulting Version are 0.
575 | * @since 1.1.0
576 | */
577 | public Version withPatch(int newPatch) {
578 | return new Version(this.major, this.minor, newPatch, this.preReleaseParts,
579 | this.buildMetaDataParts);
580 | }
581 |
582 | /**
583 | * Creates a new Version from this one, replacing only the pre-release part with the
584 | * given String. All other parts will remain the same as in this Version. You can
585 | * remove the pre-release part by passing an empty String.
586 | *
587 | * @param newPreRelease The new pre-release identifier.
588 | * @return A new Version.
589 | * @throws VersionFormatException If the given String is not a valid pre-release
590 | * identifier.
591 | * @throws IllegalArgumentException If preRelease is null.
592 | * @since 1.1.0
593 | */
594 | public Version withPreRelease(String newPreRelease) {
595 | require(newPreRelease != null, "newPreRelease is null");
596 | final String[] newPreReleaseParts = parsePreRelease(newPreRelease);
597 | return new Version(this.major, this.minor, this.patch, newPreReleaseParts,
598 | this.buildMetaDataParts);
599 | }
600 |
601 | /**
602 | * Creates a new Version from this one, replacing only the pre-release part with the
603 | * given array. All other parts will remain the same as in this Version. You can
604 | * remove the pre-release part by passing an empty array.
605 | *
606 | * The passed array will be copied to not allow external modification to the new
607 | * Version's inner state.
608 | *
609 | *
610 | * A single part within the array is allowed to contain a dot ('.'). Such parts will
611 | * be treated as if the array contained those parts as single elements.
612 | *
613 | *
614 | *
615 | * v.withPreRelease(new String[] { "a.b" })
616 | * <=>
617 | * v.withPreRelease(new String[] { "a", "b" })
618 | *
619 | *
620 | * @param newPreRelease the new pre release parts.
621 | * @return A new Version.
622 | * @throws VersionFormatException If the any element of the given array is not a valid
623 | * pre release identifier part.
624 | * @throws IllegalArgumentException If newPreRelease is null.
625 | * @since 1.2.0
626 | */
627 | public Version withPreRelease(String[] newPreRelease) {
628 | require(newPreRelease != null, "newPreRelease is null");
629 | final String joined = join(newPreRelease);
630 | final String[] newPreReleaseParts = parsePreRelease(joined);
631 | return new Version(this.major, this.minor, this.patch, newPreReleaseParts,
632 | this.buildMetaDataParts);
633 | }
634 |
635 | /**
636 | * Creates a new Version from this one, replacing only the build-meta-data part with
637 | * the given String. All other parts will remain the same as in this Version. You can
638 | * remove the build-meta-data part by passing an empty String.
639 | *
640 | * @param newBuildMetaData The new build meta data identifier.
641 | * @return A new Version.
642 | * @throws VersionFormatException If the given String is not a valid build-meta-data
643 | * identifier.
644 | * @throws IllegalArgumentException If newBuildMetaData is null.
645 | * @since 1.1.0
646 | */
647 | public Version withBuildMetaData(String newBuildMetaData) {
648 | require(newBuildMetaData != null, "newBuildMetaData is null");
649 | final String[] newBuildMdParts = parseBuildMd(newBuildMetaData);
650 | return new Version(this.major, this.minor, this.patch, this.preReleaseParts,
651 | newBuildMdParts);
652 | }
653 |
654 | /**
655 | * Creates a new Version from this one, replacing only the build-meta-data part with
656 | * the given array. All other parts will remain the same as in this Version. You can
657 | * remove the build-meta-data part by passing an empty array.
658 | *
659 | * The passed array will be copied to not allow external modification to the new
660 | * Version's inner state.
661 | *
662 | * A single part within the array is allowed to contain a dot ('.'). Such parts will
663 | * be treated as if the array contained those parts as single elements.
664 | *
665 | *
666 | *
667 | * v.withBuildMetaData(new String[] { "a.b" })
668 | * <=>
669 | * v.withBuildMetaData(new String[] { "a", "b" })
670 | *
671 | *
672 | * @param newBuildMetaData the new build meta data parts.
673 | * @return A new Version.
674 | * @throws VersionFormatException If the any element of the given array is not a valid
675 | * build meta data identifier part.
676 | * @throws IllegalArgumentException If newBuildMetaData is null.
677 | * @since 1.2.0
678 | */
679 | public Version withBuildMetaData(String[] newBuildMetaData) {
680 | require(newBuildMetaData != null, "newBuildMetaData is null");
681 | final String joined = join(newBuildMetaData);
682 | final String[] newBuildMdParts = parseBuildMd(joined);
683 | return new Version(this.major, this.minor, this.patch, this.preReleaseParts,
684 | newBuildMdParts);
685 | }
686 |
687 | private String[] verifyAndCopyArray(String parts[], boolean allowLeading0) {
688 | final String[] result = new String[parts.length];
689 | for (int i = 0; i < parts.length; ++i) {
690 | final String part = parts[i];
691 | require(part != null, "version part is null");
692 | if (part.isEmpty()) {
693 | throw new VersionFormatException(
694 | "Incomplete version part in " + join(parts));
695 | }
696 | result[i] = part;
697 |
698 | // note: pass "pre-release" because this string will not be used when parsing
699 | // build-meta-data
700 | parseIDPart(part.toCharArray(), part, 0, false, allowLeading0, false, false,
701 | null, "pre-release");
702 | }
703 | return result;
704 | }
705 |
706 | /**
707 | * Drops both the pre-release and the build meta data from this version.
708 | *
709 | * @return The nearest stable version.
710 | * @since 2.1.0
711 | */
712 | public Version toStable() {
713 | return new Version(this.major, this.minor, this.patch, EMPTY_ARRAY, EMPTY_ARRAY);
714 | }
715 |
716 | /**
717 | * Given this Version, returns the next major Version. That is, the major part is
718 | * incremented by 1 and the remaining parts are set to 0. This also drops the
719 | * pre-release and build-meta-data.
720 | *
721 | * @return The incremented version.
722 | * @see #nextMajor(String)
723 | * @see #nextMajor(String[])
724 | * @since 1.2.0
725 | */
726 | public Version nextMajor() {
727 | return new Version(this.major + 1, 0, 0, EMPTY_ARRAY, EMPTY_ARRAY);
728 | }
729 |
730 | /**
731 | * Given this Version, returns the next major Version. That is, the major part is
732 | * incremented by 1 and the remaining parts are set to 0. The pre-release part will be
733 | * set to the given identifier and the build-meta-data is dropped.
734 | *
735 | * @param newPrelease The pre-release part for the resulting Version.
736 | * @return The incremented version.
737 | * @throws VersionFormatException If the given String is not a valid pre-release
738 | * identifier.
739 | * @throws IllegalArgumentException If newPreRelease is null.
740 | * @see #nextMajor()
741 | * @see #nextMajor(String[])
742 | * @since 1.2.0
743 | */
744 | public Version nextMajor(String newPrelease) {
745 | require(newPrelease != null, "newPreRelease is null");
746 | final String[] preReleaseParts = parsePreRelease(newPrelease);
747 | return new Version(this.major + 1, 0, 0, preReleaseParts, EMPTY_ARRAY);
748 | }
749 |
750 | /**
751 | * Given this Version, returns the next major Version. That is, the major part is
752 | * incremented by 1 and the remaining parts are set to 0. The pre-release part will be
753 | * set to the given identifier and the build-meta-data is dropped.
754 | *
755 | * @param newPrelease The pre-release part for the resulting Version.
756 | * @return The incremented version.
757 | * @throws VersionFormatException If the any element of the given array is not a valid
758 | * pre release identifier part.
759 | * @throws IllegalArgumentException If newPreRelease is null.
760 | * @see #nextMajor()
761 | * @see #nextMajor(String)
762 | * @since 1.2.0
763 | */
764 | public Version nextMajor(String[] newPrelease) {
765 | require(newPrelease != null, "newPreRelease is null");
766 | final String[] newPreReleaseParts = verifyAndCopyArray(newPrelease, false);
767 | return new Version(this.major + 1, 0, 0, newPreReleaseParts, EMPTY_ARRAY);
768 | }
769 |
770 | /**
771 | * Given this version, returns the next minor version. That is, the major part remains
772 | * the same, the minor version is incremented and all other parts are reset/dropped.
773 | *
774 | * @return The incremented version.
775 | * @see #nextMinor(String)
776 | * @see #nextMinor(String[])
777 | * @since 1.2.0
778 | */
779 | public Version nextMinor() {
780 | return new Version(this.major, this.minor + 1, 0, EMPTY_ARRAY, EMPTY_ARRAY);
781 | }
782 |
783 | /**
784 | * Given this version, returns the next minor version. That is, the major part remains
785 | * the same and the minor version is incremented. The pre-release part will be set to
786 | * the given identifier and the build-meta-data is dropped.
787 | *
788 | * @param newPrelease The pre-release part for the resulting Version.
789 | * @return The incremented version.
790 | * @throws VersionFormatException If the given String is not a valid pre-release
791 | * identifier.
792 | * @throws IllegalArgumentException If newPreRelease is null.
793 | * @see #nextMinor()
794 | * @see #nextMinor(String[])
795 | * @since 1.2.0
796 | */
797 | public Version nextMinor(String newPrelease) {
798 | require(newPrelease != null, "newPreRelease is null");
799 | final String[] preReleaseParts = parsePreRelease(newPrelease);
800 | return new Version(this.major, this.minor + 1, 0, preReleaseParts, EMPTY_ARRAY);
801 | }
802 |
803 | /**
804 | * Given this version, returns the next minor version. That is, the major part remains
805 | * the same and the minor version is incremented. The pre-release part will be set to
806 | * the given identifier and the build-meta-data is dropped.
807 | *
808 | * @param newPrelease The pre-release part for the resulting Version.
809 | * @return The incremented version.
810 | * @throws VersionFormatException If the any element of the given array is not a valid
811 | * pre release identifier part.
812 | * @throws IllegalArgumentException If newPreRelease is null.
813 | * @see #nextMinor()
814 | * @see #nextMinor(String)
815 | * @since 1.2.0
816 | */
817 | public Version nextMinor(String[] newPrelease) {
818 | require(newPrelease != null, "newPreRelease is null");
819 | final String[] newPreReleaseParts = verifyAndCopyArray(newPrelease, false);
820 | return new Version(this.major, this.minor + 1, 0, newPreReleaseParts,
821 | EMPTY_ARRAY);
822 | }
823 |
824 | /**
825 | * Given this version, returns the next patch version. That is, the major and minor
826 | * parts remain the same, the patch version is incremented and all other parts are
827 | * reset/dropped.
828 | *
829 | * @return The incremented version.
830 | * @see #nextPatch(String)
831 | * @see #nextPatch(String[])
832 | * @since 1.2.0
833 | */
834 | public Version nextPatch() {
835 | return new Version(this.major, this.minor, this.patch + 1, EMPTY_ARRAY,
836 | EMPTY_ARRAY);
837 | }
838 |
839 | /**
840 | * Given this version, returns the next patch version. That is, the major and minor
841 | * parts remain the same and the patch version is incremented. The pre-release part
842 | * will be set to the given identifier and the build-meta-data is dropped.
843 | *
844 | * @param newPrelease The pre-release part for the resulting Version.
845 | * @return The incremented version.
846 | * @throws VersionFormatException If the given String is not a valid pre-release
847 | * identifier.
848 | * @throws IllegalArgumentException If newPreRelease is null.
849 | * @see #nextPatch()
850 | * @see #nextPatch(String[])
851 | * @since 1.2.0
852 | */
853 | public Version nextPatch(String newPrelease) {
854 | require(newPrelease != null, "newPreRelease is null");
855 | final String[] preReleaseParts = parsePreRelease(newPrelease);
856 | return new Version(this.major, this.minor, this.patch + 1, preReleaseParts,
857 | EMPTY_ARRAY);
858 | }
859 |
860 | /**
861 | * Given this version, returns the next patch version. That is, the major and minor
862 | * parts remain the same and the patch version is incremented. The pre-release part
863 | * will be set to the given identifier and the build-meta-data is dropped.
864 | *
865 | * @param newPrelease The pre-release part for the resulting Version.
866 | * @return The incremented version.
867 | * @throws VersionFormatException If the any element of the given array is not a valid
868 | * pre release identifier part.
869 | * @throws IllegalArgumentException If newPreRelease is null.
870 | * @see #nextPatch()
871 | * @see #nextPatch(String)
872 | * @since 1.2.0
873 | */
874 | public Version nextPatch(String[] newPrelease) {
875 | require(newPrelease != null, "newPreRelease is null");
876 | final String[] newPreReleaseParts = verifyAndCopyArray(newPrelease, false);
877 | return new Version(this.major, this.minor, this.patch + 1, newPreReleaseParts,
878 | EMPTY_ARRAY);
879 | }
880 |
881 | /**
882 | * Derives a new Version instance from this one by only incrementing the pre-release
883 | * identifier. The build-meta-data will be dropped, all other fields remain the same.
884 | *
885 | *
886 | * The incrementation of the pre-release identifier behaves as follows:
887 | *
888 | *
889 | * - In case the identifier is currently empty, it becomes "1" in the result.
890 | * - If the identifier's last part is numeric, that last part will be incremented in
891 | * the result.
892 | * - If the last part is not numeric, the identifier is interpreted as
893 | * {@code identifier.0} which becomes {@code identifier.1} after increment.
894 | *
895 | * Examples:
896 | *
897 | *
898 | * Pre-release identifier incrementation behavior
899 | *
900 | * Version |
901 | * After increment |
902 | *
903 | *
904 | * 1.2.3 |
905 | * 1.2.3-1 |
906 | *
907 | *
908 | * 1.2.3+build.meta.data |
909 | * 1.2.3-1 |
910 | *
911 | *
912 | * 1.2.3-foo |
913 | * 1.2.3-foo.1 |
914 | *
915 | *
916 | * 1.2.3-foo.1 |
917 | * 1.2.3-foo.2 |
918 | *
919 | *
920 | *
921 | * @return The incremented Version.
922 | * @since 1.2.0
923 | */
924 | public Version nextPreRelease() {
925 | final String[] newPreReleaseParts = incrementIdentifier(this.preReleaseParts);
926 | return new Version(this.major, this.minor, this.patch, newPreReleaseParts,
927 | EMPTY_ARRAY);
928 | }
929 |
930 | /**
931 | * Derives a new Version instance from this one by only incrementing the
932 | * build-meta-data identifier. All other fields remain the same.
933 | *
934 | *
935 | * The incrementation of the build-meta-data identifier behaves as follows:
936 | *
937 | *
938 | * - In case the identifier is currently empty, it becomes "1" in the result.
939 | * - If the identifier's last part is numeric, that last part will be incremented in
940 | * the result. Leading 0's will be removed.
941 | * - If the last part is not numeric, the identifier is interpreted as
942 | * {@code identifier.0} which becomes {@code identifier.1} after increment.
943 | *
944 | * Examples:
945 | *
946 | *
947 | * Build meta data incrementation behavior
948 | *
949 | * Version |
950 | * After increment |
951 | *
952 | *
953 | * 1.2.3 |
954 | * 1.2.3+1 |
955 | *
956 | *
957 | * 1.2.3-pre.release |
958 | * 1.2.3-pre.release+1 |
959 | *
960 | *
961 | * 1.2.3+foo |
962 | * 1.2.3+foo.1 |
963 | *
964 | *
965 | * 1.2.3+foo.1 |
966 | * 1.2.3+foo.2 |
967 | *
968 | *
969 | *
970 | * @return The incremented Version.
971 | * @since 1.2.0
972 | */
973 | public Version nextBuildMetaData() {
974 | final String[] newBuildMetaData = incrementIdentifier(this.buildMetaDataParts);
975 | return new Version(this.major, this.minor, this.patch, this.preReleaseParts,
976 | newBuildMetaData);
977 | }
978 |
979 | private String[] incrementIdentifier(String[] parts) {
980 | if (parts.length == 0) {
981 | return new String[] { "1" };
982 | }
983 | final int lastIdx = parts.length - 1;
984 | final String lastPart = parts[lastIdx];
985 |
986 | int num = isNumeric(lastPart);
987 | int newLength = parts.length;
988 | if (num >= 0) {
989 | num += 1;
990 | } else {
991 | newLength += 1;
992 | num = 1;
993 | }
994 | final String[] result = Arrays.copyOf(parts, newLength);
995 | result[newLength - 1] = String.valueOf(num);
996 | return result;
997 | }
998 |
999 | /**
1000 | * Tries to parse the given String as a semantic version and returns whether the
1001 | * String is properly formatted according to the semantic version specification.
1002 | *
1003 | *
1004 | * Note: this method does not throw an exception upon null
input, but
1005 | * returns false
instead.
1006 | *
1007 | *
1008 | * @param version The String to check.
1009 | * @return Whether the given String is formatted as a semantic version.
1010 | * @since 0.5.0
1011 | */
1012 | public static boolean isValidVersion(String version) {
1013 | return version != null && !version.isEmpty() && parse(version, true) != null;
1014 | }
1015 |
1016 | /**
1017 | * Returns whether the given String is a valid pre-release identifier. That is, this
1018 | * method returns true
if, and only if the {@code preRelease} parameter
1019 | * is either the empty string or properly formatted as a pre-release identifier
1020 | * according to the semantic version specification.
1021 | *
1022 | *
1023 | * Note: this method does not throw an exception upon null
input, but
1024 | * returns false
instead.
1025 | *
1026 | * @param preRelease The String to check.
1027 | * @return Whether the given String is a valid pre-release identifier.
1028 | * @since 0.5.0
1029 | */
1030 | public static boolean isValidPreRelease(String preRelease) {
1031 | if (preRelease == null) {
1032 | return false;
1033 | } else if (preRelease.isEmpty()) {
1034 | return true;
1035 | }
1036 |
1037 | return parseID(preRelease.toCharArray(), preRelease, 0, true, false, false, null,
1038 | "") != FAILURE;
1039 | }
1040 |
1041 | /**
1042 | * Returns whether the given String is a valid build meta data identifier. That is,
1043 | * this method returns true
if, and only if the {@code buildMetaData}
1044 | * parameter is either the empty string or properly formatted as a build meta data
1045 | * identifier according to the semantic version specification.
1046 | *
1047 | *
1048 | * Note: this method does not throw an exception upon null
input, but
1049 | * returns false
instead.
1050 | *
1051 | *
1052 | * @param buildMetaData The String to check.
1053 | * @return Whether the given String is a valid build meta data identifier.
1054 | * @since 0.5.0
1055 | */
1056 | public static boolean isValidBuildMetaData(String buildMetaData) {
1057 | if (buildMetaData == null) {
1058 | return false;
1059 | } else if (buildMetaData.isEmpty()) {
1060 | return true;
1061 | }
1062 |
1063 | return parseID(buildMetaData.toCharArray(), buildMetaData, 0, true, true, false,
1064 | null, "") != FAILURE;
1065 | }
1066 |
1067 | /**
1068 | * Returns the greater of the two given versions by comparing them using their natural
1069 | * ordering. If both versions are equal, then the first argument is returned.
1070 | *
1071 | * @param v1 The first version.
1072 | * @param v2 The second version.
1073 | * @return The greater version.
1074 | * @throws IllegalArgumentException If either argument is null
.
1075 | * @since 0.4.0
1076 | */
1077 | public static Version max(Version v1, Version v2) {
1078 | require(v1 != null, "v1 is null");
1079 | require(v2 != null, "v2 is null");
1080 | return compare(v1, v2, false) < 0
1081 | ? v2
1082 | : v1;
1083 | }
1084 |
1085 | /**
1086 | * Returns the lower of the two given versions by comparing them using their natural
1087 | * ordering. If both versions are equal, then the first argument is returned.
1088 | *
1089 | * @param v1 The first version.
1090 | * @param v2 The second version.
1091 | * @return The lower version.
1092 | * @throws IllegalArgumentException If either argument is null
.
1093 | * @since 0.4.0
1094 | */
1095 | public static Version min(Version v1, Version v2) {
1096 | require(v1 != null, "v1 is null");
1097 | require(v2 != null, "v2 is null");
1098 | return compare(v1, v2, false) <= 0
1099 | ? v1
1100 | : v2;
1101 | }
1102 |
1103 | /**
1104 | * Compares two versions, following the semantic version specification. Here
1105 | * is a quote from semantic version 2.0.0
1106 | * specification:
1107 | *
1108 | *
1109 | * Precedence refers to how versions are compared to each other when ordered.
1110 | * Precedence MUST be calculated by separating the version into major, minor, patch
1111 | * and pre-release identifiers in that order (Build metadata does not figure into
1112 | * precedence). Precedence is determined by the first difference when comparing each
1113 | * of these identifiers from left to right as follows: Major, minor, and patch
1114 | * versions are always compared numerically. Example: 1.0.0 < 2.0.0 < 2.1.0 <
1115 | * 2.1.1. When major, minor, and patch are equal, a pre-release version has lower
1116 | * precedence than a normal version. Example: 1.0.0-alpha < 1.0.0. Precedence for
1117 | * two pre-release versions with the same major, minor, and patch version MUST be
1118 | * determined by comparing each dot separated identifier from left to right until a
1119 | * difference is found as follows: identifiers consisting of only digits are compared
1120 | * numerically and identifiers with letters or hyphens are compared lexically in ASCII
1121 | * sort order. Numeric identifiers always have lower precedence than non-numeric
1122 | * identifiers. A larger set of pre-release fields has a higher precedence than a
1123 | * smaller set, if all of the preceding identifiers are equal. Example: 1.0.0-alpha
1124 | * < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 <
1125 | * 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0.
1126 | *
1127 | *
1128 | *
1129 | * This method fulfills the general contract for Java's {@link Comparator Comparators}
1130 | * and {@link Comparable Comparables}.
1131 | *
1132 | *
1133 | * @param v1 The first version for comparison.
1134 | * @param v2 The second version for comparison.
1135 | * @return A value below 0 iff {@code v1 < v2}, a value above 0 iff
1136 | * {@code v1 > v2 and 0 iff v1 = v2}.
1137 | * @throws NullPointerException If either parameter is null.
1138 | * @since 0.2.0
1139 | */
1140 | public static int compare(Version v1, Version v2) {
1141 | // throw NPE to comply with Comparable specification
1142 | if (v1 == null) {
1143 | throw new NullPointerException("v1 is null");
1144 | } else if (v2 == null) {
1145 | throw new NullPointerException("v2 is null");
1146 | }
1147 | return compare(v1, v2, false);
1148 | }
1149 |
1150 | /**
1151 | * Compares two Versions with additionally considering the build meta data field if
1152 | * all other parts are equal. Note: This is not part of the semantic version
1153 | * specification.
1154 | *
1155 | *
1156 | * Comparison of the build meta data parts happens exactly as for pre release
1157 | * identifiers. Considering of build meta data first kicks in if both versions are
1158 | * equal when using their natural order.
1159 | *
1160 | *
1161 | *
1162 | * This method fulfills the general contract for Java's {@link Comparator Comparators}
1163 | * and {@link Comparable Comparables}.
1164 | *
1165 | *
1166 | * @param v1 The first version for comparison.
1167 | * @param v2 The second version for comparison.
1168 | * @return A value below 0 iff {@code v1 < v2}, a value above 0 iff
1169 | * {@code v1 > v2 and 0 iff v1 = v2}.
1170 | * @throws NullPointerException If either parameter is null.
1171 | * @since 0.3.0
1172 | */
1173 | public static int compareWithBuildMetaData(Version v1, Version v2) {
1174 | // throw NPE to comply with Comparable specification
1175 | if (v1 == null) {
1176 | throw new NullPointerException("v1 is null");
1177 | } else if (v2 == null) {
1178 | throw new NullPointerException("v2 is null");
1179 | }
1180 | return compare(v1, v2, true);
1181 | }
1182 |
1183 | private static int compare(Version v1, Version v2,
1184 | boolean withBuildMetaData) {
1185 | int result = 0;
1186 | if (v1 != v2) {
1187 | final int mc, mm, mp, pr, md;
1188 | if ((mc = compareInt(v1.major, v2.major)) != 0) {
1189 | result = mc;
1190 | } else if ((mm = compareInt(v1.minor, v2.minor)) != 0) {
1191 | result = mm;
1192 | } else if ((mp = compareInt(v1.patch, v2.patch)) != 0) {
1193 | result = mp;
1194 | } else if ((pr = comparePreRelease(v1, v2)) != 0) {
1195 | result = pr;
1196 | } else if (withBuildMetaData && ((md = compareBuildMetaData(v1, v2)) != 0)) {
1197 | result = md;
1198 | }
1199 | }
1200 | return result;
1201 | }
1202 |
1203 | private static int compareInt(int a, int b) {
1204 | return a - b;
1205 | }
1206 |
1207 | private static int comparePreRelease(Version v1, Version v2) {
1208 | return compareLiterals(v1.preReleaseParts, v2.preReleaseParts);
1209 | }
1210 |
1211 | private static int compareBuildMetaData(Version v1, Version v2) {
1212 | return compareLiterals(v1.buildMetaDataParts, v2.buildMetaDataParts);
1213 | }
1214 |
1215 | private static int compareLiterals(String[] v1Literal, String[] v2Literal) {
1216 | final int result;
1217 | if (v1Literal.length > 0 && v2Literal.length > 0) {
1218 | // compare pre release parts
1219 | result = compareIdentifiers(v1Literal, v2Literal);
1220 | } else if (v1Literal.length > 0) {
1221 | // other is greater, because it is no pre release
1222 | result = -1;
1223 | } else if (v2Literal.length > 0) {
1224 | // this is greater because other is no pre release
1225 | result = 1;
1226 | } else {
1227 | result = 0;
1228 | }
1229 | return result;
1230 | }
1231 |
1232 | private static int compareIdentifiers(String[] parts1, String[] parts2) {
1233 | final int min = Math.min(parts1.length, parts2.length);
1234 | for (int i = 0; i < min; ++i) {
1235 | final int r = compareIdentifierParts(parts1[i], parts2[i]);
1236 | if (r != 0) {
1237 | // versions differ in part i
1238 | return r;
1239 | }
1240 | }
1241 |
1242 | // all id's are equal, so compare amount of id's
1243 | return compareInt(parts1.length, parts2.length);
1244 | }
1245 |
1246 | private static int compareIdentifierParts(String p1, String p2) {
1247 | final int num1 = isNumeric(p1);
1248 | final int num2 = isNumeric(p2);
1249 |
1250 | final int result;
1251 | if (num1 < 0 && num2 < 0) {
1252 | // both are not numerical -> compare lexically
1253 | result = p1.compareTo(p2);
1254 | } else if (num1 >= 0 && num2 >= 0) {
1255 | // both are numerical
1256 | result = compareInt(num1, num2);
1257 | } else if (num1 >= 0) {
1258 | // only part1 is numerical -> p2 is greater
1259 | result = -1;
1260 | } else {
1261 | // only part2 is numerical -> p1 is greater
1262 | result = 1;
1263 | }
1264 | return result;
1265 | }
1266 |
1267 | /**
1268 | * Determines whether s is a positive number. If it is, the number is returned,
1269 | * otherwise the result is -1.
1270 | *
1271 | * @param s The String to check.
1272 | * @return The positive number (incl. 0) if s a number, or -1 if it is not.
1273 | */
1274 | private static int isNumeric(String s) {
1275 | final char[] chars = s.toCharArray();
1276 | int num = 0;
1277 |
1278 | // note: this method does not account for leading zeroes as could occur in build
1279 | // meta data parts. Leading zeroes are thus simply ignored when parsing the
1280 | // number.
1281 | for (int i = 0; i < chars.length; ++i) {
1282 | final char c = chars[i];
1283 | if (c >= '0' && c <= '9') {
1284 | num = num * DECIMAL + Character.digit(c, DECIMAL);
1285 | } else {
1286 | return -1;
1287 | }
1288 | }
1289 | return num;
1290 | }
1291 |
1292 | private static String[] parsePreRelease(String preRelease) {
1293 | if (preRelease != null && !preRelease.isEmpty()) {
1294 | final List parts = new ArrayList();
1295 | parseID(preRelease.toCharArray(), preRelease, 0, false, false, false, parts,
1296 | "pre-release");
1297 | return parts.toArray(new String[parts.size()]);
1298 | }
1299 | return EMPTY_ARRAY;
1300 | }
1301 |
1302 | private static String[] parseBuildMd(String buildMetaData) {
1303 | if (buildMetaData != null && !buildMetaData.isEmpty()) {
1304 | final List parts = new ArrayList();
1305 | parseID(buildMetaData.toCharArray(), buildMetaData, 0, false, true, false,
1306 | parts, "build-meta-data");
1307 | return parts.toArray(new String[parts.size()]);
1308 | }
1309 | return EMPTY_ARRAY;
1310 | }
1311 |
1312 | private static final Version createInternal(int major, int minor, int patch,
1313 | String preRelease, String buildMetaData) {
1314 |
1315 | final String[] preReleaseParts = parsePreRelease(preRelease);
1316 | final String[] buildMdParts = parseBuildMd(buildMetaData);
1317 | return new Version(major, minor, patch, preReleaseParts, buildMdParts);
1318 | }
1319 |
1320 | /**
1321 | * Creates a new Version from the provided components. Neither value of
1322 | * {@code major, minor} or {@code patch} must be lower than 0 and at least one must be
1323 | * greater than zero. {@code preRelease} or {@code buildMetaData} may be the empty
1324 | * String. In this case, the created {@code Version} will have no pre release resp.
1325 | * build meta data field. If those parameters are not empty, they must conform to the
1326 | * semantic version specification.
1327 | *
1328 | * @param major The major version.
1329 | * @param minor The minor version.
1330 | * @param patch The patch version.
1331 | * @param preRelease The pre release version or the empty string.
1332 | * @param buildMetaData The build meta data field or the empty string.
1333 | * @return The version instance.
1334 | * @throws VersionFormatException If {@code preRelease} or {@code buildMetaData} does
1335 | * not conform to the semantic version specification.
1336 | */
1337 | public static final Version create(int major, int minor, int patch,
1338 | String preRelease,
1339 | String buildMetaData) {
1340 | require(preRelease != null, "preRelease is null");
1341 | require(buildMetaData != null, "buildMetaData is null");
1342 | return createInternal(major, minor, patch, preRelease, buildMetaData);
1343 | }
1344 |
1345 | /**
1346 | * Creates a new Version from the provided components. The version's build meta data
1347 | * field will be empty. Neither value of {@code major, minor} or {@code patch} must be
1348 | * lower than 0 and at least one must be greater than zero. {@code preRelease} may be
1349 | * the empty String. In this case, the created version will have no pre release field.
1350 | * If it is not empty, it must conform to the specifications of the semantic version.
1351 | *
1352 | * @param major The major version.
1353 | * @param minor The minor version.
1354 | * @param patch The patch version.
1355 | * @param preRelease The pre release version or the empty string.
1356 | * @return The version instance.
1357 | * @throws VersionFormatException If {@code preRelease} is not empty and does not
1358 | * conform to the semantic versioning specification
1359 | */
1360 | public static final Version create(int major, int minor, int patch,
1361 | String preRelease) {
1362 | return create(major, minor, patch, preRelease, "");
1363 | }
1364 |
1365 | /**
1366 | * Creates a new Version from the three provided components. The version's pre release
1367 | * and build meta data fields will be empty. Neither value must be lower than 0 and at
1368 | * least one must be greater than zero.
1369 | *
1370 | * @param major The major version.
1371 | * @param minor The minor version.
1372 | * @param patch The patch version.
1373 | * @return The version instance.
1374 | */
1375 | public static final Version create(int major, int minor, int patch) {
1376 | return new Version(major, minor, patch, EMPTY_ARRAY, EMPTY_ARRAY);
1377 | }
1378 |
1379 | /**
1380 | * Creates a new Version from the two provided components, leaving the patch version
1381 | * 0. The version's pre release and build meta data fields will be empty. Neither
1382 | * value must be lower than 0 and at least one must be greater than zero.
1383 | *
1384 | * @param major The major version.
1385 | * @param minor The minor version.
1386 | * @return The version instance.
1387 | * @since 2.1.0
1388 | */
1389 | public static final Version create(int major, int minor) {
1390 | return new Version(major, minor, 0, EMPTY_ARRAY, EMPTY_ARRAY);
1391 | }
1392 |
1393 | /**
1394 | * Creates a new Version with the provided major version, leaving the minor and patch
1395 | * version 0. The version's pre release and build meta data fields will be empty. The
1396 | * major value must be lower than or equal to 0.
1397 | *
1398 | * @param major The major version.
1399 | * @return The version instance.
1400 | * @since 2.1.0
1401 | */
1402 | public static final Version create(int major) {
1403 | return new Version(major, 0, 0, EMPTY_ARRAY, EMPTY_ARRAY);
1404 | }
1405 |
1406 | private static void checkParams(int major, int minor, int patch) {
1407 | require(major >= 0, "major < 0");
1408 | require(minor >= 0, "minor < 0");
1409 | require(patch >= 0, "patch < 0");
1410 | }
1411 |
1412 | private static void require(boolean condition, String message) {
1413 | if (!condition) {
1414 | throw new IllegalArgumentException(message);
1415 | }
1416 | }
1417 |
1418 | /**
1419 | * Tries to parse the provided String as a semantic version. If the string does not
1420 | * conform to the semantic version specification, a {@link VersionFormatException}
1421 | * will be thrown.
1422 | *
1423 | * @param versionString The String to parse.
1424 | * @return The parsed version.
1425 | * @throws VersionFormatException If the String is no valid version
1426 | * @throws IllegalArgumentException If {@code versionString} is null
.
1427 | */
1428 | public static final Version parseVersion(String versionString) {
1429 | require(versionString != null, "versionString is null");
1430 | return parse(versionString, false);
1431 | }
1432 |
1433 | /**
1434 | * Tries to parse the provided String as a semantic version. If
1435 | * {@code allowPreRelease} is false
, the String must have neither a
1436 | * pre-release nor a build meta data part. Thus the given String must have the format
1437 | * {@code X.Y.Z} where at least one part must be greater than zero.
1438 | *
1439 | *
1440 | * If {@code allowPreRelease} is true
, the String is parsed according to
1441 | * the normal semantic version specification.
1442 | *
1443 | *
1444 | * @param versionString The String to parse.
1445 | * @param allowPreRelease Whether pre-release and build meta data field are allowed.
1446 | * @return The parsed version.
1447 | * @throws VersionFormatException If the String is no valid version
1448 | * @since 0.4.0
1449 | */
1450 | public static Version parseVersion(String versionString,
1451 | boolean allowPreRelease) {
1452 | final Version version = parseVersion(versionString);
1453 | if (!allowPreRelease && (version.isPreRelease() || version.hasBuildMetaData())) {
1454 | throw new VersionFormatException(String.format(
1455 | "Version string '%s' is expected to have no pre-release or "
1456 | + "build meta data part",
1457 | versionString));
1458 | }
1459 | return version;
1460 | }
1461 |
1462 | /**
1463 | * Returns the lower of this version and the given version according to its natural
1464 | * ordering. If versions are equal, {@code this} is returned.
1465 | *
1466 | * @param other The version to compare with.
1467 | * @return The lower version.
1468 | * @throws IllegalArgumentException If {@code other} is null
.
1469 | * @since 0.5.0
1470 | * @see #min(Version, Version)
1471 | */
1472 | public Version min(Version other) {
1473 | return min(this, other);
1474 | }
1475 |
1476 | /**
1477 | * Returns the greater of this version and the given version according to its natural
1478 | * ordering. If versions are equal, {@code this} is returned.
1479 | *
1480 | * @param other The version to compare with.
1481 | * @return The greater version.
1482 | * @throws IllegalArgumentException If {@code other} is null
.
1483 | * @since 0.5.0
1484 | * @see #max(Version, Version)
1485 | */
1486 | public Version max(Version other) {
1487 | return max(this, other);
1488 | }
1489 |
1490 | /**
1491 | * Gets this version's major number.
1492 | *
1493 | * @return The major version.
1494 | */
1495 | public int getMajor() {
1496 | return this.major;
1497 | }
1498 |
1499 | /**
1500 | * Gets this version's minor number.
1501 | *
1502 | * @return The minor version.
1503 | */
1504 | public int getMinor() {
1505 | return this.minor;
1506 | }
1507 |
1508 | /**
1509 | * Gets this version's path number.
1510 | *
1511 | * @return The patch number.
1512 | */
1513 | public int getPatch() {
1514 | return this.patch;
1515 | }
1516 |
1517 | /**
1518 | * Gets the pre release identifier parts of this version as array. Modifying the
1519 | * resulting array will have no influence on the internal state of this object.
1520 | *
1521 | * @return Pre release version as array. Array is empty if this version has no pre
1522 | * release part.
1523 | */
1524 | public String[] getPreReleaseParts() {
1525 | if (this.preReleaseParts.length == 0) {
1526 | return EMPTY_ARRAY;
1527 | }
1528 | return Arrays.copyOf(this.preReleaseParts, this.preReleaseParts.length);
1529 | }
1530 |
1531 | /**
1532 | * Gets the pre release identifier of this version. If this version has no such
1533 | * identifier, an empty string is returned.
1534 | *
1535 | *
1536 | * Note: This method will always reconstruct a new String by joining the single
1537 | * identifier parts.
1538 | *
1539 | *
1540 | * @return This version's pre release identifier or an empty String if this version
1541 | * has no such identifier.
1542 | */
1543 | public String getPreRelease() {
1544 | return join(this.preReleaseParts);
1545 | }
1546 |
1547 | /**
1548 | * Gets this version's build meta data. If this version has no build meta data, the
1549 | * returned string is empty.
1550 | *
1551 | *
1552 | * Note: This method will always reconstruct a new String by joining the single
1553 | * identifier parts.
1554 | *
1555 | *
1556 | * @return The build meta data or an empty String if this version has no build meta
1557 | * data.
1558 | */
1559 | public String getBuildMetaData() {
1560 | return join(this.buildMetaDataParts);
1561 | }
1562 |
1563 | private static String join(String[] parts) {
1564 | if (parts.length == 0) {
1565 | return "";
1566 | }
1567 | final StringBuilder b = new StringBuilder();
1568 | for (int i = 0; i < parts.length; i++) {
1569 | final String part = parts[i];
1570 | b.append(part);
1571 | if (i < parts.length - 1) {
1572 | b.append(".");
1573 | }
1574 | }
1575 | return b.toString();
1576 | }
1577 |
1578 | /**
1579 | * Gets the build meta data identifier parts of this version as array. Modifying the
1580 | * resulting array will have no influence on the internal state of this object.
1581 | *
1582 | * @return Build meta data as array. Array is empty if this version has no build meta
1583 | * data part.
1584 | */
1585 | public String[] getBuildMetaDataParts() {
1586 | if (this.buildMetaDataParts.length == 0) {
1587 | return EMPTY_ARRAY;
1588 | }
1589 | return Arrays.copyOf(this.buildMetaDataParts, this.buildMetaDataParts.length);
1590 | }
1591 |
1592 | /**
1593 | * Determines whether this version is still under initial development.
1594 | *
1595 | * @return true
iff this version's major part is zero.
1596 | */
1597 | public boolean isInitialDevelopment() {
1598 | return this.major == 0;
1599 | }
1600 |
1601 | /**
1602 | * Whether this is a 'stable' version. That is, it has no pre-release identifier.
1603 | *
1604 | * @return true
iff {@link #getPreRelease()} is empty.
1605 | * @see #isPreRelease()
1606 | * @since 2.1.0
1607 | */
1608 | public boolean isStable() {
1609 | return this.preReleaseParts.length == 0;
1610 | }
1611 |
1612 | /**
1613 | * Determines whether this is a pre release version.
1614 | *
1615 | * @return true
iff {@link #getPreRelease()} is not empty.
1616 | * @see #isStable()
1617 | */
1618 | public boolean isPreRelease() {
1619 | return this.preReleaseParts.length > 0;
1620 | }
1621 |
1622 | /**
1623 | * Determines whether this version has a build meta data field.
1624 | *
1625 | * @return true
iff {@link #getBuildMetaData()} is not empty.
1626 | */
1627 | public boolean hasBuildMetaData() {
1628 | return this.buildMetaDataParts.length > 0;
1629 | }
1630 |
1631 | /**
1632 | * Creates a String representation of this version by joining its parts together as by
1633 | * the semantic version specification.
1634 | *
1635 | * @return The version as a String.
1636 | */
1637 | @Override
1638 | public String toString() {
1639 | final StringBuilder b = new StringBuilder(TO_STRING_ESTIMATE);
1640 | b.append(this.major).append(".")
1641 | .append(this.minor).append(".")
1642 | .append(this.patch);
1643 |
1644 | if (isPreRelease()) {
1645 | b.append("-").append(getPreRelease());
1646 | }
1647 | if (hasBuildMetaData()) {
1648 | b.append("+").append(getBuildMetaData());
1649 | }
1650 | return b.toString();
1651 | }
1652 |
1653 | /**
1654 | * The hash code for a version instance is computed from the fields {@link #getMajor()
1655 | * major}, {@link #getMinor() minor}, {@link #getPatch() patch} and
1656 | * {@link #getPreRelease() pre-release}.
1657 | *
1658 | * @return A hash code for this object.
1659 | */
1660 | @Override
1661 | public int hashCode() {
1662 | final int h = this.hash;
1663 | if (h == NOT_YET_CALCULATED) {
1664 | this.hash = calculateHashCode();
1665 | }
1666 | return this.hash;
1667 | }
1668 |
1669 | private int calculateHashCode() {
1670 | int h = HASH_PRIME + this.major;
1671 | h = HASH_PRIME * h + this.minor;
1672 | h = HASH_PRIME * h + this.patch;
1673 | h = HASH_PRIME * h + Arrays.hashCode(this.preReleaseParts);
1674 | return h;
1675 | }
1676 |
1677 | /**
1678 | * Determines whether this version is equal to the passed object. This is the case if
1679 | * the passed object is an instance of Version and this version
1680 | * {@link #compareTo(Version) compared} to the provided one yields 0. Thus, this
1681 | * method ignores the {@link #getBuildMetaData()} field.
1682 | *
1683 | * @param obj the object to compare with.
1684 | * @return true
iff {@code obj} is an instance of {@code Version} and
1685 | * {@code this.compareTo((Version) obj) == 0}
1686 | * @see #compareTo(Version)
1687 | */
1688 | @Override
1689 | public boolean equals(Object obj) {
1690 | return testEquality(obj, false);
1691 | }
1692 |
1693 | /**
1694 | * Determines whether this version is equal to the passed object (as determined by
1695 | * {@link #equals(Object)} and additionally considers the build meta data part of both
1696 | * versions for equality.
1697 | *
1698 | * @param obj The object to compare with.
1699 | * @return true
iff {@code this.equals(obj)} and
1700 | * {@code this.getBuildMetaData().equals(((Version) obj).getBuildMetaData())}
1701 | * @since 0.4.0
1702 | */
1703 | public boolean equalsWithBuildMetaData(Object obj) {
1704 | return testEquality(obj, true);
1705 | }
1706 |
1707 | private boolean testEquality(Object obj, boolean includeBuildMd) {
1708 | return obj == this || obj instanceof Version
1709 | && compare(this, (Version) obj, includeBuildMd) == 0;
1710 | }
1711 |
1712 | /**
1713 | * Compares this version to the provided one, following the semantic
1714 | * versioning specification. See {@link #compare(Version, Version)} for more
1715 | * information.
1716 | *
1717 | * @param other The version to compare to.
1718 | * @return A value lower than 0 if this < other, a value greater than 0 if this
1719 | * > other and 0 if this == other. The absolute value of the result has no
1720 | * semantical interpretation.
1721 | */
1722 | @Override
1723 | public int compareTo(Version other) {
1724 | return compare(this, other);
1725 | }
1726 |
1727 | /**
1728 | * Compares this version to the provided one. Unlike the {@link #compareTo(Version)}
1729 | * method, this one additionally considers the build meta data field of both versions,
1730 | * if all other parts are equal. Note: This is not part of the semantic
1731 | * version specification.
1732 | *
1733 | *
1734 | * Comparison of the build meta data parts happens exactly as for pre release
1735 | * identifiers. Considering of build meta data first kicks in if both versions are
1736 | * equal when using their natural order.
1737 | *
1738 | *
1739 | * @param other The version to compare to.
1740 | * @return A value lower than 0 if this < other, a value greater than 0 if this
1741 | * > other and 0 if this == other. The absolute value of the result has no
1742 | * semantical interpretation.
1743 | * @since 0.3.0
1744 | */
1745 | public int compareToWithBuildMetaData(Version other) {
1746 | return compareWithBuildMetaData(this, other);
1747 | }
1748 |
1749 | /**
1750 | * Returns a new Version where all identifiers are converted to upper case letters.
1751 | *
1752 | * @return A new version with lower case identifiers.
1753 | * @since 1.1.0
1754 | */
1755 | public Version toUpperCase() {
1756 | return new Version(this.major, this.minor, this.patch,
1757 | copyCase(this.preReleaseParts, true),
1758 | copyCase(this.buildMetaDataParts, true));
1759 | }
1760 |
1761 | /**
1762 | * Returns a new Version where all identifiers are converted to lower case letters.
1763 | *
1764 | * @return A new version with lower case identifiers.
1765 | * @since 1.1.0
1766 | */
1767 | public Version toLowerCase() {
1768 | return new Version(this.major, this.minor, this.patch,
1769 | copyCase(this.preReleaseParts, false),
1770 | copyCase(this.buildMetaDataParts, false));
1771 | }
1772 |
1773 | private static String[] copyCase(String[] source, boolean toUpper) {
1774 | final String[] result = new String[source.length];
1775 | for (int i = 0; i < source.length; i++) {
1776 | final String string = source[i];
1777 | result[i] = toUpper ? string.toUpperCase(Locale.ROOT) : string.toLowerCase(Locale.ROOT);
1778 | }
1779 | return result;
1780 | }
1781 |
1782 | /**
1783 | * Tests whether this version is strictly greater than the given other version in
1784 | * terms of precedence. Does not consider the build meta data part.
1785 | *
1786 | * Convenience method for {@code this.compareTo(other) > 0} except that this method
1787 | * throws an {@link IllegalArgumentException} if other is null.
1788 | *
1789 | *
1790 | * @param other The version to compare to.
1791 | * @return Whether this version is strictly greater.
1792 | * @since 1.1.0
1793 | */
1794 | public boolean isGreaterThan(Version other) {
1795 | require(other != null, "other must no be null");
1796 | return compareTo(other) > 0;
1797 | }
1798 |
1799 | /**
1800 | * Tests whether this version is equal to or greater than the given other version in
1801 | * terms of precedence. Does not consider the build meta data part.
1802 | *
1803 | * Convenience method for {@code this.compareTo(other) >= 0} except that this method
1804 | * throws an {@link IllegalArgumentException} if other is null.
1805 | *
1806 | * @param other The version to compare to.
1807 | * @return Whether this version is greater or equal.
1808 | * @since 2.1.0
1809 | */
1810 | public boolean isGreaterThanOrEqualTo(Version other) {
1811 | require(other != null, "other must no be null");
1812 | return compareTo(other) >= 0;
1813 | }
1814 |
1815 | /**
1816 | * Tests whether this version is strictly lower than the given other version in terms
1817 | * of precedence. Does not consider the build meta data part.
1818 | *
1819 | * Convenience method for {@code this.compareTo(other) < 0} except that this method
1820 | * throws an {@link IllegalArgumentException} if other is null.
1821 | *
1822 | * @param other The version to compare to.
1823 | * @return Whether this version is strictly lower.
1824 | * @since 1.1.0
1825 | */
1826 | public boolean isLowerThan(Version other) {
1827 | require(other != null, "other must no be null");
1828 | return compareTo(other) < 0;
1829 | }
1830 |
1831 | /**
1832 | * Tests whether this version is equal to or lower than the given other version in
1833 | * terms of precedence. Does not consider the build meta data part.
1834 | *
1835 | * Convenience method for {@code this.compareTo(other) <= 0} except that this method
1836 | * throws an {@link IllegalArgumentException} if other is null.
1837 | *
1838 | * @param other The version to compare to.
1839 | * @return Whether this version is lower or equal.
1840 | * @since 2.1.0
1841 | */
1842 | public boolean isLowerThanOrEqualTo(Version other) {
1843 | require(other != null, "other must no be null");
1844 | return compareTo(other) <= 0;
1845 | }
1846 |
1847 | /**
1848 | * Handles proper deserialization of objects serialized with a version prior to 1.1.0
1849 | *
1850 | * @return the deserialized object.
1851 | * @throws ObjectStreamException If deserialization fails.
1852 | * @since 1.1.0
1853 | */
1854 | private Object readResolve() throws ObjectStreamException {
1855 | if (this.preRelease != null || this.buildMetaData != null) {
1856 | return createInternal(this.major, this.minor, this.patch,
1857 | this.preRelease,
1858 | this.buildMetaData);
1859 | }
1860 | return this;
1861 | }
1862 | }
1863 |
--------------------------------------------------------------------------------
/src/main/java/de/skuzzle/semantic/package-info.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Contains a single class semantic version (specification 2.0) implementation.
3 | */
4 | package de.skuzzle.semantic;
5 |
--------------------------------------------------------------------------------
/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * The MIT License (MIT)
3 | *
4 | * Copyright (c) 2017 Simon Taddiken
5 | *
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this
7 | * software and associated documentation files (the "Software"), to deal in the Software
8 | * without restriction, including without limitation the rights to use, copy, modify,
9 | * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
10 | * permit persons to whom the Software is furnished to do so, subject to the following
11 | * conditions:
12 | *
13 | * The above copyright notice and this permission notice shall be included in all copies
14 | * or substantial portions of the Software.
15 | *
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17 | * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
20 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
21 | * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 | */
23 | module de.skuzzle.semantic {
24 | exports de.skuzzle.semantic;
25 | }
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/CustomGsonSerialization.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import java.lang.reflect.Type;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | import com.google.gson.Gson;
10 | import com.google.gson.GsonBuilder;
11 | import com.google.gson.JsonDeserializationContext;
12 | import com.google.gson.JsonDeserializer;
13 | import com.google.gson.JsonElement;
14 | import com.google.gson.JsonParseException;
15 | import com.google.gson.JsonPrimitive;
16 | import com.google.gson.JsonSerializationContext;
17 | import com.google.gson.JsonSerializer;
18 |
19 | public class CustomGsonSerialization {
20 |
21 | private static class SemanticVersionSerializer implements JsonSerializer, JsonDeserializer {
22 |
23 | @Override
24 | public Version deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context)
25 | throws JsonParseException {
26 |
27 | final String versionString = json.getAsString();
28 | return Version.parseVersion(versionString);
29 | }
30 |
31 | @Override
32 | public JsonElement serialize(Version src, Type typeOfSrc, JsonSerializationContext context) {
33 | return new JsonPrimitive(src.toString());
34 | }
35 | }
36 |
37 | private static final class ObjectWithVersionField {
38 | private Version version;
39 | private String differentField;
40 |
41 | public ObjectWithVersionField() {
42 | }
43 |
44 | public ObjectWithVersionField(Version version, String differentField) {
45 | this.version = version;
46 | this.differentField = differentField;
47 | }
48 |
49 | public Version getVersion() {
50 | return this.version;
51 | }
52 | }
53 |
54 | @Test
55 | public void testCustomGsonSerialization() throws Exception {
56 | final Gson gson = new GsonBuilder()
57 | .registerTypeAdapter(Version.class, new SemanticVersionSerializer())
58 | .create();
59 |
60 | final ObjectWithVersionField object = new ObjectWithVersionField(Version.create(1, 2, 3, "pre-release"),
61 | "someString");
62 | final String json = gson.toJson(object);
63 | final ObjectWithVersionField object2 = gson.fromJson(json, ObjectWithVersionField.class);
64 | assertEquals(object.getVersion(), object2.getVersion());
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/CustomJacksonSerialization.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import java.io.IOException;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | import com.fasterxml.jackson.core.JsonGenerator;
10 | import com.fasterxml.jackson.core.JsonParser;
11 | import com.fasterxml.jackson.core.JsonProcessingException;
12 | import com.fasterxml.jackson.databind.DeserializationContext;
13 | import com.fasterxml.jackson.databind.JsonDeserializer;
14 | import com.fasterxml.jackson.databind.JsonSerializer;
15 | import com.fasterxml.jackson.databind.ObjectMapper;
16 | import com.fasterxml.jackson.databind.SerializerProvider;
17 | import com.fasterxml.jackson.databind.module.SimpleModule;
18 |
19 | public class CustomJacksonSerialization {
20 |
21 | private static class SemanticVersionDeserializer extends JsonDeserializer {
22 |
23 | @Override
24 | public Version deserialize(JsonParser p, DeserializationContext ctxt)
25 | throws IOException, JsonProcessingException {
26 |
27 | final String versionString = p.readValueAs(String.class);
28 | return Version.parseVersion(versionString);
29 | }
30 | }
31 |
32 | private static final class SemanticVersionSerializer extends JsonSerializer {
33 |
34 | @Override
35 | public void serialize(Version value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
36 | gen.writeString(value.toString());
37 | }
38 |
39 | }
40 |
41 | private static final class ObjectWithVersionField {
42 | private Version version;
43 | private String differentField;
44 |
45 | public ObjectWithVersionField() {
46 | }
47 |
48 | public ObjectWithVersionField(Version version, String differentField) {
49 | this.version = version;
50 | this.differentField = differentField;
51 | }
52 |
53 | public Version getVersion() {
54 | return this.version;
55 | }
56 |
57 | public String getDifferentField() {
58 | return this.differentField;
59 | }
60 |
61 | }
62 |
63 | @Test
64 | public void testCustomGsonSerialization() throws Exception {
65 | final SimpleModule module = new SimpleModule();
66 | module.addDeserializer(Version.class, new SemanticVersionDeserializer());
67 | module.addSerializer(Version.class, new SemanticVersionSerializer());
68 | final ObjectMapper objectMapper = new ObjectMapper().registerModule(module);
69 |
70 | final ObjectWithVersionField object = new ObjectWithVersionField(Version.create(1, 2, 3, "pre-release"),
71 | "someString");
72 | final String json = objectMapper.writeValueAsString(object);
73 | final ObjectWithVersionField object2 = objectMapper.readValue(json, ObjectWithVersionField.class);
74 | assertEquals(object.getVersion(), object2.getVersion());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/IncrementationTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 |
7 | import org.junit.jupiter.api.Test;
8 |
9 | public class IncrementationTest {
10 |
11 | @Test
12 | void testToStableIsAlreadyStable() throws Exception {
13 | final Version v = Version.create(1, 2, 3).withBuildMetaData("build");
14 | final Version stable = v.toStable();
15 | assertEquals(v, stable);
16 | assertFalse(stable.hasBuildMetaData());
17 | }
18 |
19 | @Test
20 | void testToStableDropIdentifiers() throws Exception {
21 | final Version v = Version.create(1, 2, 3).withPreRelease("SNAPSHOT").withBuildMetaData("build");
22 | final Version stable = v.toStable();
23 | final Version expected = Version.create(1, 2, 3);
24 | assertEquals(expected, stable);
25 | assertFalse(stable.hasBuildMetaData());
26 | }
27 |
28 | @Test
29 | public void testIncrementMajor() throws Exception {
30 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
31 | assertEquals(Version.create(2, 0, 0), v.nextMajor());
32 | }
33 |
34 | @Test
35 | public void testIncrementMajorWithPreReleaseString() throws Exception {
36 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
37 | assertEquals(Version.create(2, 0, 0, "new.pre.release"),
38 | v.nextMajor("new.pre.release"));
39 | }
40 |
41 | @Test
42 | public void testIncrementMajorWithPreReleaseArray() throws Exception {
43 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
44 | assertEquals(Version.create(2, 0, 0, "new.pre.release"),
45 | v.nextMajor(new String[] { "new", "pre", "release" }));
46 | }
47 |
48 | @Test
49 | public void testIncrementMajorWithPreReleaseStringNull() throws Exception {
50 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
51 | assertThrows(IllegalArgumentException.class, () -> v.nextMajor((String) null));
52 | }
53 |
54 | @Test
55 | public void testIncrementMajorWithPreReleaseArrayNull() throws Exception {
56 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
57 | assertThrows(IllegalArgumentException.class, () -> v.nextMajor((String[]) null));
58 | }
59 |
60 | @Test
61 | public void testIncrementMinor() throws Exception {
62 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
63 | assertEquals(Version.create(1, 3, 0), v.nextMinor());
64 | }
65 |
66 | @Test
67 | public void testIncrementMinorWithPreReleaseString() throws Exception {
68 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
69 | assertEquals(Version.create(1, 3, 0, "new.pre.release"),
70 | v.nextMinor("new.pre.release"));
71 | }
72 |
73 | @Test
74 | public void testIncrementMinorWithPreReleaseArray() throws Exception {
75 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
76 | assertEquals(Version.create(1, 3, 0, "new.pre.release"),
77 | v.nextMinor(new String[] { "new", "pre", "release" }));
78 | }
79 |
80 | @Test
81 | public void testIncrementMinorWithPreReleaseStringNull() throws Exception {
82 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
83 | assertThrows(IllegalArgumentException.class, () -> v.nextMinor((String) null));
84 | }
85 |
86 | @Test
87 | public void testIncrementMinorWithPreReleaseArrayNull() throws Exception {
88 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
89 | assertThrows(IllegalArgumentException.class, () -> v.nextMinor((String[]) null));
90 | }
91 |
92 | @Test
93 | public void testIncrementPatch() throws Exception {
94 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
95 | assertEquals(Version.create(1, 2, 4), v.nextPatch());
96 | }
97 |
98 | @Test
99 | public void testIncrementPatchWithPreReleaseString() throws Exception {
100 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
101 | assertEquals(Version.create(1, 2, 4, "new.pre.release"),
102 | v.nextPatch("new.pre.release"));
103 | }
104 |
105 | @Test
106 | public void testIncrementPatchWithPreReleaseArray() throws Exception {
107 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
108 | assertEquals(Version.create(1, 2, 4, "new.pre.release"),
109 | v.nextPatch(new String[] { "new", "pre", "release" }));
110 | }
111 |
112 | @Test
113 | public void testIncrementPatchWithPreReleaseStringNull() throws Exception {
114 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
115 | assertThrows(IllegalArgumentException.class, () -> v.nextPatch((String) null));
116 | }
117 |
118 | @Test
119 | public void testIncrementPatchWithPreReleaseArrayNull() throws Exception {
120 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
121 | assertThrows(IllegalArgumentException.class, () -> v.nextPatch((String[]) null));
122 | }
123 |
124 | @Test
125 | public void testIncrementPreReleaseEmpty() throws Exception {
126 | final Version v = Version.create(1, 2, 3, "", "build");
127 | assertEquals(Version.create(1, 2, 3, "1"), v.nextPreRelease());
128 | }
129 |
130 | @Test
131 | public void testIncrementPreReleaseNumeric() throws Exception {
132 | final Version v = Version.create(1, 2, 3, "pre-release.1", "build");
133 | assertEquals(Version.create(1, 2, 3, "pre-release.2"), v.nextPreRelease());
134 | }
135 |
136 | @Test
137 | public void testIncrementPreReleaseNonNumeric() throws Exception {
138 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
139 | assertEquals(Version.create(1, 2, 3, "pre-release.1"), v.nextPreRelease());
140 | }
141 |
142 | @Test
143 | public void testIncrementBuildMDEmpty() throws Exception {
144 | final Version v = Version.create(1, 2, 3, "pre-release", "");
145 | assertEquals(Version.create(1, 2, 3, "pre-release", "1"),
146 | v.nextBuildMetaData());
147 | }
148 |
149 | @Test
150 | public void testIncrementBuildMDNumeric() throws Exception {
151 | final Version v = Version.create(1, 2, 3, "pre-release", "build.1");
152 | assertEquals(Version.create(1, 2, 3, "pre-release", "build.2"),
153 | v.nextBuildMetaData());
154 | }
155 |
156 | @Test
157 | public void testIncrementBuildMDNonNumeric() throws Exception {
158 | final Version v = Version.create(1, 2, 3, "pre-release", "build");
159 | assertEquals(Version.create(1, 2, 3, "pre-release", "build.1"),
160 | v.nextBuildMetaData());
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/Java6CompatibilityTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.UncheckedIOException;
8 | import java.nio.file.Files;
9 | import java.nio.file.Path;
10 | import java.nio.file.Paths;
11 | import java.nio.file.StandardOpenOption;
12 | import java.util.stream.Stream;
13 |
14 | import org.junit.jupiter.api.Test;
15 |
16 | public class Java6CompatibilityTest {
17 |
18 | @Test
19 | void testAllClassFilesAreCompatibleWithJava6() throws Exception {
20 | final int java6 = 0x32;
21 | allClassFiles()
22 | .filter(cf -> !isModuleInfo(cf))
23 | .forEach(cf -> testClassFileForJavaVersion(cf, java6));
24 | }
25 |
26 | @Test
27 | void testModuleInfoIsCompatibleWithJava9() throws Exception {
28 | final int java9 = 0x35;
29 | allClassFiles()
30 | .filter(this::isModuleInfo)
31 | .forEach(mi -> testClassFileForJavaVersion(mi, java9));
32 | }
33 |
34 | private boolean isModuleInfo(Path path) {
35 | return path.getFileName().toString().equals("module-info.class");
36 | }
37 |
38 | private Stream allClassFiles() throws IOException {
39 | final Path targetFolder = Paths.get("./target/classes");
40 | return Files.find(targetFolder, Integer.MAX_VALUE,
41 | (path, bfa) -> path.getFileName().toString().endsWith(".class"));
42 | }
43 |
44 | private void testClassFileForJavaVersion(Path path, int expectedClassFileVersion) {
45 | final int[] expectedSequence = { 0xCA, 0xFE, 0xBA, 0xBE, 0, 0, 0, expectedClassFileVersion };
46 |
47 | try (InputStream in = Files.newInputStream(path, StandardOpenOption.READ)) {
48 | for (int i = 0; i < expectedSequence.length; ++i) {
49 | final int expected = expectedSequence[i];
50 | final int actual = in.read();
51 | assertEquals(expected, actual,
52 | String.format("Class file %s: Expected byte %d at index %d but found %d", path, expected, i,
53 | actual));
54 | }
55 | } catch (final IOException e) {
56 | throw new UncheckedIOException(e);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/ParsingTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertFalse;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 | import static org.junit.jupiter.api.DynamicTest.dynamicTest;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.List;
11 |
12 | import org.junit.jupiter.api.DynamicTest;
13 | import org.junit.jupiter.api.TestFactory;
14 |
15 | import de.skuzzle.semantic.Version.VersionFormatException;
16 |
17 | public class ParsingTest {
18 |
19 | private static final String INCOMPLETE_VERSION_PART = "Incomplete version part in %s";
20 |
21 | private static String unexpectedChar(char c) {
22 | return "Unexpected char in %s: " + Character.toString(c);
23 | }
24 |
25 | private static final String[] LEGAL_VERSIONS = {
26 | "123.456.789",
27 | "0.1.0",
28 | "0.0.1",
29 | "0.0.1-0a",
30 | "0.0.1-0a+a0",
31 | "1.1.0-a",
32 | "1.1.0+a",
33 | "1.1.0+012",
34 | "1.1.0-112",
35 | "1.1.0-0",
36 | "1.1.0-0123a",
37 | "1.1.0-0123a+0012",
38 | };
39 |
40 | private static final String[][] ILLEGAL_PRE_RELEASES = {
41 | { "01.1", "Illegal leading char '0' in pre-release part of %s" },
42 | { "1.01", "Illegal leading char '0' in pre-release part of %s" },
43 | { "pre.001", "Illegal leading char '0' in pre-release part of %s" },
44 | { "pre.01", "Illegal leading char '0' in pre-release part of %s" },
45 | { "pre..foo", INCOMPLETE_VERSION_PART },
46 | { "$", unexpectedChar('$') }
47 | };
48 |
49 | private static final String[][] ILLEGAL_VERSIONS = {
50 | { "1.", INCOMPLETE_VERSION_PART },
51 | { "1.1.", INCOMPLETE_VERSION_PART },
52 | { "1.0", INCOMPLETE_VERSION_PART },
53 | { "1.2.3-pre.foo.", INCOMPLETE_VERSION_PART },
54 | { "1", INCOMPLETE_VERSION_PART },
55 | { "1$.1.0", unexpectedChar('$') },
56 | { "1.1$.0", unexpectedChar('$') },
57 | { "1.1.1$", unexpectedChar('$') },
58 | { "$.1.1", unexpectedChar('$') },
59 | { "1.$.1", unexpectedChar('$') },
60 | { "1.1.$", unexpectedChar('$') },
61 | { "0$.1.1", unexpectedChar('$') },
62 | { "1.0$.1", unexpectedChar('$') },
63 | { "1.1.0$", unexpectedChar('$') },
64 | { "01.1.0", "Illegal leading char '0' in major part of %s" },
65 | { "1.01.0", "Illegal leading char '0' in minor part of %s" },
66 | { "1.1.01", "Illegal leading char '0' in patch part of %s" },
67 | { "1.2.3-01.1", "Illegal leading char '0' in pre-release part of %s" },
68 | { "1.2.3-1.01+abc", "Illegal leading char '0' in pre-release part of %s" },
69 | { "1.2.3-1.01", "Illegal leading char '0' in pre-release part of %s" },
70 | { "1.2.3-pre.001", "Illegal leading char '0' in pre-release part of %s" },
71 | { "1.2.3-pre.01", "Illegal leading char '0' in pre-release part of %s" },
72 | { "1.2.3-pre.01+a.b", "Illegal leading char '0' in pre-release part of %s" },
73 | { "1.2.3-pre..foo", INCOMPLETE_VERSION_PART },
74 | { "1.2.3+pre..foo", INCOMPLETE_VERSION_PART },
75 | { "1.2.3+pre.foo.", INCOMPLETE_VERSION_PART },
76 | { "1.2.3-$+foo", unexpectedChar('$') },
77 | { "1.2.3+$", unexpectedChar('$') },
78 | { "1.2.3-foo$+foo", unexpectedChar('$') },
79 | { "1.2.3-1$+foo", unexpectedChar('$') },
80 | { "1.2.3-1+1$", unexpectedChar('$') },
81 | { "1.2.3-foo+foo$", unexpectedChar('$') },
82 | { "1.2.3-1+1$", unexpectedChar('$') },
83 | { "1.2.3-0$", unexpectedChar('$') },
84 | { "1.2.3+0$", unexpectedChar('$') },
85 | { "1.2.3-0123$", unexpectedChar('$') },
86 | { "1.2.3-+", unexpectedChar('+') },
87 |
88 | };
89 |
90 | @TestFactory
91 | public Collection testParseWithException() {
92 | final List results = new ArrayList<>(
93 | ILLEGAL_VERSIONS.length);
94 |
95 | for (final String[] input : ILLEGAL_VERSIONS) {
96 | results.add(dynamicTest("Parse " + input[0],
97 | () -> {
98 | final VersionFormatException e = assertThrows(
99 | VersionFormatException.class,
100 | () -> Version.parseVersion(input[0]));
101 |
102 | final String expectedMessage = String.format(input[1], input[0]);
103 | assertEquals(expectedMessage, e.getMessage());
104 | }));
105 | }
106 |
107 | return results;
108 | }
109 |
110 | @TestFactory
111 | public Collection testWithPreReleaseException() {
112 | final List results = new ArrayList<>(
113 | ILLEGAL_PRE_RELEASES.length);
114 |
115 | for (final String input[] : ILLEGAL_PRE_RELEASES) {
116 | results.add(dynamicTest("Pre release " + input[0],
117 | () -> {
118 | final VersionFormatException e = assertThrows(
119 | VersionFormatException.class,
120 | () -> Version.create(1, 2, 3).withPreRelease(input[0]));
121 |
122 | final String expectedMessage = String.format(input[1], input[0]);
123 | assertEquals(expectedMessage, e.getMessage());
124 | }));
125 | }
126 |
127 | return results;
128 | }
129 |
130 | @TestFactory
131 | public Collection testWithPreReleaseArrayException() {
132 | final List results = new ArrayList<>(
133 | ILLEGAL_PRE_RELEASES.length);
134 |
135 | for (final String input[] : ILLEGAL_PRE_RELEASES) {
136 | results.add(dynamicTest("Pre release as array: " + input[0],
137 | () -> {
138 | final VersionFormatException e = assertThrows(
139 | VersionFormatException.class,
140 | () -> Version.create(1, 2, 3).withPreRelease(
141 | input[0].split("\\.")));
142 |
143 | final String expectedMessage = String.format(input[1], input[0]);
144 | assertEquals(expectedMessage, e.getMessage());
145 | }));
146 | }
147 |
148 | return results;
149 | }
150 |
151 | @TestFactory
152 | public Collection testParseVerifyOnly() {
153 | final List results = new ArrayList<>();
154 |
155 | for (final String[] input : ILLEGAL_VERSIONS) {
156 | results.add(dynamicTest(input[0],
157 | () -> assertFalse(Version.isValidVersion(input[0]))));
158 | }
159 | return results;
160 | }
161 |
162 | @TestFactory
163 | public Collection testParseLegalVersions() {
164 | final List results = new ArrayList<>();
165 |
166 | for (final String input : LEGAL_VERSIONS) {
167 | final DynamicTest test = dynamicTest("Parse " + input, () -> {
168 | final Version parsed = Version.parseVersion(input);
169 | assertEquals(input, parsed.toString());
170 | });
171 | results.add(test);
172 | }
173 | return results;
174 | }
175 |
176 | }
177 |
--------------------------------------------------------------------------------
/src/test/java/de/skuzzle/semantic/VersionTest.java:
--------------------------------------------------------------------------------
1 | package de.skuzzle.semantic;
2 |
3 | import static org.junit.jupiter.api.Assertions.assertArrayEquals;
4 | import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
5 | import static org.junit.jupiter.api.Assertions.assertEquals;
6 | import static org.junit.jupiter.api.Assertions.assertFalse;
7 | import static org.junit.jupiter.api.Assertions.assertSame;
8 | import static org.junit.jupiter.api.Assertions.assertThrows;
9 | import static org.junit.jupiter.api.Assertions.assertTrue;
10 | import static org.junit.jupiter.api.Assertions.fail;
11 |
12 | import java.io.ByteArrayInputStream;
13 | import java.io.ByteArrayOutputStream;
14 | import java.io.FileOutputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.io.ObjectInputStream;
18 | import java.io.ObjectOutputStream;
19 |
20 | import org.junit.jupiter.api.Assertions;
21 | import org.junit.jupiter.api.Test;
22 | import org.junitpioneer.jupiter.DefaultLocale;
23 |
24 | import de.skuzzle.semantic.Version.VersionFormatException;
25 |
26 | public class VersionTest {
27 |
28 | private static final char[] ILLEGAL_CHAR_BOUNDS = { 'a' - 1, 'z' + 1, '0' - 1,
29 | '9' + 1, 'A' - 1, 'Z' + 1, '-' - 1 };
30 |
31 | private static final char[] ILLEGAL_NUMERIC_BOUNDS = { '0' - 1, '9' + 1 };
32 |
33 | private static final Version[] SEMVER_ORG_VERSIONS = new Version[] {
34 | Version.parseVersion("1.0.0-alpha"),
35 | Version.parseVersion("1.0.0-alpha.1"),
36 | Version.parseVersion("1.0.0-alpha.beta"),
37 | Version.parseVersion("1.0.0-beta"),
38 | Version.parseVersion("1.0.0-beta.2"),
39 | Version.parseVersion("1.0.0-beta.11"),
40 | Version.parseVersion("1.0.0-rc.1"),
41 | Version.parseVersion("1.0.0"),
42 | Version.parseVersion("2.0.0"),
43 | Version.parseVersion("2.1.0"),
44 | Version.parseVersion("2.1.1")
45 | };
46 |
47 | // same as above, but exchanged pre release and build meta data
48 | private static final Version[] SEMVER_ORG_BMD_VERSIONS = new Version[] {
49 | Version.parseVersion("1.0.0-rc.1+alpha"),
50 | Version.parseVersion("1.0.0-rc.1+alpha.1"),
51 | Version.parseVersion("1.0.0-rc.1+alpha.beta"),
52 | Version.parseVersion("1.0.0-rc.1+beta"),
53 | Version.parseVersion("1.0.0-rc.1+beta.2"),
54 | Version.parseVersion("1.0.0-rc.1+beta.11"),
55 | Version.parseVersion("1.0.0-rc.1+rc.1"),
56 | Version.parseVersion("1.0.0-rc.1"),
57 | Version.parseVersion("2.0.0-rc.1"),
58 | Version.parseVersion("2.1.0-rc.1"),
59 | Version.parseVersion("2.1.1")
60 | };
61 |
62 | private static final String[][] ILLEGAL_VERSIONS = {
63 | { "1.", "Incomplete version part in 1." },
64 | { "1.1.", "Incomplete version part in 1.1." }
65 | };
66 |
67 | public static void main(String[] args) throws IOException {
68 | new VersionTest().writeBinFile();
69 | }
70 |
71 | public void writeBinFile() throws IOException {
72 | final FileOutputStream out = new FileOutputStream("versions.bin");
73 | final ObjectOutputStream oout = new ObjectOutputStream(out);
74 | for (final Version v : SEMVER_ORG_VERSIONS) {
75 | oout.writeObject(v);
76 | }
77 | for (final Version v : SEMVER_ORG_BMD_VERSIONS) {
78 | oout.writeObject(v);
79 | }
80 | oout.close();
81 | }
82 |
83 | @Test
84 | public void testIllegalVersions() throws Exception {
85 | for (final String[] input : ILLEGAL_VERSIONS) {
86 | try {
87 | Version.parseVersion(input[0]);
88 | fail("String '" + input[0] + "' should not be parsable");
89 | } catch (final VersionFormatException e) {
90 | assertEquals(e.getMessage(), input[1],
91 | "Expected different exception message");
92 | }
93 | }
94 | }
95 |
96 | @Test
97 | public void testPreReleaseEmptyString() {
98 | final Version v = Version.create(1, 1, 1, "");
99 | assertEquals("", v.getPreRelease());
100 | assertEquals("", v.getBuildMetaData());
101 | }
102 |
103 | @Test
104 | public void testPreReleaseNull() {
105 | assertThrows(IllegalArgumentException.class,
106 | () -> Version.create(1, 1, 1, null));
107 | }
108 |
109 | @Test
110 | public void testBuildMDNull() {
111 | assertThrows(IllegalArgumentException.class,
112 | () -> Version.create(1, 1, 1, "", null));
113 | }
114 |
115 | @Test
116 | public void testNegativePatch() {
117 | assertThrows(IllegalArgumentException.class,
118 | () -> Version.create(1, 1, -1));
119 | }
120 |
121 | @Test
122 | public void testNegativeMinor() {
123 | assertThrows(IllegalArgumentException.class,
124 | () -> Version.create(1, -1, 1));
125 | }
126 |
127 | @Test
128 | public void testNegativeMajor() {
129 | assertThrows(IllegalArgumentException.class,
130 | () -> Version.create(-1, 1, 1));
131 | }
132 |
133 | @Test
134 | public void parseVersionNull() {
135 | assertThrows(IllegalArgumentException.class,
136 | () -> Version.parseVersion(null));
137 | }
138 |
139 | @Test
140 | public void testSimpleVersion() {
141 | final Version v = Version.parseVersion("1.2.3");
142 | assertEquals(1, v.getMajor());
143 | assertEquals(2, v.getMinor());
144 | assertEquals(3, v.getPatch());
145 | assertEquals("", v.getPreRelease());
146 | assertEquals("", v.getBuildMetaData());
147 | }
148 |
149 | @Test
150 | public void testSemVerOrgPreReleaseSamples() {
151 | final Version v1 = Version.parseVersion("1.0.0-alpha");
152 | assertEquals("alpha", v1.getPreRelease());
153 |
154 | final Version v2 = Version.parseVersion("1.0.0-alpha.1");
155 | assertEquals("alpha.1", v2.getPreRelease());
156 |
157 | final Version v3 = Version.parseVersion("1.0.0-0.3.7");
158 | assertEquals("0.3.7", v3.getPreRelease());
159 |
160 | final Version v4 = Version.parseVersion("1.0.0-x.7.z.92");
161 | assertEquals("x.7.z.92", v4.getPreRelease());
162 | }
163 |
164 | @Test
165 | public void testSemVerOrgBuildMDSamples() {
166 | final Version v1 = Version.parseVersion("1.0.0-alpha+001");
167 | assertEquals("alpha", v1.getPreRelease());
168 | assertEquals("001", v1.getBuildMetaData());
169 |
170 | final Version v2 = Version.parseVersion("1.0.0+20130313144700");
171 | assertEquals("20130313144700", v2.getBuildMetaData());
172 |
173 | final Version v3 = Version.parseVersion("1.0.0-beta+exp.sha.5114f85");
174 | assertEquals("beta", v3.getPreRelease());
175 | assertEquals("exp.sha.5114f85", v3.getBuildMetaData());
176 | }
177 |
178 | @Test
179 | public void testVersionWithBuildMD() {
180 | final Version v = Version.parseVersion("1.2.3+some.id.foo");
181 | assertEquals("some.id.foo", v.getBuildMetaData());
182 | }
183 |
184 | @Test
185 | public void testVersionWithBuildMD2() {
186 | final Version v = Version.create(1, 2, 3, "", "some.id-1.foo");
187 | assertEquals("some.id-1.foo", v.getBuildMetaData());
188 | }
189 |
190 | @Test
191 | public void testParseMajorIsZero() throws Exception {
192 | final Version version = Version.parseVersion("0.1.2");
193 | assertEquals(0, version.getMajor());
194 | }
195 |
196 | @Test
197 | public void testVersionWithBuildMDEmptyLastPart() {
198 | assertThrows(VersionFormatException.class,
199 | () -> Version.create(1, 2, 3, "", "some.id."));
200 | }
201 |
202 | @Test
203 | public void testVersionWithBuildMDEmptyMiddlePart() {
204 | assertThrows(VersionFormatException.class,
205 | () -> Version.create(1, 2, 3, "", "some..id"));
206 | }
207 |
208 | @Test
209 | public void testParseBuildMDWithLeadingZeroInIdentifierPart() throws Exception {
210 | final Version v = Version.parseVersion("1.2.3+0abc");
211 | assertEquals("0abc", v.getBuildMetaData());
212 | }
213 |
214 | @Test
215 | public void testParsePreReleaseLastPartIsNumeric() throws Exception {
216 | final Version v = Version.parseVersion("1.2.3-a.11+buildmd");
217 | assertEquals("a.11", v.getPreRelease());
218 | assertEquals("buildmd", v.getBuildMetaData());
219 | }
220 |
221 | @Test
222 | public void testVersionWithPreRelease() {
223 | final Version v = Version.parseVersion("1.2.3-pre.release-foo.1");
224 | assertEquals("pre.release-foo.1", v.getPreRelease());
225 | final String[] expected = { "pre", "release-foo", "1" };
226 | assertArrayEquals(expected, v.getPreReleaseParts());
227 | }
228 |
229 | @Test
230 | public void testVersionWithPreReleaseAndBuildMD() {
231 | final Version v = Version
232 | .parseVersion("1.2.3-pre.release-foo.1+some.id-with-hyphen");
233 | assertEquals("pre.release-foo.1", v.getPreRelease());
234 | assertEquals("some.id-with-hyphen", v.getBuildMetaData());
235 | }
236 |
237 | @Test
238 | public void testIsValidVersionLeadingZeroMinor() throws Exception {
239 | assertFalse(Version.isValidVersion("1.01.1"));
240 | }
241 |
242 | private void shouldNotBeParseable(String template, char c) {
243 | final String v = String.format(template, c);
244 |
245 | try {
246 | Version.parseVersion(v);
247 | fail("Version " + v + " should not be parsable");
248 | } catch (final VersionFormatException e) {
249 |
250 | }
251 | }
252 |
253 | @Test
254 | public void testIllegalCharNumericParts() throws Exception {
255 | for (final char c : ILLEGAL_NUMERIC_BOUNDS) {
256 | shouldNotBeParseable("%c.2.3", c);
257 | shouldNotBeParseable("1.%c.3", c);
258 | shouldNotBeParseable("1.2.%c", c);
259 | }
260 | }
261 |
262 | @Test
263 | public void testPreReleaseInvalidChar1() throws Exception {
264 | for (final char c : ILLEGAL_CHAR_BOUNDS) {
265 | shouldNotBeParseable("1.0.0-%c", c);
266 | }
267 | }
268 |
269 | @Test
270 | public void testPreReleaseInvalidChar2() throws Exception {
271 | for (final char c : ILLEGAL_CHAR_BOUNDS) {
272 | shouldNotBeParseable("1.0.0-1.a%c", c);
273 | }
274 | }
275 |
276 | @Test
277 | public void testPreReleaseInvalidChar31() throws Exception {
278 | for (final char c : ILLEGAL_CHAR_BOUNDS) {
279 | shouldNotBeParseable("1.0.0-01a%c", c);
280 | }
281 | }
282 |
283 | @Test
284 | public void testBuildMDInvalidChar1() throws Exception {
285 | for (final char c : ILLEGAL_CHAR_BOUNDS) {
286 | shouldNotBeParseable("1.0.0+%c", c);
287 | }
288 | }
289 |
290 | @Test
291 | public void testBuildMDInvalidChar2() throws Exception {
292 | for (final char c : ILLEGAL_CHAR_BOUNDS) {
293 | shouldNotBeParseable("1.0.0+1.a%c", c);
294 | }
295 | }
296 |
297 | @Test
298 | public void testBuildMetaDataHyphenOnly() throws Exception {
299 | final Version v = Version.parseVersion("1.2.3+-");
300 | assertEquals("-", v.getBuildMetaData());
301 | }
302 |
303 | @Test
304 | public void testPreReleaseHyphenOnly() throws Exception {
305 | final Version v = Version.parseVersion("1.2.3--");
306 | assertEquals("-", v.getPreRelease());
307 | }
308 |
309 | @Test
310 | public void testParseMajorUnexpectedChar() throws Exception {
311 | assertThrows(VersionFormatException.class,
312 | () -> Version.parseVersion("1$.0.0"));
313 | }
314 |
315 | @Test
316 | public void testParsePatchUnexpectedChar() throws Exception {
317 | assertThrows(VersionFormatException.class,
318 | () -> Version.parseVersion("1.0.1$"));
319 | }
320 |
321 | @Test
322 | public void testParseLeadingZeroMinor() throws Exception {
323 | assertThrows(VersionFormatException.class,
324 | () -> Version.parseVersion("1.01.1"));
325 | }
326 |
327 | @Test
328 | public void testIsValidVersionLeadingZeroPatch() throws Exception {
329 | assertFalse(Version.isValidVersion("1.1.01"));
330 | }
331 |
332 | @Test
333 | public void testParseLeadingZeroPatch() throws Exception {
334 | assertThrows(VersionFormatException.class,
335 | () -> Version.parseVersion("1.1.01"));
336 | }
337 |
338 | @Test
339 | public void testIsValidVersionLeadingZeroMajor() throws Exception {
340 | assertFalse(Version.isValidVersion("01.1.1"));
341 | }
342 |
343 | @Test
344 | public void testParseMissingPart() throws Exception {
345 | assertThrows(VersionFormatException.class,
346 | () -> Version.parseVersion("1.0"));
347 | }
348 |
349 | @Test
350 | public void testIsValidVersionMissingPart() throws Exception {
351 | assertFalse(Version.isValidVersion("1.1"));
352 | }
353 |
354 | @Test
355 | public void testParsePrematureStop() throws Exception {
356 | assertThrows(VersionFormatException.class,
357 | () -> Version.parseVersion("1."));
358 | }
359 |
360 | @Test
361 | public void testIsValidVersionPrematureStop() throws Exception {
362 | assertFalse(Version.isValidVersion("1."));
363 | }
364 |
365 | @Test
366 | public void testParseMajorLeadingZero() throws Exception {
367 | assertThrows(VersionFormatException.class,
368 | () -> Version.parseVersion("01.0.0"));
369 | }
370 |
371 | @Test
372 | public void testPreReleaseWithLeadingZeroes() {
373 | assertThrows(VersionFormatException.class,
374 | () -> Version.parseVersion("1.2.3-pre.001"));
375 | }
376 |
377 | @Test
378 | public void testPreReleaseWithLeadingZeroes2() {
379 | assertThrows(VersionFormatException.class,
380 | () -> Version.create(1, 2, 3, "pre.001"));
381 | }
382 |
383 | @Test
384 | public void testPreReleaseWithLeadingZeroEOS() {
385 | assertThrows(VersionFormatException.class,
386 | () -> Version.parseVersion("1.2.3-pre.01"));
387 | }
388 |
389 | @Test
390 | public void testPreReleaseWithLeadingZeroEOS2() {
391 | assertThrows(VersionFormatException.class,
392 | () -> Version.create(1, 2, 3, "pre.01"));
393 | }
394 |
395 | @Test
396 | public void testPreReleaseWithLeadingZeroAndBuildMD() {
397 | assertThrows(VersionFormatException.class,
398 | () -> Version.parseVersion("1.2.3-pre.01+a.b"));
399 | }
400 |
401 | @Test
402 | public void testPreReleaseMiddleEmptyIdentifier() {
403 | assertThrows(VersionFormatException.class,
404 | () -> Version.parseVersion("1.2.3-pre..foo"));
405 | }
406 |
407 | @Test
408 | public void testPreReleaseLastEmptyIdentifier() {
409 | assertThrows(VersionFormatException.class,
410 | () -> Version.parseVersion("1.2.3-pre.foo."));
411 | }
412 |
413 | @Test
414 | public void testBuildMDMiddleEmptyIdentifier() {
415 | assertThrows(VersionFormatException.class,
416 | () -> Version.parseVersion("1.2.3+pre..foo"));
417 | }
418 |
419 | @Test
420 | public void testBuildMDLastEmptyIdentifier() {
421 | assertThrows(VersionFormatException.class,
422 | () -> Version.parseVersion("1.2.3+pre.foo."));
423 | }
424 |
425 | @Test
426 | public void testParseExpectNoPrelease() {
427 | assertThrows(VersionFormatException.class,
428 | () -> Version.parseVersion("1.2.3-foo", false));
429 | }
430 |
431 | @Test
432 | public void testParseExpectNoBuildMetaData() {
433 | assertThrows(VersionFormatException.class,
434 | () -> Version.parseVersion("1.2.3+foo", false));
435 | }
436 |
437 | @Test
438 | public void testParseExpectNoPreReleaseAndBuildMetaData() {
439 | assertThrows(VersionFormatException.class,
440 | () -> Version.parseVersion("1.2.3-foo+foo", false));
441 | }
442 |
443 | @Test
444 | public void testParseVersionIllegalCharInPreReleaseOnly() throws Exception {
445 | assertThrows(VersionFormatException.class,
446 | () -> Version.parseVersion("1.2.3-$+foo"));
447 | }
448 |
449 | @Test
450 | public void testParseVersionIllegalCharBuildMDOnly() throws Exception {
451 | assertThrows(VersionFormatException.class,
452 | () -> Version.parseVersion("1.2.3+$"));
453 | }
454 |
455 | @Test
456 | public void testParseVersionIllegalCharInPreRelease() throws Exception {
457 | assertThrows(VersionFormatException.class,
458 | () -> Version.parseVersion("1.2.3-foo$+foo"));
459 | }
460 |
461 | @Test
462 | public void testParseVersionIllegalCharInPreReleaseNumericPart() throws Exception {
463 | assertThrows(VersionFormatException.class,
464 | () -> Version.parseVersion("1.2.3-1$+foo"));
465 | }
466 |
467 | @Test
468 | public void testParseVersionIllegalCharInBuildMDNumericPart() throws Exception {
469 | assertThrows(VersionFormatException.class,
470 | () -> Version.parseVersion("1.2.3-1+1$"));
471 | }
472 |
473 | @Test
474 | public void testParseVersionIllegalCharInBuildMD() throws Exception {
475 | assertThrows(VersionFormatException.class,
476 | () -> Version.parseVersion("1.2.3-foo+foo$"));
477 | }
478 |
479 | @Test
480 | public void testParseVersionPreReleaseSingleZero() throws Exception {
481 | Version.parseVersion("1.2.3-0.1.0");
482 | }
483 |
484 | @Test
485 | public void testParseVersionPreReleaseAndBuildMDSingleZero() throws Exception {
486 | Version.parseVersion("1.2.3-0.1.0+0.0.0.1");
487 | }
488 |
489 | @Test
490 | public void testParsePreReleaseIllegalLeadingZero() throws Exception {
491 | assertThrows(VersionFormatException.class,
492 | () -> Version.parseVersion("1.2.3-01.1"));
493 | }
494 |
495 | @Test
496 | public void testParsePreReleaseIllegalLeadingZeroBeforeBuildMD() throws Exception {
497 | assertThrows(VersionFormatException.class,
498 | () -> Version.parseVersion("1.2.3-1.01+abc"));
499 | }
500 |
501 | @Test
502 | public void testParsePreReleaseIllegalLeadingZeroInLastPart() throws Exception {
503 | assertThrows(VersionFormatException.class,
504 | () -> Version.parseVersion("1.2.3-1.01"));
505 | }
506 |
507 | @Test
508 | public void testParseVersionSuccessExpectNoPreRelease() {
509 | Version.parseVersion("1.2.3", false);
510 | }
511 |
512 | @Test
513 | public void testParseVersionSuccess() {
514 | final Version version = Version.parseVersion("1.2.3-foo+bar", true);
515 | assertEquals("foo", version.getPreRelease());
516 | assertEquals("bar", version.getBuildMetaData());
517 | }
518 |
519 | @Test
520 | public void testPreReleaseLastEmptyIdentifier2() {
521 | assertThrows(VersionFormatException.class,
522 | () -> Version.create(1, 2, 3, "pre.foo."));
523 | }
524 |
525 | @Test
526 | public void testVersionAll0() {
527 | assertDoesNotThrow(() -> Version.parseVersion("0.0.0"));
528 | }
529 |
530 | @Test
531 | public void testVersionAll02() {
532 | assertDoesNotThrow(() -> Version.create(0, 0, 0));
533 | }
534 |
535 | @Test
536 | public void testVersionAll03() {
537 | assertDoesNotThrow(() -> Version.create(0, 0));
538 | }
539 |
540 | @Test
541 | public void testVersionAll04() {
542 | assertDoesNotThrow(() -> Version.create(0));
543 | }
544 |
545 | @Test
546 | public void testPreReleaseInvalid() {
547 | assertThrows(VersionFormatException.class,
548 | () -> Version.create(1, 2, 3, "pre.", "build"));
549 | }
550 |
551 | @Test
552 | public void testPreReleaseNullAndBuildMDGiven() {
553 | assertThrows(IllegalArgumentException.class,
554 | () -> Version.create(1, 2, 3, null, "build"));
555 | }
556 |
557 | @Test
558 | public void testOnlyBuildMdEmpty() {
559 | Version.create(1, 2, 3, "pre", "");
560 | }
561 |
562 | @Test
563 | public void testPreReleaseWithLeadingZeroesIdentifier() {
564 | // leading zeroes allowed in string identifiers
565 | final Version v = Version.parseVersion("1.2.3-001abc");
566 | assertEquals("001abc", v.getPreRelease());
567 | }
568 |
569 | @Test
570 | public void testPreReleaseWithLeadingZeroesIdentifier2() {
571 | // leading zeroes allowed in string identifiers
572 | final Version v = Version.create(1, 2, 3, "001abc");
573 | assertEquals("001abc", v.getPreRelease());
574 | }
575 |
576 | @Test
577 | public void testNoPrecedenceChangeByBuildMD() {
578 | final Version v1 = Version.parseVersion("1.2.3+1.0");
579 | final Version v2 = Version.parseVersion("1.2.3+2.0");
580 | assertEquals(0, v1.compareTo(v2));
581 | }
582 |
583 | @Test
584 | public void testSimplePrecedence() {
585 | final Version v1 = Version.parseVersion("1.0.0");
586 | final Version v2 = Version.parseVersion("1.0.1");
587 | final Version v3 = Version.parseVersion("1.1.0");
588 | final Version v4 = Version.parseVersion("2.0.0");
589 |
590 | assertTrue(v1.compareTo(v2) < 0);
591 | assertTrue(v2.compareTo(v3) < 0);
592 | assertTrue(v3.compareTo(v4) < 0);
593 | assertTrue(v2.compareTo(v1) > 0);
594 | assertTrue(v3.compareTo(v2) > 0);
595 | assertTrue(v4.compareTo(v3) > 0);
596 | assertTrue(v4.isGreaterThanOrEqualTo(v3));
597 | assertTrue(v3.isLowerThanOrEqualTo(v4));
598 | }
599 |
600 | @Test
601 | public void testPrecedencePreRelease() {
602 | final Version v1 = Version.parseVersion("1.0.0");
603 | final Version v2 = Version.parseVersion("1.0.0-rc1");
604 | assertTrue(v1.compareTo(v2) > 0);
605 | assertTrue(v2.compareTo(v1) < 0);
606 | assertTrue(v2.isLowerThan(v1));
607 | assertTrue(v1.isGreaterThan(v2));
608 | assertTrue(v1.isGreaterThanOrEqualTo(v2));
609 | assertTrue(v2.isLowerThanOrEqualTo(v1));
610 | }
611 |
612 | @Test
613 | public void testPrecedencePreRelease2() {
614 | final Version v1 = Version.parseVersion("1.0.0-rc1");
615 | final Version v2 = Version.parseVersion("1.0.0-rc1");
616 | assertTrue(v1.compareTo(v2) == 0);
617 | }
618 |
619 | @Test
620 | public void testPrecedencePreRelease3() {
621 | final Version v1 = Version.parseVersion("1.0.0-rc1");
622 | final Version v2 = Version.parseVersion("1.0.0-rc1.5");
623 | // the one with longer list is greater
624 | assertTrue(v1.compareTo(v2) < 0);
625 | assertTrue(v2.compareTo(v1) > 0);
626 | }
627 |
628 | @Test
629 | public void testPrecedencePreRelease4() {
630 | final Version v1 = Version.parseVersion("1.0.0-a");
631 | final Version v2 = Version.parseVersion("1.0.0-b");
632 | assertTrue(v1.compareTo(v2) < 0);
633 | assertTrue(v2.compareTo(v1) > 0);
634 | }
635 |
636 | @Test
637 | public void testPrecedencePreRelease5() {
638 | final Version v1 = Version.parseVersion("1.0.0-1");
639 | final Version v2 = Version.parseVersion("1.0.0-2");
640 | assertTrue(v1.compareTo(v2) < 0);
641 | assertTrue(v2.compareTo(v1) > 0);
642 | }
643 |
644 | @Test
645 | public void testPrecedencePreRelease6() {
646 | final Version v1 = Version.parseVersion("1.0.0-1.some.id-with-hyphen.a");
647 | final Version v2 = Version.parseVersion("1.0.0-1.some.id-with-hyphen.b");
648 | assertTrue(v1.compareTo(v2) < 0);
649 | assertTrue(v2.compareTo(v1) > 0);
650 | }
651 |
652 | @Test
653 | public void testIsGreaterNull() throws Exception {
654 | assertThrows(IllegalArgumentException.class,
655 | () -> Version.create(1, 0, 0).isGreaterThan(null));
656 | }
657 |
658 | @Test
659 | public void testIsLowerNull() throws Exception {
660 | assertThrows(IllegalArgumentException.class,
661 | () -> Version.create(1, 0, 0).isLowerThan(null));
662 | }
663 |
664 | @Test
665 | public void testIsGreaterThanOrEqualToNull() throws Exception {
666 | assertThrows(IllegalArgumentException.class,
667 | () -> Version.create(1, 0, 0).isGreaterThanOrEqualTo(null));
668 | }
669 |
670 | @Test
671 | public void testIsLowerThanOrEqualToNull() throws Exception {
672 | assertThrows(IllegalArgumentException.class,
673 | () -> Version.create(1, 0, 0).isLowerThanOrEqualTo(null));
674 | }
675 |
676 | @Test
677 | public void testInitialDevelopment() {
678 | final Version v1 = Version.create(0, 1, 0);
679 | final Version v2 = Version.create(1, 1, 0);
680 | assertTrue(v1.isInitialDevelopment());
681 | assertFalse(v2.isInitialDevelopment());
682 | }
683 |
684 | @Test
685 | public void testSemVerOrgPrecedenceSample() {
686 | for (int i = 1; i < SEMVER_ORG_VERSIONS.length; ++i) {
687 | final Version v1 = SEMVER_ORG_VERSIONS[i - 1];
688 | final Version v2 = SEMVER_ORG_VERSIONS[i];
689 | final int c = v1.compareTo(v2);
690 | assertTrue(c < 0, v1 + " is not lower than " + v2);
691 | assertTrue(v1.isLowerThan(v2));
692 | assertTrue(v1.isLowerThanOrEqualTo(v2));
693 | assertFalse(v1.isGreaterThan(v2));
694 | assertFalse(v1.isGreaterThanOrEqualTo(v2));
695 | }
696 | }
697 |
698 | @Test
699 | public void testSemVerOrgPrecedenceSampleComparator() {
700 | for (int i = 1; i < SEMVER_ORG_VERSIONS.length; ++i) {
701 | final Version v1 = SEMVER_ORG_VERSIONS[i - 1];
702 | final Version v2 = SEMVER_ORG_VERSIONS[i];
703 | final int c = Version.NATURAL_ORDER.compare(v1, v2);
704 | assertTrue(c < 0, v1 + " is not lower than " + v2);
705 | }
706 | }
707 |
708 | @Test
709 | public void testBuildMetaDataEquality() {
710 | final Version v1 = Version.create(0, 0, 1, "", "some.build-meta.data");
711 | final Version v2 = Version.create(0, 0, 1, "", "some.different.build-meta.data");
712 | assertFalse(v1.equalsWithBuildMetaData(v2));
713 | }
714 |
715 | @Test
716 | public void testBuildMDPrecedence() {
717 | for (int i = 1; i < SEMVER_ORG_BMD_VERSIONS.length; ++i) {
718 | final Version v1 = SEMVER_ORG_BMD_VERSIONS[i - 1];
719 | final Version v2 = SEMVER_ORG_BMD_VERSIONS[i];
720 | final int c = v1.compareToWithBuildMetaData(v2);
721 | assertTrue(c < 0, v1 + " is not lower than " + v2);
722 | }
723 | }
724 |
725 | @Test
726 | public void testBuildMDPrecedenceComparator() {
727 | for (int i = 1; i < SEMVER_ORG_BMD_VERSIONS.length; ++i) {
728 | final Version v1 = SEMVER_ORG_BMD_VERSIONS[i - 1];
729 | final Version v2 = SEMVER_ORG_BMD_VERSIONS[i];
730 | final int c = Version.WITH_BUILD_META_DATA_ORDER.compare(v1, v2);
731 | assertTrue(c < 0, v1 + " is not lower than " + v2);
732 | }
733 | }
734 |
735 | @Test
736 | public void testBuildMDPrecedenceReverse() {
737 | for (int i = 1; i < SEMVER_ORG_BMD_VERSIONS.length; ++i) {
738 | final Version v1 = SEMVER_ORG_BMD_VERSIONS[i - 1];
739 | final Version v2 = SEMVER_ORG_BMD_VERSIONS[i];
740 | final int c = v2.compareToWithBuildMetaData(v1);
741 | assertTrue(c > 0, v2 + " is not greater than " + v1);
742 | }
743 | }
744 |
745 | @Test
746 | public void testPreReleaseEquality() throws Exception {
747 | for (final Version version : SEMVER_ORG_VERSIONS) {
748 | final Version copy = Version.create(version.getMajor(), version.getMinor(),
749 | version.getPatch(), version.getPreRelease(),
750 | version.getBuildMetaData());
751 | assertEquals(version, copy);
752 | assertTrue(version.equalsWithBuildMetaData(copy));
753 | assertTrue(version.compareTo(copy) == 0);
754 | assertTrue(version.compareToWithBuildMetaData(copy) == 0);
755 | assertEquals(version.hashCode(), copy.hashCode());
756 | }
757 | }
758 |
759 | @Test
760 | public void testBuildMDEquality() throws Exception {
761 | for (final Version version : SEMVER_ORG_BMD_VERSIONS) {
762 | final Version copy = Version.create(version.getMajor(), version.getMinor(),
763 | version.getPatch(), version.getPreRelease(),
764 | version.getBuildMetaData());
765 | assertEquals(version, copy);
766 | assertTrue(version.equalsWithBuildMetaData(copy));
767 | assertTrue(version.compareTo(copy) == 0);
768 | assertTrue(version.compareToWithBuildMetaData(copy) == 0);
769 | assertEquals(version.hashCode(), copy.hashCode());
770 | }
771 | }
772 |
773 | @Test
774 | public void testCompareWithBuildMDNull1() throws Exception {
775 | assertThrows(NullPointerException.class,
776 | () -> Version.compareWithBuildMetaData(null, Version.create(1, 0, 0)));
777 | }
778 |
779 | @Test
780 | public void testCompareWithBuildMDNull2() throws Exception {
781 | assertThrows(NullPointerException.class,
782 | () -> Version.compareWithBuildMetaData(Version.create(1, 0, 0), null));
783 | }
784 |
785 | @Test
786 | public void testCompareNull1() {
787 | assertThrows(NullPointerException.class,
788 | () -> Version.compare(null, Version.create(1, 1, 1)));
789 | }
790 |
791 | @Test
792 | public void testCompareNull2() {
793 | assertThrows(NullPointerException.class,
794 | () -> Version.compare(Version.create(1, 1, 1), null));
795 | }
796 |
797 | @Test
798 | public void testCompareIdentical() {
799 | final Version v = Version.create(1, 1, 1);
800 | assertEquals(0, Version.compare(v, v));
801 | }
802 |
803 | @Test
804 | public void testNotEqualsNull() {
805 | final Version v = Version.create(1, 1, 1);
806 | assertFalse(v.equals(null));
807 | }
808 |
809 | @Test
810 | public void testNotEqualsForeign() {
811 | final Version v = Version.create(1, 1, 1);
812 | assertFalse(v.equals(new Object()));
813 | }
814 |
815 | @Test
816 | public void testEqualsIdentity() {
817 | final Version v = Version.create(1, 2, 3);
818 | assertEquals(v, v);
819 | }
820 |
821 | @Test
822 | public void testNotEqualsTrivial() {
823 | final Version v1 = Version.create(1, 1, 1);
824 | final Version v2 = Version.create(1, 1, 2);
825 | assertFalse(v1.equals(v2));
826 | }
827 |
828 | @Test
829 | public void testParseToString() {
830 | for (final Version v1 : SEMVER_ORG_VERSIONS) {
831 | final Version v2 = Version.parseVersion(v1.toString());
832 | assertEquals(v1, v2);
833 | assertEquals(v1.hashCode(), v2.hashCode());
834 | }
835 | }
836 |
837 | @Test
838 | public void testParseToStringUpperCase() {
839 | for (final Version v1 : SEMVER_ORG_VERSIONS) {
840 | final Version v2 = Version.parseVersion(v1.toString().toUpperCase());
841 | assertEquals(v1.toUpperCase(), v2);
842 | assertEquals(v1.toUpperCase().hashCode(), v2.hashCode());
843 | }
844 | }
845 |
846 | @Test
847 | public void testParseToStringLowerCase() {
848 | for (final Version v1 : SEMVER_ORG_VERSIONS) {
849 | final Version v2 = Version.parseVersion(v1.toString().toLowerCase());
850 | assertEquals(v1.toLowerCase(), v2);
851 | assertEquals(v1.toLowerCase().hashCode(), v2.hashCode());
852 | }
853 | }
854 |
855 | @Test
856 | public void testMin() throws Exception {
857 | final Version v1 = Version.create(1, 0, 0);
858 | final Version v2 = Version.create(0, 1, 0);
859 |
860 | assertSame(Version.min(v1, v2), Version.min(v2, v1));
861 | assertSame(v2, Version.min(v1, v2));
862 | assertSame(v2, v2.min(v1));
863 | }
864 |
865 | @Test
866 | public void testMinEquals() throws Exception {
867 | final Version v1 = Version.create(1, 0, 0);
868 | final Version v2 = Version.create(1, 0, 0);
869 |
870 | final Version min = Version.min(v1, v2);
871 | assertSame(v1, min);
872 | assertSame(v1, v1.min(v2));
873 | }
874 |
875 | @Test
876 | public void testMinNullV1() throws Exception {
877 | assertThrows(IllegalArgumentException.class,
878 | () -> Version.min(null, Version.create(1, 0, 0)));
879 | }
880 |
881 | @Test
882 | public void testMinNullV2() throws Exception {
883 | assertThrows(IllegalArgumentException.class,
884 | () -> Version.min(Version.create(1, 0, 0), null));
885 | }
886 |
887 | @Test
888 | public void testMax() throws Exception {
889 | final Version v1 = Version.create(1, 0, 0);
890 | final Version v2 = Version.create(0, 1, 0);
891 |
892 | assertSame(Version.max(v1, v2), Version.max(v2, v1));
893 | assertSame(v1, Version.max(v1, v2));
894 | assertSame(v1, v1.max(v2));
895 | }
896 |
897 | @Test
898 | public void testMaxEquals() throws Exception {
899 | final Version v1 = Version.create(1, 0, 0);
900 | final Version v2 = Version.create(1, 0, 0);
901 |
902 | final Version max = Version.max(v1, v2);
903 | assertSame(v1, max);
904 | assertSame(v1, v1.max(v2));
905 | }
906 |
907 | @Test
908 | public void testMaxNullV1() throws Exception {
909 | assertThrows(IllegalArgumentException.class,
910 | () -> Version.max(null, Version.create(1, 0, 0)));
911 | }
912 |
913 | @Test
914 | public void testMaxNullV2() throws Exception {
915 | assertThrows(IllegalArgumentException.class,
916 | () -> Version.max(Version.create(1, 0, 0), null));
917 | }
918 |
919 | @Test
920 | public void testSamePrereleaseAndWithBuildMD() throws Exception {
921 | final Version v1 = Version.parseVersion("1.0.0-a.b+a");
922 | final Version v2 = Version.parseVersion("1.0.0-a.b+b");
923 |
924 | assertTrue(v1.compareToWithBuildMetaData(v2) < 0);
925 | }
926 |
927 | @Test
928 | public void testIsNoPreReleaseIdentifierNull() throws Exception {
929 | assertFalse(Version.isValidPreRelease(null));
930 | }
931 |
932 | @Test
933 | public void testIsPreReleaseIdentifierEmptyString() throws Exception {
934 | assertTrue(Version.isValidPreRelease(""));
935 | }
936 |
937 | @Test
938 | public void testIsValidPreReleaseIdentifier() throws Exception {
939 | for (final Version v : SEMVER_ORG_VERSIONS) {
940 | assertTrue(Version.isValidPreRelease(v.getPreRelease()),
941 | v.getPreRelease() + " should be a valid identifier");
942 | }
943 | assertTrue(Version.isValidPreRelease("-"));
944 | }
945 |
946 | @Test
947 | public void testIsNotValidPreReleaseIdentifier() throws Exception {
948 | assertFalse(Version.isValidPreRelease("a+b"));
949 | }
950 |
951 | @Test
952 | public void testIsNotValidPreReleaseNumericIdentifier() throws Exception {
953 | assertFalse(Version.isValidPreRelease("123+b"));
954 | }
955 |
956 | @Test
957 | public void testIsNotValidBuildMDIdentifier() throws Exception {
958 | assertFalse(Version.isValidBuildMetaData("a+b"));
959 | }
960 |
961 | @Test
962 | public void testIsNotValidBuildMDNumericIdentifier() throws Exception {
963 | assertFalse(Version.isValidBuildMetaData("123+b"));
964 | }
965 |
966 | @Test
967 | public void testIsNoBuildMDIdentifierNull() throws Exception {
968 | assertFalse(Version.isValidBuildMetaData(null));
969 | }
970 |
971 | @Test
972 | public void testIsBuildMDIdentifierEmptyString() throws Exception {
973 | assertTrue(Version.isValidBuildMetaData(""));
974 | }
975 |
976 | @Test
977 | public void testIsValidBuildMDIdentifier() throws Exception {
978 | for (final Version v : SEMVER_ORG_BMD_VERSIONS) {
979 | assertTrue(Version.isValidBuildMetaData(v.getBuildMetaData()), v.toString());
980 | assertTrue(Version.isValidBuildMetaData(v.getPreRelease()), v.toString());
981 | }
982 | assertTrue(Version.isValidBuildMetaData("-"));
983 | }
984 |
985 | @Test
986 | public void testNullIsNoVersion() throws Exception {
987 | assertFalse(Version.isValidVersion(null));
988 | }
989 |
990 | @Test
991 | public void testEmptyStringIsNoVersion() throws Exception {
992 | assertFalse(Version.isValidVersion(""));
993 | }
994 |
995 | @Test
996 | public void testIsValidVersion() throws Exception {
997 | for (final Version v : SEMVER_ORG_VERSIONS) {
998 | assertTrue(Version.isValidVersion(v.toString()));
999 | }
1000 | }
1001 |
1002 | @Test
1003 | public void testSerialize() throws Exception {
1004 | final ByteArrayOutputStream bout = new ByteArrayOutputStream();
1005 | final ObjectOutputStream out = new ObjectOutputStream(bout);
1006 | for (final Version v : SEMVER_ORG_VERSIONS) {
1007 | out.writeObject(v);
1008 | }
1009 | out.close();
1010 | final InputStream bin = new ByteArrayInputStream(bout.toByteArray());
1011 | final ObjectInputStream in = new ObjectInputStream(bin);
1012 | for (final Version v : SEMVER_ORG_VERSIONS) {
1013 | assertEquals(v, in.readObject());
1014 | }
1015 | in.close();
1016 | }
1017 |
1018 | @Test
1019 | public void testDeserialize05() throws Exception {
1020 | // Deserialize a file which has been written by version 0.6.0
1021 | final ClassLoader cl = getClass().getClassLoader();
1022 | final InputStream inp = cl.getResourceAsStream("versions_0.6.bin");
1023 | final ObjectInputStream oin = new ObjectInputStream(inp);
1024 | for (final Version v : SEMVER_ORG_VERSIONS) {
1025 | assertEquals(v, oin.readObject());
1026 | }
1027 |
1028 | for (final Version v : SEMVER_ORG_BMD_VERSIONS) {
1029 | assertEquals(v, oin.readObject());
1030 | }
1031 | oin.close();
1032 | }
1033 |
1034 | @Test
1035 | public void testEmptyArrayPreRelease() throws Exception {
1036 | final Version v = Version.parseVersion("1.0.0");
1037 | assertEquals(0, v.getPreReleaseParts().length);
1038 | }
1039 |
1040 | @Test
1041 | public void testEmptyArrayBuildMetaData() throws Exception {
1042 | final Version v = Version.parseVersion("1.0.0");
1043 | assertEquals(0, v.getBuildMetaDataParts().length);
1044 | }
1045 |
1046 | @Test
1047 | public void testGetBuildMDParts() throws Exception {
1048 | final Version v = Version.parseVersion("1.0.0+a.b.c.001");
1049 | assertArrayEquals(new String[] { "a", "b", "c", "001" },
1050 | v.getBuildMetaDataParts());
1051 | }
1052 |
1053 | @Test
1054 | public void testWithMajorAllWillbe0() throws Exception {
1055 | final Version v = Version.create(1, 0, 0);
1056 |
1057 | assertDoesNotThrow(() -> v.withMajor(0));
1058 | }
1059 |
1060 | @Test
1061 | public void testWithMinorAllWillbe0() throws Exception {
1062 | final Version v = Version.create(0, 1, 0);
1063 |
1064 | assertDoesNotThrow(() -> v.withMinor(0));
1065 | }
1066 |
1067 | @Test
1068 | public void testWithPatchAllWillbe0() throws Exception {
1069 | final Version v = Version.create(0, 0, 1);
1070 |
1071 | assertDoesNotThrow(() -> v.withPatch(0));
1072 | }
1073 |
1074 | @Test
1075 | public void testWithMajorKeepEverythingElse() throws Exception {
1076 | final Version v = Version.create(1, 2, 3, "foo", "bar");
1077 | assertEquals(Version.create(2, 2, 3, "foo", "bar"), v.withMajor(2));
1078 | }
1079 |
1080 | @Test
1081 | public void testWithMinorKeepEverythingElse() throws Exception {
1082 | final Version v = Version.create(1, 2, 3, "foo", "bar");
1083 | assertEquals(Version.create(1, 1, 3, "foo", "bar"), v.withMinor(1));
1084 | }
1085 |
1086 | @Test
1087 | public void testWithPatchKeepEverythingElse() throws Exception {
1088 | final Version v = Version.create(1, 2, 3, "foo", "bar");
1089 | assertEquals(Version.create(1, 2, 2, "foo", "bar"), v.withPatch(2));
1090 | }
1091 |
1092 | @Test
1093 | public void testWithPreReleaseKeepEverythingElse() throws Exception {
1094 | final Version v = Version.create(1, 2, 3, "foo", "bar");
1095 | assertEquals(Version.create(1, 2, 3, "bar", "bar"), v.withPreRelease("bar"));
1096 | }
1097 |
1098 | @Test
1099 | public void testWithPreReleaseEmpty() throws Exception {
1100 | final Version v = Version.create(1, 2, 3, "foo");
1101 | assertEquals(Version.create(1, 2, 3), v.withPreRelease(""));
1102 | }
1103 |
1104 | @Test
1105 | public void testWithPreReleaseEmptyArray() throws Exception {
1106 | final Version v = Version.create(1, 2, 3, "foo");
1107 | assertEquals(Version.create(1, 2, 3), v.withPreRelease(new String[0]));
1108 | }
1109 |
1110 | @Test
1111 | public void testWithPreReleaseModifyArray() throws Exception {
1112 | final String[] newPreRelease = new String[] { "bar" };
1113 | final Version v = Version.create(1, 2, 3, "foo");
1114 | final Version v2 = v.withPreRelease(newPreRelease);
1115 | newPreRelease[0] = "foo";
1116 | assertEquals(Version.create(1, 2, 3, "bar"), v2);
1117 | }
1118 |
1119 | @Test
1120 | public void testWithPreReleaseIllegalPart() throws Exception {
1121 | final String[] newPreRelease = new String[] { "01" };
1122 |
1123 | assertThrows(VersionFormatException.class,
1124 | () -> Version.create(1, 0, 0).withPreRelease(newPreRelease));
1125 | }
1126 |
1127 | @Test
1128 | public void testWithPreReleaseNull() throws Exception {
1129 | assertThrows(IllegalArgumentException.class,
1130 | () -> Version.create(1, 2, 3).withPreRelease((String) null));
1131 | }
1132 |
1133 | @Test
1134 | public void testWithPreReleaseNull2() throws Exception {
1135 | assertThrows(IllegalArgumentException.class,
1136 | () -> Version.create(1, 2, 3).withPreRelease((String[]) null));
1137 | }
1138 |
1139 | @Test
1140 | public void testPreReleaseArrayWithDotPart() throws Exception {
1141 | final Version v = Version.create(1, 0, 0);
1142 | Assertions.assertEquals(
1143 | v.withPreRelease(new String[] { "12.a" }),
1144 | v.withPreRelease(new String[] { "12", "a" }));
1145 | }
1146 |
1147 | @Test
1148 | public void testBuildMdArrayWithDotPart() throws Exception {
1149 | assertThrows(VersionFormatException.class,
1150 | () -> Version.create(1, 0, 0).withPreRelease(new String[] { "12+a" }));
1151 | }
1152 |
1153 | @Test
1154 | public void testWithBuildMDKeepEverythingElse() throws Exception {
1155 | final Version v = Version.create(1, 2, 3, "foo", "bar");
1156 | assertEquals(Version.create(1, 2, 3, "foo", "foo"), v.withBuildMetaData("foo"));
1157 | }
1158 |
1159 | @Test
1160 | public void testWithBuildMDEmpty() throws Exception {
1161 | final Version v = Version.create(1, 2, 3, "", "foo");
1162 | assertEquals(Version.create(1, 2, 3), v.withBuildMetaData(""));
1163 | }
1164 |
1165 | @Test
1166 | public void testWithBuildMDEmptyArray() throws Exception {
1167 | final Version v = Version.create(1, 2, 3, "", "foo");
1168 | assertEquals(Version.create(1, 2, 3), v.withBuildMetaData(new String[0]));
1169 | }
1170 |
1171 | @Test
1172 | public void testBuildMDArrayWithDotPart() throws Exception {
1173 | final Version v = Version.create(1, 0, 0);
1174 | Assertions.assertEquals(
1175 | v.withBuildMetaData(new String[] { "12.a" }),
1176 | v.withBuildMetaData(new String[] { "12", "a" }));
1177 | }
1178 |
1179 | @Test
1180 | public void testWithBuildMDIllegalPartPlus() throws Exception {
1181 | assertThrows(VersionFormatException.class,
1182 | () -> Version.create(1, 0, 0).withBuildMetaData(new String[] { "12+a" }));
1183 | }
1184 |
1185 | @Test
1186 | public void testWithBuildMDModifyArray() throws Exception {
1187 | final String[] newBuildMd = new String[] { "bar" };
1188 | final Version v = Version.create(1, 2, 3, "", "foo");
1189 | final Version v2 = v.withBuildMetaData(newBuildMd);
1190 | newBuildMd[0] = "foo";
1191 | assertEquals(Version.create(1, 2, 3, "", "bar"), v2);
1192 | }
1193 |
1194 | @Test
1195 | public void testWithBuildMdNull() throws Exception {
1196 | assertThrows(IllegalArgumentException.class,
1197 | () -> Version.create(1, 2, 3).withBuildMetaData((String) null));
1198 | }
1199 |
1200 | @Test
1201 | public void testWithBuildMdNull2() throws Exception {
1202 | assertThrows(IllegalArgumentException.class,
1203 | () -> Version.create(1, 2, 3).withBuildMetaData((String[]) null));
1204 | }
1205 |
1206 | @Test
1207 | public void testParseBiggerNumbers() throws Exception {
1208 | final Version v = Version.parseVersion("1234.5678.9012");
1209 | assertEquals(1234, v.getMajor());
1210 | assertEquals(5678, v.getMinor());
1211 | assertEquals(9012, v.getPatch());
1212 | }
1213 |
1214 | @Test
1215 | void testIsNotStableWIthPreRelease() throws Exception {
1216 | assertFalse(Version.create(1, 2, 3).withPreRelease("foo").isStable());
1217 | }
1218 |
1219 | @Test
1220 | void testIsStable() throws Exception {
1221 | assertTrue(Version.create(1, 3, 4).isStable());
1222 | }
1223 |
1224 | @Test
1225 | void testIsStableWithBuildMetaData() throws Exception {
1226 | assertTrue(Version.create(1, 3, 4).withBuildMetaData("foo").isStable());
1227 | }
1228 |
1229 | // This would have produced an illegal identifier when using toLowerCase with default
1230 | // locale
1231 | // https://github.com/skuzzle/semantic-version/pull/5
1232 | @Test
1233 | @DefaultLocale("tr-TR")
1234 | void testToLowerCaseWithTurkishLocale() throws Exception {
1235 | final Version lowerCase = Version.create(1, 3, 4).withPreRelease("I")
1236 | .toLowerCase();
1237 | Version.create(1, 3, 4, lowerCase.getPreRelease());
1238 | }
1239 |
1240 | // This would have produced an illegal identifier when using toLowerCase with default
1241 | // locale
1242 | // https://github.com/skuzzle/semantic-version/pull/5
1243 | @Test
1244 | @DefaultLocale("tr-TR")
1245 | void testToUpperCaseWithTurkishLocale() throws Exception {
1246 | final Version lowerCase = Version.create(1, 3, 4)
1247 | .withPreRelease("i")
1248 | .toUpperCase();
1249 | Version.create(1, 3, 4, lowerCase.getPreRelease());
1250 | }
1251 | }
1252 |
--------------------------------------------------------------------------------
/src/test/resources/versions_0.5.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skuzzle/semantic-version/f817338295a94df30f24ac7013f82cb345ccdbd1/src/test/resources/versions_0.5.bin
--------------------------------------------------------------------------------
/src/test/resources/versions_0.6.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/skuzzle/semantic-version/f817338295a94df30f24ac7013f82cb345ccdbd1/src/test/resources/versions_0.6.bin
--------------------------------------------------------------------------------