Merge branch 'master' into amakeev/build-number
[idea/community.git] / platform / util / src / com / intellij / openapi / util / BuildNumber.java
index 36e0f2c5583c6bb5b23a4a079b37f0a23be3877d..73efdf0a16ffadb544968e41f495829f139141ad 100644 (file)
@@ -23,63 +23,73 @@ 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<BuildNumber> {
+  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";
+
+  public static final int SNAPSHOT_VALUE = Integer.MAX_VALUE;
 
   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 != SNAPSHOT_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 +101,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
   public static BuildNumber fromString(String version, @Nullable String name) {
     if (StringUtil.isEmptyOrSpaces(version)) 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,44 +120,71 @@ public class BuildNumber implements Comparable<BuildNumber> {
     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 <BuildNumber>.<BuildAttemptNumber> skipping BuildAttemptNumber
-      if (minorBuildSeparator > 0) {
-        attemptInfo = code.substring(minorBuildSeparator + 1);
-        code = code.substring(0, minorBuildSeparator);
+      if (baselineVersion >= 2016) {
+        List<String> 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 <BuildNumber>.<BuildAttemptNumber> 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);
       }
 
+      if (buildNumber >= 2016 && buildNumber <= 2999) {
+        return new BuildNumber(productCode, Format.YEAR_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) {
-    if (SNAPSHOT.equals(code) || STAR.equals(code) || BUILD_NUMBER.equals(code)) {
-      return Integer.MAX_VALUE;
+    if (SNAPSHOT.equals(code) || BUILD_NUMBER.equals(code)) {
+      return SNAPSHOT_VALUE;
     }
+    if (STAR.equals(code)) {
+      return SNAPSHOT_VALUE;
+    }
+    
     try {
       return Integer.parseInt(code);
     }
@@ -181,8 +218,18 @@ public class BuildNumber implements Comparable<BuildNumber> {
 
   @Override
   public int compareTo(@NotNull BuildNumber o) {
-    if (myBaselineVersion == o.myBaselineVersion) return myBuildNumber - o.myBuildNumber;
-    return myBaselineVersion - o.myBaselineVersion;
+    int[] c1 = myComponents;
+    int[] c2 = o.myComponents;
+    
+    for (int i = 0; i < Math.min(c1.length, c2.length); i++) {
+      if (c1[i] == c2[i] && c1[i] == SNAPSHOT_VALUE) return 0;
+      if (c1[i] == SNAPSHOT_VALUE) return 1;
+      if (c2[i] == SNAPSHOT_VALUE) return -1;
+
+      int result = c1[i] - c2[i];
+      if (result != 0) return result;
+    }
+    return c1.length - c2.length;
   }
 
   @NotNull
@@ -191,11 +238,21 @@ public class BuildNumber implements Comparable<BuildNumber> {
   }
 
   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];
+  }
+
+  public int[] getComponents() {
+    return myComponents;
+  }
+
+  @NotNull
+  public Format getFormat() {
+    return myFormat;
   }
 
   @Override
@@ -205,10 +262,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
 
     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 +272,13 @@ public class BuildNumber implements Comparable<BuildNumber> {
   @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 +327,9 @@ public class BuildNumber implements Comparable<BuildNumber> {
   }
 
   public boolean isSnapshot() {
-    return myBuildNumber == Integer.MAX_VALUE;
-  }
-
-  public String asStringWithAllDetails() {
-    return asString(true, true);
+    for (int each : myComponents) {
+      if (each == SNAPSHOT_VALUE) return true;
+    }
+    return false;
   }
 }
\ No newline at end of file