1use std::fmt;
7use std::convert::From;
8use std::cmp::Ordering;
9use std::hash;
10
11use ::ArtifactVersion;
12
13#[derive(Debug)]
14pub struct Maven2ArtifactVersion<'a> {
15 major_version: Option<u32>,
16 minor_version: Option<u32>,
17 incremental_version: Option<u32>,
18 build_number: Option<u32>,
19 qualifier: Option<&'a str>,
20 unparsed: &'a str,
21}
22
23impl<'a> Maven2ArtifactVersion<'a> {
24
25 pub fn new(maven_version: &'a str) -> Self {
27 parse_version(maven_version)
28 }
29
30 pub fn major_version(&self) -> u32 {
31 self.major_version.unwrap_or(0)
32 }
33
34 pub fn minor_version(&self) -> u32 {
35 self.minor_version.unwrap_or(0)
36 }
37
38 pub fn incremental_version(&self) -> u32 {
39 self.incremental_version.unwrap_or(0)
40 }
41
42 pub fn build_number(&self) -> u32 {
43 self.build_number.unwrap_or(0)
44 }
45
46 pub fn qualifier(&self) -> Option<&'a str> {
47 self.qualifier
48 }
49}
50
51impl<'a> ArtifactVersion for Maven2ArtifactVersion<'a> {
52
53 fn version(&self) -> &str {
54 self.unparsed
55 }
56}
57
58impl<'a> From<&'a str> for Maven2ArtifactVersion<'a> {
61
62 fn from(maven_version: &'a str) -> Maven2ArtifactVersion<'a> {
63 Maven2ArtifactVersion::new(maven_version)
64 }
65}
66
67impl<'a> fmt::Display for Maven2ArtifactVersion<'a> {
70
71 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72 write!(f, "{}", self.unparsed)
73 }
74}
75
76#[allow(unused_assignments)]
77fn parse_version(maven_version: &str) -> Maven2ArtifactVersion {
78 let mut major_version: Option<u32> = None;
79 let mut minor_version: Option<u32> = None;
80 let mut incremental_version: Option<u32> = None;
81 let mut build_number: Option<u32> = None;
82 let mut qualifier: Option<&str> = None;
83
84 let mut part1: Option<&str> = None;
86 let mut part2: Option<&str> = None;
87
88 let splitted: Vec<&str> = maven_version.splitn(2, '-').collect();
90 if splitted.len() == 2 {
91 part1 = Some(splitted[0]);
92 part2 = Some(splitted[1]);
93 } else {
94 part1 = Some(maven_version);
95 }
96
97 if let Some(q) = part2 {
99 if q.chars().count() == 1 || !q.starts_with('0') {
100 if let Ok(bn) = q.parse::<u32>() {
101 build_number = Some(bn);
102 } else {
103 qualifier = part2;
104 }
105 } else {
106 qualifier = part2;
107 }
108 }
109
110 if let Some(part1_str) = part1 {
112 if !part1_str.contains('.') && !part1_str.starts_with('0') {
113 if let Ok(mv) = part1_str.parse::<u32>() {
114 major_version = Some(mv);
115 } else {
116 qualifier = Some(maven_version);
117 build_number = None;
118 }
119
120 } else {
121 let mut fallback = false;
122 let mut token_iter = part1_str.split('.');
123
124 if let Some(value) = token_iter.next() {
126 if let Ok(i) = parse_integer_token(value) {
127 major_version = Some(i);
128 } else {
129 fallback = true;
130 }
131 } else {
132 fallback = true;
133 }
134
135 if let Some(value) = token_iter.next() {
137 if let Ok(i) = parse_integer_token(value) {
138 minor_version = Some(i);
139 } else {
140 fallback = true;
141 }
142 }
143
144 if let Some(value) = token_iter.next() {
146 if let Ok(i) = parse_integer_token(value) {
147 incremental_version = Some(i);
148 } else {
149 fallback = true;
150 }
151 }
152
153 if token_iter.next().is_some() {
155 fallback = true;
156 }
157
158 if part1_str.contains("..") || part1_str.starts_with('.') || part1_str.ends_with('.') {
159 fallback = true;
160 }
161
162 if fallback {
163 qualifier = Some(maven_version);
165 major_version = None;
166 minor_version = None;
167 incremental_version = None;
168 build_number = None;
169 }
170 }
171 }
172
173 Maven2ArtifactVersion {
174 major_version: major_version,
175 minor_version: minor_version,
176 incremental_version: incremental_version,
177 build_number: build_number,
178 qualifier: qualifier,
179 unparsed: maven_version,
180 }
181}
182
183fn parse_integer_token(token: &str) -> Result<u32, String> {
184 if token.chars().count() > 1 && token.starts_with('0') {
185 Err(format!("Number part has a leading 0: '{}'", token))
186 } else {
187 token.parse::<u32>().map_err(|_| "Number is invalid".to_string())
188 }
189}
190
191impl<'a> PartialEq for Maven2ArtifactVersion<'a> {
194
195 fn eq(&self, other: &Maven2ArtifactVersion<'a>) -> bool {
196 self.partial_cmp(other) == Some(Ordering::Equal)
197 }
198}
199
200impl<'a> Eq for Maven2ArtifactVersion<'a> {}
203
204impl<'a> PartialOrd for Maven2ArtifactVersion<'a> {
207
208 fn partial_cmp(&self, other: &Maven2ArtifactVersion<'a>) -> Option<Ordering> {
209 Some(self.cmp(other))
210 }
211}
212
213impl<'a> Ord for Maven2ArtifactVersion<'a> {
216
217 fn cmp(&self, other: &Maven2ArtifactVersion<'a>) -> Ordering {
218 let mut result: Ordering = self.major_version().cmp(&other.major_version());
219
220 if result == Ordering::Equal {
221 result = self.minor_version().cmp(&other.minor_version());
222 }
223
224 if result == Ordering::Equal {
225 result = self.incremental_version().cmp(&other.incremental_version());
226 }
227
228 if result == Ordering::Equal {
229 if let Some(qualifier) = self.qualifier() {
230 if let Some(other_qualifier) = other.qualifier() {
231 let qualifier_count = qualifier.chars().count();
232 let other_qualifier_count = other_qualifier.chars().count();
233
234 if qualifier_count > other_qualifier_count && qualifier.starts_with(other_qualifier) {
235 result = Ordering::Less;
237 } else if qualifier_count < other_qualifier_count && other_qualifier.starts_with(qualifier){
238 result = Ordering::Greater;
240 } else {
241 result = qualifier.cmp(other_qualifier);
242 }
243
244 } else {
245 result = Ordering::Less;
247 }
248 } else if other.qualifier().is_some() {
249 result = Ordering::Greater;
251 } else {
252 result = self.build_number().cmp(&other.build_number());
253 }
254 }
255
256 result
257 }
258}
259
260impl<'a> hash::Hash for Maven2ArtifactVersion<'a> {
263
264 fn hash<H: hash::Hasher>(&self, state: &mut H) {
265 self.major_version().hash(state);
266 self.minor_version().hash(state);
267 self.incremental_version().hash(state);
268 self.build_number().hash(state);
269
270 if let Some(qualifier) = self.qualifier() {
271 qualifier.hash(state);
272 }
273 }
274}
275
276#[cfg(test)]
277mod tests {
278
279 use super::*;
282
283 #[test]
284 fn test_version_parsing() {
285 check_version_parsing( "1", 1, 0, 0, 0, None );
286 check_version_parsing( "1.2", 1, 2, 0, 0, None );
287 check_version_parsing( "1.2.3", 1, 2, 3, 0, None );
288 check_version_parsing( "1.2.3-1", 1, 2, 3, 1, None );
289 check_version_parsing( "1.2.3-alpha-1", 1, 2, 3, 0, Some("alpha-1") );
290 check_version_parsing( "1.2-alpha-1", 1, 2, 0, 0, Some("alpha-1") );
291 check_version_parsing( "1.2-alpha-1-20050205.060708-1", 1, 2, 0, 0, Some("alpha-1-20050205.060708-1") );
292 check_version_parsing( "RELEASE", 0, 0, 0, 0, Some("RELEASE") );
293 check_version_parsing( "2.0-1", 2, 0, 0, 1, None );
294
295 check_version_parsing( "02", 0, 0, 0, 0, Some("02") );
297 check_version_parsing( "0.09", 0, 0, 0, 0, Some("0.09") );
298 check_version_parsing( "0.2.09", 0, 0, 0, 0, Some("0.2.09") );
299 check_version_parsing( "2.0-01", 2, 0, 0, 0, Some("01") );
300
301 check_version_parsing( "1.0.1b", 0, 0, 0, 0, Some("1.0.1b") );
303 check_version_parsing( "1.0M2", 0, 0, 0, 0, Some("1.0M2") );
304 check_version_parsing( "1.0RC2", 0, 0, 0, 0, Some("1.0RC2") );
305 check_version_parsing( "1.7.3.0", 0, 0, 0, 0, Some("1.7.3.0") );
306 check_version_parsing( "1.7.3.0-1", 0, 0, 0, 0, Some("1.7.3.0-1") );
307 check_version_parsing( "PATCH-1193602", 0, 0, 0, 0, Some("PATCH-1193602") );
308 check_version_parsing( "5.0.0alpha-2006020117", 0, 0, 0, 0, Some("5.0.0alpha-2006020117") );
309 check_version_parsing( "1.0.0.-SNAPSHOT", 0, 0, 0, 0, Some("1.0.0.-SNAPSHOT") );
310 check_version_parsing( "1..0-SNAPSHOT", 0, 0, 0, 0, Some("1..0-SNAPSHOT") );
311 check_version_parsing( "1.0.-SNAPSHOT", 0, 0, 0, 0, Some("1.0.-SNAPSHOT") );
312 check_version_parsing( ".1.0-SNAPSHOT", 0, 0, 0, 0, Some(".1.0-SNAPSHOT") );
313
314 check_version_parsing( "1.2.3.200705301630", 0, 0, 0, 0, Some("1.2.3.200705301630") );
315 check_version_parsing( "1.2.3-200705301630", 1, 2, 3, 0, Some("200705301630") );
316 }
317
318 fn check_version_parsing(maven_version: &str, major: u32, minor: u32, incremental: u32, build_number: u32, qualifier: Option<&str>) {
319 let actual = Maven2ArtifactVersion::from(maven_version);
320 let parsed = format!("'{}' parsed as ('{:?}', '{:?}', '{:?}', '{:?}', '{:?}'), ",
321 maven_version, actual.major_version, actual.minor_version,
322 actual.incremental_version, actual.build_number,
323 actual.qualifier);
324
325 assert_eq!(major, actual.major_version(), "{} check major version", parsed );
326 assert_eq!(minor, actual.minor_version(), "{} check minor version", parsed );
327 assert_eq!(incremental, actual.incremental_version(), "{} check incremental version", parsed );
328 assert_eq!(build_number, actual.build_number(), "{} check build number version", parsed );
329 assert_eq!(qualifier, actual.qualifier(), "{} check qualifier version", parsed );
330 }
331
332 #[test]
333 fn test_version_comparing() {
334 assert_version_equal( "1", "1" );
335 assert_version_older( "1", "2" );
336 assert_version_older( "1.5", "2" );
337 assert_version_older( "1", "2.5" );
338 assert_version_equal( "1", "1.0" );
339 assert_version_equal( "1", "1.0.0" );
340 assert_version_older( "1.0", "1.1" );
341 assert_version_older( "1.1", "1.2" );
342 assert_version_older( "1.0.0", "1.1" );
343 assert_version_older( "1.1", "1.2.0" );
344 assert_version_older( "1.2", "1.10" );
345
346 assert_version_older( "1.0-alpha-1", "1.0" );
347 assert_version_older( "1.0-alpha-1", "1.0-alpha-2" );
348 assert_version_older( "1.0-alpha-1", "1.0-beta-1" );
349
350 assert_version_older( "1.0-SNAPSHOT", "1.0-beta-1" );
351 assert_version_older( "1.0-SNAPSHOT", "1.0" );
352 assert_version_older( "1.0-alpha-1-SNAPSHOT", "1.0-alpha-1" );
353
354 assert_version_older( "1.0", "1.0-1" );
355 assert_version_older( "1.0-1", "1.0-2" );
356 assert_version_equal( "2.0-0", "2.0" );
357 assert_version_older( "2.0", "2.0-1" );
358 assert_version_older( "2.0.0", "2.0-1" );
359 assert_version_older( "2.0-1", "2.0.1" );
360
361 assert_version_older( "2.0.1-klm", "2.0.1-lmn" );
362 assert_version_older( "2.0.1-xyz", "2.0.1" );
363
364 assert_version_older( "2.0.1", "2.0.1-123" );
365 assert_version_older( "2.0.1-xyz", "2.0.1-123" );
366 }
367
368 #[test]
369 fn test_version_snapshot_comparing() {
370 assert_version_equal( "1-SNAPSHOT", "1-SNAPSHOT" );
371 assert_version_older( "1-SNAPSHOT", "2-SNAPSHOT" );
372 assert_version_older( "1.5-SNAPSHOT", "2-SNAPSHOT" );
373 assert_version_older( "1-SNAPSHOT", "2.5-SNAPSHOT" );
374 assert_version_equal( "1-SNAPSHOT", "1.0-SNAPSHOT" );
375 assert_version_equal( "1-SNAPSHOT", "1.0.0-SNAPSHOT" );
376 assert_version_older( "1.0-SNAPSHOT", "1.1-SNAPSHOT" );
377 assert_version_older( "1.1-SNAPSHOT", "1.2-SNAPSHOT" );
378 assert_version_older( "1.0.0-SNAPSHOT", "1.1-SNAPSHOT" );
379 assert_version_older( "1.1-SNAPSHOT", "1.2.0-SNAPSHOT" );
380 assert_version_older( "1.0-alpha-1-SNAPSHOT", "1.0-alpha-2-SNAPSHOT" );
381 assert_version_older( "1.0-alpha-1-SNAPSHOT", "1.0-beta-1-SNAPSHOT" );
382 assert_version_older( "1.0-SNAPSHOT-SNAPSHOT", "1.0-beta-1-SNAPSHOT" );
383 assert_version_older( "1.0-SNAPSHOT-SNAPSHOT", "1.0-SNAPSHOT" );
384 assert_version_older( "1.0-alpha-1-SNAPSHOT-SNAPSHOT", "1.0-alpha-1-SNAPSHOT" );
385 assert_version_older( "2.0-1-SNAPSHOT", "2.0.1-SNAPSHOT" );
386 assert_version_older( "2.0.1-klm-SNAPSHOT", "2.0.1-lmn-SNAPSHOT" );
387 }
388
389 #[test]
390 fn test_snapshot_releases() {
391 assert_version_older( "1.0-RC1", "1.0-SNAPSHOT" );
392 }
393
394 fn assert_version_older(left: &str, right: &str) {
395 assert!(new_artifact_version( left ).cmp( &new_artifact_version( right ) ) == Ordering::Less, "{} should be older than {}", left, right);
396 assert!(new_artifact_version( right ).cmp( &new_artifact_version( left ) ) == Ordering::Greater, "{} should be newer than {}", right, left);
397 }
398
399 fn assert_version_equal(left: &str, right: &str) {
400 assert!(new_artifact_version( left ).cmp( &new_artifact_version( right ) ) == Ordering::Equal, "{} should be equal to {}", left, right);
401 assert!(new_artifact_version( right ).cmp( &new_artifact_version( left ) ) == Ordering::Equal, "{} should be equal to {}", right, left);
402 }
403
404 fn new_artifact_version<'a>(version: &'a str) -> Maven2ArtifactVersion<'a> {
405 Maven2ArtifactVersion::from(version)
406 }
407}