Create Filters class to store filter functions

Enhanced the generated code to now store the filter functions within an
array contained in a struct similar to other configured parameters.
This allows the generated property watches to use an index within that
array to access a pointer to a Filters class containing a given set of
filter functions. The set of filter functions would then be applied
against all the properties being watched.

Tested:
    Generated code provides structure needed to access filter functions
    Filter functions filter property values that fail the filter(s)

Change-Id: I8f1f882704de521f2ab393530ad7ef096314975d
Signed-off-by: Matthew Barth <msbarth@us.ibm.com>
diff --git a/src/filters.hpp b/src/filters.hpp
new file mode 100644
index 0000000..c7a5bcf
--- /dev/null
+++ b/src/filters.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "data_types.hpp"
+
+#include <algorithm>
+#include <functional>
+
+namespace phosphor
+{
+namespace dbus
+{
+namespace monitoring
+{
+
+/** @class Filters
+ *  @brief Filter interface
+ *
+ *  Filters of any type can be applied to property value changes.
+ */
+class Filters
+{
+  public:
+    Filters() = default;
+    Filters(const Filters&) = delete;
+    Filters(Filters&&) = default;
+    Filters& operator=(const Filters&) = delete;
+    Filters& operator=(Filters&&) = default;
+    virtual ~Filters() = default;
+
+    /** @brief Apply filter operations to a property value. */
+    virtual bool operator()(const any_ns::any& value) = 0;
+};
+
+/** @class OperandFilters
+ *  @brief Filter property values utilzing operand based functions.
+ *
+ *  When configured, an operand filter is applied to a property value each
+ *  time it changes to determine if that property value should be filtered
+ *  from being stored or used within a given callback function.
+ */
+template <typename T>
+class OperandFilters : public Filters
+{
+  public:
+    OperandFilters() = delete;
+    OperandFilters(const OperandFilters&) = delete;
+    OperandFilters(OperandFilters&&) = default;
+    OperandFilters& operator=(const OperandFilters&) = delete;
+    OperandFilters& operator=(OperandFilters&&) = default;
+    virtual ~OperandFilters() = default;
+    explicit OperandFilters(const std::vector<std::function<bool(T)>>& _ops) :
+        Filters(), ops(std::move(_ops))
+    {
+    }
+
+    bool operator()(const any_ns::any& value) override
+    {
+        for (const auto& filterOps : ops)
+        {
+            try
+            {
+                // Apply filter operand to property value
+                if (!filterOps(any_ns::any_cast<T>(value)))
+                {
+                    // Property value should be filtered
+                    return true;
+                }
+            }
+            catch (const any_ns::bad_any_cast& bac)
+            {
+                // Unable to cast property value to filter value type
+                // to check filter, continue to next filter op
+                continue;
+            }
+        }
+
+        // Property value should not be filtered
+        return false;
+    }
+
+  private:
+    /** @brief List of operand based filter functions. */
+    const std::vector<std::function<bool(T)>> ops;
+};
+
+} // namespace monitoring
+} // namespace dbus
+} // namespace phosphor
diff --git a/src/pdmgen.py b/src/pdmgen.py
index dc1c9b7..ef1b790 100755
--- a/src/pdmgen.py
+++ b/src/pdmgen.py
@@ -636,16 +636,86 @@
             config=self.configfile)
         super(HasPathIndex, self).setup(objs)
 
+class GroupOfFilters(ConfigEntry):
+    '''Handle config file directives that require an index for filters.'''
+
+    def __init__(self, *a, **kw):
+        # Pop filters data for adding to the available filters array
+        self.type = kw.pop('type')
+        self.datatype = kw.pop('datatype', None)
+        self.filters = kw.pop('filters', None)
+
+        super(GroupOfFilters, self).__init__(**kw)
+
+    def factory(self, objs):
+        '''Modify filters to add the property value type and
+        make them of operation argument type.'''
+        if self.filters:
+            # 'type' used within OpArgument to generate filter
+            # argument values so add to each filter
+            for f in self.filters:
+                f['type'] = self.type
+            self.filters = [OpArgument(**x) for x in self.filters]
+
+        super(GroupOfFilters, self).factory(objs)
+
 class PropertyWatch(HasPropertyIndex):
     '''Handle the property watch config file directive.'''
 
     def __init__(self, *a, **kw):
