1 package de.matthias_burbach.deputy.core.project;
2
3 import java.util.ArrayList;
4 import java.util.Comparator;
5 import java.util.Iterator;
6 import java.util.List;
7
8 /***
9 * Compares versions of Maven projects.
10 *
11 * @author Matthias Burbach
12 */
13 public class VersionComparator implements Comparator {
14
15
16
17
18 /***
19 * {@inheritDoc}
20 * <p/>
21 * Note: o1 and o2 must both be instances of {@link java.lang.String}.
22 * <p/>
23 * Comparison is such that newer versions are sorted to the tops of lists.
24 */
25 public int compare(final Object o1, final Object o2) {
26 int result = 0;
27
28
29
30 String version1 = (String) o2;
31 String version2 = (String) o1;
32 if (!version1.equals(version2)) {
33
34
35
36 if (version1.trim().equalsIgnoreCase("OPENEDITION")) {
37 return -1;
38 } else if (version2.trim().equalsIgnoreCase("OPENEDITION")) {
39 return 1;
40 }
41
42
43
44 if (version1.trim().equals("SNAPSHOT")) {
45 return 1;
46 } else if (version2.trim().equals("SNAPSHOT")) {
47 return -1;
48 }
49
50
51
52
53
54
55 Iterator version1Parts = getVersionParts(version1).iterator();
56 Iterator version2Parts = getVersionParts(version2).iterator();
57
58
59
60 while (result == 0
61 && version1Parts.hasNext() && version2Parts.hasNext()) {
62 String part1 = (String) version1Parts.next();
63 String part2 = (String) version2Parts.next();
64 result = compareVersionParts(part1, part2);
65 }
66 if (result == 0) {
67
68
69
70
71 if (version1Parts.hasNext()) {
72
73
74
75 String part1 = (String) version1Parts.next();
76 if (additionalPartIsUpgrade(part1)) {
77 result = 1;
78 } else {
79 result = -1;
80 }
81 } else if (version2Parts.hasNext()) {
82
83
84
85 String part2 = (String) version2Parts.next();
86 if (additionalPartIsUpgrade(part2)) {
87 result = -1;
88 } else {
89 result = 1;
90 }
91 }
92 }
93 }
94 return result;
95 }
96
97 /***
98 * If two versions have different numbers of parts one of the versions
99 * has at least one part more than the other. This one is called the
100 * additional part.
101 * <p/>
102 * Normally, the additional part expresses an upgrade of the version
103 * without that part. Exceptionally, there are additional parts like
104 * 'alpha' or 'beta' which actually downgrade a version.
105 *
106 * @param additionalPart The addtional part to check.
107 * @return <code>true</code> if the additional part has an upgrade meaning.
108 */
109 private boolean additionalPartIsUpgrade(final String additionalPart) {
110 boolean result = true;
111 String value = additionalPart.trim().toLowerCase();
112 if (value.equals("alpha")
113 || value.equals("beta")
114 || value.equals("snapshot")) {
115 result = false;
116 }
117 return result;
118 }
119
120 /***
121 * Compares two version parts with one another.
122 *
123 * @param part1 A version part to compare with part2
124 * @param part2 A version part to compare with part1
125 * @return 0 if the parts are equals, -1 if part1 < part2, 1 else.
126 */
127 private int compareVersionParts(final String part1, final String part2) {
128 final int equal = 0;
129 final int part1IsGreater = 1;
130 final int part2IsGreater = -1;
131
132 if (part1.equals(part2)) {
133 return equal;
134 }
135
136 if (part1.equals("SNAPSHOT")) {
137 return part1IsGreater;
138 }
139 if (part2.equals(("SNAPSHOT"))) {
140 return part2IsGreater;
141 }
142
143 Integer part1AsInt = getAsInteger(part1);
144 Integer part2AsInt = getAsInteger(part2);
145 if (part1AsInt != null && part2AsInt != null) {
146
147
148
149
150
151
152 return part1AsInt.compareTo(part2AsInt);
153 }
154
155 if (part1AsInt == null && part2AsInt == null) {
156 return compareNonNumericalVersionParts(part1, part2);
157 } else if (part1AsInt != null) {
158 return compareNumericalWithNonNumericalPart(part1AsInt, part2);
159 } else {
160 return -1 * compareNumericalWithNonNumericalPart(part2AsInt, part1);
161 }
162 }
163
164 /***
165 * Compares two version parts where one of them is numerical and the other
166 * is not.
167 *
168 * @param part1AsInt The numerical part to compare with part2.
169 * @param part2 The non-numerical part to compare with part1.
170 * @return 0 if the parts are equals, -1 if part1AsInt < part2, 1 else.
171 */
172 private int compareNumericalWithNonNumericalPart(
173 final Integer part1AsInt, final String part2) {
174 int result = 1;
175 return result;
176 }
177
178 /***
179 * Compares two version parts wich both are non-numerical.
180 *
181 * @param part1 A part to compare with part2.
182 * @param part2 A part to compare with part1.
183 * @return 0 if the parts are equals, -1 if part1 < part2, 1 else.
184 */
185 private int compareNonNumericalVersionParts(
186 final String part1, final String part2) {
187 int result = 0;
188 result = part1.compareTo(part2);
189 return result;
190 }
191
192 /***
193 * @param value A string to transform into an integer.
194 * @return The integer parsed from value or <code>null</code>.
195 */
196 private Integer getAsInteger(final String value) {
197 Integer result = null;
198 try {
199 result = new Integer(Integer.parseInt(value));
200 } catch (NumberFormatException e) {
201
202 result = null;
203 }
204 return result;
205 }
206
207 /***
208 * Splits a version string into its version parts.
209 * <p/>
210 * Parts are separated by '.', or '-', or '_'.
211 * <p/>
212 * Additionally are part boundary without a separator is assumed
213 * when a digit is followed by a non-digit and vice versa.
214 *
215 * @param version The version to split into its parts.
216 * @return The list of parts of type {@link java.lang.String}.
217 */
218 private List getVersionParts(final String version) {
219 List result = new ArrayList();
220 String currentPart = null;
221 boolean isDigitMode = true;
222 for (int i = 0; i < version.length(); i++) {
223 char c = version.charAt(i);
224 if (currentPart == null) {
225 currentPart = c + "";
226 isDigitMode = Character.isDigit(c);
227 } else if (isVersionPartSeparator(c)) {
228 result.add(currentPart);
229 currentPart = null;
230 } else if (Character.isDigit(c)) {
231 if (isDigitMode) {
232 currentPart += c + "";
233 } else {
234 result.add(currentPart);
235 currentPart = c + "";
236 isDigitMode = true;
237 }
238 } else {
239 if (isDigitMode) {
240 result.add(currentPart);
241 currentPart = c + "";
242 isDigitMode = false;
243 } else {
244 currentPart += c + "";
245 }
246 }
247 }
248 if (currentPart != null) {
249 result.add(currentPart);
250 }
251 return result;
252 }
253
254 /***
255 * @param c The character to test for being a version part separator.
256 * @return <code>true</code> if c is a version part separator.
257 */
258 private boolean isVersionPartSeparator(final char c) {
259 return c == '.' || c == '-' || c == '_' || c == ' ';
260 }
261 }