1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.impl.model;
20
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.function.BinaryOperator;
26 import java.util.function.UnaryOperator;
27
28 import org.apache.maven.api.annotations.Nullable;
29 import org.apache.maven.api.di.Named;
30 import org.apache.maven.api.di.Singleton;
31 import org.apache.maven.api.services.Interpolator;
32 import org.apache.maven.api.services.InterpolatorException;
33
34 @Named
35 @Singleton
36 public class DefaultInterpolator implements Interpolator {
37
38 private static final char ESCAPE_CHAR = '\\';
39 private static final String DELIM_START = "${";
40 private static final String DELIM_STOP = "}";
41 private static final String MARKER = "$__";
42
43 @Override
44 public void interpolate(
45 Map<String, String> map,
46 UnaryOperator<String> callback,
47 BinaryOperator<String> postprocessor,
48 boolean defaultsToEmpty) {
49 Map<String, String> org = new HashMap<>(map);
50 for (String name : map.keySet()) {
51 map.compute(
52 name,
53 (k, value) -> interpolate(
54 value,
55 name,
56 null,
57 v -> {
58 String r = org.get(v);
59 if (r == null && callback != null) {
60 r = callback.apply(v);
61 }
62 return r;
63 },
64 postprocessor,
65 defaultsToEmpty));
66 }
67 }
68
69 @Override
70 public String interpolate(
71 String val, UnaryOperator<String> callback, BinaryOperator<String> postprocessor, boolean defaultsToEmpty) {
72 return interpolate(val, null, null, callback, postprocessor, defaultsToEmpty);
73 }
74
75 @Nullable
76 public String interpolate(
77 @Nullable String val,
78 @Nullable String currentKey,
79 @Nullable Set<String> cycleMap,
80 @Nullable UnaryOperator<String> callback,
81 @Nullable BinaryOperator<String> postprocessor,
82 boolean defaultsToEmpty) {
83 return substVars(val, currentKey, cycleMap, null, callback, postprocessor, defaultsToEmpty);
84 }
85
86
87
88
89
90
91
92 public void performSubstitution(Map<String, String> properties, UnaryOperator<String> callback) {
93 performSubstitution(properties, callback, true);
94 }
95
96
97
98
99
100
101
102
103 public void performSubstitution(
104 Map<String, String> properties, UnaryOperator<String> callback, boolean defaultsToEmptyString) {
105 Map<String, String> org = new HashMap<>(properties);
106 for (String name : properties.keySet()) {
107 properties.compute(
108 name, (k, value) -> substVars(value, name, null, org, callback, null, defaultsToEmptyString));
109 }
110 }
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134 public String substVars(String val, String currentKey, Set<String> cycleMap, Map<String, String> configProps) {
135 return substVars(val, currentKey, cycleMap, configProps, null);
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161 public String substVars(
162 String val,
163 String currentKey,
164 Set<String> cycleMap,
165 Map<String, String> configProps,
166 UnaryOperator<String> callback) {
167 return substVars(val, currentKey, cycleMap, configProps, callback, null, false);
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194 public static String substVars(
195 String val,
196 String currentKey,
197 Set<String> cycleMap,
198 Map<String, String> configProps,
199 UnaryOperator<String> callback,
200 BinaryOperator<String> postprocessor,
201 boolean defaultsToEmptyString) {
202 return unescape(
203 doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString));
204 }
205
206 private static String doSubstVars(
207 String val,
208 String currentKey,
209 Set<String> cycleMap,
210 Map<String, String> configProps,
211 UnaryOperator<String> callback,
212 BinaryOperator<String> postprocessor,
213 boolean defaultsToEmptyString) {
214 if (val == null || val.isEmpty()) {
215 return val;
216 }
217 if (cycleMap == null) {
218 cycleMap = new HashSet<>();
219 }
220
221
222 if (currentKey != null) {
223 cycleMap.add(currentKey);
224 }
225
226
227
228
229
230
231
232 int startDelim;
233 int stopDelim = -1;
234 do {
235 stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
236 while (stopDelim > 0 && val.charAt(stopDelim - 1) == ESCAPE_CHAR) {
237 stopDelim = val.indexOf(DELIM_STOP, stopDelim + 1);
238 }
239
240
241
242
243 startDelim = val.indexOf(DELIM_START);
244 while (stopDelim >= 0) {
245 int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
246 if ((idx < 0) || (idx > stopDelim)) {
247 break;
248 } else if (idx < stopDelim) {
249 startDelim = idx;
250 }
251 }
252 } while (startDelim >= 0 && stopDelim >= 0 && stopDelim < startDelim + DELIM_START.length());
253
254
255
256 if ((startDelim < 0) || (stopDelim < 0)) {
257 cycleMap.remove(currentKey);
258 return val;
259 }
260
261
262
263
264
265 String variable = val.substring(startDelim + DELIM_START.length(), stopDelim);
266
267 String substValue =
268 processSubstitution(variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
269
270
271
272
273 val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length());
274
275
276
277 val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
278
279 cycleMap.remove(currentKey);
280
281
282 return val;
283 }
284
285 private static String processSubstitution(
286 String variable,
287 Set<String> cycleMap,
288 Map<String, String> configProps,
289 UnaryOperator<String> callback,
290 BinaryOperator<String> postprocessor,
291 boolean defaultsToEmptyString) {
292
293
294 int startIdx = 0;
295 String substValue = null;
296
297 while (startIdx < variable.length()) {
298 int idx1 = variable.indexOf(":-", startIdx);
299 int idx2 = variable.indexOf(":+", startIdx);
300 int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2;
301
302 if (idx < 0) {
303
304 if (substValue == null) {
305 String currentVar = variable.substring(startIdx);
306 substValue = resolveVariable(
307 currentVar, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
308 }
309 break;
310 }
311
312
313 String varPart = variable.substring(startIdx, idx);
314 if (substValue == null) {
315 substValue =
316 resolveVariable(varPart, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
317 }
318
319
320 int nextIdx1 = variable.indexOf(":-", idx + 2);
321 int nextIdx2 = variable.indexOf(":+", idx + 2);
322 int nextIdx = nextIdx1 >= 0 ? nextIdx2 >= 0 ? Math.min(nextIdx1, nextIdx2) : nextIdx1 : nextIdx2;
323
324 String op = variable.substring(idx, idx + 2);
325 String opValue = variable.substring(idx + 2, nextIdx >= 0 ? nextIdx : variable.length());
326
327
328 String processedOpValue = doSubstVars(
329 opValue, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
330
331
332 if (":+".equals(op)) {
333 if (substValue != null && !substValue.isEmpty()) {
334 substValue = processedOpValue;
335
336 break;
337 }
338 } else if (":-".equals(op)) {
339 if (substValue == null || substValue.isEmpty()) {
340 substValue = processedOpValue;
341
342 break;
343 }
344 } else {
345 throw new InterpolatorException("Bad substitution operator in: ${" + variable + "}");
346 }
347
348 startIdx = nextIdx >= 0 ? nextIdx : variable.length();
349 }
350
351 if (substValue == null) {
352 if (defaultsToEmptyString) {
353 substValue = "";
354 } else {
355 substValue = MARKER + "{" + variable + "}";
356 }
357 }
358
359 return substValue;
360 }
361
362 private static String resolveVariable(
363 String variable,
364 Set<String> cycleMap,
365 Map<String, String> configProps,
366 UnaryOperator<String> callback,
367 BinaryOperator<String> postprocessor,
368 boolean defaultsToEmptyString) {
369
370
371 if (!cycleMap.add(variable)) {
372 throw new InterpolatorException("recursive variable reference: " + variable);
373 }
374
375 String substValue = null;
376
377 if (configProps != null) {
378 substValue = configProps.get(variable);
379 }
380 if (substValue == null && !variable.isEmpty() && callback != null) {
381 String s1 = callback.apply(variable);
382 String s2 =
383 doSubstVars(s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
384 substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2;
385 }
386
387
388 cycleMap.remove(variable);
389 return substValue;
390 }
391
392
393
394
395
396
397
398 @Nullable
399 public static String escape(@Nullable String val) {
400 if (val == null || val.isEmpty()) {
401 return val;
402 }
403 return val.replace("$", MARKER);
404 }
405
406
407
408
409
410
411
412 @Nullable
413 public static String unescape(@Nullable String val) {
414 if (val == null || val.isEmpty()) {
415 return val;
416 }
417 val = val.replace(MARKER, "$");
418 int escape = val.indexOf(ESCAPE_CHAR);
419 while (escape >= 0 && escape < val.length() - 1) {
420 char c = val.charAt(escape + 1);
421 if (c == '{' || c == '}') {
422 val = val.substring(0, escape) + val.substring(escape + 1);
423 }
424 escape = val.indexOf(ESCAPE_CHAR, escape + 1);
425 }
426 return val;
427 }
428 }