/* $Id: Matching.h $ */ /** @file * Declaration of template classes that provide simple API to * do matching between values and value filters constructed from strings. */ /* * Copyright (C) 2006-2023 Oracle and/or its affiliates. * * This file is part of VirtualBox base platform packages, as * available from https://www.virtualbox.org. * * 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, in version 3 of the * License. * * 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; if not, see . * * SPDX-License-Identifier: GPL-3.0-only */ #ifndef MAIN_INCLUDED_Matching_h #define MAIN_INCLUDED_Matching_h #ifndef RT_WITHOUT_PRAGMA_ONCE # pragma once #endif #include #include #include #include // min and max don't allow us to use numeric_limits::min() and max() #if defined (_MSC_VER) #undef min #undef max #endif namespace matching { using namespace std; using namespace com; class ParsedFilter_base { public: ParsedFilter_base() : mValid (false), mNull (true), mErrorPosition (0) {}; /** * Returns @c true if the filter is valid, @c false otherwise. */ bool isValid() const { return mNull || mValid; } bool isNull() const { return mNull; } /** * Returns the error position from the beginning of the filter * string if #isValid() is false. Positions are zero-based. */ size_t errorPosition() const { return mErrorPosition; } protected: /** * Returns @c true if current isNull() and isValid() values make further * detailed matching meaningful, otherwise returns @c false. * Must be called as a first method of every isMatch() implementation, * so that isMatch() will immediately return @c false if isPreMatch() returns * false. */ bool isPreMatch() const { if (isNull() || !isValid()) return false; return true; } bool mValid : 1; bool mNull : 1; size_t mErrorPosition; }; class ParsedIntervalFilter_base : public ParsedFilter_base { public: virtual ~ParsedIntervalFilter_base() { /* Make VC++ 14.2 happy */ } protected: enum Mode { Single, Start, End }; union Widest { int64_t ll; uint64_t ull; }; struct Limits { Widest min; Widest max; }; ParsedIntervalFilter_base() {} /** * Called by #parse when a value token is encountered. * This method can modify mNull, mValid and mErrorPosition when * appropriate. Parsing stops if mValid is false after this method * returns (mErrorPosition most point to the error position in this case). */ virtual void parseValue (const char *aFilter, size_t aStart, size_t aEnd, Mode aMode) = 0; static void parse (const char *aFilter, ParsedIntervalFilter_base *that); static size_t parseValue (const char *aFilter, size_t aStart, size_t aEnd, bool aIsSigned, const Limits &aLimits, Widest &val); }; /** * Represents a parsed interval filter. * The string format is: * "int:(\|([\]-[\]))|(\|([\]-[\]))+" * where \ and \ are numbers in the decimal, hex (0xNNN) or octal * (0NNN) form, and \ \< \. Spaces are allowed around \ and \. * * @tparam T type of values to match. Must be a fundamental integer type. */ template class ParsedIntervalFilter : public ParsedIntervalFilter_base { typedef ParsedIntervalFilter_base Base; typedef numeric_limits Lim; typedef std::list List; typedef std::pair Pair; typedef std::list PairList; public: ParsedIntervalFilter() {} ParsedIntervalFilter (const Bstr &aFilter) { Base::parse (Utf8Str (aFilter), this); } ParsedIntervalFilter &operator= (const Bstr &aFilter) { mValues.clear(); mIntervals.clear(); Base::parse (Utf8Str (aFilter), this); return *this; } bool isMatch (const T &aValue) const { if (!isPreMatch()) return false; { typename List::const_iterator it = std::find (mValues.begin(), mValues.end(), aValue); if (it != mValues.end()) return true; } for (typename PairList::const_iterator it = mIntervals.begin(); it != mIntervals.end(); ++ it) { if ((*it).first <= aValue && aValue <= (*it).second) return true; } return false; } protected: struct Limits : public Base::Limits { Limits() { if (Lim::is_signed) { min.ll = (int64_t) Lim::min(); max.ll = (int64_t) Lim::max(); } else { min.ull = (uint64_t) Lim::min(); max.ull = (uint64_t) Lim::max(); } } static T toValue (const Widest &aWidest) { if (Lim::is_signed) return (T) aWidest.ll; else return (T) aWidest.ull; } }; virtual void parseValue (const char *aFilter, size_t aStart, size_t aEnd, Mode aMode) { AssertReturn (Lim::is_integer, (void) 0); AssertReturn ( (Lim::is_signed && Lim::digits <= numeric_limits ::digits) || (!Lim::is_signed && Lim::digits <= numeric_limits ::digits), (void) 0); Limits limits; Widest val; size_t parsed = aEnd; if (aStart != aEnd) parsed = Base::parseValue (aFilter, aStart, aEnd, Lim::is_signed, limits, val); if (parsed != aEnd) { mValid = false; mErrorPosition = parsed; return; } switch (aMode) { /// @todo (dmik): future optimizations: // 1) join intervals when they overlap // 2) ignore single values that are within any existing interval case Base::Single: { if (aStart == aEnd) { // an empty string (contains only spaces after "int:") mValid = false; mErrorPosition = aEnd; AssertReturn (!mValues.size() && !mIntervals.size(), (void) 0); break; } mValues.push_back (limits.toValue (val)); break; } case Base::Start: { // aStart == aEnd means smth. like "-[NNN]" T m = aStart == aEnd ? limits.toValue (limits.min) : limits.toValue (val); mIntervals.push_back (Pair (m, m)); break; } case Base::End: { // aStart == aEnd means smth. like "[NNN]-" T n = aStart == aEnd ? limits.toValue (limits.max) : limits.toValue (val); if (n < mIntervals.back().first) { // error at the beginning of N mValid = false; mErrorPosition = aStart; break; } mIntervals.back().second = n; break; } } } std::list mValues; std::list > mIntervals; }; /** * Represents a boolean filter. * The string format is: "true|false|yes|no|1|0" or an empty string (any match). */ class ParsedBoolFilter : public ParsedFilter_base { public: ParsedBoolFilter() : mValue (false), mValueAny (false) {} ParsedBoolFilter (const Bstr &aFilter) { parse (aFilter); } ParsedBoolFilter &operator= (const Bstr &aFilter) { parse (aFilter); return *this; } bool isMatch (const bool aValue) const { if (!isPreMatch()) return false; return mValueAny || mValue == aValue; } bool isMatch (const BOOL aValue) const { return isMatch (bool (aValue == TRUE)); } private: void parse (const Bstr &aFilter); bool mValue : 1; bool mValueAny : 1; }; class ParsedRegexpFilter_base : public ParsedFilter_base { protected: ParsedRegexpFilter_base (bool aDefIgnoreCase = false, size_t aMinLen = 0, size_t aMaxLen = 0) : mIgnoreCase (aDefIgnoreCase) , mMinLen (aMinLen) , mMaxLen (aMaxLen) {} ParsedRegexpFilter_base (const Bstr &aFilter, bool aDefIgnoreCase = false, size_t aMinLen = 0, size_t aMaxLen = 0) : mIgnoreCase (aDefIgnoreCase) , mMinLen (aMinLen) , mMaxLen (aMaxLen) { parse (aFilter); } ParsedRegexpFilter_base &operator= (const Bstr &aFilter) { parse (aFilter); return *this; } bool isMatch (const Bstr &aValue) const; private: void parse (const Bstr &aFilter); bool mIgnoreCase : 1; size_t mMinLen; size_t mMaxLen; Bstr mSimple; }; /** * Represents a parsed regexp filter. * * The string format is: "rx:\" or "\" * where \ is a valid regexp and \ is the exact match. * * @tparam Conv * class that must define a public static function * Bstr toBstr (T aValue), where T is the * type of values that should be accepted by #isMatch(). * This function is used to get the string representation of T * for regexp matching. * @tparam aIgnoreCase * true if the case insensitive comparison should be done by default * and false otherwise * @tparam aMinLen * minimum string length, or 0 if not limited. * Used only when the filter string represents the exact match. * @tparam aMaxLen * maximum string length, or 0 if not limited. * Used only when the filter string represents the exact match. */ template class ParsedRegexpFilter : public ParsedRegexpFilter_base { public: enum { IgnoreCase = aIgnoreCase, MinLen = aMinLen, MaxLen = aMaxLen }; ParsedRegexpFilter() : ParsedRegexpFilter_base (IgnoreCase, MinLen, MaxLen) {} ParsedRegexpFilter (const Bstr &aFilter) : ParsedRegexpFilter_base (aFilter, IgnoreCase, MinLen, MaxLen) {} ParsedRegexpFilter &operator= (const Bstr &aFilter) { ParsedRegexpFilter_base::operator= (aFilter); return *this; } template bool isMatch (const T &aValue) const { if (!this->isPreMatch()) return false; return ParsedRegexpFilter_base::isMatch (Conv::toBstr (aValue)); } protected: }; /** * Joins two filters into one. * Only one filter is active (i.e. used for matching or for error reporting) * at any given time. The active filter is chosen every time when a new * filter string is assigned to an instance of this class -- the filter * for which isNull() = false after parsing the string becomes the active * one (F1 is tried first). * * Both filters must have bool isMatch(const T&) methods where T is * the same type as used in #isMatch(). * * @tparam F1 first filter class * @tparam F2 second filter class */ template class TwoParsedFilters { public: TwoParsedFilters() {} TwoParsedFilters (const Bstr &aFilter) { mFilter1 = aFilter; if (mFilter1.isNull()) mFilter2 = aFilter; } TwoParsedFilters &operator= (const Bstr &aFilter) { mFilter1 = aFilter; if (mFilter1.isNull()) mFilter2 = aFilter; else mFilter2 = F2(); // reset to null return *this; } template bool isMatch (const T &aValue) const { return mFilter1.isMatch (aValue) || mFilter2.isMatch (aValue); } bool isValid() const { return isNull() || (mFilter1.isValid() && mFilter2.isValid()); } bool isNull() const { return mFilter1.isNull() && mFilter2.isNull(); } size_t errorPosition() const { return !mFilter1.isValid() ? mFilter1.errorPosition() : !mFilter2.isValid() ? mFilter2.errorPosition() : 0; } const F1 &first() const { return mFilter1; } const F2 &second() const { return mFilter2; } private: F1 mFilter1; F2 mFilter2; }; /** * Inherits from the given parsed filter class and keeps the string used to * construct the filter as a member. * * @tparam F parsed filter class */ template class Matchable : public F { public: Matchable() {} /** * Creates a new parsed filter from the given filter string. * If the string format is invalid, #isValid() will return false. */ Matchable (const Bstr &aString) : F (aString), mString (aString) {} Matchable (CBSTR aString) : F (Bstr (aString)), mString (aString) {} /** * Assigns a new filter string to this object and recreates the parser. * If the string format is invalid, #isValid() will return false. */ Matchable &operator= (const Bstr &aString) { F::operator= (aString); mString = aString; return *this; } Matchable &operator= (CBSTR aString) { F::operator= (Bstr (aString)); mString = aString; return *this; } /** * Returns the filter string allowing to use the instance where * Str can be used. */ operator const Bstr&() const { return mString; } /** Returns the filter string */ const Bstr& string() const { return mString; } private: Bstr mString; }; } /* namespace matching */ #endif /* !MAIN_INCLUDED_Matching_h */ /* vi: set tabstop=4 shiftwidth=4 expandtab: */