|
18 | 18 | */ |
19 | 19 | package org.jooby; |
20 | 20 |
|
21 | | -import static com.google.common.base.Preconditions.checkArgument; |
22 | 21 | import static java.util.Objects.requireNonNull; |
23 | 22 |
|
24 | 23 | import java.util.Arrays; |
|
38 | 37 | import java.util.function.Supplier; |
39 | 38 |
|
40 | 39 | import com.google.common.base.Splitter; |
41 | | -import com.google.common.base.Strings; |
42 | 40 | import com.google.common.collect.ImmutableList; |
43 | 41 | import com.google.inject.Key; |
44 | 42 | import com.google.inject.name.Names; |
45 | 43 | import com.typesafe.config.Config; |
| 44 | +import com.typesafe.config.ConfigFactory; |
46 | 45 |
|
47 | 46 | import javaslang.API; |
48 | 47 | import javaslang.control.Option; |
|
70 | 69 | */ |
71 | 70 | public interface Env extends LifeCycle { |
72 | 71 |
|
| 72 | + /** |
| 73 | + * Template literal implementation, replaces <code>${expression}</code> from a String using a |
| 74 | + * {@link Config} object. |
| 75 | + * |
| 76 | + * @author edgar |
| 77 | + */ |
| 78 | + class Resolver { |
| 79 | + private String startDelim = "${"; |
| 80 | + |
| 81 | + private String endDelim = "}"; |
| 82 | + |
| 83 | + private Config source; |
| 84 | + |
| 85 | + private boolean ignoreMissing; |
| 86 | + |
| 87 | + /** |
| 88 | + * Set property source. |
| 89 | + * |
| 90 | + * @param source Source. |
| 91 | + * @return This resolver. |
| 92 | + */ |
| 93 | + public Resolver source(final Map<String, Object> source) { |
| 94 | + this.source = ConfigFactory.parseMap(source); |
| 95 | + return this; |
| 96 | + } |
| 97 | + |
| 98 | + /** |
| 99 | + * Set property source. |
| 100 | + * |
| 101 | + * @param source Source. |
| 102 | + * @return This resolver. |
| 103 | + */ |
| 104 | + public Resolver source(final Config source) { |
| 105 | + this.source = source; |
| 106 | + return this; |
| 107 | + } |
| 108 | + |
| 109 | + /** |
| 110 | + * Set start and end delimiters. |
| 111 | + * |
| 112 | + * @param start Start delimiter. |
| 113 | + * @param end End delimiter. |
| 114 | + * @return This resolver. |
| 115 | + */ |
| 116 | + public Resolver delimiters(final String start, final String end) { |
| 117 | + this.startDelim = requireNonNull(start, "Start delimiter required."); |
| 118 | + this.endDelim = requireNonNull(end, "End delmiter required."); |
| 119 | + return this; |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Ignore missing property replacement and leave the expression untouch. |
| 124 | + * |
| 125 | + * @return This resolver. |
| 126 | + */ |
| 127 | + public Resolver ignoreMissing() { |
| 128 | + this.ignoreMissing = true; |
| 129 | + return this; |
| 130 | + } |
| 131 | + |
| 132 | + /** |
| 133 | + * Returns a string with all substitutions (the <code>${foo.bar}</code> syntax, |
| 134 | + * see <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">the |
| 135 | + * spec</a>) resolved. Substitutions are looked up using the <code>source</code> param as the |
| 136 | + * root object, that is, a substitution <code>${foo.bar}</code> will be replaced with |
| 137 | + * the result of <code>getValue("foo.bar")</code>. |
| 138 | + * |
| 139 | + * @param text Text to process. |
| 140 | + * @param source The source config to use |
| 141 | + * @param startDelimiter Start delimiter. |
| 142 | + * @param endDelimiter End delimiter. |
| 143 | + * @return A processed string. |
| 144 | + */ |
| 145 | + public String resolve(final String text) { |
| 146 | + requireNonNull(text, "Text is required."); |
| 147 | + if (text.length() == 0) { |
| 148 | + return ""; |
| 149 | + } |
| 150 | + |
| 151 | + BiFunction<Integer, BiFunction<Integer, Integer, RuntimeException>, RuntimeException> err = ( |
| 152 | + start, ex) -> { |
| 153 | + String snapshot = text.substring(0, start); |
| 154 | + int line = Splitter.on('\n').splitToList(snapshot).size(); |
| 155 | + int column = start - snapshot.lastIndexOf('\n'); |
| 156 | + return ex.apply(line, column); |
| 157 | + }; |
| 158 | + |
| 159 | + StringBuilder buffer = new StringBuilder(); |
| 160 | + int offset = 0; |
| 161 | + int start = text.indexOf(startDelim); |
| 162 | + while (start >= 0) { |
| 163 | + int end = text.indexOf(endDelim, start + startDelim.length()); |
| 164 | + if (end == -1) { |
| 165 | + throw err.apply(start, (line, column) -> new IllegalArgumentException( |
| 166 | + "found '" + startDelim + "' expecting '" + endDelim + "' at " + line + ":" |
| 167 | + + column)); |
| 168 | + } |
| 169 | + buffer.append(text.substring(offset, start)); |
| 170 | + String key = text.substring(start + startDelim.length(), end); |
| 171 | + Object value; |
| 172 | + if (source.hasPath(key)) { |
| 173 | + value = source.getAnyRef(key); |
| 174 | + } else { |
| 175 | + if (ignoreMissing) { |
| 176 | + value = text.substring(start, end + endDelim.length()); |
| 177 | + } else { |
| 178 | + throw err.apply(start, (line, column) -> new NoSuchElementException( |
| 179 | + "No configuration setting found for key '" + key + "' at " + line + ":" + column)); |
| 180 | + } |
| 181 | + } |
| 182 | + buffer.append(value); |
| 183 | + offset = end + endDelim.length(); |
| 184 | + start = text.indexOf(startDelim, offset); |
| 185 | + } |
| 186 | + if (buffer.length() == 0) { |
| 187 | + return text; |
| 188 | + } |
| 189 | + if (offset < text.length()) { |
| 190 | + buffer.append(text.substring(offset)); |
| 191 | + } |
| 192 | + return buffer.toString(); |
| 193 | + } |
| 194 | + } |
| 195 | + |
73 | 196 | /** |
74 | 197 | * Utility class for generating {@link Key} for named services. |
75 | 198 | * |
@@ -258,99 +381,17 @@ default ServiceKey serviceKey() { |
258 | 381 | * @return A processed string. |
259 | 382 | */ |
260 | 383 | default String resolve(final String text) { |
261 | | - return resolve(text, config()); |
262 | | - } |
263 | | - |
264 | | - /** |
265 | | - * Returns a string with all substitutions (the <code>${foo.bar}</code> syntax, |
266 | | - * see <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">the |
267 | | - * spec</a>) resolved. Substitutions are looked up using the {@link #config()} as the root object, |
268 | | - * that is, a substitution <code>${foo.bar}</code> will be replaced with |
269 | | - * the result of <code>getValue("foo.bar")</code>. |
270 | | - * |
271 | | - * @param text Text to process. |
272 | | - * @param startDelimiter Start delimiter. |
273 | | - * @param endDelimiter End delimiter. |
274 | | - * @return A processed string. |
275 | | - */ |
276 | | - default String resolve(final String text, final String startDelimiter, |
277 | | - final String endDelimiter) { |
278 | | - return resolve(text, config(), startDelimiter, endDelimiter); |
279 | | - } |
280 | | - |
281 | | - /** |
282 | | - * Returns a string with all substitutions (the <code>${foo.bar}</code> syntax, |
283 | | - * see <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">the |
284 | | - * spec</a>) resolved. Substitutions are looked up using the <code>source</code> param as the |
285 | | - * root object, that is, a substitution <code>${foo.bar}</code> will be replaced with |
286 | | - * the result of <code>getValue("foo.bar")</code>. |
287 | | - * |
288 | | - * @param text Text to process. |
289 | | - * @param source The source config to use. |
290 | | - * @return A processed string. |
291 | | - */ |
292 | | - default String resolve(final String text, final Config source) { |
293 | | - return resolve(text, source, "${", "}"); |
| 384 | + return resolver().resolve(text); |
294 | 385 | } |
295 | 386 |
|
296 | 387 | /** |
297 | | - * Returns a string with all substitutions (the <code>${foo.bar}</code> syntax, |
298 | | - * see <a href="https://github.com/typesafehub/config/blob/master/HOCON.md">the |
299 | | - * spec</a>) resolved. Substitutions are looked up using the <code>source</code> param as the |
300 | | - * root object, that is, a substitution <code>${foo.bar}</code> will be replaced with |
301 | | - * the result of <code>getValue("foo.bar")</code>. |
| 388 | + * Creates a new environment {@link Resolver}. |
302 | 389 | * |
303 | | - * @param text Text to process. |
304 | | - * @param source The source config to use |
305 | | - * @param startDelimiter Start delimiter. |
306 | | - * @param endDelimiter End delimiter. |
307 | | - * @return A processed string. |
| 390 | + * @return |
308 | 391 | */ |
309 | | - default String resolve(final String text, final Config source, |
310 | | - final String startDelimiter, final String endDelimiter) { |
311 | | - requireNonNull(text, "Text is required."); |
312 | | - requireNonNull(source, "Config source is required."); |
313 | | - checkArgument(!Strings.isNullOrEmpty(startDelimiter), "Start delimiter is required."); |
314 | | - checkArgument(!Strings.isNullOrEmpty(endDelimiter), "End delimiter is required."); |
315 | | - if (text.length() == 0) { |
316 | | - return ""; |
317 | | - } |
318 | | - |
319 | | - BiFunction<Integer, BiFunction<Integer, Integer, RuntimeException>, RuntimeException> err = ( |
320 | | - start, ex) -> { |
321 | | - String snapshot = text.substring(0, start); |
322 | | - int line = Splitter.on('\n').splitToList(snapshot).size(); |
323 | | - int column = start - snapshot.lastIndexOf('\n'); |
324 | | - return ex.apply(line, column); |
325 | | - }; |
326 | | - |
327 | | - StringBuilder buffer = new StringBuilder(); |
328 | | - int offset = 0; |
329 | | - int start = text.indexOf(startDelimiter); |
330 | | - while (start >= 0) { |
331 | | - int end = text.indexOf(endDelimiter, start + startDelimiter.length()); |
332 | | - if (end == -1) { |
333 | | - throw err.apply(start, (line, column) -> new IllegalArgumentException( |
334 | | - "found '" + startDelimiter + "' expecting '" + endDelimiter + "' at " + line + ":" |
335 | | - + column)); |
336 | | - } |
337 | | - buffer.append(text.substring(offset, start)); |
338 | | - String key = text.substring(start + startDelimiter.length(), end); |
339 | | - if (!source.hasPath(key)) { |
340 | | - throw err.apply(start, (line, column) -> new NoSuchElementException( |
341 | | - "No configuration setting found for key '" + key + "' at " + line + ":" + column)); |
342 | | - } |
343 | | - buffer.append(source.getAnyRef(key)); |
344 | | - offset = end + endDelimiter.length(); |
345 | | - start = text.indexOf(startDelimiter, offset); |
346 | | - } |
347 | | - if (buffer.length() == 0) { |
348 | | - return text; |
349 | | - } |
350 | | - if (offset < text.length()) { |
351 | | - buffer.append(text.substring(offset)); |
352 | | - } |
353 | | - return buffer.toString(); |
| 392 | + default Resolver resolver() { |
| 393 | + return new Resolver() |
| 394 | + .source(config()); |
354 | 395 | } |
355 | 396 |
|
356 | 397 | /** |
|
0 commit comments