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 == nullreturn 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 trueif this version is equal or newer, {@code falseif 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 trueif this version is newer, {@code falseif 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 trueif this major version is newer, {@code falseif 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 }