001package co.codewizards.cloudstore.core.updater; 002 003import java.io.Serializable; 004import java.util.Arrays; 005import java.util.regex.Matcher; 006import java.util.regex.Pattern; 007 008/** 009 * This class is used to <code>version</code> the client plugins of JFire and may be used to act 010 * as a version identifier in any project. 011 * 012 * The pattern a version <code>String</code> is: 013 * <code>major</code>{@value #DEFAULT_SEPARATOR}<code>minor</code>{@value #DEFAULT_SEPARATOR} 014 * <code>release</code>[{@value #DEFAULT_SEPARATOR}<code>patchLevel</code> 015 * [{@value #DEFAULT_SEPARATOR}<code>suffix</code>]] <br> 016 * The suffix may consist of 'a-zA-Z0-9#$!_-'. Additionally the following version string is 017 * also valid: '<pre>1.2.3.test</pre>'. 018 * 019 * <p>Examples of valid versions are: 020 * <ul> 021 * <li><pre>1.2.3</pre></li> 022 * <li><pre>1.2.3.4</pre></li> 023 * <li><pre>1.2.3.4.test</pre></li> 024 * <li><pre>1.2.3.t_e!st</pre></li> 025 * </ul> 026 * </p> 027 * 028 * 029 * <p>Note: This class is immutable!</p> 030 * 031 * @author Marius Heinzmann marius[AT]NightLabs[DOT]de 032 * @author Marco Schulze marco@nightlabs.de 033 * @author Niklas Schiffler <nick@nightlabs.de> 034 */ 035public class Version implements Comparable<Version>, Serializable 036{ 037 private static final String SNAPSHOT_SUFFIX = "SNAPSHOT"; 038 039 private static final long serialVersionUID = 1L; 040 041 /** 042 * The minimum Version that can exist. 043 */ 044 public static final Version MIN_VERSION = new Version(0,0,0,0); 045 046 /** 047 * The maximum Version possible. 048 */ 049 public static final Version MAX_VERSION = new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, 050 Integer.MAX_VALUE, Integer.MAX_VALUE); 051 052 private int major; 053 private int minor; 054 private int release; 055 private int patchLevel; 056 private String suffix; 057 058 private static final String DEFAULT_SEPARATOR = "."; 059 private static final String SUFFIX_SEPARATOR = "-"; 060 private static final String EMPTYSTRING = ""; 061 062 /** 063 * Creates a new instance from the given {@code versionStr}. 064 * @param versionStr the version encoded as a String. 065 * @throws MalformedVersionException 066 */ 067 public Version(String versionStr) 068 throws MalformedVersionException 069 { 070 if (versionStr == null || versionStr.length() == 0) 071 throw new IllegalArgumentException("versionStr must NEITHER be null NOR empty!"); 072 073 parseVersion(versionStr); 074 validate(); 075 } 076 077 /** 078 * @param major 079 * @param minor 080 * @param release 081 * @param patchLevel 082 */ 083 public Version(int major, int minor, int release, int patchLevel) { 084 this(major, minor, release, patchLevel, null); 085 } 086 087 /** 088 * @param major 089 * @param minor 090 * @param release 091 * @param patchLevel 092 * @param suffix 093 */ 094 public Version(int major, int minor, int release, int patchLevel, String suffix) { 095 this.major = major; 096 this.minor = minor; 097 this.release = release; 098 this.patchLevel = patchLevel; 099 if (suffix == null) 100 suffix = EMPTYSTRING; 101// TODO: should we limit the length of a suffix?? There is no need to, but it might become a 102// Problem when persisting in the DB. 103// else if (suffix.length() > MAX_STRING.length()) 104// throw new IllegalArgumentException("The given suffix is too long! It is allowed to have a " + 105// "maximum of "+MAX_STRING.length()+" characters!"); 106 107 this.suffix = suffix; 108 validate(); 109 } 110 111 /** 112 * A regex that is used to check the suffix for valid characters. 113 */ 114 public static Pattern validityCheck = null; 115 116 private void validate() { 117 if (major < 0) 118 throw new MalformedVersionException("Negative major versions are invalid!"); 119 if (minor < 0) 120 throw new MalformedVersionException("Negative minor versions are invalid!"); 121 if (release < 0) 122 throw new MalformedVersionException("Negative release versions are invalid!"); 123 if (patchLevel < 0) 124 throw new MalformedVersionException("Negative patchlevels are invalid!"); 125 126 if (suffix == null || suffix.length() == 0) 127 return; 128 129 // This omits parsing problems of other classes which may as well be represented as strings 130 // and have other separators which may be used inside this suffix. 131 if (validityCheck == null) 132 validityCheck = Pattern.compile("[\\w#\\$!\\-]*"); 133 134 if (! validityCheck.matcher(suffix).matches()) 135 throw new MalformedVersionException("The suffix contains illegal characters! Suffix='" 136 +suffix+"'. Legal characters ='a-zA-Z0-9#$!_-'."); 137 } 138 139 /** 140 * @return Returns the major. 141 */ 142 public int getMajor() { 143 return major; 144 } 145 146 /** 147 * @param major The major with which to create a new Version. 148 * @return A copy of this Version except for the given new major. 149 */ 150 public Version changeMajor(int major) { 151 return new Version(major, minor, release, patchLevel, suffix); 152 } 153 154 /** 155 * @return Returns the minor. 156 */ 157 public int getMinor() { 158 return minor; 159 } 160 161 /** 162 * @param minor The minor with which to create a new Version. 163 * @return A copy of this Version except for the given new minor. 164 */ 165 public Version changeMinor(int minor) { 166 return new Version(major, minor, release, patchLevel, suffix); 167 } 168 169 /** 170 * @return Returns the patchLevel. 171 */ 172 public int getPatchLevel() { 173 return patchLevel; 174 } 175 176 /** 177 * @param patchLevel The patchLevel with which to create a new Version. 178 * @return A copy of this Version except for the given new patchLevel. 179 */ 180 public Version changePatchLevel(int patchLevel) { 181 return new Version(major, minor, release, patchLevel, suffix); 182 } 183 184 /** 185 * @return Returns the release. 186 */ 187 public int getRelease() { 188 return release; 189 } 190 191 /** 192 * @param release The <code>release</code> with which to create a new Version. 193 * @return A copy of this Version except for the given new <code>release</code>. 194 */ 195 public Version changeRelease(int release) { 196 return new Version(major, minor, release, patchLevel, suffix); 197 } 198 199 /** 200 * @return Returns the suffix. 201 */ 202 public String getSuffix() { 203 return suffix; 204 } 205 206 /** 207 * @param suffix The <code>suffix</code> with which to create a new Version. 208 * @return A copy of this Version except for the given new <code>suffix</code>. 209 */ 210 public Version changeSuffix(String suffix) { 211 return new Version(major, minor, release, patchLevel, suffix); 212 } 213 214 /** 215 * Returns the String representation of this version, which is defined as: 216 * <code>major</code>{@value #DEFAULT_SEPARATOR}<code>minor</code>{@value #DEFAULT_SEPARATOR} 217 * <code>release</code>[{@value #DEFAULT_SEPARATOR}<code>patchLevel</code> 218 * [{@value #DEFAULT_SEPARATOR}<code>suffix</code>]] <br> 219 * The suffix may consist of 'a-zA-Z0-9#$!_-'. Additionally the following version string is 220 * also valid: '<pre>1.2.3.test</pre>'. 221 * 222 * @return the <code>String</code> representation of this version. 223 */ 224 @Override 225 public String toString() { 226 StringBuffer sb = new StringBuffer(); 227 sb.append(major); 228 sb.append(DEFAULT_SEPARATOR); 229 sb.append(minor); 230 sb.append(DEFAULT_SEPARATOR); 231 sb.append(release); 232 if (patchLevel != 0) { 233 sb.append(DEFAULT_SEPARATOR); 234 sb.append(patchLevel); 235 } 236 if (suffix != null && !EMPTYSTRING.equals(suffix)) { 237 sb.append(SUFFIX_SEPARATOR); 238 sb.append(suffix); 239 } 240 return sb.toString(); 241 } 242 243 /** 244 * Creates a Version out of its String ({@link #toString()}) representation. The String should 245 * conform to the following pattern: 246 * <code>major</code>{@value #DEFAULT_SEPARATOR}<code>minor</code>{@value #DEFAULT_SEPARATOR} 247 * <code>release</code>{@value #DEFAULT_SEPARATOR}<code>patchLevel</code> 248 * {@value #DEFAULT_SEPARATOR}<code>suffix</code>. 249 * 250 * @param version the {@link #toString()} representation of a version. 251 * @throws MalformedVersionException if the given <code>String</code> does not conform to the 252 * pattern described. 253 */ 254 private void parseVersion(String version) 255 throws MalformedVersionException 256 { 257 Pattern versionPattern = Pattern.compile( 258// The whole rexexp not escaped 259// (\d+)\.(\d+)\.(\d+)(?:[-\.](?:(\d+)|([\w#\$!\-]+)|(?:(\d+)[-\.]([\w#\$!\-]+))))?\s*\z 260 "(\\d+)\\.(\\d+)\\.(\\d+)(?:[-\\.](?:(\\d+)|([\\w#\\$!\\-]+)|(?:(\\d+)[-\\.]([\\w#\\$!\\-]+))))?\\s*\\z"); 261// EOI = end of input; OPT(..) = optional; | = or 262// major . minor . release OPT( ./- (patchLevel | suffix | patchLevel ./- suffix)) whitespace* EOI 263// the endofinput prohibits the parsing of '1.7.0-9-asf.narf' which would otherwise be read as 264// '1.7.0-9-asf'. 265 Matcher m = versionPattern.matcher(version); 266 if(m.find()) 267 { 268 this.major = Integer.parseInt(m.group(1)); 269 this.minor = Integer.parseInt(m.group(2)); 270 this.release = Integer.parseInt(m.group(3)); 271 272 // Assumption is that if the element in the 4th group is parseable as an int -> it is an int. 273 // So given the string '1.2.3.02' the '02' is interpreted as an int, not a string! 274 if (m.group(4) != null) 275 { 276 try 277 { 278// assume we got a small JFire version without suffix. 279 this.patchLevel = Integer.parseInt(m.group(4)); 280 this.suffix = ""; 281 } 282 catch (NumberFormatException nfe) 283 { 284// since the number is too big -> it must be a suffix consisting only of numbers 285 this.patchLevel = 0; 286 this.suffix = m.group(4); 287 } 288 } 289 else if (m.group(5) != null) 290 { 291// assume that we are given an osgi-version -> group(5) = suffix 292 this.patchLevel = 0; 293 this.suffix = m.group(5); 294 } 295 else if (m.group(6) != null && m.group(7) != null) 296 { 297// assume we have a full JFire version 298 try { 299 this.patchLevel = Integer.parseInt(m.group(6)); 300 this.suffix = m.group(7); 301 } catch (NumberFormatException nfe2) { 302 throw new MalformedVersionException("The patchlevel has to be a parseable number! " + 303 "given patchlevel: '"+m.group(6)+"' given encoded version: '"+version+"'"); 304 } 305 } 306 else 307 { 308 // assume we have only major.minor.release 309 for (int i = 4; i < 8; i++) 310 { 311 if (m.group(i) != null) 312 throw new MalformedVersionException("Malformed version string: " + version); 313 } 314 } 315 } 316 } 317 318 @Override 319 public boolean equals(Object other) { 320 if (this == other) return true; 321 if (!(other instanceof Version)) return false; 322 323 final Version otherVersion = (Version) other; 324 325 return major == otherVersion.major 326 && minor == otherVersion.minor 327 && release == otherVersion.release 328 && patchLevel == otherVersion.patchLevel 329 && equals(suffix, otherVersion.suffix); 330 } 331 332 private static boolean equals(Object obj0, Object obj1) 333 { 334 if (obj0 instanceof Object[] && obj1 instanceof Object[]) 335 return obj0 == obj1 || Arrays.equals((Object[])obj0, (Object[])obj1); 336 return obj0 == obj1 || (obj0 != null && obj0.equals(obj1)); 337 } 338 339 @Override 340 public int hashCode() { 341 final int PRIME = 31; 342 int result = 1; 343 result = result * PRIME + major; 344 result = result * PRIME + minor; 345 result = result * PRIME + release; 346 result = result * PRIME + patchLevel; 347 result = result * PRIME + (suffix == null ? 0 : suffix.hashCode()); 348 return result; 349 } 350 351 /** 352 * Compares this <code>Version</code> object to another one. 353 * 354 * <p> 355 * A version is considered to be <b>less than </b> another version if its major component is less 356 * than the other version's major component, or the major components are equal and its minor 357 * component is less than the other version's minor component, or the major and minor components 358 * are equal and its release component is less than the other version's release component. 359 * The suffix is not relevant for the natural ordering of Versions, though for equality. 360 * <p> 361 * Note: Given two version <code>v1,v2</code> and <code>v1.compareTo(Version v2) = 0</code>, 362 * this does not imply that <code>v1</code> and <code>v2</code> are equal according to equals! 363 * </p> 364 * 365 * <p> 366 * A version is considered to be <b>equal to</b> another version if the 367 * major, minor and micro components are equal and the qualifier component 368 * is equal (see {@link #equals(Object)}). 369 * </p> 370 * 371 * @param other The <code>Version</code> object to be compared. 372 * @return A negative integer, zero, or a positive integer if this object is 373 * less than, equal to, or greater than the specified 374 * <code>Version</code> object. 375 */ 376 @Override 377 public int compareTo(Version other) { 378 if (this == other) 379 return 0; 380 381 int difference = major - other.major; 382 if (difference != 0) 383 return difference; 384 385 difference = minor - other.minor; 386 if (difference != 0) 387 return difference; 388 389 difference = release - other.release; 390 if (difference != 0) 391 return difference; 392 393 difference = patchLevel - other.patchLevel; 394 if (difference != 0) 395 return difference; 396 397 // We currently take only the "SNAPSHOT" suffix into account. Every other 398 // suffix is ignored when it comes to comparing. 399 if (suffix == null) { 400 if (other.suffix == null) 401 return 0; 402 403 if (other.suffix.equalsIgnoreCase(SNAPSHOT_SUFFIX)) 404 return 1; 405 406 // TODO Maybe we'll later change this to compare the suffix as well. 407 return 0; 408 } 409 410 if (suffix.equalsIgnoreCase(other.suffix)) 411 return 0; 412 413 if (suffix.equalsIgnoreCase(SNAPSHOT_SUFFIX)) 414 return -1; 415 416 // TODO Maybe we'll later change this to compare the suffix as well. 417 return 0; 418 } 419 420}