diff --git a/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/InvalidVersionException.java b/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/InvalidVersionException.java new file mode 100644 index 0000000000..1a8d9f73cc --- /dev/null +++ b/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/InvalidVersionException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012-2015 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.boot.dependency.tools; + +/** + * Thrown if a input represents an invalid version. + * + * @author Stephane Nicoll + * @since 1.2.2 + */ +@SuppressWarnings("serial") +public class InvalidVersionException extends RuntimeException { + + public InvalidVersionException(String message) { + super(message); + } +} diff --git a/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/Version.java b/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/Version.java new file mode 100644 index 0000000000..8539d9758b --- /dev/null +++ b/spring-boot-tools/spring-boot-dependency-tools/src/main/java/org/springframework/boot/dependency/tools/Version.java @@ -0,0 +1,275 @@ +/* + * Copyright 2012-2015 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.boot.dependency.tools; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Define the version number of a module. A typical version is represented + * as {@code MAJOR.MINOR.PATCH.QUALIFIER} where the qualifier can have an + * extra version. + *
+ * For example: {@code 1.2.0.RC1} is the first release candidate of 1.2.0 + * and {@code 1.5.0.M4} is the fourth milestone of 1.5.0. The special + * {@code RELEASE} qualifier indicates a final release (a.k.a. GA) + *
+ * The main purpose of parsing a version is to compare it with another
+ * version, see {@link Comparable}.
+ *
+ * @author Stephane Nicoll
+ * @since 1.2.2
+ */
+public class Version implements Comparable
+ * Return {@code null} if the text represents an invalid version.
+ * @param text the version text
+ * @return a Version instance for the specified version text
+ * @see #parse(java.lang.String)
+ */
+ public static Version safeParse(String text) {
+ try {
+ return parse(text);
+ }
+ catch (InvalidVersionException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public int compareTo(Version other) {
+ if (other == null) {
+ return 1;
+ }
+ int majorDiff = safeCompare(this.major, other.major);
+ if (majorDiff != 0) {
+ return majorDiff;
+ }
+ int minorDiff = safeCompare(this.minor, other.minor);
+ if (minorDiff != 0) {
+ return minorDiff;
+ }
+ int patch = safeCompare(this.patch, other.patch);
+ if (patch != 0) {
+ return patch;
+ }
+ return qualifierComparator.compare(this.qualifier, other.qualifier);
+ }
+
+ private static int safeCompare(Integer first, Integer second) {
+ Integer firstIndex = (first != null ? first : 0);
+ Integer secondIndex = (second != null ? second : 0);
+ return firstIndex.compareTo(secondIndex);
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("Version{");
+ sb.append("major=").append(major);
+ sb.append(", minor=").append(minor);
+ sb.append(", patch=").append(patch);
+ sb.append(", qualifier=").append(qualifier);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Version version = (Version) o;
+
+ if (major != null ? !major.equals(version.major) : version.major != null) return false;
+ if (minor != null ? !minor.equals(version.minor) : version.minor != null) return false;
+ if (patch != null ? !patch.equals(version.patch) : version.patch != null) return false;
+ return !(qualifier != null ? !qualifier.equals(version.qualifier) : version.qualifier != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = major != null ? major.hashCode() : 0;
+ result = 31 * result + (minor != null ? minor.hashCode() : 0);
+ result = 31 * result + (patch != null ? patch.hashCode() : 0);
+ result = 31 * result + (qualifier != null ? qualifier.hashCode() : 0);
+ return result;
+ }
+
+ public static class Qualifier {
+ private final String id;
+
+ private final Integer version;
+
+ public Qualifier(String id, Integer version) {
+ this.id = id;
+ this.version = version;
+ }
+
+ public Qualifier(String id) {
+ this(id, null);
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public Integer getVersion() {
+ return version;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Qualifier qualifier1 = (Qualifier) o;
+
+ if (!id.equals(qualifier1.id)) return false;
+ return !(version != null ? !version.equals(qualifier1.version) : qualifier1.version != null);
+
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id.hashCode();
+ result = 31 * result + (version != null ? version.hashCode() : 0);
+ return result;
+ }
+ }
+
+
+ private static class VersionQualifierComparator implements Comparator
+ *
+ *
+ * @author Stephane Nicoll
+ * @since 1.2.2
+ */
+public class VersionRange {
+
+ private static final Pattern RANGE_PATTERN = Pattern.compile("(\\(|\\[)(.*),(.*)(\\)|\\])");
+
+ private final Version lowerVersion;
+ private final boolean lowerInclusive;
+ private final Version higherVersion;
+ private final boolean higherInclusive;
+
+ public VersionRange(Version lowerVersion, boolean lowerInclusive,
+ Version higherVersion, boolean higherInclusive) {
+ this.lowerVersion = lowerVersion;
+ this.lowerInclusive = lowerInclusive;
+ this.higherVersion = higherVersion;
+ this.higherInclusive = higherInclusive;
+ }
+
+ /**
+ * Specify if the {@link Version} matches this range. Returns {@code true}
+ * if the version is contained within this range, {@code false} otherwise.
+ * @param version the version to match this range
+ * @return {@code true} if this version is contained within this range
+ */
+ public boolean match(Version version) {
+ Assert.notNull(version, "Version must not be null");
+ int lower = lowerVersion.compareTo(version);
+ if (lower > 0) {
+ return false;
+ } else if (!lowerInclusive && lower == 0) {
+ return false;
+ }
+ if (higherVersion != null) {
+ int higher = higherVersion.compareTo(version);
+ if (higher < 0) {
+ return false;
+ } else if (!higherInclusive && higher == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Parse the string representation of a {@link VersionRange}. Throws an
+ * {@link InvalidVersionException} if the range could not be parsed.
+ * @param text the range text
+ * @return a VersionRange instance for the specified range text
+ * @throws InvalidVersionException if the range text could not be parsed
+ */
+ public static VersionRange parse(String text) {
+ Assert.notNull(text, "Text must not be null");
+ Matcher matcher = RANGE_PATTERN.matcher(text.trim());
+ if (!matcher.matches()) {
+ // Try to read it as simple version
+ Version version = Version.parse(text);
+ return new VersionRange(version, true, null, true);
+ }
+ boolean lowerInclusive = matcher.group(1).equals("[");
+ Version lowerVersion = Version.parse(matcher.group(2));
+ Version higherVersion = Version.parse(matcher.group(3));
+ boolean higherInclusive = matcher.group(4).equals("]");
+ return new VersionRange(lowerVersion, lowerInclusive, higherVersion, higherInclusive);
+ }
+
+}
diff --git a/spring-boot-tools/spring-boot-dependency-tools/src/test/java/org/springframework/boot/dependency/tools/VersionRangeTests.java b/spring-boot-tools/spring-boot-dependency-tools/src/test/java/org/springframework/boot/dependency/tools/VersionRangeTests.java
new file mode 100644
index 0000000000..b893aca17f
--- /dev/null
+++ b/spring-boot-tools/spring-boot-dependency-tools/src/test/java/org/springframework/boot/dependency/tools/VersionRangeTests.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2012-2015 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.boot.dependency.tools;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+
+/**
+ * Tests for {@link VersionRange}.
+ *
+ * @author Stephane Nicoll
+ */
+public class VersionRangeTests {
+
+ @Rule
+ public final ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void matchSimpleRange() {
+ assertThat("1.2.0.RC3", match("[1.2.0.RC1,1.2.0.RC5]"));
+ }
+
+ @Test
+ public void matchSimpleRangeBefore() {
+ assertThat("1.1.9.RC3", not(match("[1.2.0.RC1,1.2.0.RC5]")));
+ }
+
+ @Test
+ public void matchSimpleRangeAfter() {
+ assertThat("1.2.0.RC6", not(match("[1.2.0.RC1,1.2.0.RC5]")));
+ }
+
+ @Test
+ public void matchInclusiveLowerRange() {
+ assertThat("1.2.0.RC1", match("[1.2.0.RC1,1.2.0.RC5]"));
+ }
+
+ @Test
+ public void matchInclusiveHigherRange() {
+ assertThat("1.2.0.RC5", match("[1.2.0.RC1,1.2.0.RC5]"));
+ }
+
+ @Test
+ public void matchExclusiveLowerRange() {
+ assertThat("1.2.0.RC1", not(match("(1.2.0.RC1,1.2.0.RC5)")));
+ }
+
+ @Test
+ public void matchExclusiveHigherRange() {
+ assertThat("1.2.0.RC5", not(match("[1.2.0.RC1,1.2.0.RC5)")));
+ }
+
+ @Test
+ public void matchUnboundedRangeEqual() {
+ assertThat("1.2.0.RELEASE", match("1.2.0.RELEASE"));
+ }
+
+ @Test
+ public void matchUnboundedRangeAfter() {
+ assertThat("2.2.0.RELEASE", match("1.2.0.RELEASE"));
+ }
+
+ @Test
+ public void matchUnboundedRangeBefore() {
+ assertThat("1.1.9.RELEASE", not(match("1.2.0.RELEASE")));
+ }
+
+ @Test
+ public void invalidRange() {
+ thrown.expect(InvalidVersionException.class);
+ VersionRange.parse("foo-bar");
+ }
+
+ @Test
+ public void rangeWithSpaces() {
+ assertThat("1.2.0.RC3", match("[ 1.2.0.RC1 , 1.2.0.RC5]"));
+ }
+
+ private static VersionRangeMatcher match(String range) {
+ return new VersionRangeMatcher(range);
+ }
+
+
+ static class VersionRangeMatcher extends BaseMatcher