First off, I will try to explain the problem and then I will show you the code I have come up with so far.
I've got a list of properties. Each property has a tag of type int and a value of one of the following types: (bool, string, int, long). some sample properties:
tag: 1, value: "some string"
tag: 14, value: true
tag: 20, value: 123
There is also a list of rules. Each rule is like a property, plus one of the following operators: (<, >, <=, >=, ==, !=, startsWith). The startsWith operator is used to check whether one string starts with another one.
Some sample rules:
tag: 1, value: "hello", operator: ==
tag: 14, value: true, operator: ==
Each list of rules is called a Criteria
and is used to validate a list of properties. A criteria validates a list of properties if only all the rules of the criteria are met. For example, the two rules above make up a criteria. This criteria does not validate the aforementioned list of properties because the 1st rule forces the value of tag (1) to be equal (==) to "hello" which is not.
The following criteria:
tag: 1, value: "some string", operator: ==
tag: 20, value: 100, operator: >
tag: 20, value: 120, operator: <
does not validate the above properties too, because although the first two rules are met, the last rule, which forces the value of tag (20) to be less than 120 is not true. Therefore the whole criteria does not validate the properties.
And here is a criteria that validates to true:
tag: 1, value: "some", operator: startsWith
tag: 14, value: false, operator: !=
tag: 20, value: 123, operator: >=
tag: 20, value: 100, operator: >
So the final code will look like something like this:
Param* properties = new Param[MAX_PARAMS];
properties[1] = Param("something"); // tag 1 => value: "something"
properties[2] = Param(12); // tag 2 => value: 12
properties[3] = Param(20); // tag 3 => value: 20
properties[4] = Param(string("bye")); // tag 4 => value: "bye"
multimap<int, Rule> rules;
rules.insert(RulePair(1, Rule("some", SW))); // the value of tag 1 should start with "some"
rules.insert(RulePair(3, Rule(20, LTE))); // the value of tag 3 should be <= 20
rules.insert(RulePair(3, Rule(10, SW))); // the value of tag 3 should be start with 10 (which is a meaningless rule)
rules.insert(RulePair(2, Rule(10, GT))); // the value of tag 2 should be > 10
rules.insert(RulePair(4, Rule("bye1", EQ))); // the value of tag 4 should be equal to "bye1"
if(CheckRules(rules, properties))
{
cout << "everything is fine!" << endl;
}
And this is the whole code that I have come up with. I have used boost::variant
to be able to store and compare different types in a single map. In the following code I have supposed I only need properties of type either string
or int
, but new types can be easily added. Any advice on improving the readability, performance or efficiency of this code is appreciated!
#include <map>
#include "boost/variant/variant.hpp"
#include "boost/variant/apply_visitor.hpp"
#define MAX_PARAMS 1000
using namespace std;
using namespace boost;
typedef variant<string, int> MyVariant;
struct IsEqual
{
MyVariant mValue;
bool operator()( MyVariant &other ) const
{
return mValue == other;
}
IsEqual( MyVariant const& value ):mValue(value) {}
};
struct IsLessThan
{
MyVariant mValue;
bool operator()( MyVariant &other ) const
{
return other < mValue;
}
IsLessThan( MyVariant const& value ):mValue(value) {}
};
struct IsLessThanEqual
{
MyVariant mValue;
bool operator()( MyVariant &other ) const
{
return other < mValue || other == mValue;
}
IsLessThanEqual( MyVariant const& value ):mValue(value) {}
};
struct IsGreaterThan
{
MyVariant mValue;
bool operator()( MyVariant &other ) const
{
return !(other < mValue) && !(other == mValue);
}
IsGreaterThan( MyVariant const& value ):mValue(value) {}
};
struct IsGreaterThanEqual
{
MyVariant mValue;
bool operator()( MyVariant &other ) const
{
return !(other < mValue);
}
IsGreaterThanEqual( MyVariant const& value ):mValue(value) {}
};
class StartsWith
: public boost::static_visitor<bool>
{
public:
string mPrefix;
bool operator()(string &other) const
{
return other.compare(0, mPrefix.length(), mPrefix) == 0;
}
template<typename U>
bool operator()(U &other)const
{
return false;
}
StartsWith(string const& prefix):mPrefix(prefix){}
};
enum Operator
{
EQ, // ==
NEQ, // !=
GT, // >
GTE, // >=
LT, // <
LTE, // <=
SW, // Starts With
};
string GetOperatorString(Operator op)
{
switch (op)
{
case EQ:
return "=";
case NEQ:
return "!=";
case GT:
return ">";
case GTE:
return ">=";
case LT:
return "<";
case LTE:
return "<=";
case SW:
return "should start with";
}
return "";
}
class Rule : public boost::static_visitor<bool>
{
public:
MyVariant v;
string mPrefix;
Operator mOperator;
bool operator()(MyVariant &other) const
{
if (mOperator == EQ)
{
return IsEqual(v)(other);
}
else if (mOperator == NEQ)
{
return !IsEqual(v)(other);
}
else if (mOperator == LT)
{
return IsLessThan(v)(other);
}
else if (mOperator == LTE)
{
return IsLessThanEqual(v)(other);
}
else if (mOperator == GT)
{
return IsGreaterThan(v)(other);
}
else if (mOperator == GTE)
{
return IsGreaterThanEqual(v)(other);
}
else if (mOperator == SW)
{
return apply_visitor(StartsWith(mPrefix), other);
}
}
Rule(MyVariant &v_, Operator op):v(v_),mOperator(op){}
Rule(string &prefix, Operator op):v(prefix), mPrefix(prefix),mOperator(op){}
Rule(const char* prefix, Operator op):v(prefix), mPrefix(prefix),mOperator(op){}
Rule(int _v, Operator op):v(_v), mOperator(op){}
Rule(){}
};
typedef multimap<int, Rule>::iterator RulesItr;
class Param
{
public:
Param():mAvailable(false){}
Param(MyVariant v):mAvailable(true), mValue(v){}
bool IsAvailable(){return mAvailable;}
MyVariant GetValue(){return mValue;}
private:
bool mAvailable;
MyVariant mValue;
};
bool CheckRules(multimap<int, Rule> rules, Param* properties)
{
if (rules.empty()) return true;
RulesItr nextRule = rules.begin();
int nextPropTag = 0;
Param* property;
while (nextRule != rules.end())
{
nextPropTag = nextRule->first;
if (nextPropTag >= MAX_PARAMS) return false;
property = &properties[nextPropTag];
if (!property->IsAvailable()) return false;
MyVariant propertyValue = property->GetValue();
while (nextRule != rules.end() && nextRule->first == nextPropTag)
{
Rule &tester = nextRule++->second; // read and go forward
cout << "rule: " << propertyValue << " " << GetOperatorString(tester.mOperator) << " " << tester.v;
if (!tester(propertyValue))
{
cout << ": Failed!" << endl;
return false;
}
cout << ": OK!" << endl;
}
} // while
return true;
}
typedef pair<int, Rule> RulePair;
int main(int argc, char **argv)
{
Param* properties = new Param[MAX_PARAMS];
properties[1] = Param("something"); // tag 1 => value: "something"
properties[2] = Param(12); // tag 2 => value: 12
properties[3] = Param(20); // tag 3 => value: 20
properties[4] = Param(string("bye")); // tag 4 => value: "bye"
multimap<int, Rule> rules;
rules.insert(RulePair(1, Rule("some", SW))); // the value of tag 1 should start with "some"
rules.insert(RulePair(3, Rule(20, LTE))); // the value of tag 3 should be <= 20
rules.insert(RulePair(3, Rule(10, SW))); // the value of tag 3 should be start with 10 (which is a meaningless rule)
rules.insert(RulePair(2, Rule(10, GT))); // the value of tag 2 should be > 10
rules.insert(RulePair(4, Rule("bye1", EQ))); // the value of tag 4 should be equal to "bye1"
if (CheckRules(rules, properties))
{
cout << "everything is fine!" << endl;
}
else
{
cout << "rule mismatch!" << endl;
}
return 0;
}