Merge branch 'subtree'
[nikiroo-utils.git] / src / be / nikiroo / utils / Version.java
1 package be.nikiroo.utils;
2
3 import java.io.ByteArrayOutputStream;
4 import java.io.IOException;
5 import java.io.InputStream;
6
7 /**
8 * This class describe a program {@link Version}.
9 *
10 * @author niki
11 */
12 public class Version implements Comparable<Version> {
13 private String version;
14 private int major;
15 private int minor;
16 private int patch;
17 private String tag;
18 private int tagVersion;
19
20 /**
21 * Create a new, empty {@link Version}.
22 *
23 */
24 public Version() {
25 }
26
27 /**
28 * Create a new {@link Version} with the given values.
29 *
30 * @param major
31 * the major version
32 * @param minor
33 * the minor version
34 * @param patch
35 * the patch version
36 */
37 public Version(int major, int minor, int patch) {
38 this(major, minor, patch, null, -1);
39 }
40
41 /**
42 * Create a new {@link Version} with the given values.
43 *
44 * @param major
45 * the major version
46 * @param minor
47 * the minor version
48 * @param patch
49 * the patch version
50 * @param tag
51 * a tag name for this version
52 */
53 public Version(int major, int minor, int patch, String tag) {
54 this(major, minor, patch, tag, -1);
55 }
56
57 /**
58 * Create a new {@link Version} with the given values.
59 *
60 * @param major
61 * the major version
62 * @param minor
63 * the minor version
64 * @param patch
65 * the patch version the patch version
66 * @param tag
67 * a tag name for this version
68 * @param tagVersion
69 * the version of the tagged version
70 */
71 public Version(int major, int minor, int patch, String tag, int tagVersion) {
72 if (tagVersion >= 0 && tag == null) {
73 throw new java.lang.IllegalArgumentException(
74 "A tag version cannot be used without a tag");
75 }
76
77 this.major = major;
78 this.minor = minor;
79 this.patch = patch;
80 this.tag = tag;
81 this.tagVersion = tagVersion;
82
83 this.version = generateVersion();
84 }
85
86 /**
87 * Create a new {@link Version} with the given value, which must be in the
88 * form <tt>MAJOR.MINOR.PATCH(-TAG(TAG_VERSION))</tt>.
89 *
90 * @param version
91 * the version (<tt>MAJOR.MINOR.PATCH</tt>,
92 * <tt>MAJOR.MINOR.PATCH-TAG</tt> or
93 * <tt>MAJOR.MINOR.PATCH-TAGVERSIONTAG</tt>)
94 */
95 public Version(String version) {
96 try {
97 String[] tab = version.split("\\.");
98 this.major = Integer.parseInt(tab[0].trim());
99 this.minor = Integer.parseInt(tab[1].trim());
100 if (tab[2].contains("-")) {
101 int posInVersion = version.indexOf('.');
102 posInVersion = version.indexOf('.', posInVersion + 1);
103 String rest = version.substring(posInVersion + 1);
104
105 int posInRest = rest.indexOf('-');
106 this.patch = Integer.parseInt(rest.substring(0, posInRest)
107 .trim());
108
109 posInVersion = version.indexOf('-');
110 this.tag = version.substring(posInVersion + 1).trim();
111 this.tagVersion = -1;
112
113 StringBuilder str = new StringBuilder();
114 while (!tag.isEmpty() && tag.charAt(tag.length() - 1) >= '0'
115 && tag.charAt(tag.length() - 1) <= '9') {
116 str.insert(0, tag.charAt(tag.length() - 1));
117 tag = tag.substring(0, tag.length() - 1);
118 }
119
120 if (str.length() > 0) {
121 this.tagVersion = Integer.parseInt(str.toString());
122 }
123 } else {
124 this.patch = Integer.parseInt(tab[2].trim());
125 this.tag = null;
126 this.tagVersion = -1;
127 }
128
129 this.version = generateVersion();
130 } catch (Exception e) {
131 this.major = 0;
132 this.minor = 0;
133 this.patch = 0;
134 this.tag = null;
135 this.tagVersion = -1;
136 this.version = null;
137 }
138 }
139
140 /**
141 * The 'major' version.
142 * <p>
143 * This version should only change when API-incompatible changes are made to
144 * the program.
145 *
146 * @return the major version
147 */
148 public int getMajor() {
149 return major;
150 }
151
152 /**
153 * The 'minor' version.
154 * <p>
155 * This version should only change when new, backwards-compatible
156 * functionality has been added to the program.
157 *
158 * @return the minor version
159 */
160 public int getMinor() {
161 return minor;
162 }
163
164 /**
165 * The 'patch' version.
166 * <p>
167 * This version should change when backwards-compatible bugfixes have been
168 * added to the program.
169 *
170 * @return the patch version
171 */
172 public int getPatch() {
173 return patch;
174 }
175
176 /**
177 * A tag name for this version.
178 *
179 * @return the tag
180 */
181 public String getTag() {
182 return tag;
183 }
184
185 /**
186 * The version of the tag, or -1 for no version.
187 *
188 * @return the tag version
189 */
190 public int getTagVersion() {
191 return tagVersion;
192 }
193
194 /**
195 * Check if this {@link Version} is "empty" (i.e., the version was not
196 * parse-able or not given).
197 *
198 * @return TRUE if it is empty
199 */
200 public boolean isEmpty() {
201 return version == null;
202 }
203
204 /**
205 * Check if we are more recent than the given {@link Version}.
206 * <p>
207 * Note that a tagged version is considered newer than a non-tagged version,
208 * but two tagged versions with different tags are not comparable.
209 * <p>
210 * Also, an empty version is always considered older.
211 *
212 * @param o
213 * the other {@link Version}
214 * @return TRUE if this {@link Version} is more recent than the given one
215 */
216 public boolean isNewerThan(Version o) {
217 if (isEmpty()) {
218 return false;
219 } else if (o.isEmpty()) {
220 return true;
221 }
222
223 if (major > o.major) {
224 return true;
225 }
226
227 if (major == o.major && minor > o.minor) {
228 return true;
229 }
230
231 if (major == o.major && minor == o.minor && patch > o.patch) {
232 return true;
233 }
234
235 // a tagged version is considered newer than a non-tagged one
236 if (major == o.major && minor == o.minor && patch == o.patch
237 && tag != null && o.tag == null) {
238 return true;
239 }
240
241 // 2 <> tagged versions are not comparable
242 boolean sameTag = (tag == null && o.tag == null)
243 || (tag != null && tag.equals(o.tag));
244 if (major == o.major && minor == o.minor && patch == o.patch && sameTag
245 && tagVersion > o.tagVersion) {
246 return true;
247 }
248
249 return false;
250 }
251
252 /**
253 * Check if we are older than the given {@link Version}.
254 * <p>
255 * Note that a tagged version is considered newer than a non-tagged version,
256 * but two tagged versions with different tags are not comparable.
257 * <p>
258 * Also, an empty version is always considered older.
259 *
260 * @param o
261 * the other {@link Version}
262 * @return TRUE if this {@link Version} is older than the given one
263 */
264 public boolean isOlderThan(Version o) {
265 if (o.isEmpty()) {
266 return false;
267 } else if (isEmpty()) {
268 return true;
269 }
270
271 // 2 <> tagged versions are not comparable
272 boolean sameTag = (tag == null && o.tag == null)
273 || (tag != null && tag.equals(o.tag));
274 if (major == o.major && minor == o.minor && patch == o.patch
275 && !sameTag) {
276 return false;
277 }
278
279 return !equals(o) && !isNewerThan(o);
280 }
281
282 /**
283 * Return the version of the running program if it follows the VERSION
284 * convention (i.e., if it has a file called VERSION containing the version
285 * as a {@link String} in its binary root, and if this {@link String}
286 * follows the Major/Minor/Patch convention).
287 * <p>
288 * If it does not, return an empty {@link Version} object.
289 *
290 * @return the {@link Version} of the program, or an empty {@link Version}
291 * (does not return NULL)
292 */
293 public static Version getCurrentVersion() {
294 String version = null;
295
296 InputStream in = IOUtils.openResource("VERSION");
297 if (in != null) {
298 try {
299 ByteArrayOutputStream ba = new ByteArrayOutputStream();
300 IOUtils.write(in, ba);
301 in.close();
302
303 version = ba.toString("UTF-8").trim();
304 } catch (IOException e) {
305 }
306 }
307
308 return new Version(version);
309 }
310
311 @Override
312 public int compareTo(Version o) {
313 if (equals(o)) {
314 return 0;
315 } else if (isNewerThan(o)) {
316 return 1;
317 } else {
318 return -1;
319 }
320 }
321
322 @Override
323 public boolean equals(Object obj) {
324 if (obj instanceof Version) {
325 Version o = (Version) obj;
326 if (isEmpty()) {
327 return o.isEmpty();
328 }
329
330 boolean sameTag = (tag == null && o.tag == null)
331 || (tag != null && tag.equals(o.tag));
332 return o.major == major && o.minor == minor && o.patch == patch
333 && sameTag && o.tagVersion == tagVersion;
334 }
335
336 return false;
337 }
338
339 @Override
340 public int hashCode() {
341 return version == null ? 0 : version.hashCode();
342 }
343
344 /**
345 * Return a user-readable form of this {@link Version}.
346 */
347 @Override
348 public String toString() {
349 return version == null ? "[unknown]" : version;
350 }
351
352 /**
353 * Generate the clean version {@link String} from the current values.
354 *
355 * @return the clean version string
356 */
357 private String generateVersion() {
358 String tagSuffix = "";
359 if (tag != null) {
360 tagSuffix = "-" + tag
361 + (tagVersion >= 0 ? Integer.toString(tagVersion) : "");
362 }
363
364 return String.format("%d.%d.%d%s", major, minor, patch, tagSuffix);
365 }
366 }