add ValueBuilder

This commit is contained in:
Michael Davidsaver
2017-07-03 17:34:38 +02:00
parent 1d2e5d182e
commit bef616632c
6 changed files with 537 additions and 0 deletions

View File

@@ -116,6 +116,31 @@ struct _and {};
template<typename A, typename B, class R>
struct _and<A,B, typename A::type, typename B::type, R> { typedef R type; };
/** Mangle type to best pass as an argument.
*
* POD types passed by value.
* All others by const reference.
*/
template<typename T>
struct arg_type {typedef const T& type;};
#define SIMPLE_ARG_TYPE(TYPE) template<> struct arg_type<TYPE> { typedef TYPE type; };
SIMPLE_ARG_TYPE(bool)
SIMPLE_ARG_TYPE(char)
SIMPLE_ARG_TYPE(signed char)
SIMPLE_ARG_TYPE(unsigned char)
SIMPLE_ARG_TYPE(short)
SIMPLE_ARG_TYPE(unsigned short)
SIMPLE_ARG_TYPE(int)
SIMPLE_ARG_TYPE(unsigned int)
SIMPLE_ARG_TYPE(long)
SIMPLE_ARG_TYPE(unsigned long)
SIMPLE_ARG_TYPE(long long)
SIMPLE_ARG_TYPE(unsigned long long)
SIMPLE_ARG_TYPE(float)
SIMPLE_ARG_TYPE(double)
SIMPLE_ARG_TYPE(long double)
#undef SIMPLE_ARG_TYPE
}}}
#endif // TEMPLATEMETA_H

View File

@@ -4,6 +4,7 @@ SRC_DIRS += $(PVDATA_SRC)/pv
INC += pv/pvType.h
INC += pv/pvIntrospect.h
INC += pv/valueBuilder.h
INC += pv/pvData.h
INC += pv/convert.h
INC += pv/standardField.h
@@ -13,3 +14,4 @@ INC += pv/pvdVersion.h
INC += pv/pvdVersionNum.h
LIBSRCS += pvdVersion.cpp
LIBSRCS += valueBuilder.cpp

261
src/pv/valueBuilder.cpp Normal file
View File

@@ -0,0 +1,261 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <memory>
#include <pv/pvData.h>
#include "valueBuilder.h"
namespace epics{namespace pvData{
struct ValueBuilder::child {
virtual ~child() {}
Type type;
child(Type t) : type(t) {}
virtual void build(const std::string& name, FieldBuilderPtr& builder)=0;
virtual void store(const PVFieldPtr& val)=0;
};
struct ValueBuilder::child_struct : public ValueBuilder::child
{
virtual ~child_struct() {}
child_struct(ValueBuilder *par, const std::string& id)
:child(structure)
,builder(par, id)
{}
ValueBuilder builder;
virtual void build(const std::string& name, FieldBuilderPtr& builder) OVERRIDE FINAL
{
FieldBuilderPtr nest(builder->addNestedStructure(name));
buildStruct(this->builder, nest);
builder = nest->endNested();
}
virtual void store(const PVFieldPtr& val) OVERRIDE FINAL
{
if(val->getField()->getType()!=structure)
THROW_EXCEPTION2(std::logic_error, "Structure type mis-match");
PVStructurePtr str(std::tr1::static_pointer_cast<PVStructure>(val));
storeStruct(builder, str);
}
static
void buildStruct(const ValueBuilder& self, FieldBuilderPtr& builder);
static
void storeStruct(const ValueBuilder& self, const PVStructurePtr& val);
static
void fillStruct(ValueBuilder& self, const PVStructure& val);
};
struct ValueBuilder::child_scalar_base : public ValueBuilder::child
{
virtual ~child_scalar_base() {}
ScalarType stype;
child_scalar_base(ScalarType s) : child(scalar), stype(s) {}
virtual void build(const std::string& name, FieldBuilderPtr& builder) OVERRIDE FINAL
{
builder->add(name, stype);
}
};
template <typename T>
struct ValueBuilder::child_scalar : public ValueBuilder::child_scalar_base
{
virtual ~child_scalar() {}
T value;
child_scalar(const void* v) : child_scalar_base(static_cast<ScalarType>(ScalarTypeID<T>::value)), value(*static_cast<const T*>(v)) {}
virtual void store(const PVFieldPtr& val) OVERRIDE FINAL
{
if(val->getField()->getType()!=scalar)
THROW_EXCEPTION2(std::logic_error, "Scalar type mis-match");
PVScalarPtr scalar(std::tr1::static_pointer_cast<PVScalar>(val));
scalar->putFrom(value);
}
};
ValueBuilder::ValueBuilder(const std::string &id) :parent(0),id(id) {}
void ValueBuilder::child_struct::fillStruct(ValueBuilder& self, const PVStructure& val)
{
StructureConstPtr type(val.getStructure());
const StringArray& field = type->getFieldNames();
for(StringArray::const_iterator it=field.begin(), end=field.end(); it!=end; ++it)
{
PVFieldPtr sub(val.getSubField(*it));
assert(sub);
FieldConstPtr subtype(sub->getField());
switch(subtype->getType()) {
case scalar:
{
PVScalar* subs(static_cast<PVScalar*>(sub.get()));
ScalarType stype = subs->getScalar()->getScalarType();
switch(stype) {
#define STYPE(stype) case pv##stype: { PV ##stype* ptr(static_cast<PV##stype*>(subs)); PV##stype::value_type temp(ptr->get()); self._add(*it, pv##stype, &temp); } break
STYPE(Boolean);
STYPE(Byte);
STYPE(Short);
STYPE(Int);
STYPE(Long);
STYPE(UByte);
STYPE(UShort);
STYPE(UInt);
STYPE(ULong);
STYPE(Float);
STYPE(Double);
STYPE(String);
#undef STYPE
}
}
break;
case structure:
self._add(*it, *static_cast<PVStructure*>(sub.get()));
break;
default:
THROW_EXCEPTION2(std::runtime_error, "ValueBuilder can only clone scalar and structure");
}
}
}
ValueBuilder::ValueBuilder(const PVStructure& clone) :parent(0)
{
StructureConstPtr ctype(clone.getStructure());
id = ctype->getID();
child_struct::fillStruct(*this, clone);
}
ValueBuilder::ValueBuilder(ValueBuilder* par, const std::string &id)
:parent(par)
,id(id)
{}
ValueBuilder::~ValueBuilder()
{
for(children_t::const_iterator it=children.begin(), end=children.end(); it!=end; ++it)
delete it->second;
children.clear();
}
void ValueBuilder::_add(const std::string& name, const PVStructure& V)
{
StructureConstPtr T(V.getStructure());
ValueBuilder& self = addNested(name, structure, T->getID());
child_struct::fillStruct(self, V);
self.endNested();
}
void ValueBuilder::_add(const std::string& name, ScalarType stype, const void *V)
{
const children_t::iterator it(children.find(name));
if(it!=children.end()) {
if(it->second->type!=scalar && it->second->type!=scalarArray)
THROW_EXCEPTION2(std::logic_error, "Not allowed to replace field. wrong type");
}
std::auto_ptr<child> store;
switch(stype) {
#define STYPE(stype) case stype: store.reset(new child_scalar<typename ScalarTypeTraits<stype>::type>(V)); break
STYPE(pvBoolean);
STYPE(pvByte);
STYPE(pvShort);
STYPE(pvInt);
STYPE(pvLong);
STYPE(pvUByte);
STYPE(pvUShort);
STYPE(pvUInt);
STYPE(pvULong);
STYPE(pvFloat);
STYPE(pvDouble);
STYPE(pvString);
#undef STYPE
}
if(!store.get())
THROW_EXCEPTION2(std::logic_error, "Unhandled ScalarType");
if(it!=children.end()) {
delete it->second;
children.erase(it);
}
children[name] = store.get();
store.release();
}
ValueBuilder& ValueBuilder::addNested(const std::string& name, Type type, const std::string &id)
{
if(type!=structure)
THROW_EXCEPTION2(std::invalid_argument, "addNested() only supports structure");
child_struct *sub;
children_t::const_iterator it(children.find(name));
if(it==children.end()) {
std::auto_ptr<child_struct> store(new child_struct(this, id));
sub = store.get();
children[name] = store.get();
store.release();
} else if(it->second->type==structure) {
sub = static_cast<child_struct*>(it->second);
} else {
std::ostringstream msg;
msg<<"Can't replace non-struct field '"<<name<<"' with struct";
THROW_EXCEPTION2(std::invalid_argument, msg.str());
}
sub->builder.id = id;
return sub->builder;
}
ValueBuilder& ValueBuilder::endNested()
{
if(!parent) {
THROW_EXCEPTION2(std::logic_error, "Can't end top of structure");
}
return *parent;
}
void ValueBuilder::child_struct::buildStruct(const ValueBuilder& self, FieldBuilderPtr& builder)
{
if(!self.id.empty())
builder->setId(self.id);
for(children_t::const_iterator it=self.children.begin(), end=self.children.end(); it!=end; ++it)
{
it->second->build(it->first, builder);
}
}
void ValueBuilder::child_struct::storeStruct(const ValueBuilder& self, const PVStructurePtr& val)
{
for(children_t::const_iterator it=self.children.begin(), end=self.children.end(); it!=end; ++it)
{
it->second->store(val->getSubFieldT(it->first));
}
}
PVStructure::shared_pointer ValueBuilder::buildPVStructure() const
{
if(parent)
THROW_EXCEPTION2(std::logic_error, "Only top level structure may be built. Missing endNested() ?");
StructureConstPtr type;
{
FieldBuilderPtr tbuild(getFieldCreate()->createFieldBuilder());
child_struct::buildStruct(*this, tbuild);
type = tbuild->createStructure();
}
PVStructure::shared_pointer root(getPVDataCreate()->createPVStructure(type));
child_struct::storeStruct(*this, root);
return root;
}
}}// namespace epics::pvData

