1 /*
2 * Copyright 2010-2020 Redgate Software Ltd
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package org.flywaydb.core.api;
17
18 import java.math.BigInteger;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.regex.Pattern;
22
23 /**
24 * A version of a migration.
25 *
26 * @author Axel Fontaine
27 */
28 public final class MigrationVersion implements Comparable<MigrationVersion> {
29 /**
30 * Version for an empty schema.
31 */
32 public static final MigrationVersion EMPTY = new MigrationVersion(null, "<< Empty Schema >>");
33
34 /**
35 * Latest version.
36 */
37 public static final MigrationVersion LATEST = new MigrationVersion(BigInteger.valueOf(-1), "<< Latest Version >>");
38
39 /**
40 * Current version. Only a marker. For the real version use Flyway.info().current() instead.
41 */
42 public static final MigrationVersion CURRENT = new MigrationVersion(BigInteger.valueOf(-2), "<< Current Version >>");
43
44 /**
45 * Regex for matching proper version format
46 */
47 private static final Pattern SPLIT_REGEX = Pattern.compile("\\.(?=\\d)");
48
49 /**
50 * The individual parts this version string is composed of. Ex. 1.2.3.4.0 -> [1, 2, 3, 4, 0]
51 */
52 private final List<BigInteger> versionParts;
53
54 /**
55 * The printable text to represent the version.
56 */
57 private final String displayText;
58
59 /**
60 * Create a MigrationVersion from a version String.
61 *
62 * @param version The version String. The value {@code current} will be interpreted as MigrationVersion.CURRENT,
63 * a marker for the latest version that has been applied to the database.
64 * @return The MigrationVersion
65 */
66 @SuppressWarnings("ConstantConditions")
67 public static MigrationVersion fromVersion(String version) {
68 if ("current".equalsIgnoreCase(version)) return CURRENT;
69 if ("latest".equalsIgnoreCase(version) || LATEST.getVersion().equals(version)) return LATEST;
70 if (version == null) return EMPTY;
71 return new MigrationVersion(version);
72 }
73
74 /**
75 * Creates a Version using this version string.
76 *
77 * @param version The version in one of the following formats: 6, 6.0, 005, 1.2.3.4, 201004200021. <br/>{@code null}
78 * means that this version refers to an empty schema.
79 */
80 private MigrationVersion(String version) {
81 String normalizedVersion = version.replace('_', '.');
82 this.versionParts = tokenize(normalizedVersion);
83 this.displayText = normalizedVersion;
84 }
85
86 /**
87 * Creates a Version using this version string.
88 *
89 * @param version The version in one of the following formats: 6, 6.0, 005, 1.2.3.4, 201004200021. <br/>{@code null}
90 * means that this version refers to an empty schema.
91 * @param displayText The alternative text to display instead of the version number.
92 */
93 private MigrationVersion(BigInteger version, String displayText) {
94 this.versionParts = new ArrayList<>();
95 this.versionParts.add(version);
96 this.displayText = displayText;
97 }
98
99 /**
100 * @return The textual representation of the version.
101 */
102 @Override
103 public String toString() {
104 return displayText;
105 }
106
107 /**
108 * @return Numeric version as String
109 */
110 public String getVersion() {
111 if (this.equals(EMPTY)) return null;
112 if (this.equals(LATEST)) return Long.toString(Long.MAX_VALUE);
113 return displayText;
114 }
115
116 @Override
117 public boolean equals(Object o) {
118 if (this == o) return true;
119 if (o == null || getClass() != o.getClass()) return false;
120
121 MigrationVersion version1 = (MigrationVersion) o;
122
123 return compareTo(version1) == 0;
124 }
125
126 @Override
127 public int hashCode() {
128 return versionParts == null ? 0 : versionParts.hashCode();
129 }
130
131 /**
132 * Convenience method for quickly checking whether this version is at least as new as this other version.
133 *
134 * @param otherVersion The other version.
135 * @return {@code true} if this version is equal or newer, {@code false} if it is older.
136 */
137 public boolean isAtLeast(String otherVersion) {
138 return compareTo(MigrationVersion.fromVersion(otherVersion)) >= 0;
139 }
140
141 /**
142 * Convenience method for quickly checking whether this version is newer than this other version.
143 *
144 * @param otherVersion The other version.
145 * @return {@code true} if this version is newer, {@code false} if it is not.
146 */
147 public boolean isNewerThan(String otherVersion) {
148 return compareTo(MigrationVersion.fromVersion(otherVersion)) > 0;
149 }
150
151 /**
152 * Convenience method for quickly checking whether this major version is newer than this other major version.
153 *
154 * @param otherVersion The other version.
155 * @return {@code true} if this major version is newer, {@code false} if it is not.
156 */
157 public boolean isMajorNewerThan(String otherVersion) {
158 return getMajor().compareTo(MigrationVersion.fromVersion(otherVersion).getMajor()) > 0;
159 }
160
161 /**
162 * @return The major version.
163 */
164 public BigInteger getMajor() {
165 return versionParts.get(0);
166 }
167
168 /**
169 * @return The major version as a string.
170 */
171 public String getMajorAsString() {
172 return versionParts.get(0).toString();
173 }
174
175 /**
176 * @return The minor version as a string.
177 */
178 public String getMinorAsString() {
179 if (versionParts.size() == 1) {
180 return "0";
181 }
182 return versionParts.get(1).toString();
183 }
184
185 @Override
186 public int compareTo(MigrationVersion o) {
187 if (o == null) {
188 return 1;
189 }
190
191 if (this == EMPTY) {
192 if (o == EMPTY) return 0;
193 else return -1;
194 }
195
196 if (this == CURRENT) {
197 return o == CURRENT ? 0 : -1;
198 }
199
200 if (this == LATEST) {
201 if (o == LATEST) return 0;
202 else return 1;
203 }
204
205 if (o == EMPTY) {
206 return 1;
207 }
208
209 if (o == CURRENT) {
210 return 1;
211 }
212
213 if (o == LATEST) {
214 return -1;
215 }
216 final List<BigInteger> parts1 = versionParts;
217 final List<BigInteger> parts2 = o.versionParts;
218 int largestNumberOfParts = Math.max(parts1.size(), parts2.size());
219 for (int i = 0; i < largestNumberOfParts; i++) {
220 final int compared = getOrZero(parts1, i).compareTo(getOrZero(parts2, i));
221 if (compared != 0) {
222 return compared;
223 }
224 }
225 return 0;
226 }
227
228 private BigInteger getOrZero(List<BigInteger> elements, int i) {
229 return i < elements.size() ? elements.get(i) : BigInteger.ZERO;
230 }
231
232 /**
233 * Splits this string into list of Long
234 *
235 * @param versionStr The string to split.
236 * @return The resulting array.
237 */
238 private List<BigInteger> tokenize(String versionStr) {
239 List<BigInteger> parts = new ArrayList<>();
240 for (String part : SPLIT_REGEX.split(versionStr)) {
241 parts.add(toBigInteger(versionStr, part));
242 }
243
244 for (int i = parts.size() - 1; i > 0; i--) {
245 if (!parts.get(i).equals(BigInteger.ZERO)) {
246 break;
247 }
248 parts.remove(i);
249 }
250
251 return parts;
252 }
253
254 private BigInteger toBigInteger(String versionStr, String part) {
255 try {
256 return new BigInteger(part);
257 } catch (NumberFormatException e) {
258 throw new FlywayException("Version may only contain 0..9 and . (dot). Invalid version: " + versionStr);
259 }
260 }
261 }