1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.juneau.svl;
18
19 import static org.apache.juneau.commons.lang.StateEnum.*;
20 import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
21 import static org.apache.juneau.commons.utils.CollectionUtils.*;
22 import static org.apache.juneau.commons.utils.StringUtils.*;
23 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
24 import static org.apache.juneau.commons.utils.Utils.*;
25
26 import java.io.*;
27 import java.lang.reflect.*;
28 import java.util.*;
29
30 import org.apache.juneau.commons.collections.*;
31 import org.apache.juneau.commons.lang.*;
32 import org.apache.juneau.cp.*;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55 @SuppressWarnings("resource")
56 public class VarResolverSession {
57
58 private static final AsciiSet AS1 = AsciiSet.of("\\{"), AS2 = AsciiSet.of("\\${}");
59
60 private static boolean containsVars(Collection<?> c) {
61 var f = Flag.create();
62 c.forEach(x -> {
63 if (x instanceof CharSequence && x.toString().contains("$"))
64 f.set();
65 });
66 return f.isSet();
67 }
68
69 private static boolean containsVars(Map<?,?> m) {
70 var f = Flag.create();
71 m.forEach((k, v) -> {
72 if (v instanceof CharSequence && v.toString().contains("$"))
73 f.set();
74 });
75 return f.isSet();
76 }
77
78 private static boolean containsVars(Object array) {
79 for (var i = 0; i < Array.getLength(array); i++) {
80 var o = Array.get(array, i);
81 if (o instanceof CharSequence && o.toString().contains("$"))
82 return true;
83 }
84 return false;
85 }
86
87
88
89
90
91 private static boolean isSimpleVar(String s) {
92
93
94
95
96
97 int length = s.length();
98 var state = S1;
99 for (var i = 0; i < length; i++) {
100 var c = s.charAt(i);
101 if (state == S1) {
102 if (c == '$') {
103 state = S2;
104 } else {
105 return false;
106 }
107 } else if (state == S2) {
108 if (c == '{') {
109 state = S3;
110 } else if (c < 'A' || c > 'z' || (c > 'Z' && c < 'a')) {
111 return false;
112 }
113 } else if (state == S3) {
114 if (c == '}')
115 state = S4;
116 else if (c == '{' || c == '$')
117 return false;
118 } else if (state == S4) {
119 return false;
120 }
121 }
122 return state == S4;
123 }
124
125 private final VarResolver context;
126
127 private final BeanStore beanStore;
128
129
130
131
132
133
134
135
136
137
138 public VarResolverSession(VarResolver context, BeanStore beanStore) {
139 this.context = context;
140 this.beanStore = BeanStore.of(beanStore);
141 }
142
143
144
145
146
147
148
149
150
151 public <T> VarResolverSession bean(Class<T> c, T value) {
152 beanStore.addBean(c, value);
153 return this;
154 }
155
156
157
158
159
160
161
162
163
164
165 public <T> Optional<T> getBean(Class<T> c) {
166 Optional<T> t = beanStore.getBean(c);
167 if (! t.isPresent())
168 t = context.beanStore.getBean(c);
169 return t;
170 }
171
172
173
174
175
176
177
178
179
180
181 public String resolve(String s) {
182
183 if (s == null || s.isEmpty() || (s.indexOf('$') == -1 && s.indexOf('\\') == -1))
184 return s;
185
186
187
188 if (isSimpleVar(s)) {
189 String var = s.substring(1, s.indexOf('{'));
190 String val = s.substring(s.indexOf('{') + 1, s.length() - 1);
191 Var v = getVar(var);
192 if (nn(v)) {
193 try {
194 if (v.streamed) {
195 var sw = new StringWriter();
196 v.resolveTo(this, sw, val);
197 return sw.toString();
198 }
199 s = v.doResolve(this, val);
200 if (s == null)
201 s = "";
202 return (v.allowRecurse() ? resolve(s) : s);
203 } catch (VarResolverException e) {
204 throw e;
205 } catch (Exception e) {
206 throw new VarResolverException(e, "Problem occurred resolving variable ''{0}'' in string ''{1}''", var, s);
207 }
208 }
209 return s;
210 }
211
212 try {
213 return resolveTo(s, new StringWriter()).toString();
214 } catch (IOException e) {
215 throw toRex(e);
216 }
217 }
218
219
220
221
222
223
224
225 public String[] resolve(String[] in) {
226 var out = new String[in.length];
227 for (var i = 0; i < in.length; i++)
228 out[i] = resolve(in[i]);
229 return out;
230 }
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251 @SuppressWarnings({ "rawtypes", "unchecked" })
252 public <T> T resolve(T o) {
253 if (o == null)
254 return null;
255 if (o instanceof CharSequence o2)
256 return (T)resolve(o2.toString());
257 if (isArray(o)) {
258 if (! containsVars(o))
259 return o;
260 var o2 = Array.newInstance(o.getClass().getComponentType(), Array.getLength(o));
261 for (var i = 0; i < Array.getLength(o); i++)
262 Array.set(o2, i, resolve(Array.get(o, i)));
263 return (T)o2;
264 }
265 if (o instanceof Set o2) {
266 try {
267 if (! containsVars(o2))
268 return o;
269 Set o3 = info(o).getDeclaredConstructor(x -> x.isPublic() && x.getParameterCount() == 0).map(ci -> safe(() -> (Set)ci.inner().newInstance())).orElseGet(LinkedHashSet::new);
270 Set o4 = o3;
271 o2.forEach(x -> o4.add(resolve(x)));
272 return (T)o3;
273 } catch (VarResolverException e) {
274 throw e;
275 } catch (Exception e) {
276 throw new VarResolverException(e, "Problem occurred resolving set.");
277 }
278 }
279 if (o instanceof List o2) {
280 try {
281 if (! containsVars(o2))
282 return o;
283 List o3 = info(o).getDeclaredConstructor(x -> x.isPublic() && x.getParameterCount() == 0).map(ci -> safe(() -> (List)ci.inner().newInstance())).orElseGet(() -> list());
284 List o4 = o3;
285 o2.forEach(x -> o4.add(resolve(x)));
286 return (T)o3;
287 } catch (VarResolverException e) {
288 throw e;
289 } catch (Exception e) {
290 throw new VarResolverException(e, "Problem occurred resolving collection.");
291 }
292 }
293 if (o instanceof Map o2) {
294 try {
295 if (! containsVars(o2))
296 return o;
297 Map o3 = info(o).getDeclaredConstructor(x -> x.isPublic() && x.getParameterCount() == 0).map(ci -> safe(() -> (Map)ci.inner().newInstance())).orElseGet(LinkedHashMap::new);
298 Map o4 = o3;
299 o2.forEach((k, v) -> o4.put(k, resolve(v)));
300 return (T)o3;
301 } catch (VarResolverException e) {
302 throw e;
303 } catch (Exception e) {
304 throw new VarResolverException(e, "Problem occurred resolving map.");
305 }
306 }
307 return o;
308 }
309
310
311
312
313
314
315
316
317
318
319
320
321
322 public Writer resolveTo(String s, Writer out) throws IOException {
323
324
325
326
327
328 var state = S1;
329 var isInEscape = false;
330 var hasInternalVar = false;
331 var hasInnerEscapes = false;
332 var varType = (String)null;
333 var varVal = (String)null;
334 var x = 0;
335 var x2 = 0;
336 var depth = 0;
337 var length = s.length();
338 for (var i = 0; i < length; i++) {
339 var c = s.charAt(i);
340 if (state == S1) {
341 if (isInEscape) {
342 if (c == '\\' || c == '$') {
343 out.append(c);
344 } else {
345 out.append('\\').append(c);
346 }
347 isInEscape = false;
348 } else if (c == '\\') {
349 isInEscape = true;
350 } else if (c == '$') {
351 x = i;
352 x2 = i;
353 state = S2;
354 } else {
355 out.append(c);
356 }
357 } else if (state == S2) {
358 if (isInEscape) {
359 isInEscape = false;
360 } else if (c == '\\') {
361 hasInnerEscapes = true;
362 isInEscape = true;
363 } else if (c == '{') {
364 varType = s.substring(x + 1, i);
365 x = i;
366 state = S3;
367 } else if (c < 'A' || c > 'z' || (c > 'Z' && c < 'a')) {
368 if (hasInnerEscapes)
369 out.append(unescapeChars(s.substring(x, i + 1), AS1));
370 else
371 out.append(s, x, i + 1);
372 x = i + 1;
373 state = S1;
374 hasInnerEscapes = false;
375 }
376 } else if (state == S3) {
377 if (isInEscape) {
378 isInEscape = false;
379 } else if (c == '\\') {
380 isInEscape = true;
381 hasInnerEscapes = true;
382 } else if (c == '{') {
383 depth++;
384 hasInternalVar = true;
385 } else if (c == '}') {
386 if (depth > 0) {
387 depth--;
388 } else {
389 varVal = s.substring(x + 1, i);
390 Var r = getVar(varType);
391 if (r == null) {
392 if (hasInnerEscapes)
393 out.append(unescapeChars(s.substring(x2, i + 1), AS2));
394 else
395 out.append(s, x2, i + 1);
396 x = i + 1;
397 } else {
398 varVal = (hasInternalVar && r.allowNested() ? resolve(varVal) : varVal);
399 try {
400 if (r.streamed)
401 r.resolveTo(this, out, varVal);
402 else {
403 String replacement = r.doResolve(this, varVal);
404 if (replacement == null)
405 replacement = "";
406
407 if (replacement.indexOf('$') != -1 && r.allowRecurse())
408 replacement = resolve(replacement);
409 out.append(replacement);
410 }
411 } catch (VarResolverException e) {
412 throw e;
413 } catch (Exception e) {
414 throw new VarResolverException(e, "Problem occurred resolving variable ''{0}'' in string ''{1}''", varType, s);
415 }
416 x = i + 1;
417 }
418 state = S1;
419 hasInnerEscapes = false;
420 }
421 }
422 }
423 }
424 if (isInEscape)
425 out.append('\\');
426 else if (state == S2)
427 out.append('$').append(unescapeChars(s.substring(x + 1), AS1));
428 else if (state == S3)
429 out.append('$').append(varType).append('{').append(unescapeChars(s.substring(x + 1), AS2));
430 return out;
431 }
432
433 protected FluentMap<String,Object> properties() {
434
435 return filteredBeanPropertyMap()
436 .a("context.beanStore", this.context.beanStore)
437 .a("var", this.context.getVarMap().keySet())
438 .a("session.beanStore", beanStore);
439
440 }
441
442 @Override
443 public String toString() {
444 return r(properties());
445 }
446
447
448
449
450
451
452
453 protected Var getVar(String name) {
454 Var v = this.context.getVarMap().get(name);
455 return nn(v) && v.canResolve(this) ? v : null;
456 }
457 }