001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.version;
018
019import java.io.Serializable;
020import java.util.Comparator;
021import java.util.Objects;
022import java.util.Optional;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.conqat.lib.commons.error.FormatException;
027
028import com.fasterxml.jackson.annotation.JsonCreator;
029import com.fasterxml.jackson.annotation.JsonProperty;
030
031/**
032 * A class to describe versions of software (or other) artifacts. A version has
033 * a major and a minor version number. Version are ordered. This class is
034 * immutable.
035 */
036public final class Version implements Comparable<Version>, Serializable {
037
038        /** The name of the JSON property name for {@link #major}. */
039        private static final String MAJOR_PROPERTY = "major";
040
041        /** The name of the JSON property name for {@link #minor}. */
042        private static final String MINOR_PROPERTY = "minor";
043
044        /** The name of the JSON property name for {@link #patch}. */
045        private static final String PATCH_PROPERTY = "patch";
046
047        /** Version used for serialization. */
048        private static final long serialVersionUID = 1;
049
050        /** Major version. */
051        @JsonProperty(MAJOR_PROPERTY)
052        private final int major;
053
054        /** Minor version. */
055        @JsonProperty(MINOR_PROPERTY)
056        private final int minor;
057
058        /** Patch level. */
059        @JsonProperty(PATCH_PROPERTY)
060        private final int patch;
061
062        /**
063         * Create a new version.
064         * 
065         * @param major
066         *            major version number.
067         * @param minor
068         *            minor version number.
069         * @throws IllegalArgumentException
070         *             if one of the version numbers is less than 0.
071         */
072        public Version(int major, int minor) {
073                this(major, minor, 0);
074        }
075
076        /**
077         * Create a new version.
078         *
079         * @param major
080         *            major version number.
081         * @param minor
082         *            minor version number.
083         * @param patch
084         *            patch version number.
085         * @throws IllegalArgumentException
086         *             if one of the version numbers is less than 0.
087         */
088        @JsonCreator
089        public Version(@JsonProperty(MAJOR_PROPERTY) int major, @JsonProperty(MINOR_PROPERTY) int minor,
090                        @JsonProperty(PATCH_PROPERTY) int patch) {
091                if (major < 0 || minor < 0 || patch < 0) {
092                        throw new IllegalArgumentException("Versions may not be less than 0.");
093                }
094
095                this.major = major;
096                this.minor = minor;
097                this.patch = patch;
098        }
099
100        /**
101         * Parses a version from a string. The format has to be "major.minor.patch",
102         * while patch is optional.
103         * 
104         * @throws FormatException
105         *             if the string does not follow the expected pattern.
106         */
107        public static Version parseVersion(String s) throws FormatException {
108                Matcher m = Pattern.compile("\\s*(\\d+)\\.(\\d+)(?:\\.(\\d+))?\\s*").matcher(s);
109                if (!m.matches()) {
110                        throw new FormatException("The provided string did not match the pattern!");
111                }
112
113                int major = Integer.parseInt(m.group(1));
114                int minor = Integer.parseInt(m.group(2));
115                int patch = Optional.ofNullable(m.group(3)).map(Integer::parseInt).orElse(0);
116                return new Version(major, minor, patch);
117        }
118
119        /**
120         * Compares to version numbers by their major and minor numbers.
121         */
122        @Override
123        public int compareTo(Version other) {
124                return Comparator.comparingInt(Version::getMajor).thenComparingInt(Version::getMinor)
125                                .thenComparingInt(Version::getPatch).compare(this, other);
126        }
127
128        /**
129         * Two version are equal if their major and minor version numbers are equal.
130         */
131        @Override
132        public boolean equals(Object other) {
133                if (other == this) {
134                        return true;
135                }
136
137                if (!(other instanceof Version)) {
138                        return false;
139                }
140
141                return compareTo((Version) other) == 0;
142
143        }
144
145        /** Get major version number. */
146        public int getMajor() {
147                return major;
148        }
149
150        /** Get minor version number. */
151        public int getMinor() {
152                return minor;
153        }
154
155        /** Get patch version number. */
156        public int getPatch() {
157                return patch;
158        }
159
160        @Override
161        public int hashCode() {
162                return Objects.hash(major, minor, patch);
163        }
164
165        /**
166         * This method is used to check version compatibility in dependency management.
167         * <p>
168         * Consider the following situation and artefact A (the depender) depends on
169         * another artefact B (the dependee). A claims that it requires B in version
170         * 1.3. B states that it has version 1.5 but is downward compatible to version
171         * 1.1.
172         * <p>
173         * Using this method one can find out if the version provided by B satisfies A's
174         * requirement. It is satisfied iff
175         * 
176         * <pre>
177         * requiredVersion &lt;= currentVersion &amp;&amp; requiredVersion &gt;= compatibleVersion
178         * </pre>
179         * 
180         * where <code>requiredVersion</code> is this instance and the other two are
181         * provided as method parameters.
182         * 
183         * @throws IllegalArgumentException
184         *             if <code>compatibleVersion</code> is greater than
185         *             <code>currentVersion</code>.
186         */
187        public boolean isSatisfied(Version currentVersion, Version compatibleVersion) {
188                if (compatibleVersion.compareTo(currentVersion) > 0) {
189                        throw new IllegalArgumentException("Compatible version greater than current version.");
190                }
191
192                return compareTo(currentVersion) <= 0 && compareTo(compatibleVersion) >= 0;
193        }
194
195        /**
196         * String representation: major.minor
197         */
198        @Override
199        public String toString() {
200                String result = major + "." + minor;
201                if (patch > 0) {
202                        result += "." + patch;
203                }
204                return result;
205        }
206
207}