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