File indexing completed on 2024-04-28 15:39:58

0001 // SPDX-FileCopyrightText: 2003 - 2022 Jesper K. Pedersen <blackie@kde.org>
0002 // SPDX-FileCopyrightText: 2023 - 2024 Johannes Zarl-Zierl <johannes@zarl-zierl.at>
0003 //
0004 // SPDX-License-Identifier: GPL-2.0-or-later
0005 
0006 #ifndef MEMBERMAP_H
0007 #define MEMBERMAP_H
0008 #include <kpabase/StringSet.h>
0009 
0010 #include <qmap.h>
0011 #include <qobject.h>
0012 #include <qstringlist.h>
0013 
0014 namespace DB
0015 {
0016 using Utilities::StringSet;
0017 
0018 class Category;
0019 
0020 /**
0021  * @brief The MemberMap class represents tag groups for all categories.
0022  * The MemberMap can handle direct tag group membership, and also computes the membership closure (i.e. indirect membership).
0023  *
0024  * Tag groups in a category are a directed acyclic graph (DAG).
0025  * All MemberMap functions preserve the validity of the DAG (after loading the database is completed).
0026  */
0027 class MemberMap : public QObject
0028 {
0029     Q_OBJECT
0030 public:
0031     MemberMap();
0032     MemberMap(const MemberMap &);
0033     virtual MemberMap &operator=(const MemberMap &);
0034 
0035     // TODO: this should return a StringSet
0036     /**
0037      * @brief groups
0038      * @param category the category name
0039      * @return the groups directly available from category (non closure that is)
0040      */
0041     virtual QStringList groups(const QString &category) const;
0042     /**
0043      * @brief deleteGroup deletes the a group from the category.
0044      * Does nothing if category does not have any groups or groupName is not a member of category.
0045      * @param category the category name
0046      * @param groupName
0047      */
0048     virtual void deleteGroup(const QString &category, const QString &groupName);
0049 
0050     // TODO: this should return a StringSet
0051     /**
0052      * @brief members
0053      *
0054      * @param category the name of the category
0055      * @param memberGroup the name of the tag group
0056      * @param closure if \c true, return indirect members as well. If \c false, only return direct members.
0057      *
0058      * @return all the members of memberGroup
0059      */
0060     virtual QStringList members(const QString &category, const QString &memberGroup, bool closure) const;
0061     /**
0062      * @brief setMembers set (direct) members for a tag group, ensuring no cycles in the group membership DAG.
0063      *
0064      * If the group already exists, its contents are replaced.
0065      *
0066      * @param category a valid category name
0067      * @param memberGroup a tag group name
0068      * @param members a list of tags
0069      */
0070     virtual void setMembers(const QString &category, const QString &memberGroup, const QStringList &members);
0071     /**
0072      * @brief isEmpty
0073      * @return \c true, if the MemberMap is empty, \c false otherwise.
0074      */
0075     virtual bool isEmpty() const;
0076     /**
0077      * @brief isGroup
0078      * @param category
0079      * @param memberGroup
0080      * @return \c true if item is a group for category, \c false otherwise.
0081      */
0082     virtual bool isGroup(const QString &category, const QString &memberGroup) const;
0083     /**
0084      * @brief groupMap returns a map of all groups in the given category.
0085      * Note: the returned map contains direct and indirect members for each group (i.e. all closure members).
0086      *
0087      * Example: { USA |-> [Chicago, Grand Canyon, Santa Clara], Denmark |-> [Esbjerg, Odense] }
0088      *
0089      * @param category
0090      * @return a map from groupName to list of items for category, or an empty map if category has no groups.
0091      */
0092     virtual QMap<QString, StringSet> groupMap(const QString &category) const;
0093 
0094     // TODO(jzarl): document MemberMap::inverseMap
0095     virtual QMap<QString, StringSet> inverseMap(const QString &category) const;
0096     /**
0097      * @brief renameGroup renames a group in category.
0098      * Does nothing, if category does not have any groups, if oldName is not a member of category, or if newName does already exist.
0099      * @param category the category name
0100      * The string \c newName is trimmed to disallow leading or trailing whitespace in names.
0101      * @param oldName
0102      * @param newName
0103      */
0104     virtual void renameGroup(const QString &category, const QString &oldName, const QString &newName);
0105     /**
0106      * @brief renameCategory renames the category from oldName to newName
0107      * Does nothing, if the oldName does not have any groups, if oldName is the same as newName, or if newName already exists.
0108      * The string \c newName is trimmed to disallow leading or trailing whitespace in names.
0109      * @param oldName
0110      * @param newName
0111      */
0112     virtual void renameCategory(const QString &oldName, const QString &newName);
0113 
0114     /**
0115      * @brief addGroup adds the tag group to category.
0116      * The string \c group is trimmed to disallow leading or trailing whitespace in names.
0117      * Does nothing if the group name is empty.
0118      * @param category the category name
0119      * @param group the tag group name
0120      */
0121     virtual void addGroup(const QString &category, const QString &group);
0122     /**
0123      * @brief canAddMemberToGroup checks if adding a tag to a group is possible.
0124      * Adding a tag to a group is not possible, if doing so would create a cycle in the group membership DAG.
0125      * @param category
0126      * @param group
0127      * @param item
0128      * @return \c true, if it is possible to add a tag to the group, or \c false otherwise.
0129      */
0130     bool canAddMemberToGroup(const QString &category, const QString &group, const QString &item) const
0131     {
0132         // If there already is a path from item to group then adding the
0133         // item to group would create a cycle, which we don't want.
0134         return !hasPath(category, item, group);
0135     }
0136     virtual void addMemberToGroup(const QString &category, const QString &group, const QString &item);
0137     /**
0138      * @brief removeMemberFromGroup removes a tag from a tag group in the given category.
0139      * Do nothing if the category or group is not known, or if the tag does not exist.
0140      * @param category
0141      * @param group the name of the tag group
0142      * @param item the name of the tag
0143      */
0144     virtual void removeMemberFromGroup(const QString &category, const QString &group, const QString &item);
0145 
0146     /**
0147      * @brief memberMap
0148      * @return the raw member map data
0149      */
0150     virtual const QMap<QString, QMap<QString, StringSet>> &memberMap() const { return m_members; }
0151 
0152     /**
0153      * @brief hasPath checks if the group directly or indirectly contains the tag.
0154      *
0155      * @param category a category name
0156      * @param from a group name
0157      * @param to a tag name
0158      * @return \c true if tag \c to can be reached from the \c from group in the group membership DAG, \c false otherwise.
0159      */
0160     virtual bool hasPath(const QString &category, const QString &from, const QString &to) const;
0161     /**
0162      * @brief contains checks if item is a member of category.
0163      * @param category
0164      * @param item
0165      * @return \c true, if item is contained in category.
0166      */
0167     virtual bool contains(const QString &category, const QString &item) const;
0168 
0169 protected:
0170     /**
0171      * @brief Recalculate indirect group memberships.
0172      *
0173      * This methods create the map _closureMembers from _members
0174      * This is to avoid finding the closure each and every time it is needed.
0175      *
0176      * This method needs to be called whenever indirect group membership is affected by an action.
0177      */
0178     void calculate() const;
0179     /**
0180      * @brief calculateClosure calculates the closure for group, that is finds all members for group.
0181      *
0182      * Imagine there is a group called USA, and that this groups has a group inside it called Califonia,
0183      * Califonia consists of members San Fransisco and Los Angeless.
0184      * This function then maps USA to include Califonia, San Fransisco and Los Angeless.
0185      * @param resultSoFar
0186      * @param category
0187      * @param group
0188      * @return
0189      */
0190     QStringList calculateClosure(QMap<QString, StringSet> &resultSoFar, const QString &category, const QString &group) const;
0191 
0192 public Q_SLOTS:
0193     /**
0194      * @brief deleteCategory deletes the category and its groups from the MemberMap
0195      * Does nothing if category does not have any groups.
0196      * @param category
0197      */
0198     virtual void deleteCategory(const QString &category);
0199     /**
0200      * @brief deleteItem deletes a tag from the category
0201      * Does nothing if the category is not known, or if the tag does not exist.
0202      * @param category a valid pointer to a category
0203      * @param name name of the tag
0204      */
0205     virtual void deleteItem(DB::Category *category, const QString &name);
0206     /**
0207      * @brief renameItem renames a tag of the given category.
0208      * Does nothing if the category is not known, if oldName does not exist, or if the new name is the same as the old name.
0209      * The string \c newName is trimmed to disallow leading or trailing whitespace in names.
0210      * @param category a valid pointer to a category
0211      * @param oldName
0212      * @param newName
0213      */
0214     virtual void renameItem(DB::Category *category, const QString &oldName, const QString &newName);
0215     /**
0216      * @brief setLoading tells the MemberMap to avoid costly calculations and checks while the database is loaded from disk.
0217      * @param isLoading
0218      */
0219     void setLoading(bool isLoading);
0220 
0221 Q_SIGNALS:
0222     void dirty();
0223 
0224 private:
0225     void markDirty(const QString &category);
0226     void regenerateFlatList(const QString &category);
0227     // This is the primary data structure
0228     // { category |-> { group |-> [ member ] } } <- VDM syntax ;-)
0229     QMap<QString, QMap<QString, StringSet>> m_members;
0230     mutable QMap<QString, QSet<QString>> m_flatMembers;
0231 
0232     // These are the data structures used to develop closures, they are only
0233     // needed to speed up the program *SIGNIFICANTLY* ;-)
0234     mutable bool m_dirty;
0235     mutable QMap<QString, QMap<QString, StringSet>> m_closureMembers;
0236 
0237     bool m_loading;
0238 };
0239 }
0240 
0241 #endif /* MEMBERMAP_H */
0242 
0243 // vi:expandtab:tabstop=4 shiftwidth=4: