Refactor DependenciesConstraint to use custom allocator, hidden behind a more robust encapsulation strategy

This commit is contained in:
Tristan Penman 2015-12-27 14:51:10 +11:00
parent 76f8655d21
commit c7f2f9b802
7 changed files with 371 additions and 71 deletions

View File

@ -5,6 +5,8 @@
#include <valijson/constraints/constraint.hpp>
#include <valijson/constraints/constraint_visitor.hpp>
#include <valijson/internal/custom_allocator.hpp>
namespace valijson {
namespace constraints {
@ -18,6 +20,17 @@ namespace constraints {
template<typename ConstraintType>
struct BasicConstraint: Constraint
{
typedef internal::CustomAllocator<void *> Allocator;
typedef std::basic_string<char, std::char_traits<char>,
internal::CustomAllocator<char> > String;
BasicConstraint()
: allocator() { }
BasicConstraint(Allocator::CustomAlloc allocFn, Allocator::CustomFree freeFn)
: allocator(allocFn, freeFn) { }
virtual ~BasicConstraint<ConstraintType>() { }
virtual bool accept(ConstraintVisitor &visitor) const
@ -41,6 +54,10 @@ struct BasicConstraint: Constraint
throw;
}
}
protected:
Allocator allocator;
};
} // namespace constraints

View File

