1 package com.atlassian.plugin.util;
2
3 import org.apache.commons.lang3.StringUtils;
4
5 import java.math.BigInteger;
6 import java.util.Comparator;
7 import java.util.regex.Matcher;
8 import java.util.regex.Pattern;
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class VersionStringComparator implements Comparator<String> {
27 public static final String DELIMITER_PATTERN = "[\\.-]";
28 public static final String COMPONENT_PATTERN = "[\\d\\w]+";
29 public static final String VALID_VERSION_PATTERN = COMPONENT_PATTERN + "(?:" + DELIMITER_PATTERN + COMPONENT_PATTERN + ")*";
30 private static final Pattern START_WITH_INT_PATTERN = Pattern.compile("(^\\d+)");
31 public static final Pattern SNAPSHOT_PATTERN = Pattern.compile(".*-SNAPSHOT$");
32
33 public static boolean isValidVersionString(final String version) {
34 return (version != null) && version.matches(VALID_VERSION_PATTERN);
35 }
36
37 public static boolean isSnapshotVersion(final String version) {
38 return (version != null) && SNAPSHOT_PATTERN.matcher(version).matches();
39 }
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 public int compare(final String version1, final String version2) {
66
67 String thisVersion = "0";
68 if (StringUtils.isNotEmpty(version1)) {
69 thisVersion = version1.replaceAll(" ", "");
70 }
71 String compareVersion = "0";
72 if (StringUtils.isNotEmpty(version2)) {
73 compareVersion = version2.replaceAll(" ", "");
74 }
75
76 if (!thisVersion.matches(VALID_VERSION_PATTERN) || !compareVersion.matches(VALID_VERSION_PATTERN)) {
77 throw new IllegalArgumentException("Version number '" + thisVersion + "' cannot be compared to '" + compareVersion + "'");
78 }
79
80
81 final String[] v1 = thisVersion.split(DELIMITER_PATTERN);
82 final String[] v2 = compareVersion.split(DELIMITER_PATTERN);
83
84 final Comparator<String> componentComparator = new VersionStringComponentComparator();
85
86
87 for (int i = 0; i < (v1.length > v2.length ? v1.length : v2.length); i++) {
88 final String component1 = i >= v1.length ? "0" : v1[i];
89 final String component2 = i >= v2.length ? "0" : v2[i];
90
91 if (componentComparator.compare(component1, component2) != 0) {
92 return componentComparator.compare(component1, component2);
93 }
94 }
95
96 return 0;
97 }
98
99 private class VersionStringComponentComparator implements Comparator<String> {
100 public static final int FIRST_GREATER = 1;
101 public static final int SECOND_GREATER = -1;
102
103 public int compare(final String component1, final String component2) {
104 if (component1.equalsIgnoreCase(component2)) {
105 return 0;
106 }
107
108 if (isInteger(component1) && isInteger(component2)) {
109 return new BigInteger(component1).compareTo(new BigInteger(component2));
110 }
111
112
113 final BigInteger comp1BigIntPart = getStartingInteger(component1);
114 final BigInteger comp2BigIntPart = getStartingInteger(component2);
115 if (comp1BigIntPart != null && comp2BigIntPart != null) {
116 if (comp1BigIntPart.compareTo(comp2BigIntPart) > 0) {
117 return FIRST_GREATER;
118 }
119
120 if (comp2BigIntPart.compareTo(comp1BigIntPart) > 0) {
121 return SECOND_GREATER;
122 }
123 }
124
125
126
127
128
129 if (isInteger(component1)) {
130 return FIRST_GREATER;
131 }
132 if (isInteger(component2)) {
133 return SECOND_GREATER;
134 }
135
136
137 return component1.compareToIgnoreCase(component2);
138 }
139
140 private boolean isInteger(final String string) {
141 return string.matches("\\d+");
142 }
143
144 private BigInteger getStartingInteger(final String string) {
145 Matcher matcher = START_WITH_INT_PATTERN.matcher(string);
146 if (matcher.find()) {
147
148 return new BigInteger(matcher.group(1));
149 }
150 return null;
151 }
152
153 }
154 }