91
src/pv/valueBuilder.h Normal file
View File

@@ -0,0 +1,91 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#ifndef VALUEBUILDER_H
#define VALUEBUILDER_H
#include <map>
#include <pv/templateMeta.h>
#include <pv/pvIntrospect.h>
namespace epics{namespace pvData{
class PVStructure;
/** Incrementally define and initialize a PVStructure
*
* Equivalent to FieldBuilder with the added ability to assign initial values.
*
@code
epics::pvData::PVStructurePtr val(epics::pvData::ValueBuilder()
.add<pvInt>("intfld", 42)
.addNested("sub")
.add<pvString>("strfld", "testing")
.endNested()
.buildPVStructure());
@endcode
*/
class ValueBuilder
{
public:
//! empty structure
explicit ValueBuilder(const std::string& id=std::string());
//! Clone existing definition and value
explicit ValueBuilder(const PVStructure&);
~ValueBuilder();
//! Add a scalar field with a given name and initial value
template<ScalarType ENUM>
FORCE_INLINE ValueBuilder& add(const std::string& name, typename meta::arg_type<typename ScalarTypeTraits<ENUM>::type>::type V)
{
_add(name, ENUM, &V);
return *this;
}
FORCE_INLINE ValueBuilder& add(const std::string& name, const PVStructure& V) {
_add(name, V);
return *this;
}
//! Start a sub-structure
ValueBuilder& addNested(const std::string& name, Type type=structure, const std::string& id = std::string());
//! End a sub-structure
ValueBuilder& endNested();
/** Complete building structure
*
* @note ValueBuilder may be re-used after calling buildPVStructure()
*/
std::tr1::shared_ptr<PVStructure> buildPVStructure() const;
private:
void _add(const std::string& name, ScalarType stype, const void *V);
void _add(const std::string& name, const PVStructure& V);
ValueBuilder(ValueBuilder*, const std::string &id = std::string());
ValueBuilder * const parent;
struct child;
friend struct child;
struct child_struct;
friend struct child_struct;
struct child_scalar_base;
friend struct child_scalar_base;
template <typename T>
struct child_scalar;
template <typename T>
friend struct child_scalar;
typedef std::map<std::string, child*> children_t;
children_t children;
std::string id;
ValueBuilder(const ValueBuilder&);
ValueBuilder& operator=(const ValueBuilder&);
};
}}// namespace epics::pvData
#endif // VALUEBUILDER_H

View File

@@ -61,3 +61,7 @@ TESTPROD_HOST += testFieldBuilder
testFieldBuilder_SRCS += testFieldBuilder.cpp
testHarness_SRCS += testFieldBuilder.cpp
TESTS += testFieldBuilder
TESTPROD_HOST += testValueBuilder
testValueBuilder_SRCS += testValueBuilder.cpp
TESTS += testValueBuilder

View File

