1 package com.github.mygreen.supercsv.localization;
2
3 import java.util.Formatter;
4 import java.util.LinkedHashMap;
5 import java.util.LinkedList;
6 import java.util.Map;
7 import java.util.Objects;
8 import java.util.Optional;
9
10 import org.slf4j.Logger;
11 import org.slf4j.LoggerFactory;
12
13 import com.github.mygreen.supercsv.expression.ExpressionEvaluationException;
14 import com.github.mygreen.supercsv.expression.ExpressionLanguage;
15 import com.github.mygreen.supercsv.expression.ExpressionLanguageJEXLImpl;
16 import com.github.mygreen.supercsv.util.StackUtils;
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 public class MessageInterpolator {
33
34 private static final Logger logger = LoggerFactory.getLogger(MessageInterpolator.class);
35
36 private ExpressionLanguage expressionLanguage;
37
38
39
40
41 private int maxRecursiveDepth = 5;
42
43
44
45
46
47
48 public MessageInterpolator() {
49 this.expressionLanguage = new ExpressionLanguageJEXLImpl();
50 }
51
52
53
54
55
56 public MessageInterpolator(final ExpressionLanguage expressionLanguage) {
57 Objects.requireNonNull(expressionLanguage, "expressionLanguage should not be null.");
58 this.expressionLanguage = expressionLanguage;
59 }
60
61
62
63
64
65
66
67
68 public String interpolate(final String message, final Map<String, ?> vars) {
69 return interpolate(message, vars, false);
70 }
71
72
73
74
75
76
77
78
79
80 public String interpolate(final String message, final Map<String, ?> vars, boolean recursive) {
81 return parse(message, vars, recursive, 0, null);
82 }
83
84
85
86
87
88
89
90
91
92
93
94 public String interpolate(final String message, final Map<String, ?> vars, boolean recursive,
95 final MessageResolver messageResolver) {
96 return parse(message, vars, recursive, 0, messageResolver);
97 }
98
99
100
101
102
103
104
105
106
107
108 protected String parse(final String message, final Map<String, ?> vars, final boolean recursive, final int currentRecursiveDepth,
109 final MessageResolver messageResolver) {
110
111
112 final StringBuilder sb = new StringBuilder(message.length());
113
114
115
116
117
118
119 final LinkedList<String> stack = new LinkedList<String>();
120
121 final int length = message.length();
122
123 for(int i=0; i < length; i++) {
124 final char c = message.charAt(i);
125
126 if(StackUtils.equalsTopElement(stack, "\\")) {
127
128 String escapedChar = StackUtils.popup(stack) + c;
129
130 if(!stack.isEmpty()) {
131
132 stack.push(escapedChar);
133
134 } else {
135
136 sb.append(c);
137
138 }
139
140 } else if(c == '\\') {
141
142 stack.push(String.valueOf(c));
143
144 } else if(c == '$') {
145 stack.push(String.valueOf(c));
146
147 } else if(c == '{') {
148
149 if(!stack.isEmpty() && !StackUtils.equalsAnyBottomElement(stack, new String[]{"$", "{"})) {
150
151 throw new MessageParseException(message, "expression not start with '{' or '$'");
152
153 } else {
154 stack.push(String.valueOf(c));
155 }
156
157
158 } else if(c == '}') {
159
160 if(StackUtils.equalsAnyBottomElement(stack, new String[]{"{", "$"})) {
161
162 String expression = StackUtils.popupAndConcat(stack) + c;
163
164
165 expression = removeEscapeChar(expression, '\\');
166
167 String result = evaluate(expression, vars, recursive, currentRecursiveDepth, messageResolver);
168 sb.append(result);
169
170 } else {
171 sb.append(c);
172
173 }
174
175 } else {
176
177 if(stack.isEmpty()) {
178 sb.append(c);
179
180 } else {
181 stack.push(String.valueOf(c));
182 }
183
184 }
185
186 }
187
188 if(!stack.isEmpty()) {
189 String val = StackUtils.popupAndConcat(stack);
190 val = removeEscapeChar(val, '\\');
191 sb.append(val);
192 }
193
194 return sb.toString();
195 }
196
197 private String evaluate(final String expression, final Map<String, ?> values, final boolean recursive,
198 final int currentRecursiveDepth, final MessageResolver messageResolver) {
199
200 if(expression.startsWith("{")) {
201
202 final String varName = expression.substring(1, expression.length()-1);
203
204 if(values.containsKey(varName)) {
205
206 final Object value = values.get(varName);
207 final String eval = Objects.toString(value, "");
208 return eval;
209
210 } else if(messageResolver != null) {
211
212 final Optional<String> eval = messageResolver.getMessage(varName);
213 if(!eval.isPresent()) {
214
215 return String.format("{%s}", varName);
216 }
217
218 if(recursivable(recursive, maxRecursiveDepth, currentRecursiveDepth, eval.get())) {
219 return parse(eval.get(), values, recursive, currentRecursiveDepth + 1, messageResolver);
220 } else {
221 return eval.get();
222 }
223
224 } else {
225
226 return expression.toString();
227 }
228
229 } else if(expression.startsWith("${")) {
230
231 final String expr = expression.substring(2, expression.length()-1);
232 final String eval = evaluateExpression(expr, values);
233 return eval;
234
235 }
236
237 throw new MessageParseException(expression, "not support expression.");
238
239 }
240
241
242
243
244
245
246
247
248
249
250 private boolean recursivable(final boolean recursive, final int maxRecursion, final int currentDepth, final String expression) {
251
252 if(!recursive) {
253 return false;
254 }
255
256 if(maxRecursion <= 0) {
257
258 return true;
259 }
260
261 if(currentDepth <= maxRecursion) {
262 return true;
263 }
264
265 logger.warn("Over recursive depth : currentDepth={}, maxDepth={}, expression={}.", currentDepth, maxRecursion, expression);
266
267 return false;
268
269 }
270
271
272
273
274
275
276
277
278 protected String evaluateExpression(final String expression, final Map<String, ?> values) throws ExpressionEvaluationException {
279
280 final Map<String, Object> context = new LinkedHashMap<String, Object>();
281 context.putAll(values);
282
283
284 context.computeIfAbsent("formatter", key -> new Formatter());
285
286
287
288
289
290
291 final String evalValue = Objects.toString(expressionLanguage.evaluate(expression, context), "");
292 if(logger.isTraceEnabled()) {
293 logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, evalValue);
294 }
295
296 return evalValue;
297 }
298
299
300
301
302
303
304
305 private String removeEscapeChar(final String str, final char escapeChar) {
306
307 if(str == null || str.isEmpty()) {
308 return str;
309 }
310
311 final String escapeStr = String.valueOf(escapeChar);
312 StringBuilder sb = new StringBuilder();
313
314 final LinkedList<String> stack = new LinkedList<>();
315
316 final int length = str.length();
317 for(int i=0; i < length; i++) {
318 final char c = str.charAt(i);
319
320 if(StackUtils.equalsTopElement(stack, escapeStr)) {
321
322 StackUtils.popup(stack);
323 sb.append(c);
324
325 } else if(c == escapeChar) {
326
327 stack.push(String.valueOf(c));
328
329 } else {
330 sb.append(c);
331 }
332
333 }
334
335 if(!stack.isEmpty()) {
336 sb.append(StackUtils.popupAndConcat(stack));
337 }
338
339 return sb.toString();
340
341 }
342
343
344
345
346
347 public ExpressionLanguage getExpressionLanguage() {
348 return expressionLanguage;
349 }
350
351
352
353
354
355 public void setExpressionLanguage(ExpressionLanguage expressionLanguage) {
356 this.expressionLanguage = expressionLanguage;
357 }
358
359
360
361
362
363
364
365 public int getMaxRecursiveDepth() {
366 return maxRecursiveDepth;
367 }
368
369
370
371
372
373
374
375 public void setMaxRecursiveDepth(int maxRecursiveDepth) {
376 this.maxRecursiveDepth = maxRecursiveDepth;
377 }
378 }