1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 package info.magnolia.module.model;
35
36 import java.util.regex.Matcher;
37 import java.util.regex.Pattern;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class VersionRange {
55
56 private final static Pattern boundedRangeSyntax = Pattern.compile("^([(\\[\\]])(.*),(.*)([)\\[\\]])$");
57
58 private final static Pattern notBoundedByBrackets = Pattern.compile("^([^(\\[\\]])(.*)([^)\\[\\]])$", Pattern.DOTALL);
59 private final Version from;
60 private final Version to;
61 private final Boundary leftBoundary;
62 private final Boundary rightBoundary;
63
64 public static VersionRange parse(String input) {
65 return new VersionRange(input);
66 }
67
68
69
70
71 public VersionRange(String input) {
72 final String rangeDef = input == null ? "*" : input;
73 final int slashIdx = rangeDef.indexOf('/');
74 final int commaIdx = rangeDef.indexOf(',');
75 final Matcher boundedRangeMatcher = input != null ? boundedRangeSyntax.matcher(input) : null;
76 final Matcher notBoundedByBracketsMatcher = input != null ? notBoundedByBrackets.matcher(input) : null;
77 if (slashIdx < 0 && commaIdx < 0) {
78 this.from = newVersionOrStar(rangeDef, Version.UNDEFINED_FROM);
79 this.to = newVersionOrStar(rangeDef, Version.UNDEFINED_TO);
80 this.leftBoundary = Boundary.leftInclSq;
81 this.rightBoundary = Boundary.rightInclSq;
82 } else if (slashIdx >= 0 && notBoundedByBracketsMatcher != null && notBoundedByBracketsMatcher.matches()) {
83 this.from = newVersionOrStar(rangeDef.substring(0, slashIdx), Version.UNDEFINED_FROM);
84 this.to = newVersionOrStar(rangeDef.substring(slashIdx + 1), Version.UNDEFINED_TO);
85 this.leftBoundary = Boundary.leftInclSq;
86 this.rightBoundary = Boundary.rightInclSq;
87 } else if (boundedRangeMatcher != null && boundedRangeMatcher.matches()) {
88 this.leftBoundary = Boundary.bySymbol(boundedRangeMatcher.group(1).charAt(0), Hand.left);
89 this.from = newVersionOrStarOrEmpty(boundedRangeMatcher.group(2), Version.UNDEFINED_FROM);
90 this.to = newVersionOrStarOrEmpty(boundedRangeMatcher.group(3), Version.UNDEFINED_TO);
91 this.rightBoundary = Boundary.bySymbol(boundedRangeMatcher.group(4).charAt(0), Hand.right);
92 } else {
93 throw new IllegalArgumentException("Can't parse version range: " + input);
94 }
95 validate(input);
96 }
97
98
99
100
101 public VersionRange(Version from, Version to) {
102 this(from, Boundary.leftInclSq, to, Boundary.rightInclSq);
103 }
104
105
106
107
108 private VersionRange(Version from, Boundary leftBoundary, Version to, Boundary rightBoundary) {
109 this.from = from;
110 this.to = to;
111 this.leftBoundary = leftBoundary;
112 this.rightBoundary = rightBoundary;
113 validate("from:" + from + ",to:" + to + ",leftBoundary:" + leftBoundary + ",rightBoundary:" + rightBoundary);
114 }
115
116 private Version newVersionOrStarOrEmpty(String rangeDef, Version ifUndefined) {
117 if ("".equals(rangeDef.trim())) {
118 return ifUndefined;
119 }
120 return newVersionOrStar(rangeDef, ifUndefined);
121 }
122
123 private Version newVersionOrStar(String rangeDef, Version ifUndefined) {
124 if ("*".equals(rangeDef.trim())) {
125 return ifUndefined;
126 }
127 return Version.parseVersion(rangeDef);
128 }
129
130
131
132
133 private void validate(String inputSpec) {
134 if (from.isStrictlyAfter(to)) {
135 throw new IllegalArgumentException("Invalid range: " + from + " is not after " + to + " (specified as \"" + inputSpec + "\")");
136 }
137 }
138
139
140
141
142 public Version getFrom() {
143 return from;
144 }
145
146
147
148
149 public Version getTo() {
150 return to;
151 }
152
153 public boolean contains(Version other) {
154 if (other.isEquivalent(from) && leftBoundary.include) {
155 return true;
156 }
157 if (other.isEquivalent(to) && rightBoundary.include) {
158 return true;
159 }
160 return other.isStrictlyAfter(from) && to.isStrictlyAfter(other);
161 }
162
163 @Override
164 public String toString() {
165 final StringBuilder sb = new StringBuilder();
166 if (from.equals(Version.UNDEFINED_FROM)) {
167 sb.append(Boundary.leftParen.symbol);
168 } else {
169 sb.append(leftBoundary.symbol);
170 sb.append(from);
171 }
172 sb.append(',');
173 if (to.equals(Version.UNDEFINED_TO)) {
174 sb.append(Boundary.rightParen.symbol);
175 } else {
176 sb.append(to);
177 sb.append(rightBoundary.symbol);
178 }
179 return sb.toString();
180 }
181
182 private static enum Hand {left, right}
183
184 private static enum Boundary {
185 leftExclSq(Hand.left, ']', false), leftInclSq(Hand.left, '[', true), leftParen(Hand.left, '(', false),
186 rightExclSq(Hand.right, '[', false), rightInclSq(Hand.right, ']', true), rightParen(Hand.right, ')', false);
187
188 private final Hand hand;
189 private final char symbol;
190 private final boolean include;
191
192 private Boundary(Hand hand, char symbol, boolean include) {
193 this.hand = hand;
194 this.symbol = symbol;
195 this.include = include;
196 }
197
198 static Boundary bySymbol(char c, Hand h) {
199 for (Boundary boundary : values()) {
200 if (boundary.hand == h && boundary.symbol == c) {
201 return boundary;
202 }
203 }
204 throw new IllegalArgumentException("No " + Boundary.class.getSimpleName() + " found by symbol " + c + " for " + h.name() + " boundary. ");
205 }
206 }
207
208 }