diff --git a/Framework/Kernel/CMakeLists.txt b/Framework/Kernel/CMakeLists.txt index a7e6585ff5e0965e821e8409aaedf90337b69f80..308be7b94628c5f27ab0ac84c8c33dd0c1e15ee9 100644 --- a/Framework/Kernel/CMakeLists.txt +++ b/Framework/Kernel/CMakeLists.txt @@ -90,6 +90,7 @@ set ( SRC_FILES src/PropertyManagerProperty.cpp src/PropertyNexus.cpp src/PropertyWithValue.cpp + src/PropertyWithValueJSONDecoder.cpp src/ProxyInfo.cpp src/PseudoRandomNumberGenerator.cpp src/Quat.cpp @@ -163,7 +164,7 @@ set ( INC_FILES inc/MantidKernel/ConfigService.h inc/MantidKernel/ConfigObserver.h inc/MantidKernel/ConfigPropertyObserver.h - inc/MantidKernel/DateAndTime.h + inc/MantidKernel/DateAndTime.h inc/MantidKernel/DataItem.h inc/MantidKernel/DataService.h inc/MantidKernel/DateAndTimeHelpers.h @@ -242,7 +243,7 @@ set ( INC_FILES inc/MantidKernel/NetworkProxy.h inc/MantidKernel/NeutronAtom.h inc/MantidKernel/NexusDescriptor.h - inc/MantidKernel/normal_distribution.h + inc/MantidKernel/normal_distribution.h inc/MantidKernel/NullValidator.h inc/MantidKernel/OptionalBool.h inc/MantidKernel/ParaViewVersion.h @@ -260,6 +261,7 @@ set ( INC_FILES inc/MantidKernel/PropertyManager_fwd.h inc/MantidKernel/PropertyNexus.h inc/MantidKernel/PropertyWithValue.h + inc/MantidKernel/PropertyWithValueJSONDecoder inc/MantidKernel/ProxyInfo.h inc/MantidKernel/PseudoRandomNumberGenerator.h inc/MantidKernel/QuasiRandomNumberSequence.h @@ -410,6 +412,7 @@ set ( TEST_FILES PropertyNexusTest.h PropertyTest.h PropertyWithValueTest.h + PropertyWithValueJSONDecoderTest.h ProxyInfoTest.h QuatTest.h ReadLockTest.h diff --git a/Framework/Kernel/inc/MantidKernel/PropertyWithValueJSONDecoder.h b/Framework/Kernel/inc/MantidKernel/PropertyWithValueJSONDecoder.h new file mode 100644 index 0000000000000000000000000000000000000000..9809308a680ffc8eec415f3c4200c799edade731 --- /dev/null +++ b/Framework/Kernel/inc/MantidKernel/PropertyWithValueJSONDecoder.h @@ -0,0 +1,27 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef PROPERTYWITHVALUEJSONDECODER_H +#define PROPERTYWITHVALUEJSONDECODER_H + +#include "MantidKernel/DllConfig.h" +#include <memory> + +namespace Json { +class Value; +} + +namespace Mantid { +namespace Kernel { +class Property; + +/// Attempt to create a Property from a Json value object +MANTID_KERNEL_DLL std::unique_ptr<Property> decode(const Json::Value &value); + +} // namespace Kernel +} // namespace Mantid + +#endif // PROPERTYWITHVALUEJSONDECODER_H diff --git a/Framework/Kernel/src/PropertyWithValueJSONDecoder.cpp b/Framework/Kernel/src/PropertyWithValueJSONDecoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0658d31969c268d609e7594dcc50cc7ae90772da --- /dev/null +++ b/Framework/Kernel/src/PropertyWithValueJSONDecoder.cpp @@ -0,0 +1,100 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#include "MantidKernel/PropertyWithValueJSONDecoder.h" +#include "MantidKernel/PropertyWithValue.h" +#include <json/json.h> +#include <unordered_map> + +using Json::StreamWriterBuilder; +using Json::Value; +using Json::writeString; + +namespace Mantid { +namespace Kernel { + +namespace { +// Define a lookup mapping Json::ValueTypes to functions able to +// create a PropertyWithValue object from that type +using FromJsonFn = std::function<std::unique_ptr<Property>( + const std::string &, const Json::Value &)>; +using FromJsonConverters = std::map<Json::ValueType, FromJsonFn>; + +// A pointer to a member function for doing the Json::Value->C++ type conversion +template <typename T> using ValueAsTypeMemFn = T (Json::Value::*)() const; + +// A generic function accepting a pointer to a Json::Value::as* function, e.g. +// asInt, plus the name and value of the property +template <typename ValueType> +std::unique_ptr<Property> fromJson(ValueAsTypeMemFn<ValueType> asFn, + const std::string &name, + const Json::Value &value) { + return std::make_unique<PropertyWithValue<ValueType>>(name, + (value.*(asFn))()); +} + +// Returns (and creates on first call) the map of Json::ValueType to +// FromJsonFn for converting a JsonValue to a Property object +const FromJsonConverters &converters() { + static FromJsonConverters converters; + if (converters.empty()) { + using namespace std::placeholders; + // Build a map of Json types to fromJson functions of the appropriate type + converters.insert(std::make_pair( + Json::booleanValue, + std::bind(fromJson<bool>, &Json::Value::asBool, _1, _2))); + converters.insert(std::make_pair( + Json::intValue, std::bind(fromJson<int>, &Json::Value::asInt, _1, _2))); + converters.insert(std::make_pair( + Json::realValue, + std::bind(fromJson<double>, &Json::Value::asDouble, _1, _2))); + converters.insert(std::make_pair( + Json::stringValue, + std::bind(fromJson<std::string>, &Json::Value::asString, _1, _2))); + } + return converters; +} + +/** + * @brief Create a PropertyWithValue object from the given Json::Value + * @param name The name of the new property + * @param value The value as Json::Value + * @return A pointer to a new Property object + * @throws std::invalid_argument if the type of the Json::Value is not known + */ +std::unique_ptr<Property> createProperty(const std::string &name, + const Json::Value &value) { + auto conversionFnIter = converters().find(value.type()); + if (conversionFnIter == converters().end()) { + throw std::invalid_argument( + "Cannot create property with name " + name + + ". Unable to find converter for Json::ValueType to C++ type"); + } + return conversionFnIter->second(name, value); +} + +} // namespace + +/** + * @brief decode + * @param value A value as a Json serialized quantity + * @return + */ +std::unique_ptr<Property> decode(const Json::Value &value) { + if (value.size() != 1) { + StreamWriterBuilder wbuilder; + throw std::invalid_argument( + "Expected Json::Value with a single member. Found " + + writeString(wbuilder, value)); + } + Value::Members members(value.getMemberNames()); + assert(members.size() == 1); + + return createProperty(members[0], value[members.front()]); +} + +} // namespace Kernel +} // namespace Mantid diff --git a/Framework/Kernel/test/PropertyWithValueJSONDecoderTest.h b/Framework/Kernel/test/PropertyWithValueJSONDecoderTest.h new file mode 100644 index 0000000000000000000000000000000000000000..8ffed9ca21f7b65882e36e615dc2d7625d3e7dd6 --- /dev/null +++ b/Framework/Kernel/test/PropertyWithValueJSONDecoderTest.h @@ -0,0 +1,86 @@ +// Mantid Repository : https://github.com/mantidproject/mantid +// +// Copyright © 2007 ISIS Rutherford Appleton Laboratory UKRI, +// NScD Oak Ridge National Laboratory, European Spallation Source +// & Institut Laue - Langevin +// SPDX - License - Identifier: GPL - 3.0 + +#ifndef PROPERTYWITHVALUEJSONDECODERTEST_H +#define PROPERTYWITHVALUEJSONDECODERTEST_H + +#include <cxxtest/TestSuite.h> +#include <jsoncpp/json/value.h> + +#include "MantidKernel/PropertyWithValue.h" +#include "MantidKernel/PropertyWithValueJSONDecoder.h" + +class PropertyWithValueJSONDecoderTest : public CxxTest::TestSuite { +public: + static PropertyWithValueJSONDecoderTest *createSuite() { + return new PropertyWithValueJSONDecoderTest; + } + + static void destroySuite(PropertyWithValueJSONDecoderTest *suite) { + return delete suite; + } + + void testDecodeSingleJSONIntAsProperty() { + doSimpleObjectDecodeTest("IntProperty", 10); + } + + void testDecodeSingleJSONDoubleAsProperty() { + doSimpleObjectDecodeTest("DoubleProperty", 10.5); + } + + void testDecodeSingleJSONStringAsProperty() { + doSimpleObjectDecodeTest("StringProperty", std::string("My value")); + } + + void testDecodeSingleJSONBoolAsProperty() { + doSimpleObjectDecodeTest("BoolProperty", false); + } + + // ----------------------- Failure tests ----------------------- + + void testDecodeThrowsWithEmptyValue() { + using Mantid::Kernel::decode; + Json::Value root; + TSM_ASSERT_THROWS("Expected decode to throw for empty value", decode(root), + std::invalid_argument); + } + + void testDecodeThrowsWithGreaterThanOneMember() { + using Mantid::Kernel::decode; + Json::Value root; + root["one"] = 1; + root["two"] = 2; + + TSM_ASSERT_THROWS("Expected decode to throw with more than 1 member", + decode(root), std::invalid_argument); + } + + void testDecodeThrowsWithNonObjectValue() { + using Mantid::Kernel::decode; + TSM_ASSERT_THROWS("Expected decode to throw with non-object type", + decode(Json::Value(10)), std::invalid_argument); + } + +private: + template <typename ValueType> + void doSimpleObjectDecodeTest(const std::string &propName, + const ValueType &propValue) { + Json::Value root; + root[propName] = propValue; + + using Mantid::Kernel::decode; + auto property = decode(root); + TSM_ASSERT("Decode failed to create a Property. ", property); + using Mantid::Kernel::PropertyWithValue; + auto typedProperty = + dynamic_cast<PropertyWithValue<ValueType> *>(property.get()); + TSM_ASSERT("Property has unexpected type ", typedProperty); + TS_ASSERT_EQUALS(propName, typedProperty->name()); + TS_ASSERT_EQUALS(propValue, (*typedProperty)()); + } +}; + +#endif // PROPERTYWITHVALUEJSONDECODERTEST_H