From bef616632c1c3f9f3d03a6a4110c207e8d1ab578 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 3 Jul 2017 17:34:38 +0200 Subject: [PATCH] add ValueBuilder --- src/misc/pv/templateMeta.h | 25 +++ src/pv/Makefile | 2 + src/pv/valueBuilder.cpp | 261 ++++++++++++++++++++++++++++++++ src/pv/valueBuilder.h | 91 +++++++++++ testApp/pv/Makefile | 4 + testApp/pv/testValueBuilder.cpp | 154 +++++++++++++++++++ 6 files changed, 537 insertions(+) create mode 100644 src/pv/valueBuilder.cpp create mode 100644 src/pv/valueBuilder.h create mode 100644 testApp/pv/testValueBuilder.cpp diff --git a/src/misc/pv/templateMeta.h b/src/misc/pv/templateMeta.h index 1f9ad54..20323a4 100644 --- a/src/misc/pv/templateMeta.h +++ b/src/misc/pv/templateMeta.h @@ -116,6 +116,31 @@ struct _and {}; template struct _and { typedef R type; }; +/** Mangle type to best pass as an argument. + * + * POD types passed by value. + * All others by const reference. + */ +template +struct arg_type {typedef const T& type;}; +#define SIMPLE_ARG_TYPE(TYPE) template<> struct arg_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 diff --git a/src/pv/Makefile b/src/pv/Makefile index f0b6b1b..074d56f 100644 --- a/src/pv/Makefile +++ b/src/pv/Makefile @@ -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 diff --git a/src/pv/valueBuilder.cpp b/src/pv/valueBuilder.cpp new file mode 100644 index 0000000..9efd87f --- /dev/null +++ b/src/pv/valueBuilder.cpp @@ -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 + +#include + +#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(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 +struct ValueBuilder::child_scalar : public ValueBuilder::child_scalar_base +{ + virtual ~child_scalar() {} + T value; + child_scalar(const void* v) : child_scalar_base(static_cast(ScalarTypeID::value)), value(*static_cast(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(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(sub.get())); + ScalarType stype = subs->getScalar()->getScalarType(); + switch(stype) { +#define STYPE(stype) case pv##stype: { PV ##stype* ptr(static_cast(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(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 store; + switch(stype) { +#define STYPE(stype) case stype: store.reset(new child_scalar::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 store(new child_struct(this, id)); + sub = store.get(); + children[name] = store.get(); + store.release(); + } else if(it->second->type==structure) { + sub = static_cast(it->second); + } else { + std::ostringstream msg; + msg<<"Can't replace non-struct field '"<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 diff --git a/src/pv/valueBuilder.h b/src/pv/valueBuilder.h new file mode 100644 index 0000000..22a98da --- /dev/null +++ b/src/pv/valueBuilder.h @@ -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 + +#include +#include + +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("intfld", 42) + .addNested("sub") + .add("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 + FORCE_INLINE ValueBuilder& add(const std::string& name, typename meta::arg_type::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 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 + struct child_scalar; + template + friend struct child_scalar; + + typedef std::map children_t; + children_t children; + std::string id; + + ValueBuilder(const ValueBuilder&); + ValueBuilder& operator=(const ValueBuilder&); +}; + +}}// namespace epics::pvData + +#endif // VALUEBUILDER_H diff --git a/testApp/pv/Makefile b/testApp/pv/Makefile index a32dedd..a2df88d 100644 --- a/testApp/pv/Makefile +++ b/testApp/pv/Makefile @@ -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 diff --git a/testApp/pv/testValueBuilder.cpp b/testApp/pv/testValueBuilder.cpp new file mode 100644 index 0000000..8bf831c --- /dev/null +++ b/testApp/pv/testValueBuilder.cpp @@ -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 +#include + +#include +#include +#include + +namespace pvd = epics::pvData; + +namespace { + +void testBuild() +{ + testDiag("testBuild()"); + pvd::ValueBuilder builder; + builder.add("A", 42) + .add("B", 43); + + pvd::PVStructurePtr val(builder.buildPVStructure()); + + testOk1(val->getStructure()->getID()=="structure"); + { + pvd::PVIntPtr A(val->getSubField("A")); + testOk1(!!A && A->get()==42); + } + { + pvd::PVShortPtr B(val->getSubField("B")); + testOk1(!!B && B->get()==43); + } +} + +void testSubStruct() +{ + testDiag("testSubStruct()"); + pvd::PVStructurePtr val(pvd::ValueBuilder("foo") + .add("A", 42) + .addNested("X", pvd::structure, "bar") + .add("Y", 111) + .add("Z", "hello world") + .endNested() + .add("B", 43) + .buildPVStructure()); + + testOk1(val->getStructure()->getID()=="foo"); + testOk1(val->getSubFieldT("X")->getStructure()->getID()=="bar"); + { + pvd::PVIntPtr A(val->getSubField("A")); + testOk1(!!A && A->get()==42); + } + { + pvd::PVIntPtr B(val->getSubField("B")); + testOk1(!!B && B->get()==43); + } + { + pvd::PVIntPtr Y(val->getSubField("X.Y")); + testOk1(!!Y && Y->get()==111); + } + { + pvd::PVStringPtr Y(val->getSubField("X.Z")); + testOk1(!!Y && Y->get()=="hello world"); + } +} + +void testReplace() +{ + testDiag("testReplace()"); + pvd::PVStructurePtr val(pvd::ValueBuilder("foo") + .add("A", 42) + .add("A", 43) + .addNested("X", pvd::structure, "bar") + .add("Y", 111) + .endNested() + .addNested("X", pvd::structure, "baz") + .add("Y", 112) + .endNested() + .buildPVStructure()); + + testDiag("top id %s", val->getStructure()->getID().c_str()); + testOk1(val->getStructure()->getID()=="foo"); + testDiag("X id %s", val->getSubFieldT("X")->getStructure()->getID().c_str()); + testOk1(val->getSubFieldT("X")->getStructure()->getID()=="baz"); + { + pvd::PVIntPtr A(val->getSubField("A")); + testOk1(!!A && A->get()==43); + } + { + pvd::PVIntPtr Y(val->getSubField("X.Y")); + testOk1(!!Y && Y->get()==112); + } +} + +void testAppend() +{ + testDiag("testAppend()"); + + pvd::PVStructurePtr base(pvd::createRequest("field(foo)record[bar=5]")); + std::cerr<getSubField("field.foo")); + testOk1(!base->getSubField("field.other")); + testOk1(base->getSubFieldT("record._options.bar")->get()=="5"); + testOk1(!base->getSubField("record._options.foo")); + + pvd::PVStructurePtr mod(pvd::ValueBuilder(*base) + .addNested("field") + .addNested("other") + .endNested() + .endNested() + .addNested("record") + .addNested("_options") + .add("bar", 4) + .add("foo", 1) + .endNested() + .endNested() + .buildPVStructure()); + std::cerr<getField().get()!=mod->getField().get()); + testOk1(base->getField()!=mod->getField()); + + // base unchanged + testOk1(!!base->getSubField("field.foo")); + testOk1(!base->getSubField("field.other")); + testOk1(base->getSubFieldT("record._options.bar")->get()=="5"); + testOk1(!base->getSubField("record._options.foo")); + + testOk1(!!mod->getSubField("field.foo")); + testOk1(!!mod->getSubField("field.other")); + testOk1(mod->getSubFieldT("record._options.bar")->get()==4); + testOk1(mod->getSubFieldT("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(); +}