class OperationFactory {

    public Composite fromChangeSet(ResourceAddress address, Map<String, Object> changeSet,
            Metadata metadata) {

        // TODO Is it safe to always use ATTRIBUTES as path when calling ResourceDescription methods?
        Map<String, Operation> operations = new HashMap<>();
        HashMap<String, Object> localChanges = new HashMap<>(changeSet);
        ResourceDescription resourceDescription = metadata.getDescription();

        // look for alternatives
        Set<String> conflicts = new HashSet<>();
        Map<String, List<String>> allAlternatives = localChanges.keySet().stream()
                .filter(name -> {
                    Object value = changeSet.get(name);
                    return !isNullOrEmpty(value);
                })
                .collect(toMap(identity(), name -> resourceDescription.findAlternatives(ATTRIBUTES, name)));
        allAlternatives.forEach((attribute, alternatives) -> {

            logger.debug("Alternatives resolution for {} -> [{}]", attribute, String.join(", ", alternatives));
            HashSet<String> intersection = new HashSet<>(alternatives);
            intersection.retainAll(changeSet.keySet());
            if (intersection.isEmpty()) {

                // the easy part: no conflicts
                alternatives.forEach(alternative -> {
                    boolean alternativeDoesntExist = resourceDescription.findAttribute(ATTRIBUTES, alternative) == null;
                    if (resourceDescription.isDeprecated(ATTRIBUTES, alternative) || alternativeDoesntExist) {
                        logger.debug("Skip undefine operations for deprecated or non-existent alternative {}",
                                alternative);
                    } else {
                        logger.debug("Add undefine operations for alternative {}", alternative);
                        operations.putIfAbsent(alternative, undefineAttribute(address, alternative));
                        List<String> requires = resourceDescription.findRequires(ATTRIBUTES, alternative);
                        if (!requires.isEmpty()) {
                            logger.debug("Add undefine operations for attributes which require {}: [{}]", alternative,
                                    String.join(", ", requires));
                            requires.forEach(r -> operations.putIfAbsent(r, undefineAttribute(address, r)));
                        }
                    }
                });

            } else {
                // possible conflicts: one or more alternatives are also in the change-set
                // just collect for now and resolve later
                conflicts.add(attribute);
                conflicts.addAll(intersection);
                logger.debug("Record conflict {} <-> [{}]", attribute, String.join(", ", intersection));
            }

            alternatives.forEach(localChanges::remove);
        });

        if (!conflicts.isEmpty()) {
            // try to resolve conflicts: only one of the conflicting attributes must have a value other than
            // null, empty or default
            logger.debug("Try to resolve conflicts between alternatives [{}]", String.join(", ", conflicts));
            Map<Boolean, List<String>> resolution = conflicts.stream().collect(groupingBy(conflict -> {
                Object value = changeSet.get(conflict);
                return isNullOrEmpty(value) || resourceDescription.isDefaultValue(ATTRIBUTES, conflict, value);
            }));
            List<String> undefine = resolution.getOrDefault(true, Collections.emptyList());
            List<String> write = resolution.getOrDefault(false, Collections.emptyList());
            if (write.size() > 1) {
                logger.error(
                        "More than one conflicting alternative attribute which is not null, empty or default: [{}]. This should have been caught by a form validation. Adding the write operations anyway to get an appropriate error message from the server.",
                        String.join(", ", write));
            }

            logger.debug("Add undefine operations for [{}], write operation for [{}]",
                    String.join(", ", undefine), String.join(", ", write));
            undefine.forEach(u -> {
                operations.putIfAbsent(u, undefineAttribute(address, u));
                localChanges.remove(u);
                // process requires of the current undefine attribute
                List<String> requires = resourceDescription.findRequires(ATTRIBUTES, u);
                requires.forEach(ur -> {
                    operations.putIfAbsent(ur, undefineAttribute(address, ur));
                    localChanges.remove(ur);
                });
            });

            write.forEach(w -> {
                operations.putIfAbsent(w, writeAttribute(address, w, changeSet.get(w), resourceDescription));
                localChanges.remove(w);
                List<String> writeAlternatives = resourceDescription.findAlternatives(ATTRIBUTES, w);
                // process alternatives of the current write attribute
                writeAlternatives.forEach(wa -> {
                    operations.putIfAbsent(wa, undefineAttribute(address, wa));
                    localChanges.remove(wa);
                });
            });
        }

        // handle the remaining attributes
        logger.debug("Process remaining attributes [{}]", String.join(", ", localChanges.keySet()));
        localChanges.forEach((name, value) ->
                operations.putIfAbsent(name, writeAttribute(address, name, value, resourceDescription)));
        return new Composite(operations.values().stream().filter(Objects::nonNull).collect(toList()));
    }

    public  OperationFactory();
    public  OperationFactory(Function<String, String> nameFn);

}