Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
Expression |
|
| 2.347826086956522;2.348 | ||||
Expression$Builder |
|
| 2.347826086956522;2.348 |
1 | /* | |
2 | * Copyright 2012, Ryan J. McDonough | |
3 | * | |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package com.damnhandy.uri.template; | |
17 | ||
18 | ||
19 | import com.damnhandy.uri.template.impl.Modifier; | |
20 | import com.damnhandy.uri.template.impl.Operator; | |
21 | import com.damnhandy.uri.template.impl.VarSpec; | |
22 | ||
23 | import java.util.ArrayList; | |
24 | import java.util.List; | |
25 | import java.util.regex.Matcher; | |
26 | import java.util.regex.Pattern; | |
27 | ||
28 | /** | |
29 | * <p> | |
30 | * An Expression represents the text between '{' and '}', including the enclosing | |
31 | * braces, as defined in <a href="ietf.org/html/rfc6570#section-2">Section 2 of RFC6570</a>. | |
32 | * </p> | |
33 | * <pre> | |
34 | * http://www.example.com/foo{?query,number} | |
35 | * \___________/ | |
36 | * ^ | |
37 | * | | |
38 | * | | |
39 | * The expression | |
40 | * </pre> | |
41 | * <p> | |
42 | * This class models this representation and adds helper functions for replacement and reverse matching. | |
43 | * </p> | |
44 | * | |
45 | * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a> | |
46 | * @version $Revision: 1.1 $ | |
47 | * @since 2.0 | |
48 | */ | |
49 | public class Expression extends UriTemplateComponent | |
50 | { | |
51 | ||
52 | /** | |
53 | * The serialVersionUID | |
54 | */ | |
55 | private static final long serialVersionUID = -5305648325957481840L; | |
56 | ||
57 | /** | |
58 | * A regex quoted string that matches the expression for replacement in the | |
59 | * expansion process. For example: | |
60 | * <pre> | |
61 | * \Q{?query,number}\E | |
62 | * </pre> | |
63 | */ | |
64 | private String replacementPattern; | |
65 | ||
66 | /** | |
67 | * That {@link Operator} value that is associated with this Expression | |
68 | */ | |
69 | private Operator op; | |
70 | ||
71 | /** | |
72 | * The character position where this expression occurs in the URI template | |
73 | */ | |
74 | private final int location; | |
75 | /** | |
76 | * The the parsed {@link VarSpec} instances found in the expression. | |
77 | */ | |
78 | private List<VarSpec> varSpecs; | |
79 | ||
80 | /** | |
81 | * The Patter that would be used to reverse match this expression | |
82 | */ | |
83 | private Pattern matchPattern; | |
84 | ||
85 | ||
86 | /** | |
87 | * Creates a new {@link Builder} to create a simple expression according | |
88 | * to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.2">3.2.2</a>. | |
89 | * Calling: | |
90 | * <pre> | |
91 | * String exp = Expression.simple(var("var")).build().toString(); | |
92 | * </pre> | |
93 | * <p> | |
94 | * Will return the following expression: | |
95 | * </p> | |
96 | * <pre> | |
97 | * {var} | |
98 | * </pre> | |
99 | * | |
100 | * @return | |
101 | */ | |
102 | public static Builder simple(VarSpec... varSpec) | |
103 | { | |
104 | 12 | return Builder.create(Operator.NUL, varSpec); |
105 | } | |
106 | ||
107 | ||
108 | /** | |
109 | * Creates a new {@link Builder} to create an expression that will use reserved expansion | |
110 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.3">3.2.3</a>. | |
111 | * Calling: | |
112 | * <pre> | |
113 | * String exp = Expression.reserved().var("var").build().toString(); | |
114 | * </pre> | |
115 | * <p> | |
116 | * Will return the following expression: | |
117 | * </p> | |
118 | * <pre> | |
119 | * {+var} | |
120 | * </pre> | |
121 | * | |
122 | * @return | |
123 | */ | |
124 | public static Builder reserved(VarSpec... varSpec) | |
125 | { | |
126 | 14 | return Builder.create(Operator.RESERVED, varSpec); |
127 | } | |
128 | ||
129 | /** | |
130 | * Creates a new {@link Builder} to create an expression with a fragment operator | |
131 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.4">3.2.4</a>. | |
132 | * Calling: | |
133 | * <pre> | |
134 | * String exp = Expression.fragment().var("var").build().toString(); | |
135 | * </pre> | |
136 | * <p> | |
137 | * Will return the following expression: | |
138 | * </p> | |
139 | * <pre> | |
140 | * {#var} | |
141 | * </pre> | |
142 | * | |
143 | * @return | |
144 | */ | |
145 | public static Builder fragment(VarSpec... varSpec) | |
146 | { | |
147 | 20 | return Builder.create(Operator.FRAGMENT, varSpec); |
148 | } | |
149 | ||
150 | /** | |
151 | * Creates a new {@link Builder} to create an expression using label expansion | |
152 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.5">3.2.5</a>. | |
153 | * Calling: | |
154 | * <pre> | |
155 | * String exp = Expression.label(var("var")).build().toString(); | |
156 | * </pre> | |
157 | * <p> | |
158 | * Will return the following expression: | |
159 | * </p> | |
160 | * <pre> | |
161 | * {.var} | |
162 | * </pre> | |
163 | * | |
164 | * @return | |
165 | */ | |
166 | public static Builder label(VarSpec... varSpec) | |
167 | { | |
168 | 8 | return Builder.create(Operator.NAME_LABEL, varSpec); |
169 | } | |
170 | ||
171 | /** | |
172 | * Creates a new {@link Builder} to create an expression using path expansion | |
173 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.6">3.2.6</a>. | |
174 | * Calling: | |
175 | * <pre> | |
176 | * String exp = Expression.path().var("var").build().toString(); | |
177 | * </pre> | |
178 | * <p> | |
179 | * Will return the following expression: | |
180 | * </p> | |
181 | * <pre> | |
182 | * {/var} | |
183 | * </pre> | |
184 | * | |
185 | * @return | |
186 | */ | |
187 | public static Builder path(VarSpec... varSpec) | |
188 | { | |
189 | 28 | return Builder.create(Operator.PATH, varSpec); |
190 | } | |
191 | ||
192 | /** | |
193 | * Creates a new {@link Builder} to create an expression using path-style parameter | |
194 | * (a.k.a. matrix parameter) expansion according to | |
195 | * section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.7">3.2.7</a>. | |
196 | * Calling: | |
197 | * <pre> | |
198 | * String exp = Expression.matrix().var("var").build().toString(); | |
199 | * </pre> | |
200 | * <p> | |
201 | * Will return the following expression: | |
202 | * </p> | |
203 | * <pre> | |
204 | * {;var} | |
205 | * </pre> | |
206 | * | |
207 | * @return | |
208 | */ | |
209 | public static Builder matrix(VarSpec... varSpec) | |
210 | { | |
211 | 4 | return Builder.create(Operator.MATRIX, varSpec); |
212 | } | |
213 | ||
214 | /** | |
215 | * Creates a new {@link Builder} to create an expression using form-style query string expansion | |
216 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.8">3.2.8</a>. | |
217 | * Calling: | |
218 | * <pre> | |
219 | * String exp = Expression.query().var("var").build().toString(); | |
220 | * </pre> | |
221 | * <p> | |
222 | * Will return the following expression: | |
223 | * </p> | |
224 | * <pre> | |
225 | * {?var} | |
226 | * </pre> | |
227 | * | |
228 | * @return | |
229 | */ | |
230 | public static Builder query(VarSpec... varSpec) | |
231 | { | |
232 | 18 | return Builder.create(Operator.QUERY, varSpec); |
233 | } | |
234 | ||
235 | /** | |
236 | * Creates a new {@link Builder} to create an expression using form-style query continuation expansion | |
237 | * according to section <a href="http://tools.ietf.org/html/rfc6570#section-3.2.9">3.2.9</a>. | |
238 | * Calling: | |
239 | * <pre> | |
240 | * String exp = Expression.continuation().var("var").build().toString(); | |
241 | * </pre> | |
242 | * <p> | |
243 | * Will return the following expression: | |
244 | * </p> | |
245 | * <pre> | |
246 | * {&var} | |
247 | * </pre> | |
248 | * | |
249 | * @return | |
250 | */ | |
251 | public static Builder continuation(VarSpec... varSpec) | |
252 | { | |
253 | 8 | return Builder.create(Operator.CONTINUATION, varSpec); |
254 | } | |
255 | ||
256 | ||
257 | /** | |
258 | * Create a new Expression. | |
259 | * | |
260 | * @param rawExpression | |
261 | * @param startPosition | |
262 | * @throws MalformedUriTemplateException | |
263 | */ | |
264 | public Expression(final String rawExpression, final int startPosition) throws MalformedUriTemplateException | |
265 | { | |
266 | 1052 | super(startPosition); |
267 | 1052 | this.location = startPosition; |
268 | 1052 | this.parseRawExpression(rawExpression); |
269 | 1004 | } |
270 | ||
271 | /** | |
272 | * Create a new Expression | |
273 | * | |
274 | * @param op | |
275 | * @param varSpecs | |
276 | * @throws MalformedUriTemplateException | |
277 | */ | |
278 | public Expression(final Operator op, final List<VarSpec> varSpecs) | |
279 | { | |
280 | 112 | super(0); |
281 | 112 | this.op = op; |
282 | 112 | this.varSpecs = varSpecs; |
283 | 112 | this.replacementPattern = Pattern.quote(toString()); |
284 | 112 | this.location = 0; |
285 | 112 | } |
286 | ||
287 | /** | |
288 | * @param rawExpression | |
289 | * @throws MalformedUriTemplateException | |
290 | */ | |
291 | private void parseRawExpression(String rawExpression) throws MalformedUriTemplateException | |
292 | { | |
293 | 1052 | final String expressionReplacement = Pattern.quote(rawExpression); |
294 | 1052 | String token = rawExpression.substring(1, rawExpression.length() - 1); |
295 | // Check for URI operators | |
296 | 1052 | Operator operator = Operator.NUL; |
297 | 1052 | String firstChar = token.substring(0, 1); |
298 | 1052 | if (UriTemplate.containsOperator(firstChar)) |
299 | { | |
300 | try | |
301 | { | |
302 | 824 | operator = Operator.fromOpCode(firstChar); |
303 | } | |
304 | 4 | catch (IllegalArgumentException e) |
305 | { | |
306 | 4 | throw new MalformedUriTemplateException("Invalid operator", this.location, e); |
307 | 820 | } |
308 | 820 | token = token.substring(1, token.length()); |
309 | } | |
310 | 1048 | String[] varspecStrings = token.split(","); |
311 | 1048 | List<VarSpec> varspecs = new ArrayList<VarSpec>(); |
312 | ||
313 | 2416 | for (String varname : varspecStrings) |
314 | { | |
315 | 1412 | int subStrPos = varname.indexOf(Modifier.PREFIX.getValue()); |
316 | /* | |
317 | * Prefix variable | |
318 | */ | |
319 | 1412 | if (subStrPos > 0) |
320 | { | |
321 | 102 | String[] pair = varname.split(Modifier.PREFIX.getValue()); |
322 | try | |
323 | { | |
324 | 102 | Integer pos = Integer.valueOf(varname.substring(subStrPos + 1)); |
325 | 94 | varspecs.add(new VarSpec(pair[0], Modifier.PREFIX, pos)); |
326 | } | |
327 | 8 | catch (NumberFormatException e) |
328 | { | |
329 | 8 | throw new MalformedUriTemplateException("The prefix value for " + pair[0] + " was not a number", this.location, e); |
330 | 94 | } |
331 | 94 | } |
332 | ||
333 | /* | |
334 | * Variable will be exploded | |
335 | */ | |
336 | 1310 | else if (varname.lastIndexOf(Modifier.EXPLODE.getValue()) > 0) |
337 | { | |
338 | 248 | varspecs.add(new VarSpec(varname, Modifier.EXPLODE)); |
339 | } | |
340 | /* | |
341 | * Standard Value | |
342 | */ | |
343 | else | |
344 | { | |
345 | 1062 | varspecs.add(new VarSpec(varname, Modifier.NONE)); |
346 | } | |
347 | } | |
348 | 1004 | this.replacementPattern = expressionReplacement; |
349 | 1004 | this.op = operator; |
350 | 1004 | this.varSpecs = varspecs; |
351 | 1004 | } |
352 | ||
353 | ||
354 | ||
355 | ||
356 | private Pattern buildMatchingPattern() | |
357 | { | |
358 | 0 | StringBuilder b = new StringBuilder(); |
359 | 0 | for (VarSpec v : getVarSpecs()) |
360 | { | |
361 | 0 | b.append("(?<").append(v.getVariableName()).append(">[^\\/]+)"); |
362 | 0 | } |
363 | 0 | return Pattern.compile(b.toString()); |
364 | } | |
365 | ||
366 | /** | |
367 | * Returns a string that contains a regular expression that matches the | |
368 | * URI template expression. | |
369 | * | |
370 | * @return the match pattern | |
371 | */ | |
372 | @Override | |
373 | public Pattern getMatchPattern() | |
374 | { | |
375 | 0 | if (this.matchPattern == null) |
376 | { | |
377 | 0 | this.matchPattern = buildMatchingPattern(); |
378 | } | |
379 | 0 | return this.matchPattern; |
380 | } | |
381 | ||
382 | /** | |
383 | * Get the replacementToken. | |
384 | * | |
385 | * @return the replacementToken. | |
386 | */ | |
387 | public String getReplacementPattern() | |
388 | { | |
389 | 594 | return replacementPattern; |
390 | } | |
391 | ||
392 | /** | |
393 | * Get the {@link Operator} value for this expression. | |
394 | * | |
395 | * @return the operator value. | |
396 | */ | |
397 | public Operator getOperator() | |
398 | { | |
399 | 1886 | return op; |
400 | } | |
401 | ||
402 | /** | |
403 | * Get the varSpecs. | |
404 | * | |
405 | * @return the varSpecs. | |
406 | */ | |
407 | public List<VarSpec> getVarSpecs() | |
408 | { | |
409 | 660 | return varSpecs; |
410 | } | |
411 | ||
412 | /** | |
413 | * Returns the string representation of the expression. | |
414 | * | |
415 | * @see Object#toString() | |
416 | */ | |
417 | public String toString() | |
418 | { | |
419 | 416 | StringBuilder b = new StringBuilder(); |
420 | 416 | b.append("{").append(this.getOperator().getOperator()); |
421 | 928 | for (int i = 0; i < varSpecs.size(); i++) |
422 | { | |
423 | 512 | VarSpec v = varSpecs.get(i); |
424 | 512 | b.append(v.getValue()); |
425 | // Double check that the value doesn't already contain the modifier | |
426 | 512 | int r = v.getValue().lastIndexOf(v.getModifier().getValue()); |
427 | 512 | if(v.getModifier() != null && v.getValue().lastIndexOf(v.getModifier().getValue()) == -1) |
428 | { | |
429 | 112 | b.append(v.getModifier().getValue()); |
430 | } | |
431 | ||
432 | 512 | if (v.getModifier() == Modifier.PREFIX) |
433 | { | |
434 | 64 | b.append(v.getPosition()); |
435 | } | |
436 | 512 | if (i != (varSpecs.size() - 1)) |
437 | { | |
438 | 96 | b.append(","); |
439 | } | |
440 | } | |
441 | 416 | return b.append("}").toString(); |
442 | } | |
443 | ||
444 | /** | |
445 | * Returns the value of this component | |
446 | * | |
447 | * @return the string value of this component. | |
448 | */ | |
449 | @Override | |
450 | public String getValue() | |
451 | { | |
452 | 280 | return toString(); |
453 | } | |
454 | ||
455 | @Override | |
456 | public int hashCode() | |
457 | { | |
458 | 0 | final int prime = 31; |
459 | 0 | int result = 1; |
460 | 0 | result = prime * result + ((op == null) ? 0 : op.hashCode()); |
461 | 0 | result = prime * result + ((varSpecs == null) ? 0 : varSpecs.hashCode()); |
462 | 0 | return result; |
463 | } | |
464 | ||
465 | @Override | |
466 | public boolean equals(Object obj) | |
467 | { | |
468 | 0 | if (this == obj) |
469 | { | |
470 | 0 | return true; |
471 | } | |
472 | 0 | if (obj == null) |
473 | { | |
474 | 0 | return false; |
475 | } | |
476 | 0 | if (getClass() != obj.getClass()) |
477 | { | |
478 | 0 | return false; |
479 | } | |
480 | 0 | Expression other = (Expression) obj; |
481 | 0 | if (op != other.op) |
482 | { | |
483 | 0 | return false; |
484 | } | |
485 | 0 | if (varSpecs == null) |
486 | { | |
487 | 0 | if (other.varSpecs != null) |
488 | { | |
489 | 0 | return false; |
490 | } | |
491 | } | |
492 | 0 | else if (!varSpecs.equals(other.varSpecs)) |
493 | { | |
494 | 0 | return false; |
495 | } | |
496 | 0 | return true; |
497 | } | |
498 | ||
499 | ||
500 | /** | |
501 | * Builder class for creating an {@link Expression}. | |
502 | * | |
503 | * @author <a href="ryan@damnhandy.com">Ryan J. McDonough</a> | |
504 | * @version $Revision: 1.1 $ | |
505 | */ | |
506 | public static class Builder | |
507 | { | |
508 | /** | |
509 | * | |
510 | */ | |
511 | private Operator operator; | |
512 | ||
513 | /** | |
514 | * | |
515 | */ | |
516 | private List<VarSpec> varSpecs; | |
517 | ||
518 | /** | |
519 | * Create a new ExpressionBuilder. | |
520 | * | |
521 | * @param operator | |
522 | */ | |
523 | private Builder(Operator operator, VarSpec... varSpec) | |
524 | 112 | { |
525 | 112 | this.operator = operator; |
526 | 112 | this.varSpecs = new ArrayList<VarSpec>(); |
527 | 236 | for (VarSpec v : varSpec) |
528 | { | |
529 | 124 | varSpecs.add(v); |
530 | } | |
531 | 112 | } |
532 | ||
533 | /** | |
534 | * @param operator | |
535 | * @return the builder | |
536 | */ | |
537 | static Builder create(Operator operator, VarSpec... varSpec) | |
538 | { | |
539 | 112 | return new Builder(operator, varSpec); |
540 | } | |
541 | ||
542 | ||
543 | public Expression build() | |
544 | { | |
545 | 112 | return new Expression(operator, varSpecs); |
546 | } | |
547 | } | |
548 | ||
549 | } |