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
272 int idx1 = variable.lastIndexOf(":-");
273 int idx2 = variable.lastIndexOf(":+");
274 int idx = idx1 >= 0 ? idx2 >= 0 ? Math.min(idx1, idx2) : idx1 : idx2;
275 String op = null;
276 if (idx >= 0) {
277 op = variable.substring(idx);
278 variable = variable.substring(0, idx);
279 }
280
281
282 if (!cycleMap.add(variable)) {
283 throw new InterpolatorException("recursive variable reference: " + variable);
284 }
285
286 String substValue = null;
287
288
289 if (configProps != null) {
290 substValue = configProps.get(variable);
291 }
292 if (substValue == null) {
293 if (!variable.isEmpty()) {
294 if (callback != null) {
295 String s1 = callback.apply(variable);
296 String s2 = doSubstVars(
297 s1, variable, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
298 substValue = postprocessor != null ? postprocessor.apply(variable, s2) : s2;
299 }
300 }
301 }
302
303 if (op != null) {
304 if (op.startsWith(":-")) {
305 if (substValue == null || substValue.isEmpty()) {
306 substValue = op.substring(":-".length());
307 }
308 } else if (op.startsWith(":+")) {
309 if (substValue != null && !substValue.isEmpty()) {
310 substValue = op.substring(":+".length());
311 }
312 } else {
313 throw new InterpolatorException("Bad substitution: ${" + org + "}");
314 }
315 }
316
317 if (substValue == null) {
318 if (defaultsToEmptyString) {
319 substValue = "";
320 } else {
321
322
323 substValue = MARKER + "{" + variable + "}";
324 }
325 }
326
327
328
329
330 cycleMap.remove(variable);
331
332
333
334
335 val = val.substring(0, startDelim) + substValue + val.substring(stopDelim + DELIM_STOP.length());
336
337
338
339 val = doSubstVars(val, currentKey, cycleMap, configProps, callback, postprocessor, defaultsToEmptyString);
340
341 cycleMap.remove(currentKey);
342
343
344 return val;
345 }
346
347
348
349
350
351
352
353 @Nullable
354 public static String escape(@Nullable String val) {
355 if (val == null || val.isEmpty()) {
356 return val;
357 }
358 return val.replace("$", MARKER);
359 }
360
361
362
363
364
365
366
367 @Nullable
368 public static String unescape(@Nullable String val) {
369 if (val == null || val.isEmpty()) {
370 return val;
371 }
372 val = val.replace(MARKER, "$");
373 int escape = val.indexOf(ESCAPE_CHAR);
374 while (escape >= 0 && escape < val.length() - 1) {
375 char c = val.charAt(escape + 1);
376 if (c == '{' || c == '}' || c == ESCAPE_CHAR) {
377 val = val.substring(0, escape) + val.substring(escape + 1);
378 }
379 escape = val.indexOf(ESCAPE_CHAR, escape + 1);
380 }
381 return val;
382 }
383 }