-        self.filters = [OpArgument(**x) for x in kw.pop('filters', {})]
+        # Pop optional filters for the properties being watched
+        self.filters = kw.pop('filters', None)
         self.callback = kw.pop('callback', None)
         super(PropertyWatch, self).__init__(**kw)
 
+    def factory(self, objs):
+        '''Create any filters for this property watch.'''
+
+        if self.filters:
+            # Get the datatype(i.e. "int64_t") of the properties in this watch
+            # (Made available after all `super` classes init'd)
+            datatype = objs['propertygroup'][get_index(
+                objs,
+                'propertygroup',
+                self.properties,
+                config=self.configfile)].datatype
+            # Get the type(i.e. "int64") of the properties in this watch
+            # (Made available after all `super` classes init'd)
+            type = objs['propertygroup'][get_index(
+                objs,
+                'propertygroup',
+                self.properties,
+                config=self.configfile)].type
+            # Construct the data needed to make the filters for
+            # this watch available.
+            # *Note: 'class', 'subclass', 'name' are required for
+            # storing the filter data(i.e. 'type', 'datatype', & 'filters')
+            args = {
+                'type': type,
+                'datatype': datatype,
+                'filters': self.filters,
+                'class': 'filtersgroup',
+                'filtersgroup': 'filters',
+                'name': self.name,
+            }
+            # Init GroupOfFilters class with this watch's filters' arguments
+            group = GroupOfFilters(configfile=self.configfile, **args)
+            # Store this group of filters so it can be indexed later
+            add_unique(group, objs, config=self.configfile)
+            group.factory(objs)
+
+        super(PropertyWatch, self).factory(objs)
+
     def setup(self, objs):
-        '''Resolve optional callback.'''
+        '''Resolve optional filters and callback.'''
+
+        if self.filters:
+            # Watch has filters, provide array index to access them
+            self.filters = get_index(
+                objs,
+                'filtersgroup',
+                self.name,
+                config=self.configfile)
 
         if self.callback:
             self.callback = get_index(
@@ -673,7 +743,6 @@
                 config=self.configfile)
         super(PathWatch, self).setup(objs)
 
-
 class Callback(HasPropertyIndex):
     '''Interface and common logic for callbacks.'''
 
@@ -1261,6 +1330,7 @@
         self.callbackgroups = kw.pop('callbackgroup', [])
         self.pathcallbackgroups = kw.pop('pathcallbackgroup', [])
         self.conditions = kw.pop('condition', [])
+        self.filters = kw.pop('filtersgroup', [])
 
         super(Everything, self).__init__(**kw)
 
@@ -1293,6 +1363,7 @@
                     callbackgroups=self.callbackgroups,
                     pathcallbackgroups=self.pathcallbackgroups,
                     conditions=self.conditions,
+                    filters=self.filters,
                     indent=Indent()))
 
 if __name__ == '__main__':
diff --git a/src/propertywatch.hpp b/src/propertywatch.hpp
index 3168abe..ecb8e9c 100644
--- a/src/propertywatch.hpp
+++ b/src/propertywatch.hpp
@@ -8,6 +8,7 @@
 #pragma once
 
 #include "data_types.hpp"
+#include "filters.hpp"
 #include "watch.hpp"
 
 namespace phosphor
@@ -116,14 +117,14 @@
     PropertyWatchOfType& operator=(const PropertyWatchOfType&) = delete;
     PropertyWatchOfType& operator=(PropertyWatchOfType&&) = default;
     ~PropertyWatchOfType() = default;
-    PropertyWatchOfType(const std::vector<std::function<bool(T)>>& filterOps,
-                        const PropertyIndex& watchIndex, Callback& callback) :
+    PropertyWatchOfType(const PropertyIndex& watchIndex, Callback& callback,
+                        Filters* filterOps = nullptr) :
         PropertyWatch<DBusInterfaceType>(watchIndex, &callback),
         filterOps(filterOps)
     {
     }
-    PropertyWatchOfType(const std::vector<std::function<bool(T)>>& filterOps,
-                        const PropertyIndex& watchIndex) :
+    PropertyWatchOfType(const PropertyIndex& watchIndex,
+                        Filters* filterOps = nullptr) :
         PropertyWatch<DBusInterfaceType>(watchIndex, nullptr),
         filterOps(filterOps)
     {
@@ -176,8 +177,8 @@
                          const InterfacesAdded<T>& interfaces);
 
   private:
