jdaugherty commented on code in PR #15950:
URL: https://github.com/apache/grails-core/pull/15950#discussion_r3555834389


##########
grails-web-databinding/src/main/groovy/grails/web/databinding/DataBindingUtils.java:
##########
@@ -300,6 +320,586 @@ public static BindingResult 
bindObjectToDomainInstance(PersistentEntity entity,
         return bindingResult;
     }
 
+    private static void assignNullToMissingIncludedProperties(Object object, 
DataBindingSource bindingSource, List include, List exclude, String filter) {
+        for (Object includedProperty : include) {
+            if (includedProperty instanceof CharSequence) {
+                String propertyName = includedProperty.toString();
+                if (propertyName.indexOf('*') == -1 && 
!isExcludedProperty(propertyName, exclude) && isBindingAllowed(object, 
propertyName)) {
+                    if (assignNullToMissingIndexedProperties(object, 
bindingSource, propertyName, filter)) {
+                        continue;
+                    }
+                    if (!bindingSourceContainsProperty(bindingSource, 
propertyName, filter)) {
+                        setPropertyToNull(object, propertyName);
+                    }
+                }
+            }
+        }
+    }
+
+    private static boolean isExcludedProperty(String propertyName, List 
exclude) {
+        if (exclude == null) {
+            return false;
+        }
+        for (Object excludedProperty : exclude) {
+            if (excludedProperty instanceof CharSequence) {
+                String excludedPropertyName = excludedProperty.toString();
+                if (propertyName.equals(excludedPropertyName) || 
propertyName.startsWith(excludedPropertyName + ".") || 
rootPropertyName(propertyName).equals(excludedPropertyName)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean isBindingAllowed(Object object, String 
propertyName) {
+        if (object == null) {
+            return false;
+        }
+
+        if (!isPropertyAllowedByWhitelist(object, propertyName)) {
+            return false;
+        }
+
+        int separator = propertyPathSeparator(propertyName);
+        if (separator == -1) {
+            return true;
+        }
+
+        Object nestedObject = getPropertyValue(object, 
propertyName.substring(0, separator));
+        String nestedPropertyName = propertyName.substring(separator + 1);
+        if (nestedObject instanceof Collection) {
+            for (Object item : (Collection) nestedObject) {
+                if (item != null && !isBindingAllowed(item, 
nestedPropertyName)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        if (nestedObject instanceof Map) {
+            for (Object value : ((Map) nestedObject).values()) {
+                if (value != null && !isBindingAllowed(value, 
nestedPropertyName)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return nestedObject == null || isBindingAllowed(nestedObject, 
nestedPropertyName);
+    }
+
+    private static boolean isPropertyAllowedByWhitelist(Object object, String 
propertyName) {
+        List bindingIncludeList = getBindingIncludeList(object);
+        if (bindingIncludeList == null || bindingIncludeList.isEmpty()) {
+            return true;
+        }
+        for (Object includedProperty : bindingIncludeList) {
+            if (includedProperty instanceof CharSequence) {
+                String includedPropertyName = includedProperty.toString();
+                if (propertyName.equals(includedPropertyName) || 
includedPropertyName.startsWith(propertyName + ".")) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static String rootPropertyName(String propertyName) {
+        int dotIndex = propertyName.indexOf('.');
+        int bracketIndex = propertyName.indexOf('[');
+        int endIndex = -1;
+        if (dotIndex > -1 && bracketIndex > -1) {
+            endIndex = Math.min(dotIndex, bracketIndex);
+        }
+        else if (dotIndex > -1) {
+            endIndex = dotIndex;
+        }
+        else if (bracketIndex > -1) {
+            endIndex = bracketIndex;
+        }
+        return endIndex == -1 ? propertyName : propertyName.substring(0, 
endIndex);
+    }
+
+    private static boolean assignNullToMissingIndexedProperties(Object object, 
DataBindingSource bindingSource, String propertyName, String filter) {
+        String sourcePropertyName = filter == null ? propertyName : filter + 
"." + propertyName;
+        return assignNullToMissingIndexedProperties(object, bindingSource, 
BLANK, sourcePropertyName, propertyName);
+    }
+
+    private static boolean assignNullToMissingIndexedProperties(Object object, 
Object source, String targetPathPrefix, String sourcePropertyName, String 
targetPropertyName) {
+        int sourceSeparator = propertyPathSeparator(sourcePropertyName);
+        int targetSeparator = propertyPathSeparator(targetPropertyName);
+        if (sourceSeparator == -1 || targetSeparator == -1) {
+            return false;
+        }
+
+        String sourceRootPropertyName = sourcePropertyName.substring(0, 
sourceSeparator);
+        String targetRootPropertyName = targetPropertyName.substring(0, 
targetSeparator);
+        String nestedSourcePropertyName = 
sourcePropertyName.substring(sourceSeparator + 1);
+        String nestedTargetPropertyName = 
targetPropertyName.substring(targetSeparator + 1);
+        String[] sourceSegments = splitPropertyPath(sourcePropertyName);
+        String[] targetSegments = splitPropertyPath(targetPropertyName);
+        if (sourceSegments.length > targetSegments.length) {
+            if (containsSourceProperty(source, sourceRootPropertyName)) {
+                return assignNullToMissingIndexedProperties(object, 
getSourcePropertyValue(source, sourceRootPropertyName), targetPathPrefix, 
nestedSourcePropertyName, targetPropertyName);
+            }
+            int sourceRootSegmentCount = sourceSegments.length - 
targetSegments.length + 1;
+            sourceRootPropertyName = joinPropertyPath(sourceSegments, 0, 
sourceRootSegmentCount);
+            nestedSourcePropertyName = joinPropertyPath(sourceSegments, 
sourceRootSegmentCount, sourceSegments.length);
+            targetRootPropertyName = targetSegments[0];
+            nestedTargetPropertyName = joinPropertyPath(targetSegments, 1, 
targetSegments.length);
+        }
+
+        if (containsSourceProperty(source, sourceRootPropertyName)) {
+            Object nestedSource = getSourcePropertyValue(source, 
sourceRootPropertyName);
+            if (nestedSource instanceof Collection) {
+                return assignNullToMissingCollectionProperties(object, 
(Collection) nestedSource, targetPathPrefix, targetRootPropertyName, 
nestedSourcePropertyName, nestedTargetPropertyName);
+            }
+            Object targetObject = getTargetObject(object, targetPathPrefix);
+            if (nestedSource instanceof Map && hasNestedSourceEntries((Map) 
nestedSource) && shouldExpandMapEntries(targetObject, targetObject == null ? 
null : targetObject.getClass(), targetRootPropertyName)) {
+                return assignNullToMissingMapProperties(object, (Map) 
nestedSource, targetPathPrefix, targetRootPropertyName, 
nestedSourcePropertyName, nestedTargetPropertyName);
+            }
+        }
+
+        boolean indexed = false;
+        String indexedSourcePropertyPrefix = sourceRootPropertyName + "[";
+        for (String indexedSourcePropertyName : 
getIndexedSourcePropertyNames(source, indexedSourcePropertyPrefix)) {
+            indexed = true;
+            String targetIndexedPropertyName = 
appendPropertyPath(targetPathPrefix, targetRootPropertyName + 
indexedSourcePropertyName.substring(sourceRootPropertyName.length()));
+            if (containsSourceProperty(source, indexedSourcePropertyName)) {
+                Object nestedSource = getSourcePropertyValue(source, 
indexedSourcePropertyName);
+                if (!assignNullToMissingIndexedProperties(object, 
nestedSource, targetIndexedPropertyName, nestedSourcePropertyName, 
nestedTargetPropertyName) && !containsPropertyPath(nestedSource, 
nestedSourcePropertyName)) {
+                    setPropertyToNull(object, targetIndexedPropertyName + "." 
+ nestedTargetPropertyName);
+                }
+            }
+            else if (!containsPropertyPath(source, indexedSourcePropertyName + 
"." + nestedSourcePropertyName)) {
+                setPropertyToNull(object, targetIndexedPropertyName + "." + 
nestedTargetPropertyName);
+            }
+        }
+        return indexed;
+    }
+
+    private static boolean assignNullToMissingCollectionProperties(Object 
object, Collection collection, String targetPathPrefix, String 
targetRootPropertyName, String nestedSourcePropertyName, String 
nestedTargetPropertyName) {
+        int index = 0;
+        for (Object item : collection) {
+            String targetIndexedPropertyName = 
appendPropertyPath(targetPathPrefix, targetRootPropertyName + "[" + index + 
"]");
+            assignNullToMissingNestedProperty(object, item, 
targetIndexedPropertyName, nestedSourcePropertyName, nestedTargetPropertyName);
+            index++;
+        }
+        return true;
+    }
+
+    private static boolean assignNullToMissingMapProperties(Object object, Map 
map, String targetPathPrefix, String targetRootPropertyName, String 
nestedSourcePropertyName, String nestedTargetPropertyName) {
+        for (Object entryObject : map.entrySet()) {
+            Map.Entry entry = (Map.Entry) entryObject;
+            String targetIndexedPropertyName = 
appendPropertyPath(targetPathPrefix, targetRootPropertyName + "[" + 
entry.getKey() + "]");
+            assignNullToMissingNestedProperty(object, entry.getValue(), 
targetIndexedPropertyName, nestedSourcePropertyName, nestedTargetPropertyName);
+        }
+        return true;
+    }
+
+    private static void assignNullToMissingNestedProperty(Object object, 
Object nestedSource, String targetIndexedPropertyName, String 
nestedSourcePropertyName, String nestedTargetPropertyName) {
+        if (!assignNullToMissingIndexedProperties(object, nestedSource, 
targetIndexedPropertyName, nestedSourcePropertyName, nestedTargetPropertyName) 
&& !containsPropertyPath(nestedSource, nestedSourcePropertyName)) {
+            setPropertyToNull(object, targetIndexedPropertyName + "." + 
nestedTargetPropertyName);
+        }
+    }
+
+    private static String joinPropertyPath(String[] segments, int start, int 
end) {
+        StringBuilder propertyPath = new StringBuilder();
+        for (int i = start; i < end; i++) {
+            if (propertyPath.length() > 0) {
+                propertyPath.append('.');
+            }
+            propertyPath.append(segments[i]);
+        }
+        return propertyPath.toString();
+    }
+
+    private static boolean bindingSourceContainsProperty(DataBindingSource 
bindingSource, String propertyName, String filter) {
+        String sourcePropertyName = filter == null ? propertyName : filter + 
"." + propertyName;
+        int exactPrefixSegments = filter == null ? 0 : 
splitPropertyPath(filter).length;
+        return containsPropertyPath(bindingSource, sourcePropertyName, 
exactPrefixSegments) || containsPropertyPath(bindingSource, 
checkboxMarkerPropertyName(sourcePropertyName), exactPrefixSegments);
+    }
+
+    private static boolean containsPropertyPath(Object source, String 
propertyName) {
+        return containsPropertyPath(source, propertyName, 0);
+    }
+
+    private static boolean containsPropertyPath(Object source, String 
propertyName, int exactPrefixSegments) {
+        if (containsSourceProperty(source, propertyName)) {
+            return true;
+        }
+        if (containsIndexedPropertyPath(source, propertyName, 
exactPrefixSegments)) {
+            return true;
+        }
+        int separator = propertyPathSeparator(propertyName);
+        if (separator == -1) {
+            return false;
+        }
+        String rootPropertyName = propertyName.substring(0, separator);
+        if (!containsSourceProperty(source, rootPropertyName)) {
+            return containsIndexedNestedPropertyPath(source, rootPropertyName, 
propertyName.substring(separator + 1));
+        }
+        Object nestedSource = getSourcePropertyValue(source, rootPropertyName);
+        String nestedPropertyName = propertyName.substring(separator + 1);
+        if (nestedSource instanceof Collection) {
+            for (Object item : (Collection) nestedSource) {
+                if (containsPropertyPath(item, nestedPropertyName)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+        return containsPropertyPath(nestedSource, nestedPropertyName);
+    }
+
+    private static boolean containsIndexedNestedPropertyPath(Object source, 
String rootPropertyName, String nestedPropertyName) {
+        String indexedSourcePropertyPrefix = rootPropertyName + "[";
+        for (String indexedSourcePropertyName : 
getIndexedSourcePropertyNames(source, indexedSourcePropertyPrefix)) {
+            if (containsSourceProperty(source, indexedSourcePropertyName) && 
containsPropertyPath(getSourcePropertyValue(source, indexedSourcePropertyName), 
nestedPropertyName)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean containsIndexedPropertyPath(Object source, String 
propertyName, int exactPrefixSegments) {
+        for (String indexedPropertyName : getSourcePropertyNames(source)) {
+            if (indexedPropertyPathMatches(indexedPropertyName, propertyName, 
exactPrefixSegments)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static int propertyPathSeparator(String propertyName) {
+        return propertyPathSeparator(propertyName, false);
+    }
+
+    private static int propertyPathSeparator(String propertyName, boolean 
last) {
+        int separator = -1;
+        int bracketDepth = 0;
+        for (int i = 0; i < propertyName.length(); i++) {
+            char character = propertyName.charAt(i);
+            if (character == '[') {
+                bracketDepth++;
+            }
+            else if (character == ']' && bracketDepth > 0) {
+                bracketDepth--;
+            }
+            else if (character == '.' && bracketDepth == 0) {
+                if (!last) {
+                    return i;
+                }
+                separator = i;
+            }
+        }
+        return separator;
+    }
+
+    private static String[] splitPropertyPath(String propertyName) {
+        List<String> segments = new ArrayList<>();
+        StringBuilder segment = new StringBuilder();
+        int bracketDepth = 0;
+        for (int i = 0; i < propertyName.length(); i++) {
+            char character = propertyName.charAt(i);
+            if (character == '.' && bracketDepth == 0) {
+                segments.add(segment.toString());
+                segment.setLength(0);
+            }
+            else {
+                if (character == '[') {
+                    bracketDepth++;
+                }
+                else if (character == ']' && bracketDepth > 0) {
+                    bracketDepth--;
+                }
+                segment.append(character);
+            }
+        }
+        segments.add(segment.toString());
+        return segments.toArray(new String[0]);
+    }
+
+    private static boolean indexedPropertyPathMatches(String 
indexedPropertyName, String propertyName, int exactPrefixSegments) {
+        String[] indexedPropertySegments = 
splitPropertyPath(indexedPropertyName);
+        String[] propertySegments = splitPropertyPath(propertyName);
+        if (indexedPropertySegments.length != propertySegments.length) {
+            return false;
+        }
+        for (int i = 0; i < propertySegments.length; i++) {
+            if (indexedPropertySegments[i].equals(propertySegments[i])) {
+                continue;
+            }
+            if (i < exactPrefixSegments) {
+                return false;
+            }
+            if (!indexedSegmentMatches(indexedPropertySegments[i], 
propertySegments[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private static boolean indexedSegmentMatches(String indexedSegment, String 
segment) {
+        return indexedSegment.startsWith(segment + "[") && 
indexedSegment.endsWith("]");
+    }
+
+    private static Set<String> getIndexedSourcePropertyNames(Object source, 
String indexedSourcePropertyPrefix) {
+        Set<String> indexedSourcePropertyNames = new LinkedHashSet<>();
+        for (String propertyName : getSourcePropertyNames(source)) {
+            if (propertyName.startsWith(indexedSourcePropertyPrefix)) {
+                int closingIndex = propertyName.indexOf(']', 
indexedSourcePropertyPrefix.length());
+                if (closingIndex > -1) {
+                    indexedSourcePropertyNames.add(propertyName.substring(0, 
closingIndex + 1));
+                }
+            }
+        }
+        return indexedSourcePropertyNames;
+    }
+
+    private static boolean containsSourceProperty(Object source, String 
propertyName) {
+        if (source instanceof DataBindingSource) {
+            return ((DataBindingSource) source).containsProperty(propertyName);
+        }
+        if (source instanceof Map) {
+            return ((Map) source).containsKey(propertyName);
+        }
+        return false;
+    }
+
+    private static Set<String> getSourcePropertyNames(Object source) {
+        Set<String> propertyNames = new LinkedHashSet<>();
+        if (source instanceof DataBindingSource) {
+            propertyNames.addAll(((DataBindingSource) 
source).getPropertyNames());
+        }
+        else if (source instanceof Map) {
+            for (Object key : ((Map) source).keySet()) {
+                propertyNames.add(key.toString());
+            }
+        }
+        return propertyNames;
+    }
+
+    private static Object getSourcePropertyValue(Object source, String 
propertyName) {
+        if (source instanceof DataBindingSource) {
+            return ((DataBindingSource) source).getPropertyValue(propertyName);
+        }
+        return ((Map) source).get(propertyName);
+    }
+
+    private static String checkboxMarkerPropertyName(String propertyName) {
+        int separator = propertyPathSeparator(propertyName, true);
+        if (separator == -1) {
+            return "_" + propertyName;
+        }
+        return propertyName.substring(0, separator + 1) + "_" + 
propertyName.substring(separator + 1);
+    }
+
+    private static boolean hasNestedSourceEntries(Map map) {
+        for (Object value : map.values()) {
+            if (value instanceof Map || value instanceof Collection || value 
instanceof DataBindingSource) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean shouldExpandMapEntries(Object target, Class 
targetType, String propertyName) {
+        Object value = getTargetPropertyValue(target, propertyName);
+        if (value instanceof Map && hasStructuredTargetMapValues((Map) value)) 
{
+            return true;
+        }
+
+        Class mapValueType = getMapValueType(target, targetType, propertyName);
+        return mapValueType != null && isStructuredMapValueType(mapValueType);
+    }
+
+    private static boolean hasStructuredTargetMapValues(Map map) {
+        for (Object value : map.values()) {
+            if (value != null && isStructuredMapValueType(value.getClass())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isStructuredMapValueType(Class valueType) {
+        Package valuePackage = valueType.getPackage();
+        return !valueType.isPrimitive() &&
+            (valuePackage == null || 
!valuePackage.getName().startsWith("java.")) &&
+            !CharSequence.class.isAssignableFrom(valueType) &&
+            !Number.class.isAssignableFrom(valueType) &&
+            !Boolean.class.isAssignableFrom(valueType) &&
+            !Enum.class.isAssignableFrom(valueType) &&
+            !Map.class.isAssignableFrom(valueType) &&
+            !Collection.class.isAssignableFrom(valueType) &&
+            !Object.class.equals(valueType);
+    }
+
+    private static Class getMapValueType(Object target, Class targetType, 
String propertyName) {
+        Class resolvedTargetType = target == null ? targetType : 
target.getClass();
+        if (resolvedTargetType == null) {
+            return null;
+        }
+
+        MetaClass mc = 
GroovySystem.getMetaClassRegistry().getMetaClass(resolvedTargetType);
+        MetaProperty metaProperty = mc.getMetaProperty(propertyName);
+        if (metaProperty == null || 
!Map.class.isAssignableFrom(metaProperty.getType())) {
+            return null;
+        }
+
+        Field field = findField(resolvedTargetType, propertyName);
+        if (field == null) {
+            return null;
+        }
+        return getMapValueType(field.getGenericType());
+    }
+
+    private static Class getMapValueType(Type type) {
+        if (!(type instanceof ParameterizedType)) {
+            return null;
+        }
+
+        Type[] typeArguments = ((ParameterizedType) 
type).getActualTypeArguments();
+        if (typeArguments.length < 2) {
+            return null;
+        }
+        Type valueType = typeArguments[1];
+        if (valueType instanceof Class) {
+            return (Class) valueType;
+        }
+        if (valueType instanceof ParameterizedType && ((ParameterizedType) 
valueType).getRawType() instanceof Class) {
+            return (Class) ((ParameterizedType) valueType).getRawType();
+        }
+        return null;
+    }
+
+    private static Field findField(Class type, String propertyName) {
+        Class currentType = type;
+        while (currentType != null) {
+            try {
+                return currentType.getDeclaredField(propertyName);
+            }
+            catch (NoSuchFieldException e) {
+                currentType = currentType.getSuperclass();
+            }
+        }
+        return null;
+    }
+
+    private static Object getTargetObject(Object object, String 
targetPathPrefix) {
+        if (targetPathPrefix == null || targetPathPrefix.length() == 0) {
+            return object;
+        }
+
+        Object targetObject = object;
+        for (String propertyName : splitPropertyPath(targetPathPrefix)) {
+            if (targetObject == null) {
+                return null;
+            }
+            targetObject = getPropertyValue(targetObject, propertyName);
+        }
+        return targetObject;
+    }
+
+    private static Object getTargetPropertyValue(Object target, String 
propertyName) {
+        if (target == null) {
+            return null;
+        }
+
+        try {
+            MetaClass mc = 
GroovySystem.getMetaClassRegistry().getMetaClass(target.getClass());
+            return mc.getProperty(target, propertyName);
+        }
+        catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static String appendPropertyPath(String parentPath, String 
propertyName) {
+        if (parentPath == null || parentPath.length() == 0) {
+            return propertyName;
+        }
+        return parentPath + "." + propertyName;
+    }
+
+    private static void setPropertyToNull(Object object, String propertyName) {
+        String[] propertyNames = splitPropertyPath(propertyName);
+        Object currentObject = object;
+        for (int i = 0; i < propertyNames.length - 1 && currentObject != null; 
i++) {
+            currentObject = getPropertyValue(currentObject, propertyNames[i]);
+        }
+        if (currentObject != null) {
+            setPropertyValueToNull(currentObject, 
propertyNames[propertyNames.length - 1]);
+        }
+    }
+
+    private static Object getPropertyValue(Object object, String propertyName) 
{
+        int bracket = propertyName.indexOf('[');
+        try {
+            if (bracket == -1) {
+                MetaClass mc = 
GroovySystem.getMetaClassRegistry().getMetaClass(object.getClass());
+                return mc.getProperty(object, propertyName);
+            }
+
+            MetaClass mc = 
GroovySystem.getMetaClassRegistry().getMetaClass(object.getClass());
+            Object indexedProperty = mc.getProperty(object, 
propertyName.substring(0, bracket));
+            return getIndexedValue(indexedProperty, 
propertyName.substring(bracket + 1, propertyName.indexOf(']', bracket)));
+        }
+        catch (Exception e) {
+            return null;
+        }
+    }
+
+    private static Object getIndexedValue(Object indexedProperty, String 
index) {
+        if (indexedProperty instanceof List) {
+            List list = (List) indexedProperty;
+            Integer parsedIndex = parseIndex(index);
+            return parsedIndex != null && parsedIndex >= 0 && parsedIndex < 
list.size() ? list.get(parsedIndex) : null;

Review Comment:
   This PR appears to be doing more than what it says it does.  It appears to 
have overlap with the other changes.



-- 
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]

Reply via email to