1use std::convert::{AsRef, From};
10use std::fmt;
11use std::hash;
12use std::cmp::Ordering;
13use std::cmp;
14
15use ::ArtifactVersion;
16
17const RELEASE_VERSION_INDEX: &'static str = "5"; #[derive(Debug)]
24pub struct Maven3ArtifactVersion<'a> {
25 version: &'a str,
26 canonical: String,
27 items: Vec<Item>,
28}
29
30impl<'a> Maven3ArtifactVersion<'a> {
32
33 pub fn new(version: &'a str) -> Self {
45 let items = parse_items(version);
46 let canonical = make_canonical(&items);
47 Maven3ArtifactVersion{version, canonical, items}
48 }
49
50 pub fn canonical(&self) -> &str {
51 &self.canonical
52 }
53}
54
55impl<'a> ArtifactVersion for Maven3ArtifactVersion<'a> {
56
57 fn version(&self) -> &str {
58 self.version
59 }
60}
61
62impl<'a> From<&'a str> for Maven3ArtifactVersion<'a> {
65
66 fn from(version: &'a str) -> Maven3ArtifactVersion<'a> {
67 Maven3ArtifactVersion::new(version)
68 }
69}
70
71impl<'a> Eq for Maven3ArtifactVersion<'a> {}
74
75impl<'a> PartialEq for Maven3ArtifactVersion<'a> {
76
77 fn eq(&self, other: &Maven3ArtifactVersion) -> bool {
78 self.canonical == other.canonical
79 }
80}
81
82impl<'a> Ord for Maven3ArtifactVersion<'a> {
85
86 fn cmp(&self, other: &Maven3ArtifactVersion) -> Ordering {
87 compare_all_items(&self.items, &other.items)
88 }
89}
90
91fn compare_all_items(first: &[Item], second: &[Item]) -> Ordering {
92 use self::Item::*;
93
94 let first_length = first.len();
95 let second_length = second.len();
96
97 for index in 0..cmp::max(first_length, second_length) {
98 let left: Option<&Item> = first.get(index);
99 let right: Option<&Item> = second.get(index);
100
101 let result = match left {
102 Some(&Integer(i)) => compare_integer_with(i, right),
103 Some(&Str(ref s)) => compare_str_with(s, right),
104 Some(&Minus) => {
105 match right {
106 Some(&Integer(_)) => Ordering::Less, Some(&Str(_)) => Ordering::Greater, Some(&Minus) => compare_all_items(&first[(index + 1)..], &second[(index + 1)..]),
109 None => compare_all_items(&first[(index + 1)..], &second[index..]),
110 }
111 },
112 None => {
113 let result_ordering = match right {
114 Some(_) => compare_all_items(&second[index..], &first[index..]),
115 None => Ordering::Equal,
116 };
117
118 match result_ordering {
119 Ordering::Greater => Ordering::Less,
120 Ordering::Less => Ordering::Greater,
121 _ => Ordering::Equal,
122 }
123 },
124 };
125
126 if result != Ordering::Equal {
127 return result;
128 }
129 }
130
131 Ordering::Equal
132}
133
134fn compare_integer_with(value: u32, item: Option<&Item>) -> Ordering {
135 use self::Item::*;
136
137 match item {
138 Some(&Integer(ref i)) => value.cmp(i),
139 Some(&Str(_)) | Some(&Minus) => Ordering::Greater, None => {
141 if value == 0 {
143 Ordering::Equal
144 } else {
145 Ordering::Greater
146 }
147 },
148 }
149}
150
151fn compare_str_with(value: &str, item: Option<&Item>) -> Ordering {
152 use self::Item::*;
153
154 match item {
155 Some(&Integer(_)) | Some(&Minus) => Ordering::Less, Some(&Str(ref s)) => comparable_str_qualifier(value).cmp(&comparable_str_qualifier(s)),
157 None => {
158 let cmp_qualifier: &str = &comparable_str_qualifier(value);
160 cmp_qualifier.cmp(RELEASE_VERSION_INDEX)
161 },
162 }
163}
164
165impl<'a> PartialOrd for Maven3ArtifactVersion<'a> {
166
167 fn partial_cmp(&self, other: &Maven3ArtifactVersion) -> Option<Ordering> {
168 Some(self.cmp(other))
169 }
170}
171
172impl<'a> hash::Hash for Maven3ArtifactVersion<'a> {
175
176 fn hash<H: hash::Hasher>(&self, state: &mut H) {
177 self.canonical.hash(state);
178 }
179}
180
181impl<'a> fmt::Display for Maven3ArtifactVersion<'a> {
184
185 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
186 write!(f, "{}", self.version)
187 }
188}
189
190fn make_canonical(items: &[Item]) -> String {
191 let mut buffer = String::new();
192
193 let mut prev_val = false;
194 for item in items {
195 match *item {
196 Item::Integer(i) => {
197 if prev_val {
198 buffer.push('.');
199 }
200 prev_val = true;
201 buffer.push_str(&format!("{}", i));
202 },
203 Item::Str(ref s) => {
204 if prev_val {
205 buffer.push('.');
206 }
207 prev_val = true;
208 buffer.push_str(s);
209 },
210 Item::Minus => {
211 prev_val = false;
212 buffer.push('-');
213 },
214 };
215 }
216
217 buffer
218}
219
220#[derive(Debug)]
225enum Item {
226 Integer(u32),
227 Str(String),
228 Minus, }
230
231impl Item {
232
233 fn is_minus(&self) -> bool {
234 match *self {
235 Item::Minus => true,
236 _ => false,
237 }
238 }
239}
240
241impl fmt::Display for Item {
242
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 match *self {
245 Item::Integer(i) => write!(f, "{}", i),
246 Item::Str(ref s) => write!(f, "{}", s),
247 Item::Minus => write!(f, "-"),
248 }
249 }
250}
251
252fn comparable_str_qualifier(qualifier: &str) -> String {
253 match qualifier {
254 "alpha" => "0".to_owned(),
255 "beta" => "1".to_owned(),
256 "milestone" => "2".to_owned(),
257 "rc" => "3".to_owned(),
258 "snapshot" => "4".to_owned(),
259 "" => "5".to_owned(),
260 "sp" => "6".to_owned(),
261 _ => format!("7-{}", qualifier), }
263}
264
265fn parse_items(version: &str) -> Vec<Item> {
266 let version = version.to_lowercase();
267 let mut items: Vec<Item> = Vec::new();
268
269 let mut is_digit = false;
270 let mut start_index = 0;
271
272 let chars: Vec<char> = version.chars().collect();
273 for (index, c) in chars.iter().enumerate() {
274 if *c == '.' {
275 if index == start_index {
276 items.push(Item::Integer(0));
277 } else {
278 let substring: String = chars[start_index..index].into_iter().collect();
279 items.push(parse_item(is_digit, &substring));
280 }
281 start_index = index + 1;
282
283 } else if *c == '-' {
284 if index == start_index {
285 items.push(Item::Integer(0));
286 } else {
287 let substring: String = chars[start_index..index].into_iter().collect();
288 items.push(parse_item(is_digit, &substring));
289 }
290
291 start_index = index + 1;
292 items.push(Item::Minus);
293
294 } else if c.is_digit(10) {
295 if !is_digit && index > start_index {
296 let substring: String = chars[start_index..index].into_iter().collect();
297 items.push(to_string_item(substring, true));
298
299 start_index = index;
300 items.push(Item::Minus);
301 }
302 is_digit = true;
303
304 } else {
305 if is_digit && index > start_index {
306 let substring: String = chars[start_index..index].into_iter().collect();
307 items.push(parse_item(true, substring));
308
309 start_index = index;
310 items.push(Item::Minus);
311 }
312 is_digit = false;
313 }
314 }
315
316 if chars.len() > start_index {
317 let substring: String = chars[start_index..].into_iter().collect();
318 items.push(parse_item(is_digit, substring));
319 }
320
321 normalize(&mut items);
322
323 items
324}
325
326fn parse_item<T: AsRef<str>>(is_digit: bool, buf: T) -> Item {
327 if is_digit {
328 to_integer_item(buf.as_ref())
329 } else {
330 to_string_item(buf, false)
331 }
332}
333
334fn to_integer_item(value: &str) -> Item {
335 Item::Integer(value.parse::<u32>().unwrap())
336}
337
338fn to_string_item<T: AsRef<str>>(value: T, followed_by_digit: bool) -> Item {
339 let mut value: &str = value.as_ref();
340 if followed_by_digit && value.chars().count() == 1 {
341 value = match value.chars().nth(0) {
342 Some('a') => "alpha",
343 Some('b') => "beta",
344 Some('m') => "milestone",
345 _ => value,
346 }
347 }
348 value = match value {
349 "ga" | "final" => "",
350 "cr" => "rc",
351 _ => value,
352 };
353 Item::Str(value.to_string())
354}
355
356fn normalize(items: &mut Vec<Item>) {
358 let mut start_index = items.len() - 1;
359
360 for index in (0..items.len()).rev() {
361 if items[index].is_minus() {
362 normalize_sublist(items, (index + 1), start_index + 1);
363 start_index = index;
364 }
365 }
366
367 normalize_sublist(items, 0, start_index + 1);
368}
369
370fn normalize_sublist(items: &mut Vec<Item>, start_index: usize, end_index: usize) {
371 for index in (start_index..end_index).rev() {
372 let is_null = match items[index] {
374 Item::Minus => (items.len() - index) <= 1, Item::Integer(i) => i == 0,
376 Item::Str(ref s) => comparable_str_qualifier(s) == RELEASE_VERSION_INDEX,
377 };
378
379 if is_null {
380 items.remove(index);
382 } else if !items[index].is_minus() {
383 break;
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
390
391 use super::*;
394 use std::hash::{Hash, Hasher};
395 use std::collections::hash_map::DefaultHasher;
396
397 const VERSIONS_QUALIFIER: [&'static str; 22] = ["1-alpha2snapshot", "1-alpha2", "1-alpha-123", "1-beta-2", "1-beta123", "1-m2", "1-m11", "1-rc", "1-cr2",
398 "1-rc123", "1-SNAPSHOT", "1", "1-sp", "1-sp2", "1-sp123", "1-abc", "1-def", "1-pom-1", "1-1-snapshot", "1-1", "1-2", "1-123" ];
399
400 const VERSIONS_NUMBER: [&'static str; 25] = ["2.0", "2-1", "2.0.a", "2.0.0.a", "2.0.2", "2.0.123", "2.1.0", "2.1-a", "2.1b", "2.1-c", "2.1-1", "2.1.0.1",
401 "2.2", "2.123", "11.a2", "11.a11", "11.b2", "11.b11", "11.m2", "11.m11", "11", "11.a", "11b", "11c", "11m"];
402
403 fn new_artifact_version(version: &str) -> Maven3ArtifactVersion {
404 let artifact_version = Maven3ArtifactVersion::new(version);
405
406 {
407 let canonical = artifact_version.canonical();
408
409 let parsed_artifact_version = Maven3ArtifactVersion::new(canonical);
410 let parsed_canonical = parsed_artifact_version.canonical();
411
412 assert_eq!(canonical, parsed_canonical, "canonical( {} ) = {} -> canonical: {}", version, canonical, parsed_canonical);
414 }
415
416 artifact_version
417 }
418
419 fn calc_hash<T: Hash>(t: &T) -> u64 {
420 let mut s = DefaultHasher::new();
421 t.hash(&mut s);
422 s.finish()
423 }
424
425 fn check_versions_equal(v1: &str, v2: &str) {
426 let c1 = new_artifact_version(v1);
427 let c2 = new_artifact_version(v2);
428
429 assert!(c1.cmp(&c2) == Ordering::Equal, "expected {} == {}", v1, v2);
430 assert!(c2.cmp(&c1) == Ordering::Equal, "expected {} == {}", v2, v1);
431 assert!(calc_hash(&c1) == calc_hash(&c2), "expected same hashcode for {} and {}", v1, v2);
432 assert!(c1 == c2, "expected {} == {}", v1, v2);
433 assert!(c2 == c1, "expected {} == {}", v2, v1);
434 }
435
436 fn check_versions_order(v1: &str, v2: &str) {
437 let c1 = new_artifact_version(v1);
438 let c2 = new_artifact_version(v2);
439 assert!(c1 < c2, "expected {} < {}", v1, v2);
440 assert!(c2 > c1, "expected {} > {}", v2, v1);
441 }
442
443 fn check_all_versions_order(versions: &[&'static str]) {
444 let c: Vec<Maven3ArtifactVersion> = versions.iter().map(|version| Maven3ArtifactVersion::new(version)).collect();
445 for i in 1..versions.len() {
446 let low = &c[i - 1];
447
448 for j in i..versions.len() {
449 let high = &c[j];
450 assert!(low < high, "expected {} < {}", low, high);
451 assert!(high > low, "expected {} > {}", high, low);
452 }
453 }
454 }
455
456 #[test]
457 fn test_versions_qualifier() {
458 check_all_versions_order(&VERSIONS_QUALIFIER);
459 }
460
461 #[test]
462 fn test_versions_number() {
463 check_all_versions_order(&VERSIONS_NUMBER);
464 }
465
466 #[test]
467 fn test_versions_equal() {
468 new_artifact_version( "1.0-alpha" );
469 check_versions_equal( "1", "1" );
470 check_versions_equal( "1", "1.0" );
471 check_versions_equal( "1", "1.0.0" );
472 check_versions_equal( "1.0", "1.0.0" );
473 check_versions_equal( "1", "1-0" );
474 check_versions_equal( "1", "1.0-0" );
475 check_versions_equal( "1.0", "1.0-0" );
476 check_versions_equal( "1a", "1-a" );
478 check_versions_equal( "1a", "1.0-a" );
479 check_versions_equal( "1a", "1.0.0-a" );
480 check_versions_equal( "1.0a", "1-a" );
481 check_versions_equal( "1.0.0a", "1-a" );
482 check_versions_equal( "1x", "1-x" );
483 check_versions_equal( "1x", "1.0-x" );
484 check_versions_equal( "1x", "1.0.0-x" );
485 check_versions_equal( "1.0x", "1-x" );
486 check_versions_equal( "1.0.0x", "1-x" );
487
488 check_versions_equal( "1ga", "1" );
490 check_versions_equal( "1final", "1" );
491 check_versions_equal( "1cr", "1rc" );
492
493 check_versions_equal( "1a1", "1-alpha-1" );
495 check_versions_equal( "1b2", "1-beta-2" );
496 check_versions_equal( "1m3", "1-milestone-3" );
497
498 check_versions_equal( "1X", "1x" );
500 check_versions_equal( "1A", "1a" );
501 check_versions_equal( "1B", "1b" );
502 check_versions_equal( "1M", "1m" );
503 check_versions_equal( "1Ga", "1" );
504 check_versions_equal( "1GA", "1" );
505 check_versions_equal( "1Final", "1" );
506 check_versions_equal( "1FinaL", "1" );
507 check_versions_equal( "1FINAL", "1" );
508 check_versions_equal( "1Cr", "1Rc" );
509 check_versions_equal( "1cR", "1rC" );
510 check_versions_equal( "1m3", "1Milestone3" );
511 check_versions_equal( "1m3", "1MileStone3" );
512 check_versions_equal( "1m3", "1MILESTONE3" );
513 }
514
515 #[test]
516 fn test_version_comparing() {
517 check_versions_order( "1", "2" );
518 check_versions_order( "1.5", "2" );
519 check_versions_order( "1", "2.5" );
520 check_versions_order( "1.0", "1.1" );
521 check_versions_order( "1.1", "1.2" );
522 check_versions_order( "1.0.0", "1.1" );
523 check_versions_order( "1.0.1", "1.1" );
524 check_versions_order( "1.1", "1.2.0" );
525
526 check_versions_order( "1.0-alpha-1", "1.0" );
527 check_versions_order( "1.0-alpha-1", "1.0-alpha-2" );
528 check_versions_order( "1.0-alpha-1", "1.0-beta-1" );
529
530 check_versions_order( "1.0-beta-1", "1.0-SNAPSHOT" );
531 check_versions_order( "1.0-SNAPSHOT", "1.0" );
532 check_versions_order( "1.0-alpha-1-SNAPSHOT", "1.0-alpha-1" );
533
534 check_versions_order( "1.0", "1.0-1" );
535 check_versions_order( "1.0-1", "1.0-2" );
536 check_versions_order( "1.0.0", "1.0-1" );
537
538 check_versions_order( "2.0-1", "2.0.1" );
539 check_versions_order( "2.0.1-klm", "2.0.1-lmn" );
540 check_versions_order( "2.0.1", "2.0.1-xyz" );
541
542 check_versions_order( "2.0.1", "2.0.1-123" );
543 check_versions_order( "2.0.1-xyz", "2.0.1-123" );
544 }
545
546 #[test]
547 fn test_mng5568() {
548 let a = "6.1.0";
549 let b = "6.1.0rc3";
550 let c = "6.1H.5-beta"; check_versions_order( b, a ); check_versions_order( b, c ); check_versions_order( a, c );
555 }
556}