2 * Copyright 2000-2013 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.util;
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.util.io.FileUtil;
20 import com.intellij.openapi.util.text.StringUtil;
21 import org.jetbrains.annotations.NotNull;
22 import org.jetbrains.annotations.Nullable;
25 import java.io.IOException;
30 public class BuildNumber implements Comparable<BuildNumber> {
31 private static final String BUILD_NUMBER = "__BUILD_NUMBER__";
32 private static final String STAR = "*";
33 private static final String SNAPSHOT = "SNAPSHOT";
34 private static final String FALLBACK_VERSION = "999.SNAPSHOT";
36 private static final int TOP_BASELINE_VERSION = fromFile().getBaselineVersion();
38 private final String myProductCode;
39 private final int myBaselineVersion;
40 private final int myBuildNumber;
41 private final String myAttemptInfo;
43 public BuildNumber(String productCode, int baselineVersion, int buildNumber) {
44 this(productCode, baselineVersion, buildNumber, null);
47 public BuildNumber(String productCode, int baselineVersion, int buildNumber, String attemptInfo) {
48 myProductCode = productCode;
49 myBaselineVersion = baselineVersion;
50 myBuildNumber = buildNumber;
51 myAttemptInfo = StringUtil.isEmpty(attemptInfo) ? null : attemptInfo;
54 public String asString() {
55 return asString(true);
58 public String asStringWithoutProductCode() {
59 return asString(false);
62 private String asString(boolean includeProductCode) {
63 StringBuilder builder = new StringBuilder();
65 if (includeProductCode && !StringUtil.isEmpty(myProductCode)) {
66 builder.append(myProductCode).append('-');
69 builder.append(myBaselineVersion).append('.');
71 if (myBuildNumber != Integer.MAX_VALUE) {
72 builder.append(myBuildNumber);
75 builder.append(SNAPSHOT);
78 if (myAttemptInfo != null) {
79 builder.append('.').append(myAttemptInfo);
82 return builder.toString();
85 public static BuildNumber fromString(String version) {
86 return fromString(version, null);
89 public static BuildNumber fromString(String version, @Nullable String name) {
90 if (version == null) return null;
92 if (BUILD_NUMBER.equals(version)) {
93 final String productCode = name != null ? name : "";
94 return new BuildNumber(productCode, TOP_BASELINE_VERSION, Integer.MAX_VALUE);
97 String code = version;
98 int productSeparator = code.indexOf('-');
99 final String productCode;
100 if (productSeparator > 0) {
101 productCode = code.substring(0, productSeparator);
102 code = code.substring(productSeparator + 1);
108 int baselineVersionSeparator = code.indexOf('.');
111 String attemptInfo = null;
113 if (baselineVersionSeparator > 0) {
115 String baselineVersionString = code.substring(0, baselineVersionSeparator);
116 if (baselineVersionString.trim().isEmpty()) return null;
117 baselineVersion = Integer.parseInt(baselineVersionString);
118 code = code.substring(baselineVersionSeparator + 1);
120 catch (NumberFormatException e) {
121 throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name);
124 int minorBuildSeparator = code.indexOf('.'); // allow <BuildNumber>.<BuildAttemptNumber> skipping BuildAttemptNumber
125 if (minorBuildSeparator > 0) {
126 attemptInfo = code.substring(minorBuildSeparator + 1);
127 code = code.substring(0, minorBuildSeparator);
129 buildNumber = parseBuildNumber(version, code, name);
132 buildNumber = parseBuildNumber(version, code, name);
134 if (buildNumber <= 2000) {
135 // it's probably a baseline, not a build number
136 return new BuildNumber(productCode, buildNumber, 0, null);
139 baselineVersion = getBaseLineForHistoricBuilds(buildNumber);
142 return new BuildNumber(productCode, baselineVersion, buildNumber, attemptInfo);
145 private static int parseBuildNumber(String version, String code, String name) {
146 if (SNAPSHOT.equals(code) || STAR.equals(code) || BUILD_NUMBER.equals(code)) {
147 return Integer.MAX_VALUE;
150 return Integer.parseInt(code);
152 catch (NumberFormatException e) {
153 throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name);
157 private static BuildNumber fromFile() {
159 final String homePath = PathManager.getHomePath();
160 final File buildTxtFile = FileUtil.findFirstThatExist(homePath + "/build.txt", homePath + "/community/build.txt");
161 if (buildTxtFile != null) {
162 String text = FileUtil.loadFile(buildTxtFile).trim();
163 return fromString(text);
166 catch (IOException ignored) { }
171 public static BuildNumber fallback() {
172 return fromString(FALLBACK_VERSION);
176 public String toString() {
181 public int compareTo(@NotNull BuildNumber o) {
182 if (myBaselineVersion == o.myBaselineVersion) return myBuildNumber - o.myBuildNumber;
183 return myBaselineVersion - o.myBaselineVersion;
186 public String getProductCode() {
187 return myProductCode;
190 public int getBaselineVersion() {
191 return myBaselineVersion;
194 public int getBuildNumber() {
195 return myBuildNumber;
199 public boolean equals(Object o) {
200 if (this == o) return true;
201 if (o == null || getClass() != o.getClass()) return false;
203 BuildNumber that = (BuildNumber)o;
205 if (myBaselineVersion != that.myBaselineVersion) return false;
206 if (myBuildNumber != that.myBuildNumber) return false;
207 if (!myProductCode.equals(that.myProductCode)) return false;
208 if (!Comparing.equal(myAttemptInfo, that.myAttemptInfo)) return false;
214 public int hashCode() {
215 int result = myProductCode.hashCode();
216 result = 31 * result + myBaselineVersion;
217 result = 31 * result + myBuildNumber;
218 if (myAttemptInfo != null) result = 31 * result + myAttemptInfo.hashCode();
222 // See http://www.jetbrains.net/confluence/display/IDEADEV/Build+Number+Ranges for historic build ranges
223 private static int getBaseLineForHistoricBuilds(int bn) {
224 if (bn == Integer.MAX_VALUE) {
225 return TOP_BASELINE_VERSION; // SNAPSHOTS
229 return 88; // Maia, 9x builds
233 return 85; // 8.1 builds
237 return 81; // 8.0.x builds
241 return 80; // 8.0, including pre-release builds
249 return 72; // 7.0 final
253 return 69; // 7.0 pre-M2
257 return 65; // 7.0 pre-M1
265 return 55; // 6.0 branch, including all 6.0 EAP builds
269 return 50; // 5.1 branch
275 public boolean isSnapshot() {
276 return myBuildNumber == Integer.MAX_VALUE;