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 String org = variable;
267
268 String substValue = processSubstitution(
269 variable, org, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
270
271
272
273
274 val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length());
275
276
277
278 val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
279
280 cycleMap.remove(currentKey);
281
282
283 return val;
284 }
285
286 private static String processSubstitution(
287 String variable,
288 String org,
289 Set<String> cycleMap,
290 Map<String, String> configProps,
291 UnaryOperator<String> callback,
292 BinaryOperator<String> postprocessor,
293 boolean defaultsToEmptyString) {
294
295
296 int startIdx = 0;
297 String currentVar = variable;
298 String substValue = null;
299
300 while (startIdx < variable.length()) {
301 int idx1 = variable.indexOf(":-", startIdx);
302 int idx2 = variable.indexOf(":+", startIdx);
303 int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2;
304
305 if (idx < 0) {
306
307 if (substValue == null) {
308 currentVar = variable.substring(startIdx);
309 substValue = resolveVariable(
310 currentVar, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
311 }
312 break;
313 }
314
315
316 String varPart = variable.substring(startIdx, idx);
317 if (substValue == null) {
318 substValue =
319 resolveVariable(varPart, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
320 }
321
322
323 int nextIdx1 = variable.indexOf(":-", idx + 2);
324 int nextIdx2 = variable.indexOf(":+", idx + 2);
325 int nextIdx = nextIdx1 >= 0 ? nextIdx2 >= 0 ? Math.min(nextIdx1, nextIdx2) : nextIdx1 : nextIdx2;
326
327 String op = variable.substring(idx, idx + 2);
328 String opValue = variable.substring(idx + 2, nextIdx >= 0 ? nextIdx : variable.length());
329
330
331 String processedOpValue =
332 doSubstVars(opValue, org, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
333
334
335 if (":+".equals(op)) {
336 if (substValue != null && !substValue.isEmpty()) {
337 substValue = processedOpValue;
338 }
339 } else if (":-".equals(op)) {
340 if (substValue == null || substValue.isEmpty()) {
341 substValue = processedOpValue;
342 }
343 } else {
344 throw new InterpolatorException("Bad substitution operator in: ${" + org + "}");
345 }
346
347 startIdx = nextIdx >= 0 ? nextIdx : variable.length();
348 }
349
350 if (substValue == null) {
351 if (defaultsToEmptyString) {
352 substValue = "";
353 } else {
354 substValue = MARKER + "{" + variable + "}";
355 }
356 }
357
358 return substValue;
359 }
360
361 private static String resolveVariable(
362 String variable,
363 Set<String> cycleMap,
364 Map<String, String> configProps,
365 UnaryOperator<String> callback,
366 BinaryOperator<String> postprocessor,
367 boolean defaultsToEmptyString) {
368
369
370 if (!cycleMap.add(variable)) {
371 throw new InterpolatorException("recursive variable reference: " + variable);
372 }
373
374 String substValue = null;
375
376 if (configProps != null) {
377 substValue = configProps.get(variable);
378 }
379 if (substValue == null && !variable.isEmpty() && callback != null) {
380 String s1 = callback.apply(variable);
381 String s2 =
382 doSubstVars(s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
383 substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2;
384 }
385
386
387 cycleMap.remove(variable);
388 return substValue;
389 }
390
391
392
393
394
395
396
397 @Nullable
398 public static String escape(@Nullable String val) {
399 if (val == null || val.isEmpty()) {
400 return val;
401 }
402 return val.replace("$", MARKER);
403 }
404
405
406
407
408
409
410
411 @Nullable
412 public static String unescape(@Nullable String val) {
413 if (val == null || val.isEmpty()) {
414 return val;
415 }
416 val = val.replace(MARKER, "$");
417 int escape = val.indexOf(ESCAPE_CHAR);
418 while (escape >= 0 && escape < val.length() - 1) {
419 char c = val.charAt(escape + 1);
420 if (c == '{' || c == '}') {
421 val = val.substring(0, escape) + val.substring(escape + 1);
422 }
423 escape = val.indexOf(ESCAPE_CHAR, escape + 1);
424 }
425 return val;
426 }
427 }