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 <= currentVersion && requiredVersion >= 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}