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