1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.internal.impl.model.profile;
20
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.function.Function;
25
26
27
28
29
30
31
32 public class ConditionParser {
33
34
35
36
37
38 public interface ExpressionFunction {
39
40
41
42
43
44
45 Object apply(List<Object> args);
46 }
47
48 private final Map<String, ExpressionFunction> functions;
49 private final Function<String, String> propertyResolver;
50 private List<String> tokens;
51 private int current;
52
53
54
55
56
57
58
59 public ConditionParser(Map<String, ExpressionFunction> functions, Function<String, String> propertyResolver) {
60 this.functions = functions;
61 this.propertyResolver = propertyResolver;
62 }
63
64
65
66
67
68
69
70 public Object parse(String expression) {
71 this.tokens = tokenize(expression);
72 this.current = 0;
73 return parseExpression();
74 }
75
76
77
78
79
80
81
82
83 private List<String> tokenize(String expression) {
84 List<String> tokens = new ArrayList<>();
85 StringBuilder sb = new StringBuilder();
86 char quoteType = 0;
87 boolean inPropertyReference = false;
88
89 for (int i = 0; i < expression.length(); i++) {
90 char c = expression.charAt(i);
91
92 if (quoteType != 0) {
93 if (c == quoteType) {
94 quoteType = 0;
95 sb.append(c);
96 tokens.add(sb.toString());
97 sb.setLength(0);
98 } else {
99 sb.append(c);
100 }
101 continue;
102 }
103
104 if (inPropertyReference) {
105 if (c == '}') {
106 inPropertyReference = false;
107 tokens.add("${" + sb + "}");
108 sb.setLength(0);
109 } else {
110 sb.append(c);
111 }
112 continue;
113 }
114
115 if (c == '$' && i + 1 < expression.length() && expression.charAt(i + 1) == '{') {
116 if (!sb.isEmpty()) {
117 tokens.add(sb.toString());
118 sb.setLength(0);
119 }
120 inPropertyReference = true;
121 i++;
122 continue;
123 }
124
125 if (c == '"' || c == '\'') {
126 if (!sb.isEmpty()) {
127 tokens.add(sb.toString());
128 sb.setLength(0);
129 }
130 quoteType = c;
131 sb.append(c);
132 } else if (c == ' ' || c == '(' || c == ')' || c == ',' || c == '+' || c == '>' || c == '<' || c == '='
133 || c == '!') {
134 if (!sb.isEmpty()) {
135 tokens.add(sb.toString());
136 sb.setLength(0);
137 }
138 if (c != ' ') {
139 if ((c == '>' || c == '<' || c == '=' || c == '!')
140 && i + 1 < expression.length()
141 && expression.charAt(i + 1) == '=') {
142 tokens.add(c + "=");
143 i++;
144 } else {
145 tokens.add(String.valueOf(c));
146 }
147 }
148 } else {
149 sb.append(c);
150 }
151 }
152
153 if (inPropertyReference) {
154 throw new RuntimeException("Unclosed property reference: ${");
155 }
156
157 if (!sb.isEmpty()) {
158 tokens.add(sb.toString());
159 }
160
161 return tokens;
162 }
163
164
165
166
167
168
169
170 private Object parseExpression() {
171 Object result = parseLogicalOr();
172 if (current < tokens.size()) {
173 throw new RuntimeException("Unexpected tokens after end of expression");
174 }
175 return result;
176 }
177
178
179
180
181
182
183 private Object parseLogicalOr() {
184 Object left = parseLogicalAnd();
185 while (current < tokens.size() && tokens.get(current).equals("||")) {
186 current++;
187 Object right = parseLogicalAnd();
188 left = (boolean) left || (boolean) right;
189 }
190 return left;
191 }
192
193
194
195
196
197
198 private Object parseLogicalAnd() {
199 Object left = parseComparison();
200 while (current < tokens.size() && tokens.get(current).equals("&&")) {
201 current++;
202 Object right = parseComparison();
203 left = (boolean) left && (boolean) right;
204 }
205 return left;
206 }
207
208
209
210
211
212
213 private Object parseComparison() {
214 Object left = parseAddSubtract();
215 while (current < tokens.size()
216 && (tokens.get(current).equals(">")
217 || tokens.get(current).equals("<")
218 || tokens.get(current).equals(">=")
219 || tokens.get(current).equals("<=")
220 || tokens.get(current).equals("==")
221 || tokens.get(current).equals("!="))) {
222 String operator = tokens.get(current);
223 current++;
224 Object right = parseAddSubtract();
225 left = compare(left, operator, right);
226 }
227 return left;
228 }
229
230
231
232
233
234
235 private Object parseAddSubtract() {
236 Object left = parseMultiplyDivide();
237 while (current < tokens.size()
238 && (tokens.get(current).equals("+") || tokens.get(current).equals("-"))) {
239 String operator = tokens.get(current);
240 current++;
241 Object right = parseMultiplyDivide();
242 if (operator.equals("+")) {
243 left = add(left, right);
244 } else {
245 left = subtract(left, right);
246 }
247 }
248 return left;
249 }
250
251
252
253
254
255
256 private Object parseMultiplyDivide() {
257 Object left = parseUnary();
258 while (current < tokens.size()
259 && (tokens.get(current).equals("*") || tokens.get(current).equals("/"))) {
260 String operator = tokens.get(current);
261 current++;
262 Object right = parseUnary();
263 if (operator.equals("*")) {
264 left = multiply(left, right);
265 } else {
266 left = divide(left, right);
267 }
268 }
269 return left;
270 }
271
272
273
274
275
276
277 private Object parseUnary() {
278 if (current < tokens.size() && tokens.get(current).equals("-")) {
279 current++;
280 Object value = parseUnary();
281 return negate(value);
282 }
283 return parseTerm();
284 }
285
286
287
288
289
290
291
292 private Object parseTerm() {
293 if (current >= tokens.size()) {
294 throw new RuntimeException("Unexpected end of expression");
295 }
296
297 String token = tokens.get(current);
298 if (token.equals("(")) {
299 return parseParentheses();
300 } else if (functions.containsKey(token)) {
301 return parseFunction();
302 } else if ((token.startsWith("\"") && token.endsWith("\"")) || (token.startsWith("'") && token.endsWith("'"))) {
303 current++;
304 return token.length() > 1 ? token.substring(1, token.length() - 1) : "";
305 } else if (token.equalsIgnoreCase("true") || token.equalsIgnoreCase("false")) {
306 current++;
307 return Boolean.parseBoolean(token);
308 } else if (token.startsWith("${") && token.endsWith("}")) {
309 current++;
310 String propertyName = token.substring(2, token.length() - 1);
311 return propertyResolver.apply(propertyName);
312 } else {
313 try {
314 current++;
315 return Double.parseDouble(token);
316 } catch (NumberFormatException e) {
317
318 return parseVariableOrUnknownFunction();
319 }
320 }
321 }
322
323
324
325
326
327
328
329 private Object parseVariableOrUnknownFunction() {
330 current--;
331 String name = tokens.get(current);
332 current++;
333
334
335 if (current < tokens.size() && tokens.get(current).equals("(")) {
336
337 List<Object> args = parseArgumentList();
338 if (functions.containsKey(name)) {
339 return functions.get(name).apply(args);
340 } else {
341 throw new RuntimeException("Unknown function: " + name);
342 }
343 } else {
344
345
346
347 throw new RuntimeException("Unknown variable: " + name);
348 }
349 }
350
351
352
353
354
355
356
357 private List<Object> parseArgumentList() {
358 List<Object> args = new ArrayList<>();
359 current++;
360 while (current < tokens.size() && !tokens.get(current).equals(")")) {
361 args.add(parseLogicalOr());
362 if (current < tokens.size() && tokens.get(current).equals(",")) {
363 current++;
364 }
365 }
366 if (current >= tokens.size() || !tokens.get(current).equals(")")) {
367 throw new RuntimeException("Mismatched parentheses: missing closing parenthesis in function call");
368 }
369 current++;
370 return args;
371 }
372
373
374
375
376
377
378 private Object parseFunction() {
379 String functionName = tokens.get(current);
380 current++;
381 List<Object> args = parseArgumentList();
382 return functions.get(functionName).apply(args);
383 }
384
385
386
387
388
389
390
391 private Object parseParentheses() {
392 current++;
393 Object result = parseLogicalOr();
394 if (current >= tokens.size() || !tokens.get(current).equals(")")) {
395 throw new RuntimeException("Mismatched parentheses: missing closing parenthesis");
396 }
397 current++;
398 return result;
399 }
400
401
402
403
404
405
406
407
408
409 private static Object add(Object left, Object right) {
410 if (left instanceof String || right instanceof String) {
411 return toString(left) + toString(right);
412 } else if (left instanceof Number && right instanceof Number) {
413 return ((Number) left).doubleValue() + ((Number) right).doubleValue();
414 } else {
415 throw new RuntimeException("Cannot add " + left + " and " + right);
416 }
417 }
418
419
420
421
422
423
424
425
426 private Object negate(Object value) {
427 if (value instanceof Number) {
428 return -((Number) value).doubleValue();
429 }
430 throw new RuntimeException("Cannot negate non-numeric value: " + value);
431 }
432
433
434
435
436
437
438
439
440
441 private static Object subtract(Object left, Object right) {
442 if (left instanceof Number && right instanceof Number) {
443 return ((Number) left).doubleValue() - ((Number) right).doubleValue();
444 } else {
445 throw new RuntimeException("Cannot subtract " + right + " from " + left);
446 }
447 }
448
449
450
451
452
453
454
455
456
457 private static Object multiply(Object left, Object right) {
458 if (left instanceof Number && right instanceof Number) {
459 return ((Number) left).doubleValue() * ((Number) right).doubleValue();
460 } else {
461 throw new RuntimeException("Cannot multiply " + left + " and " + right);
462 }
463 }
464
465
466
467
468
469
470
471
472
473
474 private static Object divide(Object left, Object right) {
475 if (left instanceof Number && right instanceof Number) {
476 double divisor = ((Number) right).doubleValue();
477 if (divisor == 0) {
478 throw new ArithmeticException("Division by zero");
479 }
480 return ((Number) left).doubleValue() / divisor;
481 } else {
482 throw new RuntimeException("Cannot divide " + left + " by " + right);
483 }
484 }
485
486
487
488
489
490
491
492
493
494
495
496
497 private static Object compare(Object left, String operator, Object right) {
498 if (left == null && right == null) {
499 return true;
500 }
501 if (left == null || right == null) {
502 if ("==".equals(operator)) {
503 return false;
504 } else if ("!=".equals(operator)) {
505 return true;
506 }
507 }
508 if (left instanceof Number && right instanceof Number) {
509 double leftVal = ((Number) left).doubleValue();
510 double rightVal = ((Number) right).doubleValue();
511 return switch (operator) {
512 case ">" -> leftVal > rightVal;
513 case "<" -> leftVal < rightVal;
514 case ">=" -> leftVal >= rightVal;
515 case "<=" -> leftVal <= rightVal;
516 case "==" -> Math.abs(leftVal - rightVal) < 1e-9;
517 case "!=" -> Math.abs(leftVal - rightVal) >= 1e-9;
518 default -> throw new IllegalStateException("Unknown operator: " + operator);
519 };
520 } else if (left instanceof String && right instanceof String) {
521 int comparison = ((String) left).compareTo((String) right);
522 return switch (operator) {
523 case ">" -> comparison > 0;
524 case "<" -> comparison < 0;
525 case ">=" -> comparison >= 0;
526 case "<=" -> comparison <= 0;
527 case "==" -> comparison == 0;
528 case "!=" -> comparison != 0;
529 default -> throw new IllegalStateException("Unknown operator: " + operator);
530 };
531 }
532 throw new RuntimeException("Cannot compare " + left + " and " + right + " with operator " + operator);
533 }
534
535
536
537
538
539
540
541
542
543 public static String toString(Object value) {
544 if (value instanceof Double || value instanceof Float) {
545 double doubleValue = ((Number) value).doubleValue();
546 if (doubleValue == Math.floor(doubleValue) && !Double.isInfinite(doubleValue)) {
547 return String.format("%.0f", doubleValue);
548 }
549 }
550 return String.valueOf(value);
551 }
552
553
554
555
556
557
558
559
560
561
562
563
564 public static Boolean toBoolean(Object value) {
565 if (value instanceof Boolean b) {
566 return b;
567 } else if (value instanceof String s) {
568 return !s.isBlank();
569 } else if (value instanceof Number b) {
570 return b.intValue() != 0;
571 } else {
572 return value != null;
573 }
574 }
575
576
577
578
579
580
581
582
583
584
585
586
587
588 public static double toDouble(Object value) {
589 if (value instanceof Number) {
590 return ((Number) value).doubleValue();
591 } else if (value instanceof String) {
592 try {
593 return Double.parseDouble((String) value);
594 } catch (NumberFormatException e) {
595 throw new RuntimeException("Cannot convert string to number: " + value);
596 }
597 } else if (value instanceof Boolean) {
598 return ((Boolean) value) ? 1.0 : 0.0;
599 } else {
600 throw new RuntimeException("Cannot convert to number: " + value);
601 }
602 }
603
604
605
606
607
608
609
610
611
612
613
614
615
616 public static int toInt(Object value) {
617 if (value instanceof Number) {
618 return ((Number) value).intValue();
619 } else if (value instanceof String) {
620 try {
621 return Integer.parseInt((String) value);
622 } catch (NumberFormatException e) {
623
624 try {
625 return (int) Double.parseDouble((String) value);
626 } catch (NumberFormatException e2) {
627 throw new RuntimeException("Cannot convert string to integer: " + value);
628 }
629 }
630 } else if (value instanceof Boolean) {
631 return ((Boolean) value) ? 1 : 0;
632 } else {
633 throw new RuntimeException("Cannot convert to integer: " + value);
634 }
635 }
636 }