MINIFICPP-1117 - minifi::Exception is now nothrow copyable

Implemented in terms of std::runtime_error

Signed-off-by: Arpad Boda <aboda@apache.org>

This closes #707
diff --git a/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp b/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
index 7e53b55..19d0f31 100644
--- a/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
+++ b/extensions/standard-processors/tests/integration/SecureSocketGetTCPTest.cpp
@@ -53,7 +53,7 @@
 class SecureSocketTest : public IntegrationBase {
  public:
   explicit SecureSocketTest(bool isSecure)
-      : isSecure(isSecure) {
+      : isSecure{ isSecure }, isRunning_{ false } {
     char format[] = "/tmp/ssth.XXXXXX";
     dir = testController.createTempDirectory(format);
   }
@@ -76,7 +76,7 @@
   }
 
   void runAssertions() {
-    assert(LogTestController::getInstance().contains("send succeed 20") == true);
+    assert(LogTestController::getInstance().contains("send succeed 20"));
   }
 
   void queryRootProcessGroup(std::shared_ptr<core::ProcessGroup> pg) {
diff --git a/extensions/standard-processors/tests/unit/ProcessorTests.cpp b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
index 4024e67..4938827 100644
--- a/extensions/standard-processors/tests/unit/ProcessorTests.cpp
+++ b/extensions/standard-processors/tests/unit/ProcessorTests.cpp
@@ -498,7 +498,7 @@
   auto context = std::make_shared<core::ProcessContext>(node, controller_services_provider, repo, repo, content_repo);
   auto psf = std::make_shared<core::ProcessSessionFactory>(context);
   if (hasException) {
-    auto expected_error = "Site2Site Protocol:HTTPClient not resolvable. No peers configured or any port specific hostname and port -- cannot schedule";
+    auto expected_error = "Site2Site Protocol: HTTPClient not resolvable. No peers configured or any port specific hostname and port -- cannot schedule";
     try {
       rpg->onSchedule(context, psf);
     } catch (std::exception &e) {
diff --git a/libminifi/include/Exception.h b/libminifi/include/Exception.h
index c90224c..0445860 100644
--- a/libminifi/include/Exception.h
+++ b/libminifi/include/Exception.h
@@ -20,6 +20,7 @@
 #ifndef __EXCEPTION_H__
 #define __EXCEPTION_H__
 
+#include "utils/StringUtils.h"
 #include <sstream>
 #include <exception>
 #include <stdexcept>
@@ -31,7 +32,6 @@
 namespace nifi {
 namespace minifi {
 
-// ExceptionType 
 enum ExceptionType {
   FILE_OPERATION_EXCEPTION = 0,
   FLOW_EXCEPTION,
@@ -44,11 +44,9 @@
   MAX_EXCEPTION
 };
 
-// Exception String 
 static const char *ExceptionStr[MAX_EXCEPTION] = { "File Operation", "Flow File Operation", "Processor Operation", "Process Session Operation", "Process Schedule Operation", "Site2Site Protocol",
     "General Operation", "Regex Operation" };
 
-// Exception Type to String 
 inline const char *ExceptionTypeToString(ExceptionType type) {
   if (type < MAX_EXCEPTION)
     return ExceptionStr[type];
@@ -56,37 +54,17 @@
     return NULL;
 }
 
-// Exception Class
-class Exception : public std::exception {
- public:
-  // Constructor
+struct Exception : public std::runtime_error {
   /*!
    * Create a new exception
    */
-  Exception(ExceptionType type, std::string errorMsg)
-      : _type(type),
-        _errorMsg(std::move(errorMsg)) {
-  }
+  Exception(ExceptionType type, const std::string& errorMsg)
+      : std::runtime_error{ org::apache::nifi::minifi::utils::StringUtils::join_pack(ExceptionTypeToString(type), ": ", errorMsg) }
+  { }
 
-  // Destructor
-  virtual ~Exception() noexcept {
-  }
-  virtual const char * what() const noexcept {
-
-    _whatStr = ExceptionTypeToString(_type);
-
-    _whatStr += ":" + _errorMsg;
-    return _whatStr.c_str();
-  }
-
- private:
-  // Exception type
-  ExceptionType _type;
-  // Exception detailed information
-  std::string _errorMsg;
-  // Hold the what result
-  mutable std::string _whatStr;
-
+  Exception(ExceptionType type, const char* errorMsg)
+      : std::runtime_error{ org::apache::nifi::minifi::utils::StringUtils::join_pack(ExceptionTypeToString(type), ": ", errorMsg) }
+  { }
 };
 
 } /* namespace minifi */
diff --git a/libminifi/include/utils/StringUtils.h b/libminifi/include/utils/StringUtils.h
index ef0a849..03267b1 100644
--- a/libminifi/include/utils/StringUtils.h
+++ b/libminifi/include/utils/StringUtils.h
@@ -27,6 +27,7 @@
 #include <sstream>
 #include <vector>
 #include <map>
+#include <type_traits>
 #include "utils/FailurePolicy.h"
 
 enum TimeUnit {
@@ -162,7 +163,107 @@
     }
     return newString;
   }
-  
+
+  /**
+   * Returns the size of the passed std::w?string, C string or char array. Returns the array size - 1 in case of arrays.
+   * @tparam CharT Character type, typically char or wchar_t (deduced)
+   * @param str
+   * @return The size of the argument string
+   */
+  template<typename CharT>
+  static size_t size(const std::basic_string<CharT>& str) noexcept { return str.size(); }
+
+  template<typename CharT>
+  static size_t size(const CharT* str) noexcept { return std::char_traits<CharT>::length(str); }
+
+  struct detail {
+
+  // add all args
+  template<typename... SizeT>
+  static size_t sum(SizeT... ns) {
+    size_t result = 0;
+    (void)(std::initializer_list<size_t>{( result += ns )...});
+    return result; // (ns + ...)
+  }
+
+  #ifndef _MSC_VER
+  // partial detection idiom impl
+  template<typename...>
+  using void_t = void;
+
+  struct nonesuch{};
+
+  template<typename Default, typename Void, template<class...> class Op, typename... Args>
+  struct detector {
+    using value_t = std::false_type;
+    using type = Default;
+  };
+
+  template<typename Default, template<class...> class Op, typename... Args>
+  struct detector<Default, void_t<Op<Args...>>, Op, Args...> {
+    using value_t = std::true_type;
+    using type = Op<Args...>;
+  };
+
+  template<template<class...> class Op, typename... Args>
+  using is_detected = typename detector<nonesuch, void, Op, Args...>::value_t;
+
+  // and operation for boolean template argument packs
+  template<bool...>
+  struct and_;
+
+  template<bool Head, bool... Tail>
+  struct and_<Head, Tail...> : std::integral_constant<bool, Head && and_<Tail...>::value>
+  {};
+
+  template<bool B>
+  struct and_<B> : std::integral_constant<bool, B>
+  {};
+
+  // implementation detail of join_pack
+  template<typename CharT>
+  struct str_detector {
+    template<typename Str>
+    using valid_string_t = decltype(std::declval<std::basic_string<CharT>>().append(std::declval<Str>()));
+  };
+
+  template<typename ResultT, typename CharT, typename... Strs>
+  using valid_string_pack_t = typename std::enable_if<and_<is_detected<str_detector<CharT>::template valid_string_t, Strs>::value...>::value, ResultT>::type;
+  #else
+  // MSVC is broken without /permissive-
+  template<typename ResultT, typename...>
+  using valid_string_pack_t = ResultT;
+  #endif
+
+  template<typename CharT, typename... Strs, valid_string_pack_t<void, CharT, Strs...>* = nullptr>
+  static std::basic_string<CharT> join_pack(const Strs&... strs) {
+    std::basic_string<CharT> result;
+    result.reserve(sum(size(strs)...));
+    (void)(std::initializer_list<int>{( result.append(strs) ,0)...});
+    return result;
+  }
+  }; /* struct detail */
+
+  /**
+   * Join all arguments
+   * @tparam CharT Deduced character type
+   * @tparam Strs Deduced string types
+   * @param head First string, used for CharT deduction
+   * @param tail Rest of the strings
+   * @return std::basic_string<CharT> containing the resulting string
+   */
+  template<typename CharT, typename... Strs>
+  static detail::valid_string_pack_t<std::basic_string<CharT>, CharT, Strs...>
+  join_pack(const std::basic_string<CharT>& head, const Strs&... tail) {
+    return detail::join_pack<CharT>(head, tail...);
+  }
+
+  template<typename CharT, typename... Strs>
+  static detail::valid_string_pack_t<std::basic_string<CharT>, CharT, Strs...>
+  join_pack(const CharT* head, const Strs&... tail) {
+    return detail::join_pack<CharT>(head, tail...);
+  }
+
   /**
    * Concatenates strings stored in an arbitrary container using the provided separator.
    * @tparam TChar char type of the string (char or wchar_t)
diff --git a/libminifi/test/unit/ExceptionTests.cpp b/libminifi/test/unit/ExceptionTests.cpp
new file mode 100644
index 0000000..4d34881
--- /dev/null
+++ b/libminifi/test/unit/ExceptionTests.cpp
@@ -0,0 +1,26 @@
+/**
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "../TestBase.h"
+#include "Exception.h"
+
+TEST_CASE("Test general exception .what()", "[testi general what]") {
+  auto ex = minifi::Exception(minifi::GENERAL_EXCEPTION, "Test exception message");
+  REQUIRE(std::string { ex.what() } == "General Operation: Test exception message");
+}
diff --git a/libminifi/test/unit/StringUtilsTests.cpp b/libminifi/test/unit/StringUtilsTests.cpp
index 771d0d0..e71b00c 100644
--- a/libminifi/test/unit/StringUtilsTests.cpp
+++ b/libminifi/test/unit/StringUtilsTests.cpp
@@ -317,3 +317,29 @@
     REQUIRE(data == utils::StringUtils::from_base64(base64.data(), base64.size()));
   }
 }
+
+TEST_CASE("TestStringUtils::testJoinPack", "[test join_pack]") {
+  std::string stdstr = "std::string";
+  const char* cstr = "c string";
+  const char carr[] = "char array";
+  REQUIRE(utils::StringUtils::join_pack("rvalue c string, ", cstr, std::string{ ", rval std::string, " }, stdstr, ", ", carr)
+              == "rvalue c string, c string, rval std::string, std::string, char array");
+}
+
+TEST_CASE("TestStringUtils::testJoinPackWstring", "[test join_pack wstring]") {
+  std::wstring stdstr = L"std::string";
+  const wchar_t* cstr = L"c string";
+  const wchar_t carr[] = L"char array";
+  REQUIRE(utils::StringUtils::join_pack(L"rvalue c string, ", cstr, std::wstring{ L", rval std::string, " }, stdstr, L", ", carr)
+              == L"rvalue c string, c string, rval std::string, std::string, char array");
+}
+
+/* doesn't and shouldn't compile
+TEST_CASE("TestStringUtils::testJoinPackNegative", "[test join_pack negative]") {
+  std::wstring stdstr = L"std::string";
+  const wchar_t* cstr = L"c string";
+  const wchar_t carr[] = L"char array";
+  REQUIRE(utils::StringUtils::join_pack("rvalue c string, ", cstr, std::string{ ", rval std::string, " }, stdstr, ", ", carr)
+              == "rvalue c string, c string, rval std::string, std::string, char array");
+}
+ */