diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
index 5206404cf107..d90c1bf3fd4e 100644
--- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
+++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java
@@ -2841,6 +2841,29 @@ public class StringUtil extends StringUtilRt {
return ignoreCase ? s1.compareToIgnoreCase(s2) : s1.compareTo(s2);
}
+ @Contract(pure = true)
+ public static int compare(@Nullable CharSequence s1, @Nullable CharSequence s2, boolean ignoreCase) {
+ if (s1 == s2) return 0;
+ if (s1 == null) return -1;
+ if (s2 == null) return 1;
+
+ int length1 = s1.length();
+ int length2 = s2.length();
+ int i = 0;
+ int j = 0;
+ for (; i < length1 && j < length2; i++, j++) {
+ int diff = compare(s1.charAt(i), s2.charAt(j), ignoreCase);
+ if (diff != 0) {
+ return diff;
+ }
+ }
+ if (i < length1) return +1;
+ if (j < length2) return -1;
+ if (length1 != length2) return length1 - length2;
+
+ return ignoreCase ? compare(s1, s2, false) : 0;
+ }
+
@Contract(pure = true)
public static int comparePairs(@Nullable String s1, @Nullable String t1, @Nullable String s2, @Nullable String t2, boolean ignoreCase) {
final int compare = compare(s1, s2, ignoreCase);
@@ -3399,4 +3422,4 @@ public class StringUtil extends StringUtilRt {
}
return true;
}
-}
\ No newline at end of file
+}
diff --git a/platform/util/src/com/intellij/util/text/SemVer.java b/platform/util/src/com/intellij/util/text/SemVer.java
index 28188fd7cc1f..5893cfe06088 100644
--- a/platform/util/src/com/intellij/util/text/SemVer.java
+++ b/platform/util/src/com/intellij/util/text/SemVer.java
@@ -19,23 +19,34 @@ import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
+import java.util.Objects;
+
/**
* Holds Semantic Version.
*/
public final class SemVer implements Comparable {
- /** @deprecated */
+ /**
+ * @deprecated
+ */
@Deprecated public static final SemVer UNKNOWN = new SemVer("?", 0, 0, 0);
private final String myRawVersion;
private final int myMajor;
private final int myMinor;
private final int myPatch;
+ @Nullable
+ private final String myPreRelease;
public SemVer(@NotNull String rawVersion, int major, int minor, int patch) {
+ this(rawVersion, major, minor, patch, null);
+ }
+
+ public SemVer(@NotNull String rawVersion, int major, int minor, int patch, @Nullable String preRelease) {
myRawVersion = rawVersion;
myMajor = major;
myMinor = minor;
myPatch = patch;
+ myPreRelease = preRelease;
}
@NotNull
@@ -55,9 +66,14 @@ public final class SemVer implements Comparable {
return myPatch;
}
+ @Nullable
+ public String getPreRelease() {
+ return myPreRelease;
+ }
+
@NotNull
public String getParsedVersion() {
- return myMajor + "." + myMinor + "." + myPatch;
+ return myMajor + "." + myMinor + "." + myPatch + (myPreRelease != null ? "-" + myPreRelease : "");
}
@Override
@@ -68,7 +84,10 @@ public final class SemVer implements Comparable {
diff = myMinor - other.myMinor;
if (diff != 0) return diff;
- return myPatch - other.myPatch;
+ diff = myPatch - other.myPatch;
+ if (diff != 0) return diff;
+
+ return comparePrerelease(myPreRelease, other.myPreRelease);
}
public boolean isGreaterOrEqualThan(int major, int minor, int patch) {
@@ -83,7 +102,10 @@ public final class SemVer implements Comparable {
if (o == null || getClass() != o.getClass()) return false;
SemVer semVer = (SemVer)o;
- return myMajor == semVer.myMajor && myMinor == semVer.myMinor && myPatch == semVer.myPatch;
+ return myMajor == semVer.myMajor
+ && myMinor == semVer.myMinor
+ && myPatch == semVer.myPatch
+ && Objects.equals(myPreRelease, semVer.myPreRelease);
}
@Override
@@ -91,6 +113,9 @@ public final class SemVer implements Comparable {
int result = myMajor;
result = 31 * result + myMinor;
result = 31 * result + myPatch;
+ if (myPreRelease != null) {
+ result = 31 * result + myPreRelease.hashCode();
+ }
return result;
}
@@ -99,6 +124,83 @@ public final class SemVer implements Comparable {
return myRawVersion;
}
+ private static int comparePrerelease(@Nullable String pre1, @Nullable String pre2) {
+ if (pre1 == null) {
+ return pre2 == null ? 0 : 1;
+ }
+ else if (pre2 == null) {
+ return -1;
+ }
+ int length1 = pre1.length();
+ int length2 = pre2.length();
+
+ if (length1 == length2 && pre1.equals(pre2)) return 0;
+
+ int start1 = 0;
+ int start2 = 0;
+ int diff;
+
+ // compare each segment separately
+ do {
+ int end1 = pre1.indexOf('.', start1);
+ int end2 = pre2.indexOf('.', start2);
+
+ if (end1 < 0) end1 = length1;
+ if (end2 < 0) end2 = length2;
+
+
+ CharSequence segment1 = new CharSequenceSubSequence(pre1, start1, end1);
+ CharSequence segment2 = new CharSequenceSubSequence(pre2, start2, end2);
+ if (isNumeric(segment1)) {
+ if (!isNumeric(segment2)) {
+ return -1;
+ }
+ diff = compareNumeric(segment1, segment2);
+ }
+ else if (isNumeric(segment2)) {
+ return 1;
+ }
+ else {
+ diff = StringUtil.compare(segment1, segment2, false);
+ }
+ start1 = end1 + 1;
+ start2 = end2 + 1;
+ }
+ while (diff == 0 && start1 < length1 && start2 < length2);
+
+ if (diff != 0) return diff;
+ if (start1 >= length1) {
+ if (start2 >= length2) {
+ return 0;
+ }
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ }
+
+ private static int compareNumeric(CharSequence segment1, CharSequence segment2) {
+ int length1 = segment1.length();
+ int length2 = segment2.length();
+ int diff = Integer.compare(length1, length2);
+ for (int i = 0; i <= length1 && diff == 0; i++) {
+ diff = segment1.charAt(i) - segment2.charAt(i);
+ }
+ return diff;
+ }
+
+ private static boolean isNumeric(CharSequence segment) {
+ int length = segment.length();
+ for (int i = 0; i < length; i++) {
+ int ch = segment.charAt(i);
+ if (ch < '0' || ch > '9') {
+ return false;
+ }
+ }
+ return true;
+ }
+
@Nullable
public static SemVer parseFromText(@Nullable String text) {
if (text != null) {
@@ -106,14 +208,16 @@ public final class SemVer implements Comparable {
if (majorEndIdx >= 0) {
int minorEndIdx = text.indexOf('.', majorEndIdx + 1);
if (minorEndIdx >= 0) {
- int patchEndIdx = text.indexOf('-', minorEndIdx + 1);
- if (patchEndIdx < 0) patchEndIdx = text.length();
+ int preReleaseIdx = text.indexOf('-', minorEndIdx + 1);
+ int patchEndIdx = preReleaseIdx >= 0 ? preReleaseIdx : text.length();
int major = StringUtil.parseInt(text.substring(0, majorEndIdx), -1);
int minor = StringUtil.parseInt(text.substring(majorEndIdx + 1, minorEndIdx), -1);
int patch = StringUtil.parseInt(text.substring(minorEndIdx + 1, patchEndIdx), -1);
+ String preRelease = preReleaseIdx >= 0 ? text.substring(preReleaseIdx + 1) : null;
+
if (major >= 0 && minor >= 0 && patch >= 0) {
- return new SemVer(text, major, minor, patch);
+ return new SemVer(text, major, minor, patch, preRelease);
}
}
}
diff --git a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java
index 0f6eafb31fa7..395ce47c6c9c 100644
--- a/platform/util/testSrc/com/intellij/util/text/SemVerTest.java
+++ b/platform/util/testSrc/com/intellij/util/text/SemVerTest.java
@@ -15,6 +15,7 @@
*/
package com.intellij.util.text;
+import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import org.junit.Test;
@@ -23,15 +24,15 @@ import static org.assertj.core.api.Assertions.assertThat;
public class SemVerTest {
@Test
public void parsing() {
- assertParsed("0.9.2", 0, 9, 2);
- assertParsed("0.9.2-", 0, 9, 2);
- assertParsed("0.9.2-dart", 0, 9, 2);
- assertParsed("4.0.0-alpha.1", 4, 0, 0);
- assertParsed("0.10.0-rc-1", 0, 10, 0);
- assertParsed("1.0.0-rc-1", 1, 0, 0);
- assertParsed("1.0.0-alpha", 1, 0, 0);
- assertParsed("1.0.0-0.3.7", 1, 0, 0);
- assertParsed("1.0.0-x.7.z.92", 1, 0, 0);
+ assertParsed("0.9.2", 0, 9, 2, null);
+ assertParsed("0.9.2-", 0, 9, 2, "");
+ assertParsed("0.9.2-dart", 0, 9, 2, "dart");
+ assertParsed("4.0.0-alpha.1", 4, 0, 0, "alpha.1");
+ assertParsed("0.10.0-rc-1", 0, 10, 0, "rc-1");
+ assertParsed("1.0.0-rc-1", 1, 0, 0, "rc-1");
+ assertParsed("1.0.0-alpha", 1, 0, 0, "alpha");
+ assertParsed("1.0.0-0.3.7", 1, 0, 0, "0.3.7");
+ assertParsed("1.0.0-x.7.z.92", 1, 0, 0, "x.7.z.92");
assertNotParsed(null);
assertNotParsed("");
@@ -42,31 +43,60 @@ public class SemVerTest {
@Test
public void comparing() {
- assertThat(parse("1.0.0")).isGreaterThan(parse("0.10.0"));
- assertThat(parse("1.0.0")).isLessThan(parse("2.10.0"));
-
- assertThat(parse("0.30.0")).isGreaterThan(parse("0.5.1000"));
- assertThat(parse("0.30.10")).isLessThan(parse("0.100.0"));
-
- assertThat(parse("2.9.123-test")).isGreaterThan(parse("2.9.100"));
- assertThat(parse("2.9.123-test")).isLessThan(parse("2.9.124"));
-
assertThat(parse("11.123.0")).isEqualTo(parse("11.123.0"));
assertThat(parse("11.123.0")).isEqualByComparingTo(parse("11.123.0"));
+ assertThat(parse("11.123.0-a.b.c-1")).isEqualTo(parse("11.123.0-a.b.c-1"));
+ assertThat(parse("11.123.0-a.b.c-1")).isEqualByComparingTo(parse("11.123.0-a.b.c-1"));
+
+ assertPrecedence("0.10.0", "1.0.0");
+ assertPrecedence("1.0.0","2.10.0");
+ assertPrecedence("0.5.1000", "0.30.0");
+ assertPrecedence("0.30.10","0.100.0");
+
+ assertPrecedence("2.9.100", "2.9.123-test");
+ assertPrecedence("2.9.123-test","2.9.124");
+ assertPrecedence("2.9.123","2.9.124-test");
+
+ assertPrecedence("1.2.3-a","1.2.3");
+
+ assertPrecedence("1.2.3-12","1.2.3-a");
+ assertPrecedence("1.2.3-22","1.2.3-100");
+ assertPrecedence("1.2.3-22","1.2.3-31");
+
+ assertPrecedence("1.2.3-a.b.c","1.2.3-a.b.d");
+ assertPrecedence("1.2.3-a.b.c","1.2.3-a.b.c.a");
+
+ assertPrecedence("1.2.3-a.b.1","1.2.3-a.b.c");
+ assertPrecedence("1.2.3-a.b.1","1.2.3-a.c.1");
+ assertPrecedence("1.2.3-a.cbc.100","1.2.3-a.cca.1");
+ assertPrecedence("1.2.3-a.cb.1","1.2.3-a.cba.1");
+
Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(4, 12, 5));
+ Assert.assertTrue(parse("4.12.5-a").isGreaterOrEqualThan(4, 12, 5));
Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(4, 12, 4));
+ Assert.assertTrue(parse("4.12.5-a").isGreaterOrEqualThan(4, 12, 4));
Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(4, 11, 0));
Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(4, 11, 9));
Assert.assertTrue(parse("4.12.5").isGreaterOrEqualThan(3, 100, 100));
Assert.assertFalse(parse("4.12.5").isGreaterOrEqualThan(4, 12, 6));
+ Assert.assertFalse(parse("4.12.5-a").isGreaterOrEqualThan(4, 12, 6));
Assert.assertFalse(parse("4.12.5").isGreaterOrEqualThan(4, 13, 0));
Assert.assertFalse(parse("4.12.5").isGreaterOrEqualThan(5, 1, 0));
}
- private static void assertParsed(String version, int expectedMajor, int expectedMinor, int expectedPatch) {
- assertThat(parse(version)).isEqualTo(new SemVer(version, expectedMajor, expectedMinor, expectedPatch));
+ private static void assertPrecedence(String lesserVersion, String higherVersion) {
+ SemVer v1 = parse(lesserVersion);
+ SemVer v2 = parse(higherVersion);
+ assertThat(v1).isLessThan(v2);
+ assertThat(v2).isGreaterThan(v1);
+ assertThat(v1).isNotEqualByComparingTo(v2);
+ assertThat(v1).isNotEqualTo(v2);
+ }
+
+ private static void assertParsed(String version, int expectedMajor, int expectedMinor, int expectedPatch, @Nullable String expectedPreRelease) {
+ assertThat(parse(version)).isEqualTo(new SemVer(version, expectedMajor, expectedMinor, expectedPatch, expectedPreRelease));
}
private static void assertNotParsed(String version) {
@@ -78,4 +108,4 @@ public class SemVerTest {
assertThat(semVer).describedAs(text).isNotNull();
return semVer;
}
-}
\ No newline at end of file
+}