-    /** @brief List of filter operations to perform on property changes. */
-    const std::vector<std::function<bool(T)>> filterOps;
+    /** @brief Optional filter operations to perform on property changes. */
+    Filters* const filterOps;
 };
 
 } // namespace monitoring
diff --git a/src/propertywatchimpl.hpp b/src/propertywatchimpl.hpp
index c639210..a1b8d68 100644
--- a/src/propertywatchimpl.hpp
+++ b/src/propertywatchimpl.hpp
@@ -158,15 +158,15 @@
         auto isFiltered = false;
         const auto& storage = std::get<storageIndex>(item->second);
         auto value = sdbusplus::message::variant_ns::get<T>(p.second);
-        for (auto& filterOp : filterOps)
+        if (filterOps)
         {
-            if (!filterOp(value))
+            any_ns::any anyValue = value;
+            if ((*filterOps)(anyValue))
             {
                 // Property value filtered, clear it from storage so
                 // callback functions do not use it
                 isFiltered = true;
                 std::get<valueIndex>(storage.get()).clear();
-                break;
             }
         }
         if (!isFiltered)
diff --git a/src/templates/generated.mako.hpp b/src/templates/generated.mako.hpp
index a6c33bf..7b1bf69 100644
--- a/src/templates/generated.mako.hpp
+++ b/src/templates/generated.mako.hpp
@@ -120,6 +120,30 @@
     }
 };
 
+struct ConfigPropertyFilters
+{
+    using PropertyFilters = std::array<std::unique_ptr<Filters>, ${len(filters)}>;
+
+    static auto& get()
+    {
+        static const PropertyFilters propertyFilters =
+        {
+% for f in filters:
+            std::make_unique<OperandFilters<${f.datatype}>>(
+                std::vector<std::function<bool(${f.datatype})>>{
+    % for o in f.filters:
+                    [](const auto& val){
+                        return val ${o.op} ${o.argument(loader, indent=indent +1)};
+                    },
+    % endfor
+                }
+            ),
+% endfor
+        };
+        return propertyFilters;
+    }
+};
+
 struct ConfigPropertyIndicies
 {
     using PropertyIndicies = std::array<PropertyIndex, ${len(instancegroups)}>;
@@ -230,22 +254,22 @@
         {
 % for w in watches:
             std::make_unique<PropertyWatchOfType<${w.datatype}, SDBusPlus>>(
-    % if w.filters:
-                std::vector<std::function<bool(${w.datatype})>>{
-        % for f in w.filters:
-                    [](const auto& val){
-                        return val ${f.op} ${f.argument(loader, indent=indent +1)};
-                    },
-        % endfor
-                },
-    % else:
-                std::vector<std::function<bool(${w.datatype})>>{},
-    % endif
     % if w.callback is None:
+        % if w.filters is None:
                 ConfigPropertyIndicies::get()[${w.instances}]),
+        % else:
+                ConfigPropertyIndicies::get()[${w.instances}],
+                ConfigPropertyFilters::get()[${w.filters}].get()),
+        % endif
     % else:
+        % if w.filters is None:
                 ConfigPropertyIndicies::get()[${w.instances}],
                 *ConfigPropertyCallbacks::get()[${w.callback}]),
+        % else:
+                ConfigPropertyIndicies::get()[${w.instances}],
+                *ConfigPropertyCallbacks::get()[${w.callback}],
+                ConfigPropertyFilters::get()[${w.filters}].get()),
+        % endif
     % endif
 % endfor
         };
diff --git a/src/test/propertywatchtest.cpp b/src/test/propertywatchtest.cpp
index 5a0c121..1c56658 100644
--- a/src/test/propertywatchtest.cpp
+++ b/src/test/propertywatchtest.cpp
@@ -123,7 +123,7 @@
     MockDBusInterface::instance(dbus);
 
     const std::vector<std::string> expectedMapperInterfaces;
-    PropertyWatchOfType<T, MockDBusInterface> watch({}, watchIndex);
+    PropertyWatchOfType<T, MockDBusInterface> watch(watchIndex);
 
     auto ndx = static_cast<size_t>(0);
     for (const auto& o : convert(watchIndex))