@@ -0,0 +1,154 @@
/*
* Copyright information and license terms for this software can be
* found in the file LICENSE that is included with the distribution
*/
#include <epicsUnitTest.h>
#include <testMain.h>
#include <pv/valueBuilder.h>
#include <pv/pvData.h>
#include <pv/createRequest.h>
namespace pvd = epics::pvData;
namespace {
void testBuild()
{
testDiag("testBuild()");
pvd::ValueBuilder builder;
builder.add<pvd::pvInt>("A", 42)
.add<pvd::pvShort>("B", 43);
pvd::PVStructurePtr val(builder.buildPVStructure());
testOk1(val->getStructure()->getID()=="structure");
{
pvd::PVIntPtr A(val->getSubField<pvd::PVInt>("A"));
testOk1(!!A && A->get()==42);
}
{
pvd::PVShortPtr B(val->getSubField<pvd::PVShort>("B"));
testOk1(!!B && B->get()==43);
}
}
void testSubStruct()
{
testDiag("testSubStruct()");
pvd::PVStructurePtr val(pvd::ValueBuilder("foo")
.add<pvd::pvInt>("A", 42)
.addNested("X", pvd::structure, "bar")
.add<pvd::pvInt>("Y", 111)
.add<pvd::pvString>("Z", "hello world")
.endNested()
.add<pvd::pvInt>("B", 43)
.buildPVStructure());
testOk1(val->getStructure()->getID()=="foo");
testOk1(val->getSubFieldT<pvd::PVStructure>("X")->getStructure()->getID()=="bar");
{
pvd::PVIntPtr A(val->getSubField<pvd::PVInt>("A"));
testOk1(!!A && A->get()==42);
}
{
pvd::PVIntPtr B(val->getSubField<pvd::PVInt>("B"));
testOk1(!!B && B->get()==43);
}
{
pvd::PVIntPtr Y(val->getSubField<pvd::PVInt>("X.Y"));
testOk1(!!Y && Y->get()==111);
}
{
pvd::PVStringPtr Y(val->getSubField<pvd::PVString>("X.Z"));
testOk1(!!Y && Y->get()=="hello world");
}
}
void testReplace()
{
testDiag("testReplace()");
pvd::PVStructurePtr val(pvd::ValueBuilder("foo")
.add<pvd::pvInt>("A", 42)
.add<pvd::pvInt>("A", 43)
.addNested("X", pvd::structure, "bar")
.add<pvd::pvInt>("Y", 111)
.endNested()
.addNested("X", pvd::structure, "baz")
.add<pvd::pvInt>("Y", 112)
.endNested()
.buildPVStructure());
testDiag("top id %s", val->getStructure()->getID().c_str());
testOk1(val->getStructure()->getID()=="foo");
testDiag("X id %s", val->getSubFieldT<pvd::PVStructure>("X")->getStructure()->getID().c_str());
testOk1(val->getSubFieldT<pvd::PVStructure>("X")->getStructure()->getID()=="baz");
{
pvd::PVIntPtr A(val->getSubField<pvd::PVInt>("A"));
testOk1(!!A && A->get()==43);
}
{
pvd::PVIntPtr Y(val->getSubField<pvd::PVInt>("X.Y"));
testOk1(!!Y && Y->get()==112);
}
}
void testAppend()
{
testDiag("testAppend()");
pvd::PVStructurePtr base(pvd::createRequest("field(foo)record[bar=5]"));
std::cerr<<base<<"\n";
testOk1(!!base->getSubField<pvd::PVStructure>("field.foo"));
testOk1(!base->getSubField<pvd::PVStructure>("field.other"));
testOk1(base->getSubFieldT<pvd::PVString>("record._options.bar")->get()=="5");
testOk1(!base->getSubField<pvd::PVStructure>("record._options.foo"));
pvd::PVStructurePtr mod(pvd::ValueBuilder(*base)
.addNested("field")
.addNested("other")
.endNested()
.endNested()
.addNested("record")
.addNested("_options")
.add<pvd::pvInt>("bar", 4)
.add<pvd::pvInt>("foo", 1)
.endNested()
.endNested()
.buildPVStructure());
std::cerr<<mod<<"\n";
testOk1(base->getField().get()!=mod->getField().get());
testOk1(base->getField()!=mod->getField());
// base unchanged
testOk1(!!base->getSubField<pvd::PVStructure>("field.foo"));
testOk1(!base->getSubField<pvd::PVStructure>("field.other"));
testOk1(base->getSubFieldT<pvd::PVString>("record._options.bar")->get()=="5");
testOk1(!base->getSubField<pvd::PVStructure>("record._options.foo"));
testOk1(!!mod->getSubField<pvd::PVStructure>("field.foo"));
testOk1(!!mod->getSubField<pvd::PVStructure>("field.other"));
testOk1(mod->getSubFieldT<pvd::PVInt>("record._options.bar")->get()==4);
testOk1(mod->getSubFieldT<pvd::PVInt>("record._options.foo")->get()==1);
}
} // namespace
MAIN(testValueBuilder)
{
testPlan(13);
try {
testBuild();
testSubStruct();
testReplace();
testAppend();
}catch(std::exception& e){
PRINT_EXCEPTION(e);
testAbort("Unexpected exception: %s", e.what());
}
return testDone();
}