Platform: build numbers: .* is treated identically as .SNAPSHOT, '2016' is parsed...
[idea/community.git] / platform / util / src / com / intellij / openapi / util / BuildNumber.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.openapi.util;
17
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;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.util.Arrays;
27 import java.util.List;
28
29 /**
30  * @author max
31  */
32 public class BuildNumber implements Comparable<BuildNumber> {
33   public enum Format { HISTORIC, BRANCH_BASED, YEAR_BASED }
34   
35   private static final String BUILD_NUMBER = "__BUILD_NUMBER__";
36   private static final String STAR = "*";
37   private static final String SNAPSHOT = "SNAPSHOT";
38   private static final String FALLBACK_VERSION = "2999.1.SNAPSHOT";
39
40   public static final int SNAPSHOT_VALUE = Integer.MAX_VALUE;
41
42   private static class Holder {
43     private static final BuildNumber CURRENT_VERSION = fromFile();
44   }
45
46   @NotNull  private final String myProductCode;
47   @NotNull  private final Format myFormat;
48   private final int[] myComponents;
49   
50   public BuildNumber(@NotNull String productCode, int baselineVersion, int buildNumber) {
51     this(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber);
52   }
53
54   BuildNumber(@NotNull String productCode, @NotNull Format format, int... components) {
55     myProductCode = productCode;
56     myFormat = format;
57     myComponents = components;
58   }
59
60   public String asString() {
61     return asString(true, true);
62   }
63
64   public String asStringWithAllDetails() {
65     return asString(true, true);
66   }
67
68   public String asStringWithoutProductCode() {
69     return asString(false, true);
70   }
71
72   public String asStringWithoutProductCodeAndSnapshot() {
73     return asString(false, false);
74   }
75
76   private String asString(boolean includeProductCode, boolean withSnapshotMarker) {
77     StringBuilder builder = new StringBuilder();
78
79     if (includeProductCode && !StringUtil.isEmpty(myProductCode)) {
80       builder.append(myProductCode).append('-');
81     }
82
83     for (int each : myComponents) {
84       if (each != SNAPSHOT_VALUE) {
85         builder.append(each);
86       }
87       else if (withSnapshotMarker) {
88         builder.append(SNAPSHOT);
89       }
90       builder.append('.');
91     }
92     if (builder.charAt(builder.length() - 1) == '.') builder.setLength(builder.length() - 1);
93
94     return builder.toString();
95   }
96
97   public static BuildNumber fromString(String version) {
98     return fromString(version, null);
99   }
100
101   public static BuildNumber fromString(String version, @Nullable String name) {
102     if (version == null) return null;
103
104     if (BUILD_NUMBER.equals(version) || SNAPSHOT.equals(version)) {
105       final String productCode = name != null ? name : "";
106       return new BuildNumber(productCode, Holder.CURRENT_VERSION.getFormat(), Holder.CURRENT_VERSION.myComponents);
107     }
108
109     String code = version;
110     int productSeparator = code.indexOf('-');
111     final String productCode;
112     if (productSeparator > 0) {
113       productCode = code.substring(0, productSeparator);
114       code = code.substring(productSeparator + 1);
115     }
116     else {
117       productCode = "";
118     }
119
120     int baselineVersionSeparator = code.indexOf('.');
121     int baselineVersion;
122     int buildNumber;
123
124     if (baselineVersionSeparator > 0) {
125       String baselineVersionString = code.substring(0, baselineVersionSeparator);
126       if (baselineVersionString.trim().isEmpty()) return null;
127       try {
128         baselineVersion = Integer.parseInt(baselineVersionString);
129       }
130       catch (NumberFormatException e) {
131         throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name);
132       }
133
134       if (baselineVersion >= 2016) {
135         List<String> stringComponents = StringUtil.split(code, ".");
136         int[] intComponents = new int[stringComponents.size()];
137         for (int i = 0; i < stringComponents.size(); i++) {
138           intComponents[i] = parseBuildNumber(version, stringComponents.get(i), name);
139         }
140
141         return new BuildNumber(productCode, Format.YEAR_BASED, intComponents);
142       }
143       else {
144         code = code.substring(baselineVersionSeparator + 1);
145
146         int minorBuildSeparator = code.indexOf('.'); // allow <BuildNumber>.<BuildAttemptNumber> skipping BuildAttemptNumber
147
148         Integer attemptInfo = null;
149         if (minorBuildSeparator > 0) {
150           attemptInfo = parseBuildNumber(version, code.substring(minorBuildSeparator + 1), name);
151           code = code.substring(0, minorBuildSeparator);
152         }
153         buildNumber = parseBuildNumber(version, code, name);
154
155         if (attemptInfo != null) {
156           return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber, attemptInfo);
157         }
158         else {
159           return new BuildNumber(productCode, Format.BRANCH_BASED, baselineVersion, buildNumber);
160         }
161       }
162     }
163     else {
164       buildNumber = parseBuildNumber(version, code, name);
165
166       if (buildNumber <= 2000) {
167         // it's probably a baseline, not a build number
168         return new BuildNumber(productCode, Format.BRANCH_BASED, buildNumber, 0);
169       }
170
171       if (buildNumber >= 2016 && buildNumber <= 2999) {
172         return new BuildNumber(productCode, Format.YEAR_BASED, buildNumber, 0);
173       }
174       
175       baselineVersion = getBaseLineForHistoricBuilds(buildNumber);
176       return new BuildNumber(productCode, Format.HISTORIC, baselineVersion, buildNumber);
177     }
178   }
179
180   private static int parseBuildNumber(String version, String code, String name) {
181     if (SNAPSHOT.equals(code) || BUILD_NUMBER.equals(code)) {
182       return SNAPSHOT_VALUE;
183     }
184     if (STAR.equals(code)) {
185       return SNAPSHOT_VALUE;
186     }
187     
188     try {
189       return Integer.parseInt(code);
190     }
191     catch (NumberFormatException e) {
192       throw new RuntimeException("Invalid version number: " + version + "; plugin name: " + name);
193     }
194   }
195
196   private static BuildNumber fromFile() {
197     try {
198       String home = PathManager.getHomePath();
199       File buildTxtFile = FileUtil.findFirstThatExist(home + "/build.txt", home + "/Resources/build.txt", home + "/community/build.txt");
200       if (buildTxtFile != null) {
201         String text = FileUtil.loadFile(buildTxtFile).trim();
202         return fromString(text);
203       }
204     }
205     catch (IOException ignored) { }
206
207     return fallback();
208   }
209
210   public static BuildNumber fallback() {
211     return fromString(FALLBACK_VERSION);
212   }
213
214   @Override
215   public String toString() {
216     return asString();
217   }
218
219   @Override
220   public int compareTo(@NotNull BuildNumber o) {
221     int[] c1 = myComponents;
222     int[] c2 = o.myComponents;
223     
224     for (int i = 0; i < Math.min(c1.length, c2.length); i++) {
225       if (c1[i] == c2[i] && c1[i] == SNAPSHOT_VALUE) return 0;
226       if (c1[i] == SNAPSHOT_VALUE) return 1;
227       if (c2[i] == SNAPSHOT_VALUE) return -1;
228
229       int result = c1[i] - c2[i];
230       if (result != 0) return result;
231     }
232     return c1.length - c2.length;
233   }
234
235   @NotNull
236   public String getProductCode() {
237     return myProductCode;
238   }
239
240   public int getBaselineVersion() {
241     return myFormat == Format.YEAR_BASED ? (myComponents[0] * 10 + myComponents[1]) : myComponents[0];
242   }
243
244   @Deprecated
245   public int getBuildNumber() {
246     return myFormat == Format.YEAR_BASED ? -1 : myComponents[1];
247   }
248
249   public int[] getComponents() {
250     return myComponents;
251   }
252
253   @NotNull
254   public Format getFormat() {
255     return myFormat;
256   }
257
258   @Override
259   public boolean equals(Object o) {
260     if (this == o) return true;
261     if (o == null || getClass() != o.getClass()) return false;
262
263     BuildNumber that = (BuildNumber)o;
264
265     if (myFormat != that.myFormat) return false;
266     if (!myProductCode.equals(that.myProductCode)) return false;
267     if (!Arrays.equals(myComponents, that.myComponents)) return false;
268
269     return true;
270   }
271
272   @Override
273   public int hashCode() {
274     int result = myProductCode.hashCode();
275     result = 31 * result + Arrays.hashCode(myComponents);
276     result = 31 * result + myFormat.hashCode();
277     return result;
278   }
279
280   // See http://www.jetbrains.net/confluence/display/IDEADEV/Build+Number+Ranges for historic build ranges
281   private static int getBaseLineForHistoricBuilds(int bn) {
282     if (bn >= 10000) {
283       return 88; // Maia, 9x builds
284     }
285
286     if (bn >= 9500) {
287       return 85; // 8.1 builds
288     }
289
290     if (bn >= 9100) {
291       return 81; // 8.0.x builds
292     }
293
294     if (bn >= 8000) {
295       return 80; // 8.0, including pre-release builds
296     }
297
298     if (bn >= 7500) {
299       return 75; // 7.0.2+
300     }
301
302     if (bn >= 7200) {
303       return 72; // 7.0 final
304     }
305
306     if (bn >= 6900) {
307       return 69; // 7.0 pre-M2
308     }
309
310     if (bn >= 6500) {
311       return 65; // 7.0 pre-M1
312     }
313
314     if (bn >= 6000) {
315       return 60; // 6.0.2+
316     }
317
318     if (bn >= 5000) {
319       return 55; // 6.0 branch, including all 6.0 EAP builds
320     }
321
322     if (bn >= 4000) {
323       return 50; // 5.1 branch
324     }
325
326     return 40;
327   }
328
329   public boolean isSnapshot() {
330     for (int each : myComponents) {
331       if (each == SNAPSHOT_VALUE) return true;
332     }
333     return false;
334   }
335 }