/** \file choice_set.h */ // -*-c++-*- // Copyright (C) 2009 Daniel Burrows // // This program is free software; you can redistribute it and/or // modify it under the terms of the GNU General Public License as // published by the Free Software Foundation; either version 2 of // the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; see the file COPYING. If not, write to // the Free Software Foundation, Inc., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. #ifndef CHOICE_SET_H #define CHOICE_SET_H #include "choice.h" #include #include #include #include template class generic_choice_set; template std::ostream &operator<<(std::ostream &out, const generic_choice_set &choices); /** \brief Stores a set of choices, with the ability to quickly answer * questions about containment. * * If several choices overlap, only the most specific choice is * stored. The reason for this is that this structure is used to * store information of the form "if ever entry in this set of * choices was chosen, then X". The only way to choose both a more * general option and a more specific one is to take the more * specific one; hence, it makes no sense to include both of them. * To emphasize this behavior, the "insert" routine is called * "insert_or_narrow". An "insert_widen" could be written without * conflict; it just happens not to be needed right now. * * This object requires that the choices form a coherent installation * set. In particular, two install_version choices that install * different versions for the same package may not coexist. * Furthermore, if two choices install the same version, one of the * choices must contain the other. The choice sets generated during * dependency resolution all have these properties "automatically". * (these restrictions could be lifted, at the cost of making this * code a bit more complex and possibly slower; you'd need to * introduce a map from dependencies to choices and store one of * those for each version) * * One set of choices contains another set if each choice in the * first set is contained by a choice in the second set. This class * uses its knowledge of the structure of choices to accelerate that * test. */ template class generic_choice_set { public: typedef generic_choice choice; private: typedef typename PackageUniverse::package package; typedef typename PackageUniverse::version version; typedef typename PackageUniverse::dep dep; public: /** \brief A slow but convenient way to iterate over this set. */ class const_iterator { friend class generic_choice_set; typename imm::map::const_iterator install_version_iter; typename imm::map::const_iterator install_version_end_iter; typename imm::set::const_iterator not_install_version_iter; const_iterator(const typename imm::map::const_iterator _install_version_iter, const typename imm::map::const_iterator _install_version_end_iter, const typename imm::set::const_iterator _not_install_version_iter) : install_version_iter(_install_version_iter), install_version_end_iter(_install_version_end_iter), not_install_version_iter(_not_install_version_iter) { } public: bool operator==(const const_iterator &other) const { return install_version_iter == other.install_version_iter && not_install_version_iter == other.not_install_version_iter; } bool operator!=(const const_iterator &other) const { return !((*this) == other); } const choice &operator*() const { if(install_version_iter != install_version_end_iter) return install_version_iter->second; else return *not_install_version_iter; } const choice *operator->() const { return &**this; } const_iterator &operator++() { if(install_version_iter != install_version_end_iter) ++install_version_iter; else ++not_install_version_iter; return *this; } const_iterator operator++(int) { const_iterator rval(*this); ++(*this); return rval; } }; private: // Note: no iterators on this structure because they'd be a pain to // write and they should never be used (use for_each instead, it's a // lot more efficient). // Stores the install-this-version choices made in this set, // organized by package. Needed to accelerate the containment test. imm::map install_version_choices; // Stores the dependencies that are broken by this choice set. We // could just store dependencies instead; I don't know whether that // would be more efficient or not. imm::set not_install_version_choices; friend std::ostream &operator<< (std::ostream &out, const generic_choice_set &choices); // Used to apply a for_each on choices to each element of the // install_version_choices set. template struct for_each_choice_pair { const F &f; for_each_choice_pair(const F &_f) : f(_f) { } bool operator()(const std::pair &p) const { return f(p.second); } }; // Used by operator<< -- could be declared outside this object, but // that would pollute the namespace. struct show_choice { mutable bool first; std::ostream &out; show_choice(std::ostream &_out, bool first) : first(true), out(_out) { } bool operator()(const choice &c) const { if(first) first = false; else out << ", "; out << c; return true; } }; struct insert_choice_narrow { generic_choice_set &parent; insert_choice_narrow(generic_choice_set &_parent) : parent(_parent) { } bool operator()(const choice &c) const { switch(c.get_type()) { case choice::install_version: // Check whether this "hits" an existing choice. { const package p(c.get_ver().get_package()); typename imm::map::node n = parent.install_version_choices.lookup(p); if(!n.isValid()) parent.install_version_choices.put(p, c); else { std::pair existing_choice_pair(n.getVal()); choice &existing_choice(existing_choice_pair.second); if(existing_choice.contains(c)) // Override the existing choice with the new one, // which is more specific. parent.install_version_choices.put(p, c); else if(c.contains(existing_choice)) ; // c is more general than the existing choice. else LOG_ERROR(aptitude::Loggers::getAptitudeResolver(), "Internal error: attempted to add conflicting choices " << c << " and " << existing_choice << " to the same set."); } } break; default: parent.not_install_version_choices.insert(c); break; } return true; } }; // Used to check the install_version_choices map for supermap-ness // when testing set containment, and to check the // not_install_version_choices map for superset-ness. struct choice_contains { choice_contains() { } // The order of "contains" is reversed because of how the // predicate is invoked (p1 and c1 are from the set that should be // a subset; see immset.h). Arguably that was a bad choice on my // part when I was designing the immset code, but I'm pretty much // stuck with it now. bool operator()(const std::pair &p1, const std::pair &p2) const { return p2.second.contains(p1.second); } bool operator()(const choice &c1, const choice &c2) const { return c2.contains(c1); } }; // Used to check the install_version_choices map for supermap-ness // when testing set containment, and to check the // not_install_version_choices map for superset-ness. struct choice_is_contained_in { choice_is_contained_in() { } bool operator()(const std::pair &p1, const std::pair &p2) const { return p1.second.contains(p2.second); } bool operator()(const choice &c1, const choice &c2) const { return c1.contains(c2); } }; generic_choice_set(const imm::map &_install_version_choices, const imm::set &_not_install_version_choices) : install_version_choices(_install_version_choices), not_install_version_choices(_not_install_version_choices) { } public: generic_choice_set() { } const_iterator begin() const { return const_iterator(install_version_choices.begin(), install_version_choices.end(), not_install_version_choices.begin()); } const_iterator end() const { return const_iterator(install_version_choices.end(), install_version_choices.end(), not_install_version_choices.end()); } /** \brief Insert every choice in the given set into this set, * overriding more general options with more specific ones. */ void insert_or_narrow(const imm::set &choices) { choices.for_each(insert_choice_narrow(*this)); } /** \brief Insert every choice in the given set into this set, * overriding more general options with more specific ones. * * \todo We should implement a fast set union operation on imm::set * (the balanced trees paper has a nice example of how to do this * IIRC). In this case we'd need some function object to ensure * that matching elements in the by-package map are combined * properly. */ void insert_or_narrow(const generic_choice_set &other) { other.for_each(insert_choice_narrow(*this)); } /** \brief Insert the given choice into this set, overriding more * general options with more specific ones. * * \param c The choice to insert. * * If the set contains a choice that is more general than c, that * choice will be dropped in favor c. On the other hand, if the * set contains a choice that is more specific than c, c will be * discarded in favor of that choice. */ void insert_or_narrow(const choice &c) { insert_choice_narrow(*this)(c); } /** \brief Remove everything that overlaps the given choice from * this set. * * This is used to narrow promotion sets while backpropagating * promotions. */ void remove_overlaps(const choice &c) { switch(c.get_type()) { case choice::install_version: install_version_choices.erase(c.get_ver().get_package()); break; default: not_install_version_choices.erase(c); break; } } /** \brief If a choice in this set contains c, store it in * out and return true. */ bool get_containing_choice(const choice &c, choice &out) const { switch(c.get_type()) { case choice::install_version: { typename imm::map::node n = install_version_choices.lookup(c.get_ver().get_package()); if(!n.isValid()) return false; else { std::pair existing_choice_pair(n.getVal()); choice &existing_choice(existing_choice_pair.second); if(existing_choice.contains(c)) { out = existing_choice; return true; } else return false; } } default: { typename imm::set::node found = not_install_version_choices.find_node(c); if(found.isValid()) { out = found.getVal(); return true; } else return false; } } } bool contains(const choice &c) const { choice dummy; return get_containing_choice(c, dummy); } /** \brief If a choice in this set is contained in c, store it in * out and return true. */ bool get_choice_contained_by(const choice &c, choice &out) const { switch(c.get_type()) { case choice::install_version: { typename imm::map::node n = install_version_choices.lookup(c.get_ver().get_package()); if(!n.isValid()) return false; else { std::pair existing_choice_pair(n.getVal()); choice &existing_choice(existing_choice_pair.second); if(c.contains(existing_choice)) { out = existing_choice; return true; } else return false; } } default: { typename imm::set::node found = not_install_version_choices.find_node(c); if(found.isValid()) { out = found.getVal(); return true; } else return false; } } } /** \brief Test whether some element of this set is contained by c. * * Used when testing promotions, for instance. */ bool has_contained_choice(const choice &c) const { choice dummy; return get_choice_contained_by(c, dummy); } typedef unsigned int size_type; size_type size() const { return install_version_choices.size() + not_install_version_choices.size(); } bool operator==(const generic_choice_set &other) const { return install_version_choices == other.install_version_choices && not_install_version_choices == other.not_install_version_choices; } bool operator!=(const generic_choice_set &other) const { return !(*this == other); } int compare(const generic_choice_set &other) const { const int cmp_install_version_choices = aptitude::util::compare3(install_version_choices, other.install_version_choices); if(cmp_install_version_choices != 0) return cmp_install_version_choices; else return aptitude::util::compare3(not_install_version_choices, other.not_install_version_choices); } bool operator<(const generic_choice_set &other) const { return compare(other) < 0; } /** \brief Check whether each entry in the other set is contained by * an entry in this set. */ bool contains(const generic_choice_set &other) const { const choice_contains f; return install_version_choices.is_supermap_of_under(other.install_version_choices, f) && not_install_version_choices.contains(other.not_install_version_choices, f); } /** \brief Check whether each entry in the other set contains an * entry in this set. */ bool subset_is_contained_in(const generic_choice_set &other) const { const choice_is_contained_in f; return install_version_choices.is_supermap_of_under(other.install_version_choices, f) && not_install_version_choices.contains(other.not_install_version_choices, f); } /** \brief Apply a function object to each element in this set. * * If the function returns \b false, it will short-circuit. */ template bool for_each(const F &f) const { if(!install_version_choices.for_each(for_each_choice_pair(f))) return false; return not_install_version_choices.for_each(f); } /** \brief Return a new choice set that does not share memory with * this set. */ generic_choice_set clone() const { return generic_choice_set(install_version_choices.clone(), not_install_version_choices.clone()); } /** \brief Retrieve the version, if any, that was chosen for the * given package. * * \param p The package to examine. * \param out A location in which to store the retrieved version. * * \return \b true if a version of the package p is installed * by this choice set; \b false if not (in which case out is * unchanged). */ bool get_version_of(const package &p, version &out) const { typename imm::map::node found = install_version_choices.lookup(p); if(found.isValid()) { out = found.getVal().second.get_ver(); return true; } else return false; } }; template std::ostream &operator<<(std::ostream &out, const generic_choice_set &choices) { out << "{"; typename generic_choice_set::show_choice f(out, true); choices.for_each(f); out << "}"; return out; } #endif // CHOICE_SET_H