From 9f9af42a92abbd1fd5590c5825b355f6472d35af Mon Sep 17 00:00:00 2001 From: Anton Makeev Date: Wed, 30 Mar 2016 19:47:02 +0200 Subject: [PATCH] Platform: build numbers: support multi-component build numbers --- .../intellij/openapi/util/BuildNumber.java | 161 +++++++++++------- .../openapi/util/BuildNumberTest.java | 90 +++++++++- 2 files changed, 188 insertions(+), 63 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/BuildNumber.java b/platform/util/src/com/intellij/openapi/util/BuildNumber.java index c2b68b44c3c2..bbfe08ee2471 100644 --- a/platform/util/src/com/intellij/openapi/util/BuildNumber.java +++ b/platform/util/src/com/intellij/openapi/util/BuildNumber.java @@ -23,63 +23,71 @@ import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.util.Arrays; +import java.util.List; /** * @author max */ public class BuildNumber implements Comparable { + public enum Format { HISTORIC, BRANCH_BASED, YEAR_BASED } + private static final String BUILD_NUMBER = "__BUILD_NUMBER__"; private static final String STAR = "*"; private static final String SNAPSHOT = "SNAPSHOT"; - private static final String FALLBACK_VERSION = "999.SNAPSHOT"; + private static final String FALLBACK_VERSION = "2999.1.SNAPSHOT"; private static class Holder { - private static final int TOP_BASELINE_VERSION = fromFile().getBaselineVersion(); + private static final BuildNumber CURRENT_VERSION = fromFile(); } - private final String myProductCode; - private final int myBaselineVersion; - private final int myBuildNumber; - private final String myAttemptInfo; - + @NotNull private final String myProductCode; + @NotNull private final Format myFormat; + private final int[] myComponents; + public BuildNumber(@NotNull String productCode, int baselineVersion, int buildNumber) { - this(productCode, baselineVersion, buildNumber, null); + this(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber); } - public BuildNumber(@NotNull String productCode, int baselineVersion, int buildNumber, @Nullable String attemptInfo) { + BuildNumber(@NotNull String productCode, @NotNull Format format, int... components) { myProductCode = productCode; - myBaselineVersion = baselineVersion; - myBuildNumber = buildNumber; - myAttemptInfo = StringUtil.isEmpty(attemptInfo) ? null : attemptInfo; + myFormat = format; + myComponents = components; } public String asString() { - return asString(true, false); + return asString(true, true); + } + + public String asStringWithAllDetails() { + return asString(true, true); } public String asStringWithoutProductCode() { + return asString(false, true); + } + + public String asStringWithoutProductCodeAndSnapshot() { return asString(false, false); } - private String asString(boolean includeProductCode, boolean withBuildAttempt) { + private String asString(boolean includeProductCode, boolean withSnapshotMarker) { StringBuilder builder = new StringBuilder(); if (includeProductCode && !StringUtil.isEmpty(myProductCode)) { builder.append(myProductCode).append('-'); } - builder.append(myBaselineVersion).append('.'); - - if (myBuildNumber != Integer.MAX_VALUE) { - builder.append(myBuildNumber); - } - else { - builder.append(SNAPSHOT); - } - - if (withBuildAttempt && myAttemptInfo != null) { - builder.append('.').append(myAttemptInfo); + for (int each : myComponents) { + if (each != Integer.MAX_VALUE) { + builder.append(each); + } + else if (withSnapshotMarker) { + builder.append(SNAPSHOT); + } + builder.append('.'); } + if (builder.charAt(builder.length() - 1) == '.') builder.setLength(builder.length() - 1); return builder.toString(); } @@ -91,9 +99,9 @@ public class BuildNumber implements Comparable { public static BuildNumber fromString(String version, @Nullable String name) { if (version == null) return null; - if (BUILD_NUMBER.equals(version)) { + if (BUILD_NUMBER.equals(version) || SNAPSHOT.equals(version)) { final String productCode = name != null ? name : ""; - return new BuildNumber(productCode, Holder.TOP_BASELINE_VERSION, Integer.MAX_VALUE); + return new BuildNumber(productCode, Holder.CURRENT_VERSION.getFormat(), Holder.CURRENT_VERSION.myComponents); } String code = version; @@ -110,38 +118,57 @@ public class BuildNumber implements Comparable { int baselineVersionSeparator = code.indexOf('.'); int baselineVersion; int buildNumber; - String attemptInfo = null; if (baselineVersionSeparator > 0) { + String baselineVersionString = code.substring(0, baselineVersionSeparator); + if (baselineVersionString.trim().isEmpty()) return null; try { - String baselineVersionString = code.substring(0, baselineVersionSeparator); - if (baselineVersionString.trim().isEmpty()) return null; baselineVersion = Integer.parseInt(baselineVersionString); - code = code.substring(baselineVersionSeparator + 1); } catch (NumberFormatException e) { throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name); } - int minorBuildSeparator = code.indexOf('.'); // allow . skipping BuildAttemptNumber - if (minorBuildSeparator > 0) { - attemptInfo = code.substring(minorBuildSeparator + 1); - code = code.substring(0, minorBuildSeparator); + if (baselineVersion >= 2016) { + List stringComponents = StringUtil.split(code, "."); + int[] intComponents = new int[stringComponents.size()]; + for (int i = 0; i < stringComponents.size(); i++) { + intComponents[i] = parseBuildNumber(version, stringComponents.get(i), name); + } + + return new BuildNumber(productCode, Format.YEAR_BASED, intComponents); + } + else { + code = code.substring(baselineVersionSeparator + 1); + + int minorBuildSeparator = code.indexOf('.'); // allow . skipping BuildAttemptNumber + + Integer attemptInfo = null; + if (minorBuildSeparator > 0) { + attemptInfo = parseBuildNumber(version, code.substring(minorBuildSeparator + 1), name); + code = code.substring(0, minorBuildSeparator); + } + buildNumber = parseBuildNumber(version, code, name); + + if (attemptInfo != null) { + return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber, attemptInfo); + } + else { + return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber); + } } - buildNumber = parseBuildNumber(version, code, name); } else { buildNumber = parseBuildNumber(version, code, name); if (buildNumber <= 2000) { // it's probably a baseline, not a build number - return new BuildNumber(productCode, buildNumber, 0, null); + return new BuildNumber(productCode, Format.BRANCH_BASED, buildNumber, 0); } - + baselineVersion = getBaseLineForHistoricBuilds(buildNumber); + return new BuildNumber(productCode, Format.HISTORIC, baselineVersion, buildNumber); } - - return new BuildNumber(productCode, baselineVersion, buildNumber, attemptInfo); } private static int parseBuildNumber(String version, String code, String name) { @@ -181,21 +208,44 @@ public class BuildNumber implements Comparable { @Override public int compareTo(@NotNull BuildNumber o) { - if (myBaselineVersion == o.myBaselineVersion) return myBuildNumber - o.myBuildNumber; - return myBaselineVersion - o.myBaselineVersion; + for (int i = 0; i < Math.min(myComponents.length, o.myComponents.length); i++) { + int result = myComponents[i] - o.myComponents[i]; + if (result != 0) return result; + + if (myComponents[i] == Integer.MAX_VALUE) return 0; // anything after first SNAPSHOT doesn't really matter + } + return myComponents.length - o.myComponents.length; } + public int compareToWithVariableLength(@NotNull BuildNumber o) { + for (int i = 0; i < Math.min(myComponents.length, o.myComponents.length); i++) { + int result = myComponents[i] - o.myComponents[i]; + if (result != 0) return result; + + if (myComponents[i] == Integer.MAX_VALUE) return 0; // anything after first SNAPSHOT doesn't really matter + } + return myComponents.length - o.myComponents.length; + } + + + @NotNull public String getProductCode() { return myProductCode; } public int getBaselineVersion() { - return myBaselineVersion; + return myFormat == Format.YEAR_BASED ? (myComponents[0] * 10 + myComponents[1]) : myComponents[0]; } + @Deprecated public int getBuildNumber() { - return myBuildNumber; + return myFormat == Format.YEAR_BASED ? -1 : myComponents[1]; + } + + @NotNull + public Format getFormat() { + return myFormat; } @Override @@ -205,10 +255,9 @@ public class BuildNumber implements Comparable { BuildNumber that = (BuildNumber)o; - if (myBaselineVersion != that.myBaselineVersion) return false; - if (myBuildNumber != that.myBuildNumber) return false; + if (myFormat != that.myFormat) return false; if (!myProductCode.equals(that.myProductCode)) return false; - if (!Comparing.equal(myAttemptInfo, that.myAttemptInfo)) return false; + if (!Arrays.equals(myComponents, that.myComponents)) return false; return true; } @@ -216,18 +265,13 @@ public class BuildNumber implements Comparable { @Override public int hashCode() { int result = myProductCode.hashCode(); - result = 31 * result + myBaselineVersion; - result = 31 * result + myBuildNumber; - if (myAttemptInfo != null) result = 31 * result + myAttemptInfo.hashCode(); + result = 31 * result + Arrays.hashCode(myComponents); + result = 31 * result + myFormat.hashCode(); return result; } // See http://www.jetbrains.net/confluence/display/IDEADEV/Build+Number+Ranges for historic build ranges private static int getBaseLineForHistoricBuilds(int bn) { - if (bn == Integer.MAX_VALUE) { - return Holder.TOP_BASELINE_VERSION; // SNAPSHOTS - } - if (bn >= 10000) { return 88; // Maia, 9x builds } @@ -276,10 +320,9 @@ public class BuildNumber implements Comparable { } public boolean isSnapshot() { - return myBuildNumber == Integer.MAX_VALUE; - } - - public String asStringWithAllDetails() { - return asString(true, true); + for (int each : myComponents) { + if (each == Integer.MAX_VALUE) return true; + } + return false; } } diff --git a/platform/util/testSrc/com/intellij/openapi/util/BuildNumberTest.java b/platform/util/testSrc/com/intellij/openapi/util/BuildNumberTest.java index c5690842a29d..6d18f4bca565 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/BuildNumberTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/BuildNumberTest.java @@ -19,21 +19,82 @@ import org.junit.Test; import static org.junit.Assert.*; -/** - * @author max - */ public class BuildNumberTest { @Test public void historicBuild() { - assertEquals(new BuildNumber("", 75, 7512), BuildNumber.fromString("7512")); + assertEquals(new BuildNumber("", BuildNumber.Format.HISTORIC, 75, 7512), BuildNumber.fromString("7512")); + assertEquals("75.7512", BuildNumber.fromString("7512").asString()); + } + + @Test + public void branchBasedBuild() throws Exception { + assertParsed(BuildNumber.fromString("145"), 145, 0, "145.0"); + assertParsed(BuildNumber.fromString("145.1"), 145, 1, "145.1"); + assertParsed(BuildNumber.fromString("145.1.2"), 145, 1, "145.1.2"); + assertParsed(BuildNumber.fromString("IU-145.1.2"), 145, 1, "IU-145.1.2"); + assertParsed(BuildNumber.fromString("IU-145.SNAPSHOT"), 145, Integer.MAX_VALUE, "IU-145.SNAPSHOT"); + assertParsed(BuildNumber.fromString("IU-145.1.SNAPSHOT"), 145, 1, "IU-145.1.SNAPSHOT"); + } + + @Test + public void yearBasedBuild() throws Exception { + assertParsed(BuildNumber.fromString("2016.1.2"), 20161, -1, "2016.1.2"); + assertParsed(BuildNumber.fromString("IU-2016.1.2"), 20161, -1, "IU-2016.1.2"); + assertParsed(BuildNumber.fromString("2016.1.2.3"), 20161, -1, "2016.1.2.3"); + assertParsed(BuildNumber.fromString("IU-2016.1.2.3"), 20161, -1, "IU-2016.1.2.3"); + assertParsed(BuildNumber.fromString("2016.1.2.3.4"), 20161, -1, "2016.1.2.3.4"); + assertParsed(BuildNumber.fromString("IU-2016.1.2.3.4"), 20161, -1, "IU-2016.1.2.3.4"); + + assertParsed(BuildNumber.fromString("IU-2016.1.SNAPSHOT"), 20161, -1, "IU-2016.1.SNAPSHOT"); + assertParsed(BuildNumber.fromString("IU-2016.1.SNAPSHOT.1"), 20161, -1, "IU-2016.1.SNAPSHOT.1"); + } + + private static void assertParsed(BuildNumber n, int expectedBaseline, int expectedBuildNumber, String asString) { + assertEquals(expectedBaseline, n.getBaselineVersion()); + assertEquals(expectedBuildNumber, n.getBuildNumber()); + assertEquals(asString, n.asString()); + } + + @Test + public void comparingYearBasedVersion() throws Exception { + assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1")) == 0); + assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.1.1")) == 0); + assertTrue(BuildNumber.fromString("2016.1.1.1").compareTo(BuildNumber.fromString("2016.1.1.1")) == 0); + + assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1.1")) < 0); + assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.1.1.1")) < 0); + assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.1.1.1")) < 0); + + assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.2")) < 0); + assertTrue(BuildNumber.fromString("2016.1").compareTo(BuildNumber.fromString("2016.2.1")) < 0); + assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.2")) < 0); + assertTrue(BuildNumber.fromString("2016.1.1").compareTo(BuildNumber.fromString("2016.2.1")) < 0); + + assertTrue(BuildNumber.fromString("146.1").compareTo(BuildNumber.fromString("2016.1")) < 0); + assertTrue(BuildNumber.fromString("146.9.9").compareTo(BuildNumber.fromString("2016.1")) < 0); } @Test public void isSnapshot() { + assertTrue(BuildNumber.fromString("SNAPSHOT").isSnapshot()); assertTrue(BuildNumber.fromString("__BUILD_NUMBER__").isSnapshot()); assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").isSnapshot()); assertTrue(BuildNumber.fromString("IC-90.*").isSnapshot()); assertFalse(BuildNumber.fromString("90.9999999").isSnapshot()); + + assertFalse(BuildNumber.fromString("2016.1").isSnapshot()); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").isSnapshot()); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").isSnapshot()); + } + + @Test + public void devSnapshotVersion() throws Exception { + BuildNumber b = BuildNumber.fromString("__BUILD_NUMBER__"); + assertTrue(b.asString(), b.getBaselineVersion() >= 2016); + assertEquals(b.asString(), -1, b.getBuildNumber()); + assertTrue(b.isSnapshot()); + + assertEquals(BuildNumber.fromString("__BUILD_NUMBER__"), BuildNumber.fromString("SNAPSHOT")); } @Test @@ -43,5 +104,26 @@ public class BuildNumberTest { assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-100.12345")) < 0); assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-100.SNAPSHOT")) < 0); assertTrue(BuildNumber.fromString("IU-90.SNAPSHOT").compareTo(BuildNumber.fromString("RM-90.SNAPSHOT")) == 0); + + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1")) > 0); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.SNAPSHOT")) == 0); + assertTrue(BuildNumber.fromString("2016.1.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1.1")) > 0); + assertTrue(BuildNumber.fromString("2016.1.1.SNAPSHOT").compareTo(BuildNumber.fromString("2016.1.1.SNAPSHOT")) == 0); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.1.1")) > 0); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.1.SNAPSHOT")) > 0); + assertTrue(BuildNumber.fromString("2016.1.SNAPSHOT.1").compareTo(BuildNumber.fromString("2016.1.SNAPSHOT.SNAPSHOT")) == 0); + } + + @Test + public void fallbackVersion() throws Exception { + assertParsed(BuildNumber.fallback(), 29991, -1, "2999.1.SNAPSHOT"); + assertEquals(BuildNumber.Format.YEAR_BASED, BuildNumber.fallback().getFormat()); + assertTrue(BuildNumber.fallback().isSnapshot()); + + assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("7512")) > 0); + assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("145")) > 0); + assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("145.12")) > 0); + assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("2016.1")) > 0); + assertTrue(BuildNumber.fallback().compareTo(BuildNumber.fromString("2016.1.2")) > 0); } }