diff --git a/src/main/java/org/springframework/data/release/model/Calver.java b/src/main/java/org/springframework/data/release/model/Calver.java new file mode 100644 index 0000000..a33721e --- /dev/null +++ b/src/main/java/org/springframework/data/release/model/Calver.java @@ -0,0 +1,219 @@ +package org.springframework.data.release.model; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Value object to represent a Calver version consisting of year, minor, micro and modifier parts. + * + * @author Mark Paluch + */ +@Getter +public class Calver implements Comparable { + + private static final Pattern PATTERN = Pattern + .compile("(\\d{4})\\.(\\d+)\\.(\\d+)(-((SR\\d+)|(RC\\d+)|(M\\d+)|(SNAPSHOT)))?"); + + private final int year; + private final int minor; + private final int micro; + private final Iteration modifier; + + private Calver(int year, int minor, int micro, Iteration modifier) { + this.year = year; + this.minor = minor; + this.micro = micro; + this.modifier = modifier; + } + + /** + * Parses the given string representation of a version into a {@link Calver} object. + * + * @param version must not be {@literal null} or empty. + * @return + */ + public static Calver parse(String version) { + + Assert.hasText(version, "Version must not be null or empty!"); + + Matcher matcher = PATTERN.matcher(version); + Assert.isTrue(matcher.find(), "Version does not match CalVer"); + + int year = Integer.parseInt(matcher.group(1)); + int minor = Integer.parseInt(matcher.group(2)); + int micro = Integer.parseInt(matcher.group(3)); + String modifier = matcher.group(5); + Iteration iteration; + if (modifier == null) { + iteration = Iteration.GA; + } else if (modifier.equals("SNAPSHOT")) { + iteration = new Iteration("SNAPSHOT", null); + } else { + iteration = Iteration.valueOf(modifier); + } + + return new Calver(year, minor, micro, iteration); + } + + /** + * Returns whether the current {@link Calver} is greater (newer) than the given one. + * + * @param version + * @return + */ + public boolean isGreaterThan(Calver version) { + return compareTo(version) > 0; + } + + /** + * Returns whether the current {@link Calver} is greater (newer) or the same as the given one. + * + * @param version + * @return + */ + public boolean isGreaterThanOrEqualTo(Calver version) { + return compareTo(version) >= 0; + } + + /** + * Returns whether the current {@link Calver} is the same as the given one. + * + * @param version + * @return + */ + public boolean is(Calver version) { + return equals(version); + } + + /** + * Returns whether the current {@link Calver} is less (older) than the given one. + * + * @param version + * @return + */ + public boolean isLessThan(Calver version) { + return compareTo(version) < 0; + } + + /** + * Returns whether the current {@link Calver} is less (older) or equal to the current one. + * + * @param version + * @return + */ + public boolean isLessThanOrEqualTo(Calver version) { + return compareTo(version) <= 0; + } + + public Calver nextMinor() { + return new Calver(this.year, this.minor + 1, 0, modifier); + } + + public Calver nextBugfix() { + return new Calver(this.year, this.minor, this.micro + 1, modifier); + } + + public Calver withBugfix(int bugfix) { + return new Calver(this.year, this.minor, bugfix, modifier); + } + + public Calver withModifier(Iteration modifier) { + return new Calver(this.year, this.minor, this.micro, modifier); + } + + /* + * (non-Javadoc) + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(Calver that) { + + if (that == null) { + return 1; + } + + if (year != that.year) { + return year - that.year; + } + + if (minor != that.minor) { + return minor - that.minor; + } + + if (micro != that.micro) { + return micro - that.micro; + } + + if (modifier != that.modifier) { + + if (modifier == null && that.modifier == null) { + return 0; + } + + if (modifier != null && that.modifier == null) { + return modifier.compareTo(Iteration.GA); + } + + if (modifier == null && that.modifier != null) { + return Iteration.GA.compareTo(modifier); + } + return modifier.compareTo(that.modifier); + } + + return 0; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Calver)) + return false; + + Calver calver = (Calver) o; + + if (year != calver.year) + return false; + if (minor != calver.minor) + return false; + if (micro != calver.micro) + return false; + return modifier.equals(calver.modifier); + } + + @Override + public int hashCode() { + int result = year; + result = 31 * result + minor; + result = 31 * result + micro; + result = 31 * result + modifier.hashCode(); + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + List digits = new ArrayList<>(); + digits.add(year); + digits.add(minor); + digits.add(micro); + + String raw = StringUtils.collectionToDelimitedString(digits, "."); + + if (!Iteration.GA.equals(this.modifier)) { + return String.format("%s-%s", raw, this.modifier.getName()); + } + + return raw; + } +} diff --git a/src/main/java/org/springframework/data/release/model/Iteration.java b/src/main/java/org/springframework/data/release/model/Iteration.java index 76b02d8..c9f2937 100644 --- a/src/main/java/org/springframework/data/release/model/Iteration.java +++ b/src/main/java/org/springframework/data/release/model/Iteration.java @@ -18,7 +18,10 @@ package org.springframework.data.release.model; import lombok.NonNull; import lombok.Value; +import java.lang.reflect.Field; + import org.springframework.util.Assert; +import org.springframework.util.ReflectionUtils; /** * Value object to represent an individual release train iteration. @@ -27,7 +30,7 @@ import org.springframework.util.Assert; * @author Mark Paluch */ @Value -public class Iteration { +public class Iteration implements Comparable { public static final Iteration SR24 = new Iteration("SR23", null); public static final Iteration SR23 = new Iteration("SR23", SR24); @@ -54,9 +57,20 @@ public class Iteration { public static final Iteration SR2 = new Iteration("SR2", SR3); public static final Iteration SR1 = new Iteration("SR1", SR2); public static final Iteration GA = new Iteration("GA", SR1); + public static final Iteration RC4 = new Iteration("RC4", GA); + public static final Iteration RC3 = new Iteration("RC3", GA); + public static final Iteration RC2 = new Iteration("RC2", GA); public static final Iteration RC1 = new Iteration("RC1", GA); + public static final Iteration M5 = new Iteration("M5", RC1); + public static final Iteration M4 = new Iteration("M4", RC1); + public static final Iteration M3 = new Iteration("M3", RC1); + public static final Iteration M2 = new Iteration("M2", RC1); public static final Iteration M1 = new Iteration("M1", RC1); + private static final int GREATER_THAN = 1; + private static final int LESS_THAN = -GREATER_THAN; + private static final int EQUAL = 0; + /** * The name of the iteration. */ @@ -71,6 +85,24 @@ public class Iteration { this.next = next; } + /** + * Lookup {@link Iteration} by its string value. + * + * @param iteration + * @return + * @throws IllegalArgumentException if iteration cannot be resolved. + */ + public static Iteration valueOf(String iteration) { + + Field field = ReflectionUtils.findField(Iteration.class, iteration); + + if (field == null) { + throw new IllegalArgumentException("Cannot resolve iteration " + iteration); + } + + return (Iteration) ReflectionUtils.getField(field, null); + } + public boolean isGAIteration() { return this.equals(GA); } @@ -106,6 +138,27 @@ public class Iteration { return this.equals(M1); } + public boolean isMilestone() { + return name.startsWith("M"); + } + + public boolean isReleaseCandidate() { + return name.startsWith("RC"); + } + + public int getIterationValue() { + + if (isMilestone()) { + return Integer.parseInt(name.substring(1)); + } + + if (isReleaseCandidate() || isServiceIteration()) { + return Integer.parseInt(name.substring(2)); + } + + return EQUAL; + } + public int getBugfixValue() { return name.startsWith("SR") ? Integer.parseInt(name.substring(2)) : 0; } @@ -118,4 +171,56 @@ public class Iteration { public String toString() { return name; } + + @Override + public int compareTo(Iteration o) { + + if (isMilestone()) { + + if (o.isMilestone()) { + return Integer.compare(getIterationValue(), o.getIterationValue()); + } + + return LESS_THAN; + } + + if (isReleaseCandidate()) { + + if (o.isMilestone()) { + return GREATER_THAN; + } + + if (o.isReleaseCandidate()) { + return Integer.compare(getIterationValue(), o.getIterationValue()); + } + + return LESS_THAN; + } + + if (isGAIteration()) { + + if (o.isMilestone() || o.isReleaseCandidate()) { + return GREATER_THAN; + } + + if (o.isGAIteration()) { + return EQUAL; + } + + return LESS_THAN; + } + + if (isServiceIteration()) { + + if (o.isMilestone() || o.isReleaseCandidate() || o.isGAIteration()) { + return GREATER_THAN; + } + + if (o.isServiceIteration()) { + return Integer.compare(getIterationValue(), o.getIterationValue()); + } + } + + return EQUAL; + } } diff --git a/src/test/java/org/springframework/data/release/model/CalverVersionUnitTests.java b/src/test/java/org/springframework/data/release/model/CalverVersionUnitTests.java new file mode 100644 index 0000000..928415b --- /dev/null +++ b/src/test/java/org/springframework/data/release/model/CalverVersionUnitTests.java @@ -0,0 +1,78 @@ +/* + * Copyright 2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.release.model; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + +/** + * Unit tests for {@link Calver}. + * + * @author Mark Paluch + */ +public class CalverVersionUnitTests { + + @Test + public void shouldParseRelease() { + + Calver version = Calver.parse("2020.0.1"); + + assertThat(version.getYear()).isEqualTo(2020); + assertThat(version.getMinor()).isEqualTo(0); + assertThat(version.getMicro()).isEqualTo(1); + } + + @Test + public void shouldParseM1Release() { + + Calver version = Calver.parse("2020.0.1-M1"); + + assertThat(version.getYear()).isEqualTo(2020); + assertThat(version.getMinor()).isEqualTo(0); + assertThat(version.getMicro()).isEqualTo(1); + assertThat(version.getModifier()).isEqualTo(Iteration.M1); + } + + @Test + public void shouldCompareReleasesCorrectly() { + + Calver version = Calver.parse("2020.0.1-RC2"); + + assertThat(version).isGreaterThan(Calver.parse("2020.0.0-RC2")); + assertThat(version).isLessThan(Calver.parse("2020.0.2-RC2")); + assertThat(version).isLessThan(Calver.parse("2020.1.0-RC2")); + assertThat(version).isLessThan(Calver.parse("2021.0.0-RC2")); + + assertThat(version).isGreaterThan(Calver.parse("2020.0.1-RC1")); + assertThat(version).isLessThan(Calver.parse("2020.0.1")); + + assertThat(Calver.parse("2020.0.1")).isEqualTo(Calver.parse("2020.0.1")); + assertThat(Calver.parse("2020.0.1")).isLessThan(Calver.parse("2020.0.1-SR1")); + assertThat(Calver.parse("2020.0.1")).isGreaterThan(Calver.parse("2020.0.1-M1")); + } + + @Test + public void shouldParseSnapshot() { + + Calver version = Calver.parse("2020.0.1-SNAPSHOT"); + + assertThat(version.getYear()).isEqualTo(2020); + assertThat(version.getMinor()).isEqualTo(0); + assertThat(version.getMicro()).isEqualTo(1); + assertThat(version.getModifier()).isEqualTo(new Iteration("SNAPSHOT", null)); + } +} diff --git a/src/test/java/org/springframework/data/release/model/IterationUnitTests.java b/src/test/java/org/springframework/data/release/model/IterationUnitTests.java index d05351e..d78738d 100644 --- a/src/test/java/org/springframework/data/release/model/IterationUnitTests.java +++ b/src/test/java/org/springframework/data/release/model/IterationUnitTests.java @@ -15,10 +15,59 @@ */ package org.springframework.data.release.model; +import static org.assertj.core.api.Assertions.*; + +import org.junit.Test; + /** - * - * @author Oliver Gierke + * Unit tests for {@link Iteration}. + * + * @author Mark Paluch */ public class IterationUnitTests { + @Test + public void shouldCompareMilestoneIterationsCorrectly() { + + assertThat(Iteration.M1).isEqualByComparingTo(Iteration.M1); + assertThat(Iteration.M1).isLessThan(Iteration.RC1); + assertThat(Iteration.M1).isLessThan(Iteration.GA); + assertThat(Iteration.M1).isLessThan(Iteration.SR1); + + assertThat(Iteration.M2).isLessThan(Iteration.M3); + assertThat(Iteration.M2).isGreaterThan(Iteration.M1); + } + + @Test + public void shouldCompareReleaseCandidateIterationsCorrectly() { + + assertThat(Iteration.RC1).isGreaterThan(Iteration.M1); + assertThat(Iteration.RC1).isEqualByComparingTo(Iteration.RC1); + assertThat(Iteration.RC1).isLessThan(Iteration.GA); + assertThat(Iteration.RC1).isLessThan(Iteration.SR1); + + assertThat(Iteration.RC2).isLessThan(Iteration.RC3); + assertThat(Iteration.RC2).isGreaterThan(Iteration.RC1); + } + + @Test + public void shouldCompareGAIterationsCorrectly() { + + assertThat(Iteration.GA).isGreaterThan(Iteration.M1); + assertThat(Iteration.GA).isGreaterThan(Iteration.RC1); + assertThat(Iteration.GA).isEqualByComparingTo(Iteration.GA); + assertThat(Iteration.GA).isLessThan(Iteration.SR1); + } + + @Test + public void shouldCompareServiceReleaseIterationsCorrectly() { + + assertThat(Iteration.SR1).isGreaterThan(Iteration.M1); + assertThat(Iteration.SR1).isGreaterThan(Iteration.RC1); + assertThat(Iteration.SR1).isGreaterThan(Iteration.GA); + assertThat(Iteration.SR1).isEqualByComparingTo(Iteration.SR1); + + assertThat(Iteration.SR2).isLessThan(Iteration.SR3); + assertThat(Iteration.SR2).isGreaterThan(Iteration.SR1); + } }