public static <T extends AbstractConfig> T mutateTree(
            final T config,
            final PropertyPath basePath,
            final Map<PropertyPath, Object> replacementValueMap) {

        final Map<PropertyPath, ObjectInfo<? extends AbstractConfig>> objectInfoMap = new HashMap<>();
        final ObjectMapper objectMapper = JsonUtil.getMapper();
        ;

        // Walk the tree to get the object info for each branch
        buildObjectInfoMap(objectMapper, config, basePath, objectInfoMap);
        // Take a copy so we can remove items as we go
        final Map<PropertyPath, Object> replacementValueMapCopy = new HashMap<>(replacementValueMap);

        final T modifiedTree = mutateBranch(config, objectInfoMap, replacementValueMapCopy);

        if (!replacementValueMapCopy.isEmpty()) {
            throw new RuntimeException(LogUtil.message(
                    "The following property paths [{}] were not found in the object tree. Has a property been renamed",
                    replacementValueMapCopy.keySet()
                            .stream()
                            .sorted()
                            .map(PropertyPath::toString)
                            .collect(Collectors.joining(", "))));
        }

        return modifiedTree;
    }
Modify one or more values in the config tree by rebuilding all the immutable {@link AbstractConfig}
branches as required. As the classes are all immutable any modified objects need to be rebuilt using
their constructors and then all ancestor objects have to be rebuilt in the same way.

@param config              The object to start walking the tree from.
@param basePath            The base {@link PropertyPath} of the config object
@param replacementValueMap A map containing the values to update, key on their {@link PropertyPath}.
@param <T>
@return The new root object containing the mutated tree.
stroom-util/src/main/java/stroom/util/config/AbstractConfigUtil.java