diff --git a/src/blackmisc/logpattern.cpp b/src/blackmisc/logpattern.cpp new file mode 100644 index 000000000..adb5f658b --- /dev/null +++ b/src/blackmisc/logpattern.cpp @@ -0,0 +1,303 @@ +/* Copyright (C) 2014 + * Swift Project Community / Contributors + * + * This file is part of Swift Project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#include "logpattern.h" +#include "logcategorylist.h" + +namespace BlackMisc +{ + CLogPattern::CLogPattern(Strategy strategy, const QSet &strings) + : m_strategy(strategy), m_strings(strings) + { + static const decltype(m_severities) s + { + CStatusMessage::SeverityDebug, + CStatusMessage::SeverityInfo, + CStatusMessage::SeverityWarning, + CStatusMessage::SeverityError + }; + m_severities = s; + } + + CLogPattern::CLogPattern() : CLogPattern(Everything, {}) + {} + + CLogPattern CLogPattern::exactMatch(const CLogCategory &category) + { + return { ExactMatch, { category.toQString() } }; + } + + CLogPattern CLogPattern::anyOf(const CLogCategoryList &categories) + { + if (categories.size() == 0) { return empty(); } + if (categories.size() == 1) { return exactMatch(categories[0]); } + return { AnyOf, QSet::fromList(categories.toQStringList()) }; + } + + CLogPattern CLogPattern::allOf(const CLogCategoryList &categories) + { + if (categories.size() == 0) { return {}; } + if (categories.size() == 1) { return exactMatch(categories[0]); } + return { AllOf, QSet::fromList(categories.toQStringList()) }; + } + + CLogPattern CLogPattern::startsWith(const QString &prefix) + { + return { StartsWith, { prefix } }; + } + + CLogPattern CLogPattern::endsWith(const QString &suffix) + { + return { EndsWith, { suffix } }; + } + + CLogPattern CLogPattern::contains(const QString &substring) + { + return { Contains, { substring } }; + } + + CLogPattern CLogPattern::empty() + { + return { Nothing, {} }; + } + + CLogPattern CLogPattern::withSeverity(CStatusMessage::StatusSeverity severity) const + { + auto result = *this; + result.m_severities = { severity }; + return result; + } + + CLogPattern CLogPattern::withSeverities(const QSet &severities) const + { + auto result = *this; + result.m_severities = severities; + return result; + } + + CLogPattern CLogPattern::withSeverityAtOrAbove(CStatusMessage::StatusSeverity minimumSeverity) const + { + auto result = *this; + result.m_severities.clear(); + switch (minimumSeverity) + { + // there are deliberately no break statements in this switch block + default: + case CStatusMessage::SeverityDebug: result.m_severities.insert(CStatusMessage::SeverityDebug); + case CStatusMessage::SeverityInfo: result.m_severities.insert(CStatusMessage::SeverityInfo); + case CStatusMessage::SeverityWarning: result.m_severities.insert(CStatusMessage::SeverityWarning); + case CStatusMessage::SeverityError: result.m_severities.insert(CStatusMessage::SeverityError); + } + return result; + } + + bool CLogPattern::checkInvariants() const + { + switch (m_strategy) + { + case Everything: return m_strings.isEmpty(); + case ExactMatch: return m_strings.size() == 1; + case AnyOf: return m_strings.size() > 1; + case AllOf: return m_strings.size() > 1; + case StartsWith: return m_strings.size() == 1; + case EndsWith: return m_strings.size() == 1; + case Contains: return m_strings.size() == 1; + case Nothing: return m_strings.isEmpty(); + default: return false; + } + } + + bool CLogPattern::match(const CStatusMessage &message) const + { + if (! checkInvariants()) + { + Q_ASSERT(false); + return true; + } + + if (! m_severities.contains(message.getSeverity())) + { + return false; + } + + switch (m_strategy) + { + default: + case Everything: return true; + case ExactMatch: return message.getCategories().contains(getString()); + case AnyOf: return std::any_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return message.getCategories().contains(s); }); + case AllOf: return std::all_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return message.getCategories().contains(s); }); + case StartsWith: return message.getCategories().containsBy([this](const CLogCategory &cat) { return cat.startsWith(getPrefix()); }); + case EndsWith: return message.getCategories().containsBy([this](const CLogCategory &cat) { return cat.endsWith(getSuffix()); }); + case Contains: return message.getCategories().containsBy([this](const CLogCategory &cat) { return cat.contains(getSubstring()); }); + case Nothing: return message.getCategories().isEmpty(); + } + } + + bool CLogPattern::isProperSubsetOf(const CLogPattern &other) const + { + if (! (checkInvariants() && other.checkInvariants())) + { + Q_ASSERT(false); + return false; + } + + // For this function to return true, the severities matched by this pattern must be a subset of the severities + // matched by the other, and the categories matched by this pattern must be a subset of the categories matched + // by the other, and at least one of these two subset relations must be a proper subset relation. + + if (! other.m_severities.contains(m_severities)) + { + // Severities are not a subset + return false; + } + if (m_strategy == other.m_strategy && m_strings == other.m_strings) + { + // Severities are a subset, and categories are an improper subset, so it all depends + // on whether the subset relation of the severities is proper or improper + return m_severities.size() < other.m_severities.size(); + } + + // If we got this far then the severity set is a (proper or improper) subset of the other severity set, + // and the question of whether this pattern is a proper subset of the other pattern depends upon whether the + // set of categories matched by this pattern is a proper subset of the set of categories matched by the other. + // The matrix below is a guide to the implementation that follows. + // + // Matrix of "categories matched by X is proper subset of categories matched by Y" for two strategies X and Y + // 0 means always false + // 1 means always true + // ? means it depends on a particular relation between their string sets + // + // Y Ev EM An Al SW EW Co No + // X + // Ev 0 0 0 0 0 0 0 0 (Everything) + // EM 1 0 ? 0 ? ? ? 0 (ExactMatch) + // An 1 0 ? 0 ? ? ? 0 (AnyOf) + // Al 1 ? ? ? ? ? ? 0 (AllOf) + // SW 1 0 0 0 ? 0 ? 0 (StartsWith) + // EW 1 0 0 0 0 ? ? 0 (EndsWith) + // Co 1 0 0 0 0 0 ? 0 (Contains) + // No 1 0 0 0 0 0 0 0 (Nothing) + + if (m_strategy != Everything && other.m_strategy == Everything) + { + return true; + } + switch (m_strategy) + { + case ExactMatch: + switch (other.m_strategy) + { + case AnyOf: return other.m_strings.contains(getString()); + case StartsWith: return getString().startsWith(other.getPrefix()); + case EndsWith: return getString().endsWith(other.getSuffix()); + case Contains: return getString().contains(other.getSubstring()); + } + case AnyOf: + switch (other.m_strategy) + { + case AnyOf: return other.m_strings.contains(m_strings) && other.m_strings.size() > m_strings.size(); + case StartsWith: return std::all_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.startsWith(other.getPrefix()); }); + case EndsWith: return std::all_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.endsWith(other.getSuffix()); }); + case Contains: return std::all_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.contains(other.getSubstring()); }); + } + case AllOf: + switch (other.m_strategy) + { + case ExactMatch: return m_strings.contains(other.getString()); + case AnyOf: return ! (m_strings & other.m_strings).isEmpty(); + case AllOf: return m_strings.contains(other.m_strings) && m_strings.size() > other.m_strings.size(); + case StartsWith: return std::any_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.startsWith(other.getPrefix()); }); + case EndsWith: return std::any_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.endsWith(other.getSuffix()); }); + case Contains: return std::any_of(m_strings.begin(), m_strings.end(), [ & ](const QString &s) { return s.contains(other.getSubstring()); }); + } + case StartsWith: + switch (other.m_strategy) + { + case StartsWith: return getPrefix().startsWith(other.getPrefix()) && getPrefix().size() > other.getPrefix().size(); + case Contains: return getPrefix().contains(other.getSubstring()); + } + case EndsWith: + switch (other.m_strategy) + { + case EndsWith: return getSuffix().endsWith(other.getSuffix()) && getSuffix().size() > other.getSuffix().size(); + case Contains: return getSuffix().contains(other.getSubstring()); + } + case Contains: + switch (other.m_strategy) + { + case Contains: return getSubstring().contains(other.getSubstring()) && getSubstring().size() > other.getSubstring().size(); + } + } + return false; + } + + void CLogPattern::registerMetadata() + { + qRegisterMetaType(); + qDBusRegisterMetaType(); + } + + uint CLogPattern::getValueHash() const + { + return 0; //TODO qHash(TupleConverter::toMetaTuple(*this)); + } + + bool CLogPattern::operator ==(const CLogPattern &other) const + { + return TupleConverter::toMetaTuple(*this) == TupleConverter::toMetaTuple(other); + } + + bool CLogPattern::operator !=(const CLogPattern &other) const + { + return TupleConverter::toMetaTuple(*this) != TupleConverter::toMetaTuple(other); + } + + QString CLogPattern::convertToQString(bool i18n) const + { + Q_UNUSED(i18n); + return {}; //TODO + } + + int CLogPattern::getMetaTypeId() const + { + return qMetaTypeId(); + } + + bool CLogPattern::isA(int metaTypeId) const + { + if (metaTypeId == qMetaTypeId()) { return true; } + return this->CValueObject::isA(metaTypeId); + } + + int CLogPattern::compareImpl(const CValueObject &otherBase) const + { + //const auto &other = static_cast(otherBase); + return 0; //TODO compare(TupleConverter::toMetaTuple(*this), TupleConverter::toMetaTuple(other)); + } + + void CLogPattern::marshallToDbus(QDBusArgument &argument) const + { + //TODO argument << TupleConverter::toMetaTuple(*this); + auto begin = Iterators::makeTransformIterator(m_severities.begin(), [](CStatusMessage::StatusSeverity s) { return static_cast(s); }); + QList severities = makeRange(begin, m_severities.end()); + argument << severities << m_strategy << m_strings.toList(); + } + + void CLogPattern::unmarshallFromDbus(const QDBusArgument &argument) + { + //TODO argument >> TupleConverter::toMetaTuple(*this); + QList severities; + QStringList strings; + argument >> severities >> m_strategy >> strings; + auto begin = Iterators::makeTransformIterator(severities.begin(), [](int i) { return static_cast(i); }); + m_severities = QSet::fromList(makeRange(begin, severities.end())); + m_strings = strings.toSet(); + } +} diff --git a/src/blackmisc/logpattern.h b/src/blackmisc/logpattern.h new file mode 100644 index 000000000..f6ed379d6 --- /dev/null +++ b/src/blackmisc/logpattern.h @@ -0,0 +1,141 @@ +/* Copyright (C) 2014 + * Swift Project Community / Contributors + * + * This file is part of Swift Project. It is subject to the license terms in the LICENSE file found in the top-level + * directory of this distribution and at http://www.swift-project.org/license.html. No part of Swift Project, + * including this file, may be copied, modified, propagated, or distributed except according to the terms + * contained in the LICENSE file. + */ + +#ifndef BLACKMISC_LOGPATTERN_H +#define BLACKMISC_LOGPATTERN_H + +//! \file + +#include "statusmessage.h" +#include + +namespace BlackMisc +{ + class CLogCategory; + class CLogCategoryList; + + /*! + * Value class for matching log messages based on their categories. + */ + class CLogPattern : public CValueObject + { + public: + //! Default constructed CLogPattern will match any message. + CLogPattern(); + + //! Returns a CLogPattern which will match any message with the given category. + static CLogPattern exactMatch(const CLogCategory &category); + + //! Returns a CLogPattern which will match any message with any of the given categories. + static CLogPattern anyOf(const CLogCategoryList &categories); + + //! Returns a CLogPattern which will match any message with all of the given categories. + static CLogPattern allOf(const CLogCategoryList &categories); + + //! Returns a CLogPattern which will match any message with a category which starts with the given prefix. + static CLogPattern startsWith(const QString &prefix); + + //! Returns a CLogPattern which will match any message with a category which ends with the given suffix. + static CLogPattern endsWith(const QString &suffix); + + //! Returns a CLogPattern which will match any message with a category which contains the given substring. + static CLogPattern contains(const QString &substring); + + //! Returns a CLogPattern which will match any message without a category. + static CLogPattern empty(); + + //! Returns a CLogPattern which will match the same messages as this one, but only with a given severity. + CLogPattern withSeverity(CStatusMessage::StatusSeverity severity) const; + + //! Returns a CLogPattern which will match the same messages as this one, but only with some given severities. + CLogPattern withSeverities(const QSet &severities) const; + + //! Returns a CLogPattern which will match the same messages, but only with a severity at or above the given severity. + CLogPattern withSeverityAtOrAbove(CStatusMessage::StatusSeverity minimumSeverity) const; + + //! Returns true if the given message matches this pattern. + bool match(const CStatusMessage &message) const; + + //! Returns true if this pattern is a proper subset of the other pattern. + //! \see https://en.wikipedia.org/wiki/Proper_subset + //! \details Pattern A is a proper subset of pattern B iff pattern B would match every category which pattern A matches, + //! plus at least one other category. This induces a partial ordering which can be used as the comparator in a + //! topological sorting algorithm, to sort patterns by their generality. + bool isProperSubsetOf(const CLogPattern &other) const; + + //! Register metadata + static void registerMetadata(); + + //! \copydoc CValueObject::toQVariant + virtual QVariant toQVariant() const override { return QVariant::fromValue(*this); } + + //! \copydoc CValueObject::convertFromQVariant + virtual void convertFromQVariant(const QVariant &variant) override { BlackMisc::setFromQVariant(this, variant); } + + //! \copydoc CValueObject::getValueHash + virtual uint getValueHash() const override; + + //! Equal operator + bool operator ==(const CLogPattern &other) const; + + //! Not equal operator + bool operator !=(const CLogPattern &other) const; + + protected: + //! \copydoc CValueObject::convertToQString() + virtual QString convertToQString(bool i18n = false) const override; + + //! \copydoc CValueObject::getMetaTypeId + virtual int getMetaTypeId() const override; + + //! \copydoc CValueObject::isA + virtual bool isA(int metaTypeId) const override; + + //! \copydoc CValueObject::compareImpl + virtual int compareImpl(const CValueObject &other) const override; + + //! \copydoc CValueObject::marshallToDbus() + virtual void marshallToDbus(QDBusArgument &argument) const override; + + //! \copydoc CValueObject::marshallFromDbus() + virtual void unmarshallFromDbus(const QDBusArgument &argument) override; + + private: + bool checkInvariants() const; + + enum Strategy + { + Everything = 0, + ExactMatch, + AnyOf, + AllOf, + StartsWith, + EndsWith, + Contains, + Nothing + }; + + CLogPattern(Strategy strategy, const QSet &strings); + + BLACK_ENABLE_TUPLE_CONVERSION(CLogPattern) + QSet m_severities; + Strategy m_strategy; + QSet m_strings; + + const QString &getString() const { Q_ASSERT(m_strategy == ExactMatch && m_strings.size() == 1); return *m_strings.begin(); } + const QString &getPrefix() const { Q_ASSERT(m_strategy == StartsWith && m_strings.size() == 1); return *m_strings.begin(); } + const QString &getSuffix() const { Q_ASSERT(m_strategy == EndsWith && m_strings.size() == 1); return *m_strings.begin(); } + const QString &getSubstring() const { Q_ASSERT(m_strategy == Contains && m_strings.size() == 1); return *m_strings.begin(); } + }; +} + +Q_DECLARE_METATYPE(BlackMisc::CLogPattern) +BLACK_DECLARE_TUPLE_CONVERSION(BlackMisc::CLogPattern, (o.m_severities, o.m_strategy, o.m_strings)) + +#endif