class JsonCnd {

    public static NodeTypeSet aggregateNodeTypes(final @NotNull NamespaceMapping mapping,
                                                 final @NotNull List<NodeTypeSet> nodeTypeSets) {

        final NamespaceMapping aggregateMapping = new NamespaceMapping(mapping);
        final Set<Name> builtinTypes = BUILTIN_NODETYPES.stream()
                .map(uncheck1(BUILTIN_RESOLVER::getQName))
                .collect(Collectors.toSet());
        final Map<Name, List<QNodeTypeDefinition>> unfilteredTypes = nodeTypeSets.stream().flatMap(
                compose1(compose1(NodeTypeSet::getNodeTypes, Map::entrySet), Set::stream))
                .filter(testKey(inSet(builtinTypes).negate()))
                .map(mapValue(Collections::singletonList))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (left, right) -> {
                    final List<QNodeTypeDefinition> combined = new ArrayList<>(left);
                    combined.addAll(right);
                    return combined;
                }));

        final Set<Name> unfilteredNames = unfilteredTypes.keySet();
        final Map<Name, QNodeTypeDefinition> satisfied = unfilteredTypes.entrySet().stream()
                .map(mapValue(types ->
                        types.stream()
                                .filter(type ->
                                        type.getDependencies().stream()
                                                .noneMatch(inSet(builtinTypes).or(inSet(unfilteredNames)).negate()))
                ))
                .map(mapValue(Stream::findFirst))
                .filter(testValue(Optional::isPresent))
                .collect(Collectors.toMap(Map.Entry::getKey, compose1(Map.Entry::getValue, Optional::get)));

        final Set<Name> unsatisfiedNames = unfilteredNames.stream().filter(inSet(satisfied.keySet()).negate())
                .collect(Collectors.toSet());

        if (!unsatisfiedNames.isEmpty()) {
            LOGGER.debug("[aggregateNodeTypes] unsatisfied node type names: {}", unsatisfiedNames.toString());
        }

        unsatisfiedNames.stream()
                .map(name -> toEntry(name, unfilteredTypes.getOrDefault(name,
                        Collections.emptyList()).stream().findFirst()))
                .filter(testValue(Optional::isPresent))
                .map(mapValue(Optional::get))
                .forEach(entry -> satisfied.put(entry.getKey(), entry.getValue()));

        return new DefaultNodeTypeSet("<aggregate>", satisfied.values(), aggregateMapping);
    }

    private  JsonCnd();

}