001package jmri; 002 003import java.util.ResourceBundle; 004 005/** 006 * Defines a simple place to get the JMRI version string. 007 * <p> 008 * JMRI version strings are of the form x.y.z-m: 009 * <ul> 010 * <li>x, called the "major" number, is a small integer that increases with time 011 * <li>y, called the "minor" number, is a small integer that increases with time 012 * <li>z, called the "test" or "build" number, is a small integer increasing 013 * with time, perhaps followed by a couple of modifier characters. As a special 014 * case, this is omitted for Production Releases. 015 * <li>m, called the modifier, is a string that further describes the build. A 016 * common modifier is "plus" which denotes an unofficial build. 017 * </ul> 018 * Hence you expect to see JMRI versions called things like "4.7.2", "4.6", 019 * "4.7.3plus", "4.7.2-pjc", "4.7.2plus-pjc". 020 * <p> 021 * The version string shown by a JMRI program or used to label a download comes 022 * in two forms, depending on whether it was built by an "official" process or 023 * not, which in turn is determined by the "release.official" property: 024 * <dl> 025 * <dt>Official<dd> 026 * <ul> 027 * <li>If the revision number e.g. 123abc (git hash) is available in 028 * release.revision_id, then "4.1.1+R123abc". Note the "R". 029 * <li>Else "4.1.1+(date)", where the date comes from the release.build_date 030 * property. 031 * </ul> 032 * <dt>Unofficial<dd> 033 * Unofficial releases are marked by "plus" after the version number, and 034 * inclusion of the building user's ID. 035 * <ul> 036 * <li>If the revision number e.g. 123abc (git hash) is available in 037 * release.revision_id, then "4.1.1plus+(user)+(date)+R123abc". Note the "R". 038 * <li>Else "4.1.1+(user)+(date)", where the date comes from the 039 * release.build_date property. 040 * </ul> 041 * </dl> 042 * The release.revision_id, release.build_user and release.build_date properties 043 * are set at build time by Ant. 044 * <p> 045 * Generally, JMRI updates its version string in the code repository right 046 * <b>after</b> a release. Between formal release 1.2.3 and 1.2.4, the string 047 * will be 1.2.4plus. 048 * <hr> 049 * This file is part of JMRI. 050 * <p> 051 * JMRI is free software; you can redistribute it and/or modify it under the 052 * terms of version 2 of the GNU General Public License as published by the Free 053 * Software Foundation. See the "COPYING" file for a copy of this license. 054 * <p> 055 * JMRI is distributed in the hope that it will be useful, but WITHOUT ANY 056 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 057 * A PARTICULAR PURPOSE. See the GNU General Public License for more details. 058 * 059 * @author Bob Jacobsen Copyright 1997-2022 060 */ 061public class Version { 062 063 static final private ResourceBundle VERSION_BUNDLE = ResourceBundle.getBundle("jmri.Version"); // NOI18N 064 065 /** 066 * Major number changes with large incompatible changes in requirements or 067 * API. 068 */ 069 static final public int major = Integer.parseInt(VERSION_BUNDLE.getString("release.major")); // NOI18N 070 071 /** 072 * Minor number changes with each production versionBundle. Odd is 073 * development, even is production. 074 */ 075 static final public int minor = Integer.parseInt(VERSION_BUNDLE.getString("release.minor")); // NOI18N 076 077 /** 078 * Test number changes with individual releases, generally fastest for test 079 * releases. In production releases, if non-zero, indicates a bug fix only 080 * release. 081 */ 082 static final public int test = Integer.parseInt(VERSION_BUNDLE.getString("release.build")); // NOI18N 083 084 /** 085 * The additional MODIFIER for the release. Used to indicate a parallel 086 * release of a feature that has not been accepted into main stream 087 * development. 088 */ 089 static final public String MODIFIER = VERSION_BUNDLE.getString("release.modifier"); // NOI18N 090 091 /** 092 * Descriptor for non-official build. Included in {@link #name()}, but not 093 * in {@link #getCanonicalVersion()}. 094 */ 095 static final public String NON_OFFICIAL = "plus"; // NOI18N 096 097 /** 098 * The user who built this versionBundle, as determined by the build 099 * machine. 100 */ 101 static final public String buildUser = VERSION_BUNDLE.getString("release.build_user"); // NOI18N 102 103 /** 104 * The Git revision ID for this versionBundle (if known). 105 */ 106 static final public String revisionId = VERSION_BUNDLE.getString("release.revision_id"); // NOI18N 107 108 /** 109 * The date/time of this build. 110 */ 111 static final public String buildDate = VERSION_BUNDLE.getString("release.build_date"); // NOI18N 112 113 /** 114 * Has this build been created as a possible "official" versionBundle? 115 */ 116 static final public boolean official = Boolean.parseBoolean(VERSION_BUNDLE.getString("release.official")); // NOI18N 117 118 /** 119 * Get the MODIFIER in the 1.2.3-MODIFIER version name. Non-official 120 * versions include {@value #NON_OFFICIAL} in the MODIFIER. 121 * 122 * @return the third term, possibly an empty String if {@link #test} is 0 123 */ 124 public static String getModifier() { 125 StringBuilder modifier = new StringBuilder(); 126 if (!official) { 127 modifier.append(NON_OFFICIAL); 128 } 129 if (!MODIFIER.isEmpty()) { 130 modifier.append("-").append(MODIFIER); // NOI18N 131 } 132 return modifier.toString().replace("--", "-"); 133 } 134 135 /** 136 * Provide the current version string. 137 * <p> 138 * This string is built using various known build parameters, including the 139 * versionBundle.{major,minor,build} values, the MODIFIER, the Git revision 140 * ID (if known) and the official property 141 * 142 * @return The current version string 143 */ 144 static public String name() { 145 String version = major + "." + minor; 146 if (test != 0) { 147 version = version + "." + test; 148 } 149 String addOn; 150 if (official) { 151 if ("unknown".equals(revisionId)) { 152 addOn = buildDate; 153 } else { 154 addOn = "R" + revisionId; 155 } 156 } else { // not official, so a development build that gets a user name 157 if ("unknown".equals(revisionId)) { 158 addOn = buildUser + "+" + buildDate; 159 } else { 160 addOn = buildUser + "+" + buildDate + "+R" + revisionId; 161 } 162 } 163 return version + getModifier() + "+" + addOn; 164 } 165 166 /** 167 * Tests that a string contains a canonical version string. 168 * <p> 169 * A canonical version string is a string in the form x.y.z[-a[-b[-...]]] 170 * where parts x, y, and z are integers and parts a, b, ... are free-form 171 * text and is different than the version string displayed using 172 * {@link #name()}. The canonical version string for a JMRI instance is 173 * available using {@link #getCanonicalVersion()}. The canonical version 174 * will not include official indicators or build metadata. 175 * 176 * @param version version string to check 177 * @return true if version is a canonical version string 178 */ 179 static public boolean isCanonicalVersion(String version) { 180 String[] parts = version.split("\\+"); 181 if (parts.length > 1) { 182 return false; 183 } 184 parts = version.split("-"); 185 String[] versions = parts[0].split("\\."); 186 if (versions.length != 3) { 187 return false; 188 } 189 try { 190 for (String part : versions) { 191 if (Integer.parseInt(part) < 0) { 192 return false; 193 } 194 } 195 } catch (NumberFormatException ex) { 196 return false; 197 } 198 return true; 199 } 200 201 /** 202 * Compares a canonical version string to the JMRI canonical version and 203 * returns an integer indicating if the string is less than, equal to, or 204 * greater than the JMRI canonical version. 205 * 206 * @param version version string to compare 207 * @return -1, 0, or 1 if version is less than, equal to, or greater than 208 * JMRI canonical version 209 * @throws IllegalArgumentException if version is not a canonical version 210 * string 211 * @see java.lang.Comparable#compareTo(java.lang.Object) 212 */ 213 static public int compareCanonicalVersions(String version) throws IllegalArgumentException { 214 return compareCanonicalVersions(version, getCanonicalVersion()); 215 } 216 217 /** 218 * Compares two canonical version strings and returns an integer indicating 219 * if the first string is less than, equal to, or greater than the second 220 * string. This comparison ignores modifiers. 221 * 222 * @param version1 a canonical version string 223 * @param version2 a canonical version string 224 * @return -1, 0, or 1 if version1 is less than, equal to, or greater than 225 * version2 226 * @throws IllegalArgumentException if either version string is not a 227 * canonical version string 228 * @see java.lang.Comparable#compareTo(java.lang.Object) 229 */ 230 static public int compareCanonicalVersions(String version1, String version2) throws IllegalArgumentException { 231 int result = 0; 232 if (!isCanonicalVersion(version1)) { 233 throw new IllegalArgumentException("Parameter version1 (" + version1 + ") is not a canonical version string."); 234 } 235 if (!isCanonicalVersion(version2)) { 236 throw new IllegalArgumentException("Parameter version2 (" + version2 + ") is not a canonical version string."); 237 } 238 String[] p1 = version1.split("-"); 239 String[] p2 = version2.split("-"); 240 String[] v1 = p1[0].split("\\."); 241 String[] v2 = p2[0].split("\\."); 242 for (int i = 0; i < 3; i++) { 243 result = v1[i].compareTo(v2[i]); 244 if (result != 0) { 245 return result; 246 } 247 } 248 return result; 249 } 250 251 /** 252 * Return the version as major.minor.test-modifiers. The test value is 253 * always present. The {@value #NON_OFFICIAL} modifier is not present in the 254 * canonical version. 255 * 256 * @return the canonical version 257 */ 258 static public String getCanonicalVersion() { 259 String version = major + "." + minor + "." + test; 260 String modifiers = getModifier().replace(NON_OFFICIAL, ""); // remove "ish" 261 if (!modifiers.isEmpty()) { 262 version = version + modifiers; 263 } 264 if (version.endsWith("-")) { 265 version = version.substring(0, version.length() - 2); 266 } 267 return version; 268 } 269 270 /** 271 * Return the application copyright as a String. 272 * 273 * @return the copyright 274 */ 275 static public String getCopyright() { 276 return Bundle.getMessage("Copyright", VERSION_BUNDLE.getString("jmri.copyright.year")); 277 } 278 279 /** 280 * Standalone print of version string and exit. 281 * 282 * This is used in the build.xml to generate parts of the installer 283 * versionBundle file name, so take care in altering this code to make sure 284 * the ant recipes are also suitably modified. 285 * 286 * @param args command-line arguments 287 */ 288 static public void main(String[] args) { 289 System.out.println(name()); 290 } 291 292}