class ChecklistExporter {

    String ntStatement(final Session session, final List<String> nodeTypeNames) throws RepositoryException {
        final List<String> subqueries = new ArrayList<>();
        final NodeTypeManager ntManager = session.getWorkspace().getNodeTypeManager();

        // covariants (find nodes of specified types or their subtypes)
        nodeTypeNames.stream()
                .filter(COVARIANT_FILTER)
                .map(COVARIANT_FORMAT)
                // TODO this can quietly suppress ALL nodetypes and lead to a JCR query parse exception
                .filter(Fun.testOrDefault1(ntManager::hasNodeType, false))
                .map(name -> String.format("SELECT [jcr:path] FROM [%s] AS a", name))
                .forEachOrdered(subqueries::add);

        // invariants (find only nodes which are of specified types)
        nodeTypeNames.stream()
                .filter(COVARIANT_FILTER.negate())
                .filter(Fun.testOrDefault1(ntManager::hasNodeType, false))
                .map(name -> String.format("SELECT [jcr:path] FROM [nt:base] AS a WHERE [a].[jcr:primaryType] = '%s' UNION SELECT [jcr:path] FROM [nt:base] AS a WHERE [a].[jcr:mixinTypes] = '%s'", name, name))
                .forEachOrdered(subqueries::add);

        return String.join(" UNION ", subqueries) + " OPTION(TRAVERSAL OK, INDEX NAME nodetype)";
    }

    private  ChecklistExporter(final List<Op> operations,
                              final List<String> exportTypeDefs,
                              final List<Rule> pathScopes,
                              final List<Rule> nodeTypeFilters,
                              final List<JcrNs> jcrNamespaces);

}