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}