Repository navigation
fix(databinding): bind the first of several indexers as the index #16982
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: 7.0.x
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -94,6 +94,9 @@ class SimpleDataBinder implements DataBinder { | |
|
|
||
| static final INDEXED_PROPERTY_REGEX = /(.*)\[\s*([^\s]*)\s*\]\s*$/ | ||
|
|
||
| /** A name with more than one indexer, split after the first one: {@code columns[0]} and {@code [data]}. */ | ||
| static final MULTIPLE_INDEXERS_REGEX = /^([^\[\]]+\[[^\[\]]*\])(\[[^\[\]]*\].*)$/ | ||
|
|
||
| int autoGrowCollectionLimit = 256 | ||
|
|
||
| SimpleDataBinder() { | ||
|
|
@@ -221,6 +224,7 @@ class SimpleDataBinder implements DataBinder { | |
|
|
||
| protected void doBind(obj, DataBindingSource source, String filter, List whiteList, List blackList, DataBindingListener listener, errors) { | ||
|
|
||
| source = groupMultipleIndexers(source) | ||
| def keys = source.getPropertyNames() | ||
| for (String key in keys) { | ||
| if (!filter || key.startsWith(filter + '.')) { | ||
|
|
@@ -268,6 +272,44 @@ class SimpleDataBinder implements DataBinder { | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Binds the first indexer of a name that has several, so {@code columns[0][data]} sets the | ||
| * {@code data} entry of the element at {@code columns[0]}, the same as {@code columns[0].data} | ||
| * does. The entries of one element are gathered into a map under {@code columns[0]}. | ||
| * | ||
| * @return the source itself when no name has more than one indexer, else a copy where those names are grouped | ||
| */ | ||
| protected DataBindingSource groupMultipleIndexers(DataBindingSource source) { | ||
| Set<String> names = source.getPropertyNames() | ||
| if (!names.any { String name -> name =~ MULTIPLE_INDEXERS_REGEX }) { | ||
| return source | ||
| } | ||
| Map<String, Object> grouped = new LinkedHashMap<String, Object>() | ||
| Map<String, Map<String, Object>> elements = new LinkedHashMap<String, Map<String, Object>>() | ||
| for (String name in names) { | ||
| def matcher = name =~ MULTIPLE_INDEXERS_REGEX | ||
| if (matcher) { | ||
| def rest = matcher.group(2) =~ /^\[\s*([^\s\]]*)\s*\](.*)$/ | ||
| rest.matches() | ||
| String key = rest.group(1) | ||
| if (key.size() > 2 && ((key.startsWith("'") && key.endsWith("'")) || (key.startsWith('"') && key.endsWith('"')))) { | ||
| key = key[1..-2] | ||
| } | ||
| elements.computeIfAbsent(matcher.group(1)) { new LinkedHashMap<String, Object>() }.put(key + rest.group(2), source.getPropertyValue(name)) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A list already holds [data: old]. The request is columns[0][data]=new. The selected map should become [data: new]. It stays [data: old]. The grouped value is bound as bean properties, and a map has none named data, so the entry is ignored. Write the grouped entries into the selected map. |
||
| } else { | ||
| grouped.put(name, source.getPropertyValue(name)) | ||
| } | ||
| } | ||
| elements.each { String element, Map<String, Object> entries -> | ||
| def existing = grouped.get(element) | ||
| if (existing instanceof Map) { | ||
| entries.putAll((Map) existing) | ||
| } | ||
| grouped.put(element, entries) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The source has factories[0] set to an existing factory, and factories[0][isActive] set to true. The existing factory should keep its identity and its other fields. This replaces it with a new map, so the name and the instance are gone. Keep a supplied object that is not a map, and apply the grouped entries to it. |
||
| } | ||
| new SimpleMapDataBindingSource(grouped) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caller set dataSourceAware to false. Adding an unrelated name such as ignored[0][name] replaces the source with one that has it true. A later property can then load and bind an existing instance. Copy the caller's setting onto the grouped source. |
||
| } | ||
|
|
||
| protected boolean isOkToBind(String propName, List whiteList, List blackList) { | ||
| 'class' != propName && 'classLoader' != propName && 'protectionDomain' != propName && 'metaClass' != propName && 'metaPropertyValues' != propName && 'properties' != propName && !blackList?.contains(propName) && (!whiteList || whiteList.contains(propName) || whiteList.find { it -> it?.toString()?.startsWith(propName + '.') }) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A parameter named ignored[0][first name] used to be skipped. It matches the outer pattern and fails the inner one, because the key has a space. group(1) then throws, and the whole bind stops. If the inner pattern does not match, skip that name.