jdaugherty commented on code in PR #16798:
URL: https://github.com/apache/grails-core/pull/16798#discussion_r4425312817
##########
grails-core/src/main/groovy/org/grails/config/NavigableMapConfig.java:
##########
@@ -55,13 +58,99 @@ public abstract class NavigableMapConfig implements Config {
protected static final Logger LOG =
LoggerFactory.getLogger(NavigableMapConfig.class);
protected ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
protected ConfigurableConversionService conversionService = new
DefaultConversionService();
+
+ /**
+ * Whether the merge resolves the placeholders in the values it writes. It
does not while it merges values that are
+ * resolved already, so a placeholder an escape kept as text is not
resolved a second time.
+ */
+ private boolean resolvingPlaceholders = true;
+
protected NavigableMap configMap = new NavigableMap() {
+
+ /**
+ * The texts the merge in progress has resolved, by the full name of
the entry and the text. The merge writes one
+ * entry under several keys: a dotted key under the whole key and in
its nested map, and a list element in its
+ * list in the nested map, in its list under the flat key and under
its indexed key. Every write takes one value,
+ * even for {@code ${random.uuid}}.
+ */
+ private Map<List<String>, String> resolvedTexts;
+
+ @Override
+ public void merge(Map sourceMap, boolean parseFlatKeys) {
+ resolvedTexts = new HashMap<>();
+ try {
+ super.merge(sourceMap, parseFlatKeys);
+ } finally {
+ resolvedTexts = null;
+ }
+ }
+
+ /**
+ * Resolves the placeholders in a value before it is written: in a
string, in the strings of a list, through
+ * the maps and lists it holds, and in a map written under an indexed
key, {@code rows[0]}, which is written into
+ * its list as it is. A map written under a plain key is merged entry
by entry, through this method.
+ */
@Override
- protected Object mergeMapEntry(NavigableMap targetMap, String
sourceKey, Object newValue) {
- if (newValue instanceof CharSequence) {
- newValue = resolvePlaceholders(newValue.toString());
+ protected void mergeMapEntry(NavigableMap rootMap, String path,
NavigableMap targetMap, String sourceKey,
+ Object sourceValue, boolean parseFlatKeys, boolean
isNestedSet) {
+ Object value = sourceValue;
+ if (resolvingPlaceholders && (sourceValue instanceof CharSequence
|| sourceValue instanceof List || sourceValue instanceof Map &&
isIndexed(sourceKey))) {
+ value = resolved(path == null || path.isEmpty() ? sourceKey :
path + '.' + sourceKey, sourceValue);
}
- return super.mergeMapEntry(targetMap, sourceKey, newValue);
+ super.mergeMapEntry(rootMap, path, targetMap, sourceKey, value,
parseFlatKeys, isNestedSet);
+ }
+
+ private boolean isIndexed(String sourceKey) {
+ int subscriptStart = sourceKey.indexOf('[');
+ return subscriptStart > -1 && sourceKey.indexOf(']') >
subscriptStart;
+ }
+
+ /**
+ * The value with the placeholders in its strings resolved, through
the lists and maps it holds. A list or map
+ * holding no placeholder is the one given, and any other is a copy of
the same kind, so the property source
+ * keeps what it holds.
+ */
+ @SuppressWarnings("unchecked")
+ private Object resolved(String name, Object value) {
+ if (value instanceof CharSequence) {
+ return resolvedText(name, value.toString());
+ }
+ if (value instanceof List) {
+ List<?> list = (List<?>) value;
+ List<Object> resolved = null;
+ for (int i = 0; i < list.size(); i++) {
+ Object element = resolved(name + '[' + i + ']',
list.get(i));
+ if (resolved == null && !Objects.equals(element,
list.get(i))) {
+ resolved = new ArrayList<>(list);
+ }
+ if (resolved != null) {
+ resolved.set(i, element);
+ }
+ }
+ return resolved != null ? resolved : list;
+ }
+ if (value instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) value;
+ Map<Object, Object> resolved = null;
+ for (Map.Entry<?, ?> entry : map.entrySet()) {
+ Object element = resolved(name + '.' + entry.getKey(),
entry.getValue());
Review Comment:
Fixed in 34220363f1. An element of a list from `application.yml` holds
`routes: {'[x]': ...}` under both `routes.x` and `routes[x]`, and each name
took its own resolution. A key in brackets that is not an index and holds no
dot now names its resolution as the dotted part it is, so both names share one
value. A key with a dot, `[a.b]`, stays in its brackets, so it is not taken for
the entry `b` of a map `a`, and an index such as `[0]` is unchanged.
`a placeholder in a list element of application.yml is resolved once, alike
for every read` now has this shape. It reads the source four times, once per
element: without the change it read five.
`getProperty('app.rows[0].routes[x]')`, `getProperty('app.rows[0].routes.x')`,
`getProperty('app.rows', List)[0].routes.x`, `navigate` and both exported names
agree.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]