@ -27,6 +27,7 @@
#include <valijson/adapters/frozen_value.hpp>
#include <valijson/constraints/basic_constraint.hpp>
#include <valijson/internal/custom_allocator.hpp>
#include <valijson/schema.hpp>
namespace valijson {
@ -77,23 +78,113 @@ struct AnyOfConstraint: BasicConstraint<AnyOfConstraint>
* A dependency constraint ensures that a given property is valid only if the
* properties that it depends on are present.
*/
struct DependenciesConstraint: BasicConstraint<DependenciesConstraint>
class DependenciesConstraint: public BasicConstraint<DependenciesConstraint>
{
// A mapping from property names to the set of names of their dependencies
typedef std::set<std::string> Dependencies;
typedef std::map<std::string, Dependencies> PropertyDependenciesMap;
public:
DependenciesConstraint()
: propertyDependencies(allocator),
schemaDependencies(allocator)
{ }
// A mapping from property names to dependent schemas
typedef std::map<std::string, const Subschema *>
PropertyDependentSchemasMap;
DependenciesConstraint(CustomAlloc allocFn, CustomFree freeFn)
: BasicConstraint(allocFn, freeFn),
propertyDependencies(allocator),
schemaDependencies(allocator)
{ }
DependenciesConstraint(const PropertyDependenciesMap &dependencies,
const PropertyDependentSchemasMap &dependentSchemas)
: dependencies(dependencies),
dependentSchemas(dependentSchemas) { }
template<typename StringType>
DependenciesConstraint & addPropertyDependency(
const StringType &propertyName,
const StringType &dependencyName)
{
const String key(propertyName.c_str(), allocator);
PropertyDependencies::iterator itr = propertyDependencies.find(key);
if (itr == propertyDependencies.end()) {
itr = propertyDependencies.insert(PropertyDependencies::value_type(
key, PropertySet(allocator))).first;
}
const PropertyDependenciesMap dependencies;
const PropertyDependentSchemasMap dependentSchemas;
itr->second.insert(String(dependencyName.c_str(), allocator));
return *this;
}
template<typename StringType, typename ContainerType>
DependenciesConstraint & addPropertyDependencies(
const StringType &propertyName,
const ContainerType &dependencyNames)
{
const String key(propertyName.c_str(), allocator);
PropertyDependencies::iterator itr = propertyDependencies.find(key);
if (itr == propertyDependencies.end()) {
itr = propertyDependencies.insert(PropertyDependencies::value_type(
key, PropertySet(allocator))).first;
}
typedef typename ContainerType::value_type ValueType;
BOOST_FOREACH( const ValueType &dependencyName, dependencyNames ) {
itr->second.insert(String(dependencyName.c_str(), allocator));
}
return *this;
}
template<typename StringType>
DependenciesConstraint & addSchemaDependency(
const StringType &propertyName,
const Subschema *schemaDependency)
{
if (schemaDependencies.insert(SchemaDependencies::value_type(
String(propertyName.c_str(), allocator),
schemaDependency)).second) {
return *this;
}
throw std::runtime_error(
"Dependencies constraint already contains a dependent "
"schema for the property '" + propertyName + "'");
}
template<typename FunctorType>
bool applyToPropertyDependencies(const FunctorType &fn) const
{
BOOST_FOREACH( const PropertyDependencies::value_type &v,
propertyDependencies ) {
if (!fn(v.first, v.second)) {
return false;
}
}
return true;
}
template<typename FunctorType>
bool applyToSchemaDependencies(const FunctorType &fn) const
{
BOOST_FOREACH( const SchemaDependencies::value_type &v,
schemaDependencies ) {
if (!fn(v.first, v.second)) {
return false;
}
}
return true;
}
private:
typedef std::set<String, std::less<String>, Allocator> PropertySet;
typedef std::map<String, PropertySet, std::less<String>, Allocator>
PropertyDependencies;
typedef std::map<String, const Subschema *, std::less<String>, Allocator>
SchemaDependencies;
/// Mapping from property names to their property-based dependencies
PropertyDependencies propertyDependencies;
/// Mapping from property names to their schema-based dependencies
SchemaDependencies schemaDependencies;
};
/**

View File

@ -7,7 +7,6 @@ namespace constraints {
struct AllOfConstraint;
struct AnyOfConstraint;
struct DependenciesConstraint;
struct EnumConstraint;
struct ItemsConstraint;
struct FormatConstraint;
@ -28,6 +27,8 @@ struct RequiredConstraint;
struct TypeConstraint;
struct UniqueItemsConstraint;
class DependenciesConstraint;
/// Interface to allow usage of the visitor pattern with Constraints
class ConstraintVisitor
{

View File

@ -0,0 +1,111 @@
#ifndef __VALIJSON_CUSTOM_ALLOCATOR_HPP
#define __VALIJSON_CUSTOM_ALLOCATOR_HPP
namespace valijson {
namespace internal {
template<class T>
class CustomAllocator
{
public:
/// Typedef for custom new-/malloc-like function
typedef void * (*CustomAlloc)(size_t size);
/// Typedef for custom free-like function
typedef void (*CustomFree)(void *);
// Standard allocator typedefs
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef std::true_type propagate_on_container_move_assignment;
template<typename U>
struct rebind
{
typedef CustomAllocator<U> other;
};
CustomAllocator()
: allocFn(::operator new),
freeFn(::operator delete) { }
CustomAllocator(CustomAlloc allocFn, CustomFree freeFn)
: allocFn(allocFn),
freeFn(freeFn) { }
CustomAllocator(const CustomAllocator &other)
: allocFn(other.allocFn),
freeFn(other.freeFn) { }
template<typename U>
CustomAllocator(CustomAllocator<U> const &other)
: allocFn(other.allocFn),
freeFn(other.freeFn) { }
CustomAllocator & operator=(const CustomAllocator &other)
{
allocFn = other.allocFn;
freeFn = other.freeFn;
}
pointer address(reference r)
{
return &r;
}
const_pointer address(const_reference r)
{
return &r;
}
pointer allocate(size_type cnt,
typename std::allocator<void>::const_pointer = 0)
{
return reinterpret_cast<pointer>(allocFn(cnt * sizeof(T)));
}
void deallocate(pointer p, size_type)
{
freeFn(p);
}
size_type max_size() const
{
return std::numeric_limits<size_type>::max() / sizeof(T);
}
void construct(pointer p, const T& t)
{
new(p) T(t);
}
void destroy(pointer p)
{
p->~T();
}
bool operator==(CustomAllocator const &other)
{
return other.allocFn == allocFn && other.freeFn == freeFn;
}
bool operator!=(CustomAllocator const &other)
{
return !operator==(other);
}
CustomAlloc allocFn;
CustomFree freeFn;
};
} // end namespace internal
} // end namespace valijson
#endif

View File

@ -617,8 +617,7 @@ private:
throw std::runtime_error("Expected object value for 'dependencies' constraint.");
}
constraints::DependenciesConstraint::PropertyDependenciesMap pdm;
constraints::DependenciesConstraint::PropertyDependentSchemasMap pdsm;
constraints::DependenciesConstraint dependenciesConstraint;
// Process each of the dependency mappings defined by the object
BOOST_FOREACH ( const typename AdapterType::ObjectMember member, node.asObject() ) {
@ -632,16 +631,19 @@ private:
// be detected.
if (member.second.maybeArray()) {
// Parse an array of dependency names
constraints::DependenciesConstraint::Dependencies &dependencies = pdm[member.first];
std::vector<std::string> dependentPropertyNames;
BOOST_FOREACH( const AdapterType dependencyName, member.second.asArray() ) {
if (dependencyName.maybeString()) {
dependencies.insert(dependencyName.getString());
dependentPropertyNames.push_back(dependencyName.getString());
} else {
throw std::runtime_error("Expected string value in dependency list of property '" +
member.first + "' in 'dependencies' constraint.");
}
}
dependenciesConstraint.addPropertyDependencies(member.first,
dependentPropertyNames);
// If the value of dependency mapping could not be processed as an
// array, we'll try to process it as an object instead. Note that
// strict type comparison is used here, since we've already
@ -651,15 +653,17 @@ private:
} else if (member.second.isObject()) {
// Parse dependent subschema
const Subschema *childSubschema = rootSchema.createSubschema();
pdsm[member.first] = childSubschema;
populateSchema<AdapterType>(rootSchema, rootNode, member.second,
*childSubschema, currentScope, nodePath, fetchDoc);
dependenciesConstraint.addSchemaDependency(member.first,
childSubschema);
// If we're supposed to be parsing a Draft3 schema, then the value
// of the dependency mapping can also be a string containing the
// name of a single dependency.
} else if (version == kDraft3 && member.second.isString()) {
pdm[member.first].insert(member.second.getString());
dependenciesConstraint.addPropertyDependency(member.first,
member.second.getString());
// All other types result in an exception being thrown.
} else {
@ -667,7 +671,7 @@ private:
}
}
return constraints::DependenciesConstraint(pdm, pdsm);
return dependenciesConstraint;
}
/**

View File

@ -176,16 +176,21 @@ public:
}
/**
* @brief Validate against the dependencies constraint represented by a
* DependenciesConstraint object.
* @brief Validate current node against a 'dependencies' constraint
*
* A dependencies constraint can specify either a mapping of attribute names
* to their dependencies, or a mapping of attribute names to child schemas
* that must be satisfied if a given attribute is present.
* A 'dependencies' constraint can be used to specify property-based or
* schema-based dependencies that must be fulfilled when a particular
* property is present in an object.
*
* @param constraint Constraint that the target must validate against.
* Property-based dependencies define a set of properties that must be
* present in addition to a particular property, whereas a schema-based
* dependency defines an additional schema that the current document must
* validate against.
*
* @return true if validation passes, false otherwise.
* @param constraint DependenciesConstraint that the current node
* must validate against
*
* @return \c true if validation passes; \c false otherwise
*/
virtual bool visit(const DependenciesConstraint &constraint)
{
@ -194,55 +199,29 @@ public:
return true;
}
// Typedef and reference for conciseness in nested loops
typedef DependenciesConstraint::PropertyDependenciesMap PDM;
const PDM &deps = constraint.dependencies;
typedef DependenciesConstraint::PropertyDependentSchemasMap PDSM;
const PDSM &depSchemas = constraint.dependentSchemas;
// Get access to the target as an object
// Object to be validated
const typename AdapterType::Object object = target.asObject();
// Flag used to track validation status if errors are non-fatal
// Cleared if validation fails
bool validated = true;
// For each property in the object, check for a list of dependencies in
// the constraint object and verify that the dependencies have been
// satisfied.
BOOST_FOREACH( const typename AdapterType::ObjectMember m, object ) {
// Check for this property in the dependency map. If it is not
// present, we can move on to the next one...
PDM::const_iterator itr = deps.find(m.first);
if (itr != deps.end()) {
BOOST_FOREACH( const std::string &name, itr->second ) {
if (object.find(name) == object.end()) {
if (!results) {
// Iterate over all dependent properties defined by this constraint,
// invoking the DependentPropertyValidator functor once for each
// set of dependent properties
constraint.applyToPropertyDependencies(ValidatePropertyDependencies(
object, context, results, &validated));
if (!results && !validated) {
return false;
}
validated = false;
results->pushError(context, "Missing dependency '" + name + "'.");
}
}
}
// Check for this property in the dependent schemas map. If it is
// present then we need to validate the current target against the
// dependent schema.
PDSM::const_iterator depSchemasItr = depSchemas.find(m.first);
if (depSchemasItr != depSchemas.end()) {
const Subschema *subschema = depSchemasItr->second;
if (!validateSchema(*subschema)) {
if (results) {
results->pushError(context, "Failed to validate against dependent schema.");
validated = false;
} else {
// Iterate over all dependent schemas defined by this constraint,
// invoking the DependentSchemaValidator function once for each schema
// that must be validated if a given property is present
constraint.applyToSchemaDependencies(ValidateSchemaDependencies(
object, context, *this, results, &validated));
if (!results && !validated) {
return false;
}
}
}
}
return validated;
}
@ -1087,6 +1066,101 @@ public:
private:
struct ValidatePropertyDependencies
{
ValidatePropertyDependencies(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
ValidationResults *results,
bool *validated)
: object(object),
context(context),
results(results),
validated(validated) { }
template<typename StringType, typename ContainerType>
bool operator()(
const StringType &propertyName,
const ContainerType &dependencyNames) const
{
const std::string propertyNameKey(propertyName.c_str());
if (object.find(propertyNameKey) == object.end()) {
return true;
}
typedef typename ContainerType::value_type ValueType;
BOOST_FOREACH( const ValueType &dependencyName, dependencyNames ) {
const std::string dependencyNameKey(dependencyName.c_str());
if (object.find(dependencyNameKey) == object.end()) {
if (validated) {
*validated = false;
}
if (results) {
results->pushError(context, "Missing dependency '" +
dependencyNameKey + "'.");
} else {
return false;
}
}
}
return true;
}
private:
const typename AdapterType::Object &object;
const std::vector<std::string> &context;
ValidationResults * const results;
bool * const validated;
};
struct ValidateSchemaDependencies
{
ValidateSchemaDependencies(
const typename AdapterType::Object &object,
const std::vector<std::string> &context,
ValidationVisitor &validationVisitor,
ValidationResults *results,
bool *validated)
: object(object),
context(context),
validationVisitor(validationVisitor),
results(results),
validated(validated) { }
template<typename StringType>
bool operator()(
const StringType &propertyName,
const Subschema *schemaDependency) const
{
const std::string propertyNameKey(propertyName.c_str());
if (object.find(propertyNameKey) == object.end()) {
return true;
}
if (!validationVisitor.validateSchema(*schemaDependency)) {
if (validated) {
*validated = false;
}
if (results) {
results->pushError(context,
"Failed to validate against dependent schema.");
} else {
return false;
}
}
return true;
}
private:
const typename AdapterType::Object &object;
const std::vector<std::string> &context;
ValidationVisitor &validationVisitor;
ValidationResults * const results;
bool * const validated;
};
/**
* @brief Callback function that passes a visitor to a constraint.
*

View File

@ -236,6 +236,7 @@
6AD030F51B03FD9900B957E5 /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
6AE1D76617E3F993008CF38C /* custom_schema.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = custom_schema.cpp; sourceTree = "<group>"; };
6AEFECC81AB53F1000275614 /* libjsoncpp.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libjsoncpp.a; sourceTree = BUILT_PRODUCTS_DIR; };
6AF3F5071C2D874F001B3445 /* custom_allocator.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = custom_allocator.hpp; path = internal/custom_allocator.hpp; sourceTree = "<group>"; };
6AF70AE118FE71F600342325 /* gtest-death-test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-death-test.h"; sourceTree = "<group>"; };
6AF70AE218FE71F600342325 /* gtest-message.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-message.h"; sourceTree = "<group>"; };
6AF70AE318FE71F600342325 /* gtest-param-test.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "gtest-param-test.h"; sourceTree = "<group>"; };
@ -362,6 +363,7 @@
6A506D1D1AF88D5E00C2C818 /* internal */ = {
isa = PBXGroup;
children = (
6AF3F5071C2D874F001B3445 /* custom_allocator.hpp */,
6A506D1E1AF88D8700C2C818 /* json_pointer.hpp */,
6A34698C1BD109A900C97DA2 /* json_reference.hpp */,
6A356B0D1C1CFD020007EB8B /* uri.hpp */,