From 074d058a7a483a66af7f8c0b928b321ad483f47c Mon Sep 17 00:00:00 2001 From: zheng_wenlong Date: Fri, 29 Sep 2017 21:06:22 +0900 Subject: Add agl-service-windowmanager-2017 Add a new binding agl-service-windowmanager-2017. A image about this see JIRA SPEC-915. [PatchSet2] Use aglwgt make package. [PatchSet3] Modify to wait until wayland compositor starts up. Bug-AGL: SPEC-925 Change-Id: I8729bb71b5e91d5b009a5bab77232d92605c43ea Signed-off-by: zheng_wenlong --- CMakeLists.txt | 94 + LICENSE | 177 + README.md | 4 + client/README | 39 + client/communication.cpp | 86 + client/communication.h | 64 + client/extra/WindowManagerSampleApp.qml | 47 + client/extra/WindowManagerSampleApp.qml.sample2 | 52 + client/main.cpp | 99 + client/main.cpp.sample2 | 92 + client/qlibwindowmanager.cpp | 56 + client/qlibwindowmanager.h | 69 + client/qmlWindowManagerSampleApp.pro | 20 + client/sample.qrc | 5 + doc/ApplicationGuide.md | 497 + export.map | 1 + generate-binding-glue.py | 154 + include/json.hpp | 13003 ++++++++++++++++++++++ layers.json | 89 + libwindowmanager/CMakeLists.txt | 85 + libwindowmanager/doc/CMakeLists.txt | 4 + libwindowmanager/doc/GNUmakefile | 7 + libwindowmanager/doc/LibWindowmanager.md | 250 + libwindowmanager/doc/LibWindowmanager.txt | 238 + libwindowmanager/libwindowmanager.cpp | 498 + libwindowmanager/libwindowmanager.h | 58 + package/root/config.xml | 19 + package/root/icon.svg | 283 + protocol/ivi-application.xml | 100 + protocol/ivi-controller.xml | 603 + scripts/CMakeLists.txt | 5 + scripts/wm-request | 74 + src/CMakeLists.txt | 129 + src/afb_binding_api.cpp | 114 + src/app.cpp | 733 ++ src/app.hpp | 188 + src/config.cpp | 26 + src/config.hpp | 49 + src/controller_hooks.hpp | 40 + src/json_helper.cpp | 102 + src/json_helper.hpp | 30 + src/layers.cpp | 154 + src/layers.hpp | 118 + src/layout.cpp | 17 + src/layout.hpp | 43 + src/main.cpp | 175 + src/policy.hpp | 35 + src/redraw_fixer.cpp | 128 + src/result.hpp | 77 + src/util.cpp | 39 + src/util.hpp | 109 + src/wayland.cpp | 765 ++ src/wayland.hpp | 354 + windowmanager.pc.in | 12 + 54 files changed, 20309 insertions(+) create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 client/README create mode 100644 client/communication.cpp create mode 100644 client/communication.h create mode 100644 client/extra/WindowManagerSampleApp.qml create mode 100644 client/extra/WindowManagerSampleApp.qml.sample2 create mode 100644 client/main.cpp create mode 100644 client/main.cpp.sample2 create mode 100644 client/qlibwindowmanager.cpp create mode 100644 client/qlibwindowmanager.h create mode 100644 client/qmlWindowManagerSampleApp.pro create mode 100644 client/sample.qrc create mode 100644 doc/ApplicationGuide.md create mode 100644 export.map create mode 100644 generate-binding-glue.py create mode 100644 include/json.hpp create mode 100644 layers.json create mode 100644 libwindowmanager/CMakeLists.txt create mode 100644 libwindowmanager/doc/CMakeLists.txt create mode 100644 libwindowmanager/doc/GNUmakefile create mode 100644 libwindowmanager/doc/LibWindowmanager.md create mode 100644 libwindowmanager/doc/LibWindowmanager.txt create mode 100644 libwindowmanager/libwindowmanager.cpp create mode 100644 libwindowmanager/libwindowmanager.h create mode 100644 package/root/config.xml create mode 100644 package/root/icon.svg create mode 100644 protocol/ivi-application.xml create mode 100644 protocol/ivi-controller.xml create mode 100644 scripts/CMakeLists.txt create mode 100644 scripts/wm-request create mode 100644 src/CMakeLists.txt create mode 100644 src/afb_binding_api.cpp create mode 100644 src/app.cpp create mode 100644 src/app.hpp create mode 100644 src/config.cpp create mode 100644 src/config.hpp create mode 100644 src/controller_hooks.hpp create mode 100644 src/json_helper.cpp create mode 100644 src/json_helper.hpp create mode 100644 src/layers.cpp create mode 100644 src/layers.hpp create mode 100644 src/layout.cpp create mode 100644 src/layout.hpp create mode 100755 src/main.cpp create mode 100644 src/policy.hpp create mode 100644 src/redraw_fixer.cpp create mode 100644 src/result.hpp create mode 100644 src/util.cpp create mode 100644 src/util.hpp create mode 100644 src/wayland.cpp create mode 100644 src/wayland.hpp create mode 100644 windowmanager.pc.in diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ec3759b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,94 @@ +# +# Copyright (c) 2017 TOYOTA MOTOR CORPORATION +# +# Licensed 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. +# + +cmake_minimum_required(VERSION 2.8) + +project(WindowManagerTMC) + +set(PACKAGE_VERSION_MAJOR 0) +set(PACKAGE_VERSION_MINOR 0) +set(PACKAGE_VERSION_REVISION 1) +set(PACKAGE_VERSION "${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}.${PACKAGE_VERSION_REVISION}") + +find_package(PkgConfig REQUIRED) +include(GNUInstallDirs) + +pkg_check_modules(WLC wayland-client>=1.11.0 REQUIRED) + +macro(wlproto var_basename proto_xml_basename) + if("${WLSCAN}" STREQUAL "") + find_program(WLSCAN NAMES wayland-scanner) + endif() + + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/protocol) + set(proto_dir "${CMAKE_CURRENT_SOURCE_DIR}/protocol") + elseif(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../protocol) + set(proto_dir "${CMAKE_CURRENT_SOURCE_DIR}/../protocol") + else() + message(FATAL_ERROR "Could not find ${CMAKE_CURRENT_SOURCE_DIR}/protocol or ${CMAKE_CURRENT_SOURCE_DIR}/../protocol") + endif() + + add_custom_command( + OUTPUT + ${proto_xml_basename}-protocol.c + #${proto_xml_basename}-server-protocol.h + ${proto_xml_basename}-client-protocol.h + MAIN_DEPENDENCY ${proto_dir}/${proto_xml_basename}.xml + COMMAND ${WLSCAN} code < ${proto_dir}/${proto_xml_basename}.xml > ${proto_xml_basename}-protocol.c + #COMMAND ${WLSCAN} server-header < ${proto_dir}/${proto_xml_basename}.xml > ${proto_xml_basename}-server-protocol.h + COMMAND ${WLSCAN} client-header < ${proto_dir}/${proto_xml_basename}.xml > ${proto_xml_basename}-client-protocol.h + ) + + set(${var_basename}_PROTO ${CMAKE_CURRENT_BINARY_DIR}/${proto_xml_basename}-protocol.c) + set(${var_basename}_CLIENT ${CMAKE_CURRENT_BINARY_DIR}/${proto_xml_basename}-client-protocol.h) + #set(${var_basename}_SERVER ${CMAKE_CURRENT_BINARY_DIR}/${proto_xml_basename}-server-protocol.h) + + include_directories(${CMAKE_CURRENT_BINARY_DIR}) +endmacro() + +# Should modernize the following somehow... +set(ENABLE_DEBUG_OUTPUT OFF CACHE BOOL "Enable debug logging") +if(ENABLE_DEBUG_OUTPUT) + add_definitions(-DDEBUG_OUTPUT) +else() + remove_definitions(-DDEBUG_OUTPUT) +endif() + +# Should modernize the following somehow... +set(ENABLE_SCOPE_TRACING OFF CACHE BOOL "Enable scope enter/leave messages for certain parts of the code.") +if(ENABLE_SCOPE_TRACING) + add_definitions(-DSCOPE_TRACING) +else() + remove_definitions(-DSCOPE_TRACING) +endif() + +set(SANITIZER_MODE "none" CACHE STRING "Build using a specific sanitizer (e.g. 'address', 'thread', 'leak', 'undefined'), depends on compiler; default none") + +set(LINK_LIBCXX OFF CACHE BOOL "Link against LLVMs libc++") + +add_subdirectory(libwindowmanager) +add_subdirectory(scripts) +add_subdirectory(src) + +configure_file(windowmanager.pc.in windowmanager.pc @ONLY) +INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/windowmanager.pc + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +install( + FILES layers.json + DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/windowmanager + COMPONENT "runtime") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..775e9b3 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +This is a WindowManager implementation for the AGL Project. +=========================================================== + +See doc/ diff --git a/client/README b/client/README new file mode 100644 index 0000000..95ab875 --- /dev/null +++ b/client/README @@ -0,0 +1,39 @@ += Example Application for TMC AGL WindowManager Client Lib +This is a example QML application that uses clients of the TMC WindowManager. + +== Dependencies +* Qt5 + QtQuick (QML) with ivi-shell support + +== Build instructions +Inside of an SDK environment run: + +---------------- +qmake +make +---------------- + +* The binary should be installed somewhere in $PATH + +== Usage +Run this application like follows: + +---------------- +qmlWindowManagerSampleApp $width $height $appLabel $colorName +---------------- + +.Note +**************** +Depending on your environment you will need to set the following +environment variable to instruct Qt to use the ivi-shell integration: +`QT_WAYLAND_SHELL_INTEGRATION=ivi-shell` +**************** + +Starts the application with a surface the size $width x $height +the Surface will request the label "$appLabel" and set its surface +color "$colorName" e.g. red. + +Note, that although the application sets an initial window size, the +window manager will send events to the application that instruct it to +set the proper, requested size for the layout. + +// vim:set tw=72 ft=asciidoc: diff --git a/client/communication.cpp b/client/communication.cpp new file mode 100644 index 0000000..1acfdca --- /dev/null +++ b/client/communication.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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 "communication.h" + +#include +#include + +communication::communication(QObject *parent) : QObject(parent) +{ + this->quit = false; +} + +communication::~communication() +{ +} + +void communication::setWidth(const unsigned int &w) +{ + this->width = w; + emit widthChanged(); +} + +void communication::setHeight(const unsigned int &h) +{ + this->height = h; + emit heightChanged(); +} + +void communication::setColor(const QString &c) +{ + this->color = c; + emit colorChanged(); +} + +void communication::setAppName(const QString &a) +{ + this->appName = a; + emit appNameChanged(); +} + +void communication::setQuit(const bool &q) +{ + this->quit = q; + emit quitChanged(); + if(q) + exit(EXIT_SUCCESS); +} + +unsigned int communication::getWidth() const +{ + return this->width; +} + +unsigned int communication::getHeight() const +{ + return this->height; +} + +QString communication::getColor() const +{ + return this->color; +} + +QString communication::getAppName() const +{ + return this->appName; +} + +bool communication::getQuit() const +{ + return this->quit; +} diff --git a/client/communication.h b/client/communication.h new file mode 100644 index 0000000..fd3072f --- /dev/null +++ b/client/communication.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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. + */ + +#ifndef COMMUNICATION_H +#define COMMUNICATION_H + +#include + +class communication : public QObject +{ + Q_OBJECT + + Q_PROPERTY(unsigned int width WRITE setWidth READ getWidth NOTIFY widthChanged) + Q_PROPERTY(unsigned int height WRITE setHeight READ getHeight NOTIFY heightChanged) + Q_PROPERTY(QString color READ getColor WRITE setColor NOTIFY colorChanged) + Q_PROPERTY(QString appName READ getAppName WRITE setAppName NOTIFY appNameChanged) + Q_PROPERTY(bool quit READ getQuit WRITE setQuit NOTIFY quitChanged) + +public: + explicit communication(QObject *parent = 0); + virtual ~communication(); + +public slots: + void setWidth(const unsigned int &); + void setHeight(const unsigned int &); + void setColor(const QString&); + void setAppName(const QString&); + void setQuit(const bool&); + + unsigned int getWidth() const; + unsigned int getHeight() const; + QString getColor() const; + QString getAppName() const; + bool getQuit() const; + +signals: + void widthChanged(); + void heightChanged(); + void colorChanged(); + void appNameChanged(); + void quitChanged(); + +private: + unsigned int width; + unsigned int height; + QString color; + QString appName; + bool quit; +}; + +#endif // COMMUNICATION_H diff --git a/client/extra/WindowManagerSampleApp.qml b/client/extra/WindowManagerSampleApp.qml new file mode 100644 index 0000000..0945af9 --- /dev/null +++ b/client/extra/WindowManagerSampleApp.qml @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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. + */ + +import QtQuick 2.2 +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.1 + +Window { + id: screen + width: COMM.width + height: COMM.height + color: COMM.color + flags: Qt.FramelessWindowHint + title: COMM.appName + opacity: 0.99 + visible: true + + Timer { + id: quitTimer + interval: 2000 + repeat: false + triggeredOnStart: false + onTriggered: COMM.quit = true + } + + Text { + id: textArea + color: "black" + font.bold: true + font.pointSize: 90 + anchors.centerIn: parent + text: COMM.appName + } +} diff --git a/client/extra/WindowManagerSampleApp.qml.sample2 b/client/extra/WindowManagerSampleApp.qml.sample2 new file mode 100644 index 0000000..1ffc071 --- /dev/null +++ b/client/extra/WindowManagerSampleApp.qml.sample2 @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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. + */ + +import QtQuick 2.2 +import QtQuick.Window 2.1 +import QtQuick.Layouts 1.1 + +Window { + id: screen + width: COMM.width + height: COMM.height + color: COMM.color + flags: Qt.FramelessWindowHint + title: COMM.appName + opacity: 0.99 + visible: true + signal created + + Timer { + id: quitTimer + interval: 2000 + repeat: false + triggeredOnStart: false + onTriggered: COMM.quit = true + } + + Text { + id: textArea + color: "black" + font.bold: true + font.pointSize: 90 + anchors.centerIn: parent + text: COMM.appName + } + onFrameSwapped: { + created() + } + +} diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..3dc2e42 --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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 +#include +#include +#include +#include +#include +#include +#include "communication.h" + +#include "qlibwindowmanager.h" + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + + qDebug() << QCoreApplication::arguments(); + + if (QCoreApplication::arguments().count() < 5) { + qWarning() << "Wrong parameters specified for the application. " + "Please restart with correct parameters:" + "width, height, name, color [port] [token]:\n\n" + "/usr/bin/WindowManagerSampleApp/" + "qmlWindowManagerSampleApp width height name color\n"; + exit(EXIT_FAILURE); + } + + QString label = QCoreApplication::arguments().at(3); + + QLibWindowmanager* qwm = new QLibWindowmanager(); + + QString token = "wm"; + int port = 1700; + if(QCoreApplication::arguments().count() == 7){ + bool ok; + port = QCoreApplication::arguments().at(5).toInt(&ok); + if(ok == false){ + port = 1700; + } + else{ + token = QCoreApplication::arguments().at(6); + } + } + const char* ctoken = token.toLatin1().data(); + if (qwm->init(port, ctoken) != 0) { + exit(EXIT_FAILURE); + } + + if (qwm->requestSurface( + QCoreApplication::arguments().at(3).toLatin1().data()) != 0) { + exit(EXIT_FAILURE); + } + + qwm->set_event_handler(QLibWindowmanager::Event_SyncDraw, [qwm](char const *label) { + fprintf(stderr, "Surface %s got syncDraw!\n", label); + qwm->endDraw(label); + }); + qwm->set_event_handler(QLibWindowmanager::Event_Active, [](char const *label) { + fprintf(stderr, "Surface %s got activated!\n", label); + }); + qwm->set_event_handler(QLibWindowmanager::Event_Visible, [](char const *label) { + fprintf(stderr, "Surface %s got visible!\n", label); + }); + qwm->set_event_handler(QLibWindowmanager::Event_FlushDraw, [](char const *label) { + fprintf(stderr, "Surface %s got flushDraw!\n", label); + }); + + communication comm; + comm.setWidth(QCoreApplication::arguments().at(1).toUInt()); + comm.setHeight(QCoreApplication::arguments().at(2).toUInt()); + comm.setAppName(label); + comm.setColor(QCoreApplication::arguments().at(4)); + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("COMM", &comm); + + engine.load( + QUrl(QStringLiteral("qrc:/extra/WindowManagerSampleApp.qml"))); + QObject *root = engine.rootObjects().first(); + QQuickWindow *window = qobject_cast(root); + QObject::connect(root, SIGNAL(frameSwapped()), qwm, SLOT(slotActivateSurface())); + + return app.exec(); +} diff --git a/client/main.cpp.sample2 b/client/main.cpp.sample2 new file mode 100644 index 0000000..5d9cc44 --- /dev/null +++ b/client/main.cpp.sample2 @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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 +#include +#include +#include +#include +#include +#include +#include "communication.h" + +#include "qlibwindowmanager.h" + +int main(int argc, char *argv[]) { + QGuiApplication app(argc, argv); + + qDebug() << QCoreApplication::arguments(); + + if (QCoreApplication::arguments().count() < 5) { + qWarning() << "Wrong parameters specified for the application. " + "Please restart with correct parameters:" + "width, height, name, color [port] [token]:\n\n" + "/usr/bin/WindowManagerSampleApp/" + "qmlWindowManagerSampleApp width height name color\n"; + exit(EXIT_FAILURE); + } + + QString label = QCoreApplication::arguments().at(3); + + QLibWindowmanager* qwm = new QLibWindowmanager(); + + QString token = "wm"; + int port = 1700; + if(QCoreApplication::arguments().count() == 7){ + bool ok; + port = QCoreApplication::arguments().at(5).toInt(&ok); + if(ok == false){ + port = 1700; + } + else{ + token = QCoreApplication::arguments().at(6); + } + } + const char* ctoken = token.toLatin1().data(); + if (qwm->init(port, ctoken) != 0) { + exit(EXIT_FAILURE); + } + + if (qwm->requestSurface( + QCoreApplication::arguments().at(3).toLatin1().data()) != 0) { + exit(EXIT_FAILURE); + } + + qwm->set_event_handler(QLibWindowmanager::Event_SyncDraw, [qwm](char const *label) { + //qwm->endDraw(label); + fprintf(stderr, "Surface %s got syncDraw!\n", label); + }); + qwm->set_event_handler(QLibWindowmanager::Event_Active, [](char const *label) { + fprintf(stderr, "Surface %s got activated!\n", label); + }); + + communication comm; + comm.setWidth(QCoreApplication::arguments().at(1).toUInt()); + comm.setHeight(QCoreApplication::arguments().at(2).toUInt()); + comm.setAppName(label); + comm.setColor(QCoreApplication::arguments().at(4)); + + QQmlApplicationEngine engine; + engine.rootContext()->setContextProperty("COMM", &comm); + + engine.load( + QUrl(QStringLiteral("qrc:/extra/WindowManagerSampleApp.qml"))); + QObject *root = engine.rootObjects().first(); + QObject::connect(root, SIGNAL(created()), qwm, SLOT(slotActivateSurface())); + + return app.exec(); +} diff --git a/client/qlibwindowmanager.cpp b/client/qlibwindowmanager.cpp new file mode 100644 index 0000000..e69f0ac --- /dev/null +++ b/client/qlibwindowmanager.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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 "qlibwindowmanager.h" +#include + +int QLibWindowmanager::init(int port, char const *token) { + return this->wm->init(port, token); +} + +int QLibWindowmanager::requestSurface(const char *label) { + applabel = label; + return this->wm->requestSurface(label); +} + +int QLibWindowmanager::activateSurface(const char *label) { + return this->wm->activateSurface(label); +} + +int QLibWindowmanager::deactivateSurface(const char *label) { + return this->wm->deactivateSurface(label); +} + +int QLibWindowmanager::endDraw(const char *label) { return this->wm->endDraw(label); } + +void QLibWindowmanager::set_event_handler(enum QEventType et, + std::function f) { + LibWindowmanager::EventType wet = (LibWindowmanager::EventType)et; + return this->wm->set_event_handler(wet, std::move(f)); +} + +void QLibWindowmanager::slotActivateSurface(){ + qDebug("%s",__FUNCTION__); + this->activateSurface(applabel.c_str()); +} + +QLibWindowmanager::QLibWindowmanager(QObject *parent) + :QObject(parent) +{ + wm = new LibWindowmanager(); +} + +QLibWindowmanager::~QLibWindowmanager() { } diff --git a/client/qlibwindowmanager.h b/client/qlibwindowmanager.h new file mode 100644 index 0000000..ed86a65 --- /dev/null +++ b/client/qlibwindowmanager.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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. + */ + +#ifndef QLIBWINDOWMANAGER_H +#define QLIBWINDOWMANAGER_H +#include +#include + #include + #include + #include + #include + #include + +class QLibWindowmanager : public QObject{ +Q_OBJECT +public: + explicit QLibWindowmanager(QObject *parent = nullptr); + ~QLibWindowmanager(); + + QLibWindowmanager(const QLibWindowmanager &) = delete; + QLibWindowmanager &operator=(const QLibWindowmanager &) = delete; + +public: + using handler_fun = std::function; + + enum QEventType { + Event_Active = 1, + Event_Inactive, + + Event_Visible, + Event_Invisible, + + Event_SyncDraw, + Event_FlushDraw, + }; + + static QLibWindowmanager &instance(); + + int init(int port, char const *token); + + // WM API + int requestSurface(const char *label); + int activateSurface(const char *label); + int deactivateSurface(const char *label); + int endDraw(const char *label); + void set_event_handler(enum QEventType et, handler_fun f); + +public slots: + void slotActivateSurface(); + +private: + LibWindowmanager* wm; + std::string applabel; + std::vector surfaceIDs; +}; +#endif // LIBWINDOWMANAGER_H diff --git a/client/qmlWindowManagerSampleApp.pro b/client/qmlWindowManagerSampleApp.pro new file mode 100644 index 0000000..3064d57 --- /dev/null +++ b/client/qmlWindowManagerSampleApp.pro @@ -0,0 +1,20 @@ +TEMPLATE = app +QT += qml quick +CONFIG += c++11 + +HEADERS += communication.h qlibwindowmanager.h + +SOURCES += main.cpp \ + communication.cpp qlibwindowmanager.cpp +RESOURCES += sample.qrc + +INCLUDEPATH += $$[QT_SYSROOT]/usr/include/afb + +LIBS += -lwindowmanager +#LIBS += -lsystemd +LIBS += -lafbwsc +#LIBS += -ljson-c + +target.path = /usr/bin/WindowManagerSampleApp + +INSTALLS += target diff --git a/client/sample.qrc b/client/sample.qrc new file mode 100644 index 0000000..be79eb6 --- /dev/null +++ b/client/sample.qrc @@ -0,0 +1,5 @@ + + + extra/WindowManagerSampleApp.qml + + diff --git a/doc/ApplicationGuide.md b/doc/ApplicationGuide.md new file mode 100644 index 0000000..25d87be --- /dev/null +++ b/doc/ApplicationGuide.md @@ -0,0 +1,497 @@ +**Window Manager Application Guide** +==== +
Revision: 0.2Beta
+
TOYOTA MOTOR CORPORATION
+
30th/Sep/2017
+ +* * * + +Introduction +============ + +This WindowManager implements simple layout switching of applications on +multiple layers and with different layer layouts. + +Intended audience +----------------- + +This documentation is intended for developers and system integrators who +need to know, how the window manager works and how it is to be used. + +Scope of this Document +---------------------- + +This document covers the window manager that was implemented for TMC and +delivered to the Automotive Grade Linux (AGL) project. It includes its +implementation details, concepts of operation, configuration and usage. + +It does not include + +- documentation of the underlying architecture, see + [HMI-Framework](https://wiki.automotivelinux.org/hmiframework). + +- documentation of the AGL application framework and its technologies, + see [AGL Application + Framework](https://wiki.automotivelinux.org/agl-distro/app-framework). + +It is highly recommended to have a good understanding of these documents +and projects before using the window manager. + +Known Issues +------------ + +Currently there is a one known issues: + +- Only single-surface Qt applications are support through the + libwindowmanager library. This is a limitation of how Qt creates surface + IDs for the ivi-application interface. + +External libraries +------------------ + +This project includes a copy of version 2.1.1 the excellent [C++11 JSON +library by Niels Lohmann](https://github.com/nlohmann/json). + +Client Library +-------------- + +A client library implementation that internally uses the *libafbwsc*, is +provided in the subdirectory `libwindowmanager/` with its own documentation +directory. + +The client library is built together with the window manager itself. + +Concepts +======== + +The window manager implements a couple of concepts in order to allow +efficient implementation. + +Layers +------ + +Layers are entities that are stacked on top of each other. Each layer +has an ID which is used for the ivi-controller interface, but this ID +also implicitly specifies its stacking order, from lowest to highest. + +Layers are always full-screen. We do not use layer dimensions as a way +to setup the scene, rather - each layer has a layout attached to it, +which specifies an area that is used by surfaces to draw on. + +Additionally, layers will generally leave surfaces on below layers +activated, and only disable surfaces on layers the are above the +currently used layer. + +It is possible to deactivate these surfaces on lower layers explicitly +using the `DeactivateSurface` API call. + +Surfaces +-------- + +Surfaces are *placed* on layers according to their name. The surface +will then be resized to dimensions, according to the layer’s layout +configuration. + +Binding API +=========== + +The binding API consists of a couple of AFB *verbs* - that is; function +calls to the Window Manager. + +Verbs (Functions) +----------------- + +Each function returns a reply containing at least a failed or successful +result of the call, additionally, when calls return something, it is +noted. The notation used has the following meaning: + + FunctionName(argument_name: argument_type)[: function_return_type] + +Where the return type may be omitted if it is void. + +- `RequestSurface(drawing_name: string): int` Request a surface ID for + the given name. This name and ID association will live until the + surface is destroyed (or e.g. the application exits). Each surface + that is managed by the window manager needs to call this function + first! + +- `ActivateSurface(drawing_name: string)` This function requests the + activation of a surface. It usually is not called by the + application, but rather by the application framework or + the HomeScreen. + +- `DeactivateSurface(drawing_name: string)` Request deactivation of + a surface. This function is not usually called by applications + themselves, but rather by the application framework or + the HomeScreen. + +- `EndDraw(drawing_name: string)` Signals the window manager, that the + surface is finished drawing. This is useful for consistent + flicker-free layout switches, see the Architecture document + for details. + +There are a couple of non-essential (mostly for debugging and +development) API calls: + +- `list_drawing_names(): json` List known surface *name* to + *ID* associations. + +- `ping()` Ping the window manager. Does also dispatch pending events + if any. + +- `debug_status(): json` Returns a json representation of the current + layers and surfaces known to the window manager. This represents the + wayland-ivi-extension object’s properties. + +- `debug_surfaces(): json` Returns a json representation of all + surfaces known to the window manager. This represents the + wayland-ivi-extension properties of the surfaces. + +- `debug_layers(): json` Returns the current layer configuration, as + configured through *layers.json*. + +- `debug_terminate()` Terminates the afb-daemon running the window + manager binding, if the environment variable + `WINMAN_DEBUG_TERMINATE` is set. + +Events +------ + +The window manager broadcasts certain events (to all applications) that +signal information on the state of the surface regarding the current +layout. + +- `Active(drawing_name: string)` Signal that the surface with the name + `drawing_name` is now active. + +- `Inactive(drawing_name: string)` Signal that the surface with the + name `drawing_name` is now inactive. This usually means, the layout + got changed, and the surface is now considered inactive + (or sleeping). + +- `Visible(drawing_name: string)` Signal applications, that the + surface with name `drawing_name` is now visible. + +- `Invisible(drawing_name: string)` Signal applications that the + surface with name `drawing_name` is now invisible. + +- `SyncDraw(drawing_name: string)` Signal applications, that the + surface with name `drawing_name` needs to redraw its content - this + usually is sent when the surface geometry changed. + +- `FlushDraw(drawing_name: string)` Signal to applications, that the + surface with name `drawing_name` can now be swapped to its newly + drawn content as the window manager is ready to activate a new + layout (i.e. a new surface geometry). + +Binding API Usage +----------------- + +For a detailed description on how the binding API is supposed to be +used, refer to the Architecture document. + +Configuration +============= + +The window manager is configured with the *layers.json* configuration +file, by default it is searched in `/etc/layers.json` but through the +use of the environment variable `LAYERS_JSON` the WM can be instructed +to use different file. Note, that the WM will not run unless this +configuration is found and valid. + +A sample configuration is provided with the window manager +implementation, this sample is installed to /etc/layers.json. + +Configuration Items +------------------- + +This section describes configuration items available through +`layers.json`. It will do this, by first providing an example, and then +going into its components. + +### main\_surface + + "main_surface": { + "surface_role": "HomeScreen", + }, + +The `main_surface` object describes a surface that will internally be +treated as the main surface - usually this mean *HomeScreen*. The only +special handling this surface receives, is that it is not allowed to +deactivate it. Placement of this surface on an layer is done by the +other configuration described below. + +- `surface_role` this configuration item specifies the name of the + main surface. Set this to e.g. `HomeScreen`. + +### mappings + +This configuration item is a list of surface-name to layer mappings. + +#### surface to layer mapping + + "mappings": [ + { + "role": "^HomeScreen$", + "name": "HomeScreen", + "layer_id": 1000, + "area": { "type": "full" }, + }, + { + "role": "MediaPlayer|Radio|Phone", + "name": "apps", + "layer_id": 1001, + "area": { "type": "rect", + "rect": { "x": 0, + "y": 100, + "width": -1, + "height": -201 } }, + "split_layouts": [] + } + ] + +Each mapping defines the following items to map corresponding surfaces +to a layer. + +- `role` defines a regular expression that application drawing names + are matched against. If applications match tis regular expression, + the surface will be visible on this layer. + +- `name` is just a name definition for this layer, it has no + functional use apart from identifying a layer with a name. + +- `layer_id` specifies which ID this layer will use. + +- `area` is an object that defines the area assigned to surfaces. + +- `split_layouts` is an optional item, that - if present - defines a + number of possible split-screen layouts for this layer. + +#### Area + +Areas can be either `full` or `rect`, whereas `full` means a full-screen +layer, this is mostly useful for the main\_surface or HomeScreen layer. +`rect` declares a layer drawing area specified as a rectangle with start +coordinates `x` and `y` as well as its dimensions `width` and `height`. + +The dimensions can be specified relative to the screen dimensions. For +this negative values for width and height mus be used. + +For example, a full-screen surface can have the following `rect` +definition: + + "rect": { "x": 0, + "y": 0, + "width": -1, + "height": -1 } + +A surface that leaves a 200pixel margin on the top and bottom can use +the following `rect` definition: + + "rect": { "x": 0, + "y": 200, + "width": -1, + "height": -401 } + +So the expression for the actual surface dimensions when using +screen-size-relative values will be: + + actual_width = screen_width + 1 + width + actual_height = screen_height + 1 + height + +Or in other words, to leave an `N` wide border around a surface, the +actual value in the dimension configuration needs to be `-N - 1`, and +appropriate offsets need to be set for `x` and `y`. + +#### split\_layouts + +This configuration item allows the specification of split-screen layouts +on layers for certain surfaces. + +A split screen layout always has a *main* surface and a *sub* surface. +In order to enter a split screen layout, first the *main* surface of the +layout must be activated, and then the *sub* surface. In order to +disable the split layout, one of the two participating surface must be +deactivated (or a surface on a layer below the current one must be +activated). + + "split_layouts": [ + { + "name": "Media Player", + "main_match": "^App MPlayer Main$", + "sub_match": "^App MPlayer Sub", + } + ] + +A split layout object has the following attributes: + +- `name` defines its name, it has no actual function other then a way + to identify this split layout. + +- `main_match` is a regular expression that matches for the *main* + surface of this split layout. + +- `sub_match` is a regular expression that matches for the *sub* + surface of this layout. + +In the above example only the surface with drawing name +`App MPlayer Main` will be used as the *main* surface, but all surfaces +that begin with `App MPlayer Sub` can be used as a *sub* surface for +this layout. + +The names must still match the layer’s role match! + +Building and Running +==================== + +Dependencies +------------ + +This project is intended to be build with the 4.0 release of AGL. + +Build dependencies are as follows: + +- afb-daemon >= 1.0 + +- libsystemd >= 222 + +- wayland-client >= 1.11 + +- cmake >= 3.6.1 + +Build Configuration +------------------- + +**Download recipe** +If repo is already done, please start with git clone +``` +$ mkdir WORK +$ cd WORK +$ repo init -b dab -m dab_4.0.0_xml -u https://gerrit.automotivelinux.org/gerrit/AGL/AGL-repo +$ repo sync +$ git clone https://gerrit.automotivelinux.org/gerrit/staging/meta-hmi-framework + +``` + +Then you can get the following recipe. +* `meta-hmi-framework/windowmanager` + + +**Bitbake** +``` +$ source meta-agl/scripts/aglsetup.sh -m m3ulcb agl-demo agl-devel agl-appfw-smack agl-hmi-framework +$ bitbake agl-service-windowmanager-2017 +``` + + +A couple of build options to configure the build are available: + +- `ENABLE_DEBUG_OUTPUT:BOOL` Compiles including very verbose debug + output from the window manager, use --verbose three times on an + afb-daemon instance to see the debug messages. + +- `ENABLE_SCOPE_TRACING:BOOL` Enables a simple scope tracing mechanism + used for a rather small portion of the window manager code. However, + it is used quite extensively in the libwindowmanager implementation. + +By default these options will be disabled. + + +Implementation Notes +==================== + +The window manager is implemented as a app-framework-binder binding. +That means, the build produces one shared object that exports a binding +interface. + +Binding code generation +----------------------- + +The binding API is rather simple; functions receive a json object +describing arguments and return a json object describing the result or +an error. In order to simplify development, the +`generate-binding-glue.py` script was added, that contains a description +of the API as a python dictionary. This script generates the header +`afb_binding_api.hpp` and the afb binding functions as +`afb_binding_glue.inl`. Where the latter is included in `main.cpp`. + +Each function for the AFB binding that is generated does the following: + +- Lock the binding mutex, so that we serialize all access to + the binding. + +- Do some debug logging (if wanted). + +- Check the binding state, i.e. the compositor might have exited + unexpectedly at which point it would not make sense to continue. + +- Extract the arguments from the json object that is provided (doing + some primitive type checking). + +- Call the afb\_binding\_api method corresponding to this binding + function + +- Check the afb\_binding\_api’s function return value, log an error + state and return the result to the afb request. + +The generated functions do also check for any "loose" exception that +comes out of the afb\_binding\_api call (which in turn might call the +actual non-trivial implementation in `App`). However, **IF** an +exception is thrown and not handled inside the afb\_binding\_call, that +internal state of the window manager might be broken at this time (hence +the talkative error log). + +Structure +--------- + +The implementation is loosely split across the following source files: + +- `main.cpp`: The program entry point as used by the afb-daemon. This + file defines the afbBindingV2 symbol tat is used by the afb-daemon + in order to load a binding. It also defines the wayland fd event + dispatcher and some globals to be used (as context for the afb calls + we receive). + +- `afb_binding_api.cpp`: The implementation of the afb + binding functions. The actual functions are generated by + `generate-binding-glue.py` which generates a **.inl** file that is + included by `main.cpp`. + +- `app.cpp` / `app.hpp`: This is the main application + logic implementation. + +- `config.cpp` / `config.hpp`: Very simple configuration + item interface. + +- `controller_hooks.hpp`: hook functions called by the wayland + controller to call into the App instance. Only a very limited number + of events are passed to the Application, which allowed the usage of + such a simple interface. + +- `json_helper.cpp` / `json_helper.hpp`: Smaller json related + helper functions. + +- `layers.cpp` / `layers.hpp`: Actually hold all the data from + layers.json configuration, do some transformations and service the + App implementation. + +- `layout.cpp` / `layout.hpp`: Very simple layout state for the + implementation of split layouts and tracking of the + surfaces involved. + +- `policy.hpp`: PolicyManager implementation stub. Gets passed the + current and new layout on layout switch and can decide upon it being + valid or not. + +- `result.hpp`: Simple result class around + `std::experimental::optional` that additionally can hold a + `char const *` to describe the error. + +- `util.cpp` / `util.hpp`: general utility functions and structs - and + preprocessor definitions (e.g. `log*()` to AFB logging functions. + +- `wayland.cpp` / `wayland.hpp`: A C++ object-oriented + libwayland-client wrapper. It is instanced in `main.cpp` and handles + all our wayland needs. + + diff --git a/export.map b/export.map new file mode 100644 index 0000000..ee2f413 --- /dev/null +++ b/export.map @@ -0,0 +1 @@ +{ global: afbBindingV*; local: *; }; diff --git a/generate-binding-glue.py b/generate-binding-glue.py new file mode 100644 index 0000000..de78634 --- /dev/null +++ b/generate-binding-glue.py @@ -0,0 +1,154 @@ +#!/usr/bin/python3 + +# +# Copyright (c) 2017 TOYOTA MOTOR CORPORATION +# +# Licensed 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. +# + +import sys + +OUT = sys.stdout + +def set_output(f): + global OUT + OUT = f + +def p(*args): + OUT.write('\n'.join(args)) + OUT.write('\n') + +def emit_func_impl(api, f): + args = f.get('args', []) + if len(args) > 0: + p(' json_object *jreq = afb_req_json(req);', '') + for arg in args: + arg['jtype'] = arg.get('jtype', arg['type']) # add jtype default + p(' json_object *j_%(name)s = nullptr;' % arg, + ' if (! json_object_object_get_ex(jreq, "%(name)s", &j_%(name)s)) {' % arg, + ' afb_req_fail(req, "failed", "Need %(type)s argument %(name)s");' % arg, + ' return;', + ' }', + ' %(type)s a_%(name)s = json_object_get_%(jtype)s(j_%(name)s);' % arg, '') + p(' auto ret = %(api)s' % api + '%(name)s(' % f + ', '.join(map(lambda x: 'a_' + x['name'], args)) + ');') + p(' if (ret.is_err()) {', + ' afb_req_fail(req, "failed", ret.unwrap_err());', + ' return;', + ' }', '') + p(' afb_req_success(req, ret.unwrap(), "success");') + +def emit_func(api, f): + p('void %(impl_name)s(afb_req req) noexcept {' % f) + p(' std::lock_guard guard(binding_m);') + p(' #ifdef ST') + p(' ST();') + p(' #endif') + p(' if (g_afb_instance == nullptr) {', + ' afb_req_fail(req, "failed", "Binding not initialized, did the compositor die?");', + ' return;', + ' }', '', + ' try {', ' // BEGIN impl') + emit_func_impl(api, f) + p(' // END impl', + ' } catch (std::exception &e) {', + ' afb_req_fail_f(req, "failed", "Uncaught exception while calling %(name)s: %%s", e.what());' % f, + ' return;', + ' }', '') + p('}', '') + +def emit_afb_verbs(api): + p('const struct afb_verb_v2 %(name)s_verbs[] = {' % api) + for f in api['functions']: + p(' { "%(name)s", %(impl_name)s, nullptr, nullptr, AFB_SESSION_NONE },' % f) + p(' {}', '};') + +def emit_binding(api): + p('namespace {') + p('std::mutex binding_m;', '') + for func in api['functions']: + emit_func(api, func) + p('} // namespace', '') + emit_afb_verbs(api) + +def generate_names(api): + for f in api['functions']: + f['impl_name'] = '%s_%s_thunk' % (api['name'], f['name']) + +def emit_afb_api(api): + p('#include "result.hpp"', '') + p('#include ', '') + p('namespace wm {', '') + p('struct App;', '') + p('struct binding_api {') + p(' typedef wm::result result_type;') + p(' struct wm::App *app;') + p(' void send_event(char const *evname, char const *label);') + for f in api['functions']: + p(' result_type %(name)s(' % f + ', '.join(map(lambda x: '%(type)s %(name)s' % x, f.get('args', []))) + ');') + p('};', '') + p('} // namespace wm', '') + +# names must always be valid in c and unique for each function (that is its arguments) +# arguments will be looked up from json request, range checking needs to be implemented +# by the actual API call +API = { + 'name': 'windowmanager', + 'api': 'g_afb_instance->app.api.', # where are our API functions + 'functions': [ + { + 'name': 'requestsurface', + #'return_type': 'int', # Or do they return all just some json? + 'args': [ # describes the functions arguments, and their names as found in the json request + { 'name': 'drawing_name', 'type': 'char const*', 'jtype': 'string' }, + ], + }, + { + 'name': 'activatesurface', + 'args': [ + { 'name': 'drawing_name', 'type': 'char const*', 'jtype': 'string' }, + ], + }, + { + 'name': 'deactivatesurface', + 'args': [ + { 'name': 'drawing_name', 'type': 'char const*', 'jtype': 'string' }, + ], + }, + { + 'name': 'enddraw', + 'args': [ + { 'name': 'drawing_name', 'type': 'char const*', 'jtype': 'string' }, + ], + }, + { 'name': 'list_drawing_names', }, + { 'name': 'ping' }, + + { 'name': 'debug_status', }, + { 'name': 'debug_layers', }, + { 'name': 'debug_surfaces', }, + { 'name': 'debug_terminate' }, + ] +} + +def main(): + with open('afb_binding_glue.inl', 'w') as out: + set_output(out) + p('// This file was generated, do not edit', '') + generate_names(API) + emit_binding(API) + with open('afb_binding_api.hpp', 'w') as out: + set_output(out) + p('// This file was generated, do not edit', '') + emit_afb_api(API) + +__name__ == '__main__' and main() diff --git a/include/json.hpp b/include/json.hpp new file mode 100644 index 0000000..6dfc183 --- /dev/null +++ b/include/json.hpp @@ -0,0 +1,13003 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.1.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License . +Copyright (c) 2013-2017 Niels Lohmann . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#include // all_of, copy, fill, find, for_each, none_of, remove, reverse, transform +#include // array +#include // assert +#include // isdigit +#include // and, not, or +#include // isfinite, labs, ldexp, signbit +#include // nullptr_t, ptrdiff_t, size_t +#include // int64_t, uint64_t +#include // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull +#include // strlen +#include // forward_list +#include // function, hash, less +#include // initializer_list +#include // setw +#include // istream, ostream +#include // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator +#include // numeric_limits +#include // locale +#include // map +#include // addressof, allocator, allocator_traits, unique_ptr +#include // accumulate +#include // stringstream +#include // domain_error, invalid_argument, out_of_range +#include // getline, stoi, string, to_string +#include // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type +#include // declval, forward, make_pair, move, pair, swap +#include // vector + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if not defined(JSON_NOEXCEPTION) || defined(__EXCEPTIONS) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ + +/*! +@brief unnamed namespace with internal helper functions + +This namespace collects some functions that could not be defined inside the +@ref basic_json class. + +@since version 2.1.0 +*/ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + if (lhs == value_t::discarded or rhs == value_t::discarded) + { + return false; + } + + return order[static_cast(lhs)] < + order[static_cast(rhs)]; +} + + +///////////// +// helpers // +///////////// + +// alias templates to reduce boilerplate +template +using enable_if_t = typename std::enable_if::type; + +template +using uncvref_t = typename std::remove_cv::type>::type; + +// taken from http://stackoverflow.com/a/26936864/266378 +template +using is_unscoped_enum = + std::integral_constant::value and + std::is_enum::value>; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same::value and has_value_type::value +will not compile when T = void (on MSVC at least). Whereas +conjunction>, has_value_type>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template struct conjunction : std::true_type {}; +template struct conjunction : B1 {}; +template +struct conjunction : std::conditional, B1>::type {}; + +template struct negation : std::integral_constant < bool, !B::value > {}; + +// dispatch utility (taken from ranges-v3) +template struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + + +////////////////// +// constructors // +////////////////// + +template struct external_constructor; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + // replace infinity and NAN by null + if (not std::isfinite(val)) + { + j = BasicJsonType{}; + } + else + { + j.m_type = value_t::number_float; + j.m_value = val; + } + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create(begin(arr), end(arr)); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor +{ + template + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template struct has_##type { \ + private: \ + template \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral()))>::value; \ + } + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +#undef NLOHMANN_JSON_HAS_HELPER + + +template +struct is_compatible_object_type_impl : std::false_type {}; + +template +struct is_compatible_object_type_impl +{ + static constexpr auto value = + std::is_constructible::value and + std::is_constructible::value; +}; + +template +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction>, + has_mapped_type, + has_key_type>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value or + std::is_same::value; +}; + +template +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction>, + negation>, + negation>, + negation>, + has_value_type, + has_iterator>::value; +}; + +template +struct is_compatible_integer_type_impl : std::false_type {}; + +template +struct is_compatible_integer_type_impl +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits; + using CompatibleLimits = std::numeric_limits; + + static constexpr auto value = + std::is_constructible::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral::value and + not std::is_same::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + + +// trait checking if JSONSerializer::from_json(json const&, udt&) exists +template +struct has_from_json +{ + private: + // also check the return type of from_json + template::from_json( + std::declval(), std::declval()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if JSONSerializer::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t::from_json(std::declval()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer::to_json exists +template +struct has_to_json +{ + private: + template::to_json( + std::declval(), std::declval()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral>()))>::value; +}; + + +///////////// +// to_json // +///////////// + +template::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor::construct(j, b); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor::construct(j, s); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t::value, int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t::value, int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor::construct(j, static_cast(val)); +} + +template::value, int> = 0> +void to_json(BasicJsonType& j, UnscopedEnumType e) noexcept +{ + external_constructor::construct(j, e); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type::value or + std::is_same::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor::construct(j, arr); +} + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t::value, + int> = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& arr) +{ + external_constructor::construct(j, arr); +} + + +/////////////// +// from_json // +/////////////// + +// overloads for basic_json template parameters +template::value and + not std::is_same::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast( + *j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast( + *j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast( + *j.template get_ptr()); + break; + } + default: + { + JSON_THROW( + std::domain_error("type must be number, but is " + j.type_name())); + } + } +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (not j.is_boolean()) + { + JSON_THROW(std::domain_error("type must be boolean, but is " + j.type_name())); + } + b = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (not j.is_string()) + { + JSON_THROW(std::domain_error("type must be string, but is " + j.type_name())); + } + s = *j.template get_ptr(); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, UnscopedEnumType& e) +{ + typename std::underlying_type::type val; + get_arithmetic_value(j, val); + e = static_cast(val); +} + +template +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (not j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + arr = *j.template get_ptr(); +} + +// forward_list doesn't have an insert method +template +void from_json(const BasicJsonType& j, std::forward_list& l) +{ + // do not perform the check when user wants to retrieve jsons + // (except when it's null.. ?) + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + if (not std::is_same::value) + { + if (not j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + for (auto it = j.rbegin(), end = j.rend(); it != end; ++it) + { + l.push_front(it->template get()); + } +} + +template +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0>) +{ + using std::begin; + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1>) +-> decltype( + arr.reserve(std::declval()), + void()) +{ + using std::begin; + using std::end; + + arr.reserve(j.size()); + std::transform( + j.begin(), j.end(), std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get(); + }); +} + +template::value and + not std::is_same::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (j.is_null()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + + // when T == BasicJsonType, do not check if value_t is correct + if (not std::is_same::value) + { + if (not j.is_array()) + { + JSON_THROW(std::domain_error("type must be array, but is " + j.type_name())); + } + } + from_json_array_impl(j, arr, priority_tag<1> {}); +} + +template::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (not j.is_object()) + { + JSON_THROW(std::domain_error("type must be object, but is " + j.type_name())); + } + + auto inner_object = j.template get_ptr(); + using std::begin; + using std::end; + // we could avoid the assignment, but this might require a for loop, which + // might be less efficient than the container constructor for some + // containers (would it?) + obj = CompatibleObjectType(begin(*inner_object), end(*inner_object)); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value and + not std::is_same::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast(j)) + { + case value_t::number_unsigned: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_integer: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::number_float: + { + val = static_cast(*j.template get_ptr()); + break; + } + case value_t::boolean: + { + val = static_cast(*j.template get_ptr()); + break; + } + default: + { + JSON_THROW(std::domain_error("type must be number, but is " + j.type_name())); + } + } +} + +struct to_json_fn +{ + private: + template + auto call(BasicJsonType& j, T&& val, priority_tag<1>) const noexcept(noexcept(to_json(j, std::forward(val)))) + -> decltype(to_json(j, std::forward(val)), void()) + { + return to_json(j, std::forward(val)); + } + + template + void call(BasicJsonType&, T&&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + } + + public: + template + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval().call(j, std::forward(val), priority_tag<1> {}))) + { + return call(j, std::forward(val), priority_tag<1> {}); + } +}; + +struct from_json_fn +{ + private: + template + auto call(const BasicJsonType& j, T& val, priority_tag<1>) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template + void call(const BasicJsonType&, T&, priority_tag<0>) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); + } + + public: + template + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; + +// taken from ranges-v3 +template +struct static_const +{ + static constexpr T value{}; +}; + +template +constexpr T static_const::value; +} // namespace detail + + +/// namespace to hold default `to_json` / `from_json` functions +namespace +{ +constexpr const auto& to_json = detail::static_const::value; +constexpr const auto& from_json = detail::static_const::value; +} + + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward(j), val))) + { + ::nlohmann::from_json(std::forward(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward(val)))) + { + ::nlohmann::to_json(j, std::forward(val)); + } +}; + + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +template < + template class ObjectType = std::map, + template class ArrayType = std::vector, + class StringType = std::string, + class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template class AllocatorType = std::allocator, + template class JSONSerializer = adl_serializer + > +class basic_json +{ + private: + template friend struct detail::external_constructor; + /// workaround type for MSVC + using basic_json_t = basic_json; + + public: + using value_t = detail::value_t; + // forward declarations + template class iter_impl; + template class json_reverse_iterator; + class json_pointer; + template + using json_serializer = JSONSerializer; + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType; + + /// the type of an element pointer + using pointer = typename std::allocator_traits::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl; + /// a const iterator for a basic_json container + using const_iterator = iter_impl; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"] = + { + {"string", "2.1.1"}, + {"major", 2}, + {"minor", 1}, + {"patch", 1} + }; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less, // key_compare + std::allocator> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + using object_t = ObjectType, + AllocatorType>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not constraint explicitly. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template + static T* create(Args&& ... args) + { + AllocatorType alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create(); + break; + } + + case value_t::array: + { + array = create(); + break; + } + + case value_t::string: + { + string = create(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + break; + } + + default: + { + if (t == value_t::null) + { + JSON_THROW(std::domain_error("961c151d2e87f2686a955a9be24d316f1362bf21 2.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create(value); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create(value); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create(value); + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + /*! + @brief JSON callback events + + This enumeration lists the parser events that can trigger calling a + callback function of type @ref parser_callback_t during parsing. + + @image html callback_events.png "Example when certain parse events are triggered" + + @since version 1.0.0 + */ + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse(std::istream&, const + parser_callback_t) or @ref parse(const CharT, const parser_callback_t), + it is called on certain events (passed as @ref parse_event_t via parameter + @a event) with a set recursion depth @a depth and context JSON value + @a parsed. The return value of the callback function is a boolean + indicating whether the element that emitted the callback shall be kept or + not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse(std::istream&, parser_callback_t) or + @ref parse(const CharT, const parser_callback_t) for examples + + @since version 1.0.0 + */ + using parser_callback_t = std::function; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] value_type the type of the value to create + + @complexity Constant. + + @throw std::bad_alloc if allocation for object, array, or string value + fails + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @since version 1.0.0 + */ + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exsits. The constructor forwards the + parameter @a val to that method (to `json_serializer::to_json` method + with `U = uncvref_t`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::set`, `std::unordered_set`, `std::multiset`, and + `unordered_multiset` with a `value_type` from which a @ref basic_json + value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t` + + @param[in] val the value to be forwarded + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @throw what `json_serializer::to_json()` throws + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template, + detail::enable_if_t::value and + not std::is_same::value and + not detail::is_basic_json_nested_type< + basic_json_t, U>::value and + detail::has_to_json::value, + int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer::to_json( + std::declval(), std::forward(val)))) + { + JSONSerializer::to_json(*this, std::forward(val)); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has now way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(std::initializer_list) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(std::initializer_list) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(std::initializer_list) and + @ref object(std::initializer_list). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw std::domain_error if @a type_deduction is `false`, @a manual_type + is `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string; example: `"cannot create object from + initializer list"` + + @complexity Linear in the size of the initializer list @a init. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(std::initializer_list init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const basic_json & element) + { + return element.is_array() and element.size() == 2 and element[0].is_string(); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (manual_type == value_t::object and not is_an_object) + { + JSON_THROW(std::domain_error("cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const basic_json & element) + { + m_value.object->emplace(*(element[0].m_value.string), element[1]); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create(init); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(std::initializer_list, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(std::initializer_list, bool, + value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw std::domain_error if @a init is not a pair whose first elements are + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) + + @complexity Linear in the size of @a init. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(std::initializer_list init = + std::initializer_list()) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @complexity Linear in @a cnt. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of primitive types (number, boolean, or string), @a first must + be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, std::out_of_range is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector`. + - In case of a null type, std::domain_error is thrown. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion.** + + @throw std::domain_error if iterators are not compatible; that is, do not + belong to the same JSON value; example: `"iterators are not compatible"` + @throw std::out_of_range if iterators are for a primitive type (number, + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` + @throw std::bad_alloc if allocation for object, array, or string fails + @throw std::domain_error if called with a null value; example: `"cannot + use construct with iterators from null"` + + @complexity Linear in distance between @a first and @a last. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (first.m_object != last.m_object) + { + JSON_THROW(std::domain_error("iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + JSON_THROW(std::out_of_range("iterators out of range")); + } + break; + } + + default: + { + break; + } + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + JSON_THROW(std::domain_error("cannot use construct with iterators from " + first.m_object->type_name())); + } + } + + assert_invariant(); + } + + /*! + @brief construct a JSON value given an input stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @deprecated This constructor is deprecated and will be removed in version + 3.0.0 to unify the interface of the library. Deserialization will be + done by stream operators or by calling one of the `parse` functions, + e.g. @ref parse(std::istream&, const parser_callback_t). That is, calls + like `json j(i);` for an input stream @a i need to be replaced by + `json j = json::parse(i);`. See the example below. + + @liveexample{The example below demonstrates constructing a JSON value from + a `std::stringstream` with and without callback + function.,basic_json__istream} + + @since version 2.0.0, deprecated in version 2.0.3, to be removed in + version 3.0.0 + */ + JSON_DEPRECATED + explicit basic_json(std::istream& i, const parser_callback_t cb = nullptr) + { + *this = parser(i, cb).parse(); + assert_invariant(); + } + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @complexity Linear in the size of @a other. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @throw std::bad_alloc if allocation for object, array, or string fails. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + { + break; + } + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post @a other is a JSON null value + + @complexity Constant. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the swap() member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() + { + assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + AllocatorType alloc; + alloc.destroy(m_value.object); + alloc.deallocate(m_value.object, 1); + break; + } + + case value_t::array: + { + AllocatorType alloc; + alloc.destroy(m_value.array); + alloc.deallocate(m_value.array, 1); + break; + } + + case value_t::string: + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + break; + } + + default: + { + // all other types need no specific destructor + break; + } + } + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + parameter. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @liveexample{The following example shows the effect of different @a indent + parameters to the result of the serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 + */ + string_t dump(const int indent = -1) const + { + std::stringstream ss; + + if (indent >= 0) + { + dump(ss, true, static_cast(indent)); + } + else + { + dump(ss, false, 0); + } + + return ss.str(); + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true iff the JSON type is primitive (string, number, + boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true iff the JSON type is structured (array or + object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true iff the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return m_type == value_t::null; + } + + /*! + @brief return whether value is a boolean + + This function returns true iff the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return m_type == value_t::boolean; + } + + /*! + @brief return whether value is a number + + This function returns true iff the JSON value is a number. This includes + both integer and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true iff the JSON value is an integer or unsigned + integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return m_type == value_t::number_integer or m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true iff the JSON value is an unsigned integer + number. This excludes floating-point and (signed) integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return m_type == value_t::number_unsigned; + } + + /*! + @brief return whether value is a floating-point number + + This function returns true iff the JSON value is a floating-point number. + This excludes integer and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return m_type == value_t::number_float; + } + + /*! + @brief return whether value is an object + + This function returns true iff the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return m_type == value_t::object; + } + + /*! + @brief return whether value is an array + + This function returns true iff the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return m_type == value_t::array; + } + + /*! + @brief return whether value is a string + + This function returns true iff the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return m_type == value_t::string; + } + + /*! + @brief return whether value is discarded + + This function returns true iff the JSON value was discarded during parsing + with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return m_type == value_t::discarded; + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (is_boolean()) + { + return m_value.boolean; + } + + JSON_THROW(std::domain_error("type must be boolean, but is " + type_name())); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This funcion helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw std::domain_error if ReferenceType does not match underlying value + type of the current JSON + */ + template + static ReferenceType get_ref_impl(ThisType& obj) + { + // helper type + using PointerType = typename std::add_pointer::type; + + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr(); + + if (ptr != nullptr) + { + return *ptr; + } + + JSON_THROW(std::domain_error("incompatible ReferenceType for get_ref, actual type is " + + obj.type_name())); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename BasicJsonType, + detail::enable_if_t::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer has a `from_json()` method of the form + `void from_json(const @ref basic_json&, ValueType&)`, and + - @ref json_serializer does not have a `from_json()` method of + the form `ValueType from_json(const @ref basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t, + detail::enable_if_t < + not std::is_same::value and + detail::has_from_json::value and + not detail::has_non_default_from_json::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval(), std::declval()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get(), which is why we + // still need the uncvref + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer has a `from_json()` method of the form + `ValueType from_json(const @ref basic_json&)` + + @note If @ref json_serializer has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer `from_json()` method throws + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t, + detail::enable_if_t::value and + detail::has_non_default_from_json::value, int> = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer::from_json(std::declval()))) + { + static_assert(not std::is_reference::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + or std::is_same::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + std::domain_error otherwise + + @throw std::domain_error in case passed type @a ReferenceType is + incompatible with the stored JSON value + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template::value and + std::is_const::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw std::domain_error in case passed type @a ValueType is incompatible + to JSON, thrown by @ref get() const + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer::value and + not std::is_same::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same>::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read and + written using `at()`.,at__size_type} + + @since version 1.0.0 + */ + reference at(size_type idx) + { + // at only works for arrays + if (is_array()) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(std::out_of_range("array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(std::domain_error("cannot use at() with " + type_name())); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` + @throw std::out_of_range if the index @a idx is out of range of the array; + that is, `idx >= size()`; example: `"array index 7 is out of range"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + `at()`.,at__size_type_const} + + @since version 1.0.0 + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (is_array()) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(std::out_of_range("array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(std::domain_error("cannot use at() with " + type_name())); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using `at()`.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (is_object()) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(std::out_of_range("key '" + key + "' not found")); + } + } + else + { + JSON_THROW(std::domain_error("cannot use at() with " + type_name())); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` + @throw std::out_of_range if the key @a key is is not stored in the object; + that is, `find(key) == end()`; example: `"key "the fast" not found"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + `at()`.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (is_object()) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(std::out_of_range("key '" + key + "' not found")); + } + } + else + { + JSON_THROW(std::domain_error("cannot use at() with " + type_name())); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array or null; example: + `"cannot use operator[] with string"` + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create(); + assert_invariant(); + } + + // operator[] only works for arrays + if (is_array()) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (is_array()) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create(); + assert_invariant(); + } + + // operator[] only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + reference operator[](T * (&key)[n]) + { + return operator[](static_cast(key)); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @note This function is required for compatibility reasons with Clang. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + template + const_reference operator[](T * (&key)[n]) const + { + return operator[](static_cast(key)); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with string"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (is_object()) + { + return m_value.object->operator[](key); + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template + const_reference operator[](T* key) const + { + // at only works for objects + if (is_object()) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(std::domain_error("cannot use operator[] with " + type_name())); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + else + { + JSON_THROW(std::domain_error("cannot use value() with " + type_name())); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template::value, int>::type = 0> + ValueType value(const json_pointer& ptr, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (std::out_of_range&) + { + return default_value; + } + } + + JSON_THROW(std::domain_error("cannot use value() with " + type_name())); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw std::out_of_range when called on `null` value. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on an iterator which does not belong to + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (this != pos.m_object) + { + JSON_THROW(std::domain_error("iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not pos.m_it.primitive_iterator.is_begin()) + { + JSON_THROW(std::out_of_range("iterator out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + { + JSON_THROW(std::domain_error("cannot use erase() with " + type_name())); + } + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw std::domain_error if called on a `null` value; example: `"cannot + use erase() with null"` + @throw std::domain_error if called on iterators which does not belong to + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template::value or + std::is_same::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (this != first.m_object or this != last.m_object) + { + JSON_THROW(std::domain_error("iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (not first.m_it.primitive_iterator.is_begin() or not last.m_it.primitive_iterator.is_end()) + { + JSON_THROW(std::out_of_range("iterators out of range")); + } + + if (is_string()) + { + AllocatorType alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + { + JSON_THROW(std::domain_error("cannot use erase() with " + type_name())); + } + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (is_object()) + { + return m_value.object->erase(key); + } + + JSON_THROW(std::domain_error("cannot use erase() with " + type_name())); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (is_array()) + { + if (idx >= size()) + { + JSON_THROW(std::out_of_range("array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else + { + JSON_THROW(std::domain_error("cannot use erase() with " + type_name())); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + iterator find(typename object_t::key_type key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(typename object_t::key_type) + */ + const_iterator find(typename object_t::key_type key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(key); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + size_type count(typename object_t::key_type key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(key) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty + + Checks if a JSON value has no elements. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + { + break; + } + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + JSON_THROW(std::domain_error("cannot use push_back() with " + type_name())); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + JSON_THROW(std::domain_error("cannot use push_back() with " + type_name())); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (not(is_null() or is_object())) + { + JSON_THROW(std::domain_error("cannot use push_back() with " + type_name())); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(std::initializer_list init) + { + if (is_object() and init.size() == 2 and init.begin()->is_string()) + { + const string_t key = *init.begin(); + push_back(typename object_t::value_type(key, *(init.begin() + 1))); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(std::initializer_list) + */ + reference operator+=(std::initializer_list init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (not(is_null() or is_array())) + { + JSON_THROW(std::domain_error("cannot use emplace_back() with " + type_name())); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw std::domain_error when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template + std::pair emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (not(is_null() or is_object())) + { + JSON_THROW(std::domain_error("cannot use emplace() with " + type_name())); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + JSON_THROW(std::domain_error("iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(std::domain_error("cannot use insert() with " + type_name())); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + JSON_THROW(std::domain_error("iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(std::domain_error("cannot use insert() with " + type_name())); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @throw std::domain_error if @a first and @a last do not belong to the same + JSON value; example: `"iterators do not fit"` + @throw std::domain_error if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (not is_array()) + { + JSON_THROW(std::domain_error("cannot use insert() with " + type_name())); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + JSON_THROW(std::domain_error("iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (first.m_object != last.m_object) + { + JSON_THROW(std::domain_error("iterators do not fit")); + } + + if (first.m_object == this or last.m_object == this) + { + JSON_THROW(std::domain_error("passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, std::initializer_list ilist) + { + // insert only works for arrays + if (not is_array()) + { + JSON_THROW(std::domain_error("cannot use insert() with " + type_name())); + } + + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + JSON_THROW(std::domain_error("iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); + return result; + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw std::domain_error when JSON value is not an array; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (is_array()) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(std::domain_error("cannot use swap() with " + type_name())); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (is_object()) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(std::domain_error("cannot use swap() with " + type_name())); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (is_string()) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(std::domain_error("cannot use swap() with " + type_name())); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same. + - Integer and floating-point numbers are automatically converted before + comparison. Floating-point numbers are compared indirectly: two + floating-point numbers `f1` and `f2` are considered equal if neither + `f1 > f2` nor `f2 > f1` holds. + - Two JSON null values are equal. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array == *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object == *rhs.m_value.object; + } + case value_t::null: + { + return true; + } + case value_t::string: + { + return *lhs.m_value.string == *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean == rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer == rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float == rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + { + return *lhs.m_value.array < *rhs.m_value.array; + } + case value_t::object: + { + return *lhs.m_value.object < *rhs.m_value.object; + } + case value_t::null: + { + return false; + } + case value_t::string: + { + return *lhs.m_value.string < *rhs.m_value.string; + } + case value_t::boolean: + { + return lhs.m_value.boolean < rhs.m_value.boolean; + } + case value_t::number_integer: + { + return lhs.m_value.number_integer < rhs.m_value.number_integer; + } + case value_t::number_unsigned: + { + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + } + case value_t::number_float: + { + return lhs.m_value.number_float < rhs.m_value.number_float; + } + default: + { + return false; + } + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /// @} + + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. The + indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + j.dump(o, pretty_print, static_cast(indentation)); + + return o; + } + + /*! + @brief serialize to stream + @copydoc operator<<(std::ostream&, const basic_json&) + */ + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from an array + + This function reads from an array of 1-byte values. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @param[in] array array to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @since version 2.0.3 + */ + template + static basic_json parse(T (&array)[N], + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(array), std::end(array), cb); + } + + /*! + @brief deserialize from string literal + + @tparam CharT character/literal type with size of 1 byte + @param[in] s string literal to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + @note String containers like `std::string` or @ref string_t can be parsed + with @ref parse(const ContiguousContainer&, const parser_callback_t) + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @sa @ref parse(std::istream&, const parser_callback_t) for a version that + reads from an input stream + + @since version 1.0.0 (originally for @ref string_t) + */ + template::value and + std::is_integral::type>::value and + sizeof(typename std::remove_pointer::type) == 1, int>::type = 0> + static basic_json parse(const CharT s, + const parser_callback_t cb = nullptr) + { + return parser(reinterpret_cast(s), cb).parse(); + } + + /*! + @brief deserialize from stream + + @param[in,out] i stream to read a serialized JSON value from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @sa @ref parse(const CharT, const parser_callback_t) for a version + that reads from a string + + @since version 1.0.0 + */ + static basic_json parse(std::istream& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @copydoc parse(std::istream&, const parser_callback_t) + */ + static basic_json parse(std::istream&& i, + const parser_callback_t cb = nullptr) + { + return parser(i, cb).parse(); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate(first, last, std::pair(true, 0), + [&first](std::pair res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert(sizeof(typename std::iterator_traits::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + // if iterator range is empty, create a parser with an empty string + // to generate "unexpected EOF" error message + if (std::distance(first, last) <= 0) + { + return parser("").parse(); + } + + return parser(first, last, cb).parse(); + } + + /*! + @brief deserialize from a container with contiguous storage + + This function reads from a container with contiguous storage of 1-byte + values. Compatible container types include `std::vector`, `std::string`, + `std::array`, and `std::initializer_list`. User-defined containers can be + used as long as they implement random-access iterators and a contiguous + storage. + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam ContiguousContainer container type with contiguous storage + @param[in] c container to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 + */ + template::value and + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits()))>::iterator_category>::value + , int>::type = 0> + static basic_json parse(const ContiguousContainer& c, + const parser_callback_t cb = nullptr) + { + // delegate the call to the iterator-range parse overload + return parse(std::begin(c), std::end(c), cb); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw std::invalid_argument in case of parse errors + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + j = parser(i).parse(); + return i; + } + + /*! + @brief deserialize from stream + @copydoc operator<<(basic_json&, std::istream&) + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + j = parser(i).parse(); + return i; + } + + /// @} + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + private: + /*! + @note Some code in the switch cases has been copied, because otherwise + copilers would complain about implicit fallthrough and there is no + portable attribute to mute such warnings. + */ + template + static void add_to_vector(std::vector& vec, size_t bytes, const T number) + { + assert(bytes == 1 or bytes == 2 or bytes == 4 or bytes == 8); + + switch (bytes) + { + case 8: + { + vec.push_back(static_cast((static_cast(number) >> 070) & 0xff)); + vec.push_back(static_cast((static_cast(number) >> 060) & 0xff)); + vec.push_back(static_cast((static_cast(number) >> 050) & 0xff)); + vec.push_back(static_cast((static_cast(number) >> 040) & 0xff)); + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + vec.push_back(static_cast((number >> 010) & 0xff)); + vec.push_back(static_cast(number & 0xff)); + break; + } + + case 4: + { + vec.push_back(static_cast((number >> 030) & 0xff)); + vec.push_back(static_cast((number >> 020) & 0xff)); + vec.push_back(static_cast((number >> 010) & 0xff)); + vec.push_back(static_cast(number & 0xff)); + break; + } + + case 2: + { + vec.push_back(static_cast((number >> 010) & 0xff)); + vec.push_back(static_cast(number & 0xff)); + break; + } + + case 1: + { + vec.push_back(static_cast(number & 0xff)); + break; + } + } + } + + /*! + @brief take sufficient bytes from a vector to fill an integer variable + + In the context of binary serialization formats, we need to read several + bytes from a byte vector and combine them to multi-byte integral data + types. + + @param[in] vec byte vector to read from + @param[in] current_index the position in the vector after which to read + + @return the next sizeof(T) bytes from @a vec, in reverse order as T + + @tparam T the integral return type + + @throw std::out_of_range if there are less than sizeof(T)+1 bytes in the + vector @a vec to read + + In the for loop, the bytes from the vector are copied in reverse order into + the return value. In the figures below, let sizeof(T)=4 and `i` be the loop + variable. + + Precondition: + + vec: | | | a | b | c | d | T: | | | | | + ^ ^ ^ ^ + current_index i ptr sizeof(T) + + Postcondition: + + vec: | | | a | b | c | d | T: | d | c | b | a | + ^ ^ ^ + | i ptr + current_index + + @sa Code adapted from . + */ + template + static T get_from_vector(const std::vector& vec, const size_t current_index) + { + if (current_index + sizeof(T) + 1 > vec.size()) + { + JSON_THROW(std::out_of_range("cannot read " + std::to_string(sizeof(T)) + " bytes from vector")); + } + + T result; + auto* ptr = reinterpret_cast(&result); + for (size_t i = 0; i < sizeof(T); ++i) + { + *ptr++ = vec[current_index + sizeof(T) - i]; + } + return result; + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + This is a straightforward implementation of the MessagePack specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static void to_msgpack_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + // nil + v.push_back(0xc0); + break; + } + + case value_t::boolean: + { + // true and false + v.push_back(j.m_value.boolean ? 0xc3 : 0xc2); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we + // used the code from the value_t::number_unsigned case + // here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= std::numeric_limits::min() and j.m_value.number_integer <= std::numeric_limits::max()) + { + // int 8 + v.push_back(0xd0); + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= std::numeric_limits::min() and j.m_value.number_integer <= std::numeric_limits::max()) + { + // int 16 + v.push_back(0xd1); + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= std::numeric_limits::min() and j.m_value.number_integer <= std::numeric_limits::max()) + { + // int 32 + v.push_back(0xd2); + add_to_vector(v, 4, j.m_value.number_integer); + } + else if (j.m_value.number_integer >= std::numeric_limits::min() and j.m_value.number_integer <= std::numeric_limits::max()) + { + // int 64 + v.push_back(0xd3); + add_to_vector(v, 8, j.m_value.number_integer); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 8 + v.push_back(0xcc); + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 16 + v.push_back(0xcd); + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 32 + v.push_back(0xce); + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= std::numeric_limits::max()) + { + // uint 64 + v.push_back(0xcf); + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // float 64 + v.push_back(0xcb); + const auto* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + v.push_back(static_cast(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + v.push_back(0xd9); + add_to_vector(v, 1, N); + } + else if (N <= 65535) + { + // str 16 + v.push_back(0xda); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // str 32 + v.push_back(0xdb); + add_to_vector(v, 4, N); + } + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + v.push_back(static_cast(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + v.push_back(0xdc); + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + // array 32 + v.push_back(0xdd); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.array) + { + to_msgpack_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + v.push_back(static_cast(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + v.push_back(0xde); + add_to_vector(v, 2, N); + } + else if (N <= 4294967295) + { + // map 32 + v.push_back(0xdf); + add_to_vector(v, 4, N); + } + + // append each element + for (const auto& el : *j.m_value.object) + { + to_msgpack_internal(el.first, v); + to_msgpack_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + /*! + @brief create a CBOR serialization of a given JSON value + + This is a straightforward implementation of the CBOR specification. + + @param[in] j JSON value to serialize + @param[in,out] v byte vector to write the serialization to + + @sa https://tools.ietf.org/html/rfc7049 + */ + static void to_cbor_internal(const basic_json& j, std::vector& v) + { + switch (j.type()) + { + case value_t::null: + { + v.push_back(0xf6); + break; + } + + case value_t::boolean: + { + v.push_back(j.m_value.boolean ? 0xf5 : 0xf4); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= std::numeric_limits::max()) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= std::numeric_limits::max()) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_integer); + } + else if (j.m_value.number_integer <= std::numeric_limits::max()) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_integer); + } + else + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_integer); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + v.push_back(static_cast(0x20 + positive_number)); + } + else if (positive_number <= std::numeric_limits::max()) + { + // int 8 + v.push_back(0x38); + add_to_vector(v, 1, positive_number); + } + else if (positive_number <= std::numeric_limits::max()) + { + // int 16 + v.push_back(0x39); + add_to_vector(v, 2, positive_number); + } + else if (positive_number <= std::numeric_limits::max()) + { + // int 32 + v.push_back(0x3a); + add_to_vector(v, 4, positive_number); + } + else + { + // int 64 + v.push_back(0x3b); + add_to_vector(v, 8, positive_number); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + v.push_back(static_cast(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= 0xff) + { + v.push_back(0x18); + // one-byte uint8_t + add_to_vector(v, 1, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffff) + { + v.push_back(0x19); + // two-byte uint16_t + add_to_vector(v, 2, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffff) + { + v.push_back(0x1a); + // four-byte uint32_t + add_to_vector(v, 4, j.m_value.number_unsigned); + } + else if (j.m_value.number_unsigned <= 0xffffffffffffffff) + { + v.push_back(0x1b); + // eight-byte uint64_t + add_to_vector(v, 8, j.m_value.number_unsigned); + } + break; + } + + case value_t::number_float: + { + // Double-Precision Float + v.push_back(0xfb); + const auto* helper = reinterpret_cast(&(j.m_value.number_float)); + for (size_t i = 0; i < 8; ++i) + { + v.push_back(helper[7 - i]); + } + break; + } + + case value_t::string: + { + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + v.push_back(0x60 + static_cast(N)); // 1 byte for string + size + } + else if (N <= 0xff) + { + v.push_back(0x78); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x79); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x7a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x7b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append string + std::copy(j.m_value.string->begin(), j.m_value.string->end(), + std::back_inserter(v)); + break; + } + + case value_t::array: + { + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + v.push_back(0x80 + static_cast(N)); // 1 byte for array + size + } + else if (N <= 0xff) + { + v.push_back(0x98); // one-byte uint8_t for N + add_to_vector(v, 1, N); + } + else if (N <= 0xffff) + { + v.push_back(0x99); // two-byte uint16_t for N + add_to_vector(v, 2, N); + } + else if (N <= 0xffffffff) + { + v.push_back(0x9a); // four-byte uint32_t for N + add_to_vector(v, 4, N); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0x9b); // eight-byte uint64_t for N + add_to_vector(v, 8, N); + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.array) + { + to_cbor_internal(el, v); + } + break; + } + + case value_t::object: + { + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + v.push_back(0xa0 + static_cast(N)); // 1 byte for object + size + } + else if (N <= 0xff) + { + v.push_back(0xb8); + add_to_vector(v, 1, N); // one-byte uint8_t for N + } + else if (N <= 0xffff) + { + v.push_back(0xb9); + add_to_vector(v, 2, N); // two-byte uint16_t for N + } + else if (N <= 0xffffffff) + { + v.push_back(0xba); + add_to_vector(v, 4, N); // four-byte uint32_t for N + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + v.push_back(0xbb); + add_to_vector(v, 8, N); // eight-byte uint64_t for N + } + // LCOV_EXCL_STOP + + // append each element + for (const auto& el : *j.m_value.object) + { + to_cbor_internal(el.first, v); + to_cbor_internal(el.second, v); + } + break; + } + + default: + { + break; + } + } + } + + + /* + @brief checks if given lengths do not exceed the size of a given vector + + To secure the access to the byte vector during CBOR/MessagePack + deserialization, bytes are copied from the vector into buffers. This + function checks if the number of bytes to copy (@a len) does not exceed + the size @s size of the vector. Additionally, an @a offset is given from + where to start reading the bytes. + + This function checks whether reading the bytes is safe; that is, offset is + a valid index in the vector, offset+len + + @param[in] size size of the byte vector + @param[in] len number of bytes to read + @param[in] offset offset where to start reading + + vec: x x x x x X X X X X + ^ ^ ^ + 0 offset len + + @throws out_of_range if `len > v.size()` + */ + static void check_length(const size_t size, const size_t len, const size_t offset) + { + // simple case: requested length is greater than the vector's length + if (len > size or offset > size) + { + JSON_THROW(std::out_of_range("len out of range")); + } + + // second case: adding offset would result in overflow + if ((size > (std::numeric_limits::max() - offset))) + { + JSON_THROW(std::out_of_range("len+offset out of range")); + } + + // last case: reading past the end of the vector + if (len + offset > size) + { + JSON_THROW(std::out_of_range("len+offset out of range")); + } + } + + /*! + @brief create a JSON value from a given MessagePack vector + + @param[in] v MessagePack serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @sa https://github.com/msgpack/msgpack/blob/master/spec.md + */ + static basic_json from_msgpack_internal(const std::vector& v, size_t& idx) + { + // make sure reading 1 byte is safe + check_length(v.size(), 1, idx); + + // store and increment index + const size_t current_idx = idx++; + + if (v[current_idx] <= 0xbf) + { + if (v[current_idx] <= 0x7f) // positive fixint + { + return v[current_idx]; + } + if (v[current_idx] <= 0x8f) // fixmap + { + basic_json result = value_t::object; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + else if (v[current_idx] <= 0x9f) // fixarray + { + basic_json result = value_t::array; + const size_t len = v[current_idx] & 0x0f; + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + else // fixstr + { + const size_t len = v[current_idx] & 0x1f; + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + } + else if (v[current_idx] >= 0xe0) // negative fixint + { + return static_cast(v[current_idx]); + } + else + { + switch (v[current_idx]) + { + case 0xc0: // nil + { + return value_t::null; + } + + case 0xc2: // false + { + return false; + } + + case 0xc3: // true + { + return true; + } + + case 0xca: // float 32 + { + // copy bytes in reverse order into the double variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v.at(current_idx + 1 + byte); + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xcb: // float 64 + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v.at(current_idx + 1 + byte); + } + idx += sizeof(double); // skip content bytes + return res; + } + + case 0xcc: // uint 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xcd: // uint 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xce: // uint 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xcf: // uint 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd0: // int 8 + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0xd1: // int 16 + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd2: // int 32 + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd3: // int 64 + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + case 0xd9: // str 8 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xda: // str 16 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdb: // str 32 + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0xdc: // array 16 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xdd: // array 32 + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_msgpack_internal(v, idx)); + } + return result; + } + + case 0xde: // map 16 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + case 0xdf: // map 32 + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_msgpack_internal(v, idx); + result[key] = from_msgpack_internal(v, idx); + } + return result; + } + + default: + { + JSON_THROW(std::invalid_argument("error parsing a msgpack @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx])))); + } + } + } + } + + /*! + @brief create a JSON value from a given CBOR vector + + @param[in] v CBOR serialization + @param[in] idx byte index to start reading from @a v + + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid CBOR + @throw std::out_of_range if the given vector ends prematurely + + @sa https://tools.ietf.org/html/rfc7049 + */ + static basic_json from_cbor_internal(const std::vector& v, size_t& idx) + { + // store and increment index + const size_t current_idx = idx++; + + switch (v.at(current_idx)) + { + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + { + return v[current_idx]; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + return get_from_vector(v, current_idx); + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return get_from_vector(v, current_idx); + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return get_from_vector(v, current_idx); + } + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + { + return static_cast(0x20 - 1 - v[current_idx]); + } + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + idx += 1; // skip content byte + // must be uint8_t ! + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + idx += 2; // skip 2 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + idx += 4; // skip 4 content bytes + return static_cast(-1) - get_from_vector(v, current_idx); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + idx += 8; // skip 8 content bytes + return static_cast(-1) - static_cast(get_from_vector(v, current_idx)); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + const auto len = static_cast(v[current_idx] - 0x60); + const size_t offset = current_idx + 1; + idx += len; // skip content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 2; + idx += len + 1; // skip size byte + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 3; + idx += len + 2; // skip 2 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 5; + idx += len + 4; // skip 4 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + const auto len = static_cast(get_from_vector(v, current_idx)); + const size_t offset = current_idx + 9; + idx += len + 8; // skip 8 size bytes + content bytes + check_length(v.size(), len, offset); + return std::string(reinterpret_cast(v.data()) + offset, len); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (v.at(idx) != 0xff) + { + string_t s = from_cbor_internal(v, idx); + result += s; + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + basic_json result = value_t::array; + const auto len = static_cast(v[current_idx] - 0x80); + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + basic_json result = value_t::array; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + result.push_back(from_cbor_internal(v, idx)); + } + return result; + } + + case 0x9f: // array (indefinite length) + { + basic_json result = value_t::array; + while (v.at(idx) != 0xff) + { + result.push_back(from_cbor_internal(v, idx)); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + basic_json result = value_t::object; + const auto len = static_cast(v[current_idx] - 0xa0); + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 1; // skip 1 size byte + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 2; // skip 2 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 4; // skip 4 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + basic_json result = value_t::object; + const auto len = static_cast(get_from_vector(v, current_idx)); + idx += 8; // skip 8 size bytes + for (size_t i = 0; i < len; ++i) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + return result; + } + + case 0xbf: // map (indefinite length) + { + basic_json result = value_t::object; + while (v.at(idx) != 0xff) + { + std::string key = from_cbor_internal(v, idx); + result[key] = from_cbor_internal(v, idx); + } + // skip break byte (0xFF) + idx += 1; + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + idx += 2; // skip two content bytes + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added to + // IEEE 754 in 2008, today's programming platforms often still + // only have limited support for them. It is very easy to + // include at least decoding support for them even without such + // support. An example of a small decoder for half-precision + // floating-point numbers in the C language is shown in Fig. 3. + const int half = (v.at(current_idx + 1) << 8) + v.at(current_idx + 2); + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = mant == 0 + ? std::numeric_limits::infinity() + : std::numeric_limits::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + // copy bytes in reverse order into the float variable + float res; + for (size_t byte = 0; byte < sizeof(float); ++byte) + { + reinterpret_cast(&res)[sizeof(float) - byte - 1] = v.at(current_idx + 1 + byte); + } + idx += sizeof(float); // skip content bytes + return res; + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + // copy bytes in reverse order into the double variable + double res; + for (size_t byte = 0; byte < sizeof(double); ++byte) + { + reinterpret_cast(&res)[sizeof(double) - byte - 1] = v.at(current_idx + 1 + byte); + } + idx += sizeof(double); // skip content bytes + return res; + } + + default: // anything else (0xFF is handled inside the other types) + { + JSON_THROW(std::invalid_argument("error parsing a CBOR @ " + std::to_string(current_idx) + ": " + std::to_string(static_cast(v[current_idx])))); + } + } + } + + public: + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + + @since version 2.0.9 + */ + static std::vector to_msgpack(const basic_json& j) + { + std::vector result; + to_msgpack_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in MessagePack format + + Deserializes a given byte vector @a v to a JSON value using the MessagePack + serialization format. + + @param[in] v a byte vector in MessagePack format + @param[in] start_index the index to start reading from @a v (0 by default) + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from MessagePack were + used in the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(const std::vector&, const size_t) for the + related CBOR format + + @since version 2.0.9, parameter @a start_index since 2.1.1 + */ + static basic_json from_msgpack(const std::vector& v, + const size_t start_index = 0) + { + size_t i = start_index; + return from_msgpack_internal(v, i); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector&, const size_t) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json& for the related MessagePack format + + @since version 2.0.9 + */ + static std::vector to_cbor(const basic_json& j) + { + std::vector result; + to_cbor_internal(j, result); + return result; + } + + /*! + @brief create a JSON value from a byte vector in CBOR format + + Deserializes a given byte vector @a v to a JSON value using the CBOR + (Concise Binary Object Representation) serialization format. + + @param[in] v a byte vector in CBOR format + @param[in] start_index the index to start reading from @a v (0 by default) + @return deserialized JSON value + + @throw std::invalid_argument if unsupported features from CBOR were used in + the given vector @a v or if the input is not valid MessagePack + @throw std::out_of_range if the given vector ends prematurely + + @complexity Linear in the size of the byte vector @a v. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(const std::vector&, const size_t) for the + related MessagePack format + + @since version 2.0.9, parameter @a start_index since 2.1.1 + */ + static basic_json from_cbor(const std::vector& v, + const size_t start_index = 0) + { + size_t i = start_index; + return from_cbor_internal(v, i); + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return basically a string representation of a the @a m_type member + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @since version 1.0.0, public since 2.1.0 + */ + std::string type_name() const + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + private: + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s) noexcept + { + return std::accumulate(s.begin(), s.end(), size_t{}, + [](size_t res, typename string_t::value_type c) + { + switch (c) + { + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + return res + 1; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // from c (1 byte) to \uxxxx (6 bytes) + return res + 5; + } + + return res; + } + } + }); + } + + /*! + @brief escape a string + + Escape a string by replacing certain special characters by a sequence of + an escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. + + @param[in] s the string to escape + @return the escaped string + + @complexity Linear in the length of string @a s. + */ + static string_t escape_string(const string_t& s) + { + const auto space = extra_space(s); + if (space == 0) + { + return s; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (const auto& c : s) + { + switch (c) + { + // quotation mark (0x22) + case '"': + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + // reverse solidus (0x5c) + case '\\': + { + // nothing to change + pos += 2; + break; + } + + // backspace (0x08) + case '\b': + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + // formfeed (0x0c) + case '\f': + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + // newline (0x0a) + case '\n': + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + // carriage return (0x0d) + case '\r': + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + // horizontal tab (0x09) + case '\t': + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + if (c >= 0x00 and c <= 0x1f) + { + // convert a number 0..15 to its hex representation + // (0..f) + static const char hexify[16] = + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + // print character c as \uxxxx + for (const char m : + { 'u', '0', '0', hexify[c >> 4], hexify[c & 0x0f] + }) + { + result[++pos] = m; + } + + ++pos; + } + else + { + // all other characters are added as-is + result[pos++] = c; + } + break; + } + } + } + + return result; + } + + + /*! + @brief locale-independent serialization for built-in arithmetic types + */ + struct numtostr + { + public: + template + numtostr(NumberType value) + { + x_write(value, std::is_integral()); + } + + const char* c_str() const + { + return m_buf.data(); + } + + private: + /// a (hopefully) large enough character buffer + std::array < char, 64 > m_buf{{}}; + + template + void x_write(NumberType x, /*is_integral=*/std::true_type) + { + // special case for "0" + if (x == 0) + { + m_buf[0] = '0'; + return; + } + + const bool is_negative = x < 0; + size_t i = 0; + + // spare 1 byte for '\0' + while (x != 0 and i < m_buf.size() - 1) + { + const auto digit = std::labs(static_cast(x % 10)); + m_buf[i++] = static_cast('0' + digit); + x /= 10; + } + + // make sure the number has been processed completely + assert(x == 0); + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < m_buf.size() - 2); + m_buf[i++] = '-'; + } + + std::reverse(m_buf.begin(), m_buf.begin() + i); + } + + template + void x_write(NumberType x, /*is_integral=*/std::false_type) + { + // special case for 0.0 and -0.0 + if (x == 0) + { + size_t i = 0; + if (std::signbit(x)) + { + m_buf[i++] = '-'; + } + m_buf[i++] = '0'; + m_buf[i++] = '.'; + m_buf[i] = '0'; + return; + } + + // get number of digits for a text -> float -> text round-trip + static constexpr auto d = std::numeric_limits::digits10; + + // the actual conversion + const auto written_bytes = snprintf(m_buf.data(), m_buf.size(), "%.*g", d, x); + + // negative value indicates an error + assert(written_bytes > 0); + // check if buffer was large enough + assert(static_cast(written_bytes) < m_buf.size()); + + // read information from locale + const auto loc = localeconv(); + assert(loc != nullptr); + const char thousands_sep = !loc->thousands_sep ? '\0' + : loc->thousands_sep[0]; + + const char decimal_point = !loc->decimal_point ? '\0' + : loc->decimal_point[0]; + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(m_buf.begin(), m_buf.begin() + written_bytes, thousands_sep); + std::fill(end, m_buf.end(), '\0'); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + for (auto& c : m_buf) + { + if (c == decimal_point) + { + c = '.'; + break; + } + } + } + + // determine if need to append ".0" + size_t i = 0; + bool value_is_int_like = true; + for (i = 0; i < m_buf.size(); ++i) + { + // break when end of number is reached + if (m_buf[i] == '\0') + { + break; + } + + // check if we find non-int character + value_is_int_like = value_is_int_like and m_buf[i] != '.' and + m_buf[i] != 'e' and m_buf[i] != 'E'; + } + + if (value_is_int_like) + { + // there must be 2 bytes left for ".0" + assert((i + 2) < m_buf.size()); + // we write to the end of the number + assert(m_buf[i] == '\0'); + assert(m_buf[i - 1] != '\0'); + + // add ".0" + m_buf[i] = '.'; + m_buf[i + 1] = '0'; + + // the resulting string is properly terminated + assert(m_buf[i + 2] == '\0'); + } + } + }; + + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. Note that + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[out] o stream to write to + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, + const unsigned int current_indent = 0) const + { + // variable to hold indentation for recursive calls + unsigned int new_indent = current_indent; + + switch (m_type) + { + case value_t::object: + { + if (m_value.object->empty()) + { + o << "{}"; + return; + } + + o << "{"; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.object->cbegin(); i != m_value.object->cend(); ++i) + { + if (i != m_value.object->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' ') << "\"" + << escape_string(i->first) << "\":" + << (pretty_print ? " " : ""); + i->second.dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') + "}"; + return; + } + + case value_t::array: + { + if (m_value.array->empty()) + { + o << "[]"; + return; + } + + o << "["; + + // increase indentation + if (pretty_print) + { + new_indent += indent_step; + o << "\n"; + } + + for (auto i = m_value.array->cbegin(); i != m_value.array->cend(); ++i) + { + if (i != m_value.array->cbegin()) + { + o << (pretty_print ? ",\n" : ","); + } + o << string_t(new_indent, ' '); + i->dump(o, pretty_print, indent_step, new_indent); + } + + // decrease indentation + if (pretty_print) + { + new_indent -= indent_step; + o << "\n"; + } + + o << string_t(new_indent, ' ') << "]"; + return; + } + + case value_t::string: + { + o << string_t("\"") << escape_string(*m_value.string) << "\""; + return; + } + + case value_t::boolean: + { + o << (m_value.boolean ? "true" : "false"); + return; + } + + case value_t::number_integer: + { + o << numtostr(m_value.number_integer).c_str(); + return; + } + + case value_t::number_unsigned: + { + o << numtostr(m_value.number_unsigned).c_str(); + return; + } + + case value_t::number_float: + { + o << numtostr(m_value.number_float).c_str(); + return; + } + + case value_t::discarded: + { + o << ""; + return; + } + + case value_t::null: + { + o << "null"; + return; + } + } + } + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + + private: + /////////////// + // iterators // + /////////////// + + /*! + @brief an iterator for primitive JSON types + + This class models an iterator for primitive JSON types (boolean, number, + string). It's only purpose is to allow the iterator/const_iterator classes + to "iterate" over primitive values. Internally, the iterator is modeled by + a `difference_type` variable. Value begin_value (`0`) models the begin, + end_value (`1`) models past the end. + */ + class primitive_iterator_t + { + public: + + difference_type get_value() const noexcept + { + return m_it; + } + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return (m_it == begin_value); + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return (m_it == end_value); + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator!=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return !(lhs == rhs); + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + friend constexpr bool operator<=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it <= rhs.m_it; + } + + friend constexpr bool operator>(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it > rhs.m_it; + } + + friend constexpr bool operator>=(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it >= rhs.m_it; + } + + primitive_iterator_t operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) + { + return os << it.m_it; + } + + primitive_iterator_t& operator++() + { + ++m_it; + return *this; + } + + primitive_iterator_t operator++(int) + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() + { + --m_it; + return *this; + } + + primitive_iterator_t operator--(int) + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) + { + m_it -= n; + return *this; + } + + private: + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = std::numeric_limits::denorm_min(); + }; + + /*! + @brief an iterator value + + @note This structure could easily be a union, but MSVC currently does not + allow unions members with complex constructors, see + https://github.com/nlohmann/json/pull/105. + */ + struct internal_iterator + { + /// iterator for JSON objects + typename object_t::iterator object_iterator; + /// iterator for JSON arrays + typename array_t::iterator array_iterator; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator; + + /// create an uninitialized internal_iterator + internal_iterator() noexcept + : object_iterator(), array_iterator(), primitive_iterator() + {} + }; + + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } + }; + + public: + /*! + @brief a template for a random access iterator for the @ref basic_json class + + This class implements a both iterators (iterator and const_iterator) for the + @ref basic_json class. + + @note An iterator is called *initialized* when a pointer to a JSON value + has been set (e.g., by a constructor or a copy assignment). If the + iterator is default-constructed, it is *uninitialized* and most + methods are undefined. **The library uses assertions to detect calls + on uninitialized iterators.** + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + + @since version 1.0.0, simplified in version 2.0.9 + */ + template + class iter_impl : public std::iterator + { + /// allow basic_json to access private members + friend class basic_json; + + // make sure U is basic_json or const basic_json + static_assert(std::is_same::value + or std::is_same::value, + "iter_impl only accepts (const) basic_json"); + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename basic_json::value_type; + /// a type to represent differences between iterators + using difference_type = typename basic_json::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional::value, + typename basic_json::const_pointer, + typename basic_json::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = typename std::conditional::value, + typename basic_json::const_reference, + typename basic_json::reference>::type; + /// the category of the iterator + using iterator_category = std::bidirectional_iterator_tag; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept + : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /* + Use operator `const_iterator` instead of `const_iterator(const iterator& + other) noexcept` to avoid two class definitions for @ref iterator and + @ref const_iterator. + + This function is only called if this class is an @ref iterator. If this + class is a @ref const_iterator this function is not called. + */ + operator const_iterator() const + { + const_iterator ret; + + if (m_object) + { + ret.m_object = m_object; + ret.m_it = m_it; + } + + return ret; + } + + /*! + @brief copy constructor + @param[in] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl& other) noexcept + : m_object(other.m_object), m_it(other.m_it) + {} + + /*! + @brief copy assignment + @param[in,out] other iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(iter_impl other) noexcept( + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value and + std::is_nothrow_move_constructible::value and + std::is_nothrow_move_assignable::value + ) + { + std::swap(m_object, other.m_object); + std::swap(m_it, other.m_it); + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case basic_json::value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case basic_json::value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case basic_json::value_t::null: + { + JSON_THROW(std::out_of_range("cannot get value")); + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return *m_object; + } + + JSON_THROW(std::out_of_range("cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case basic_json::value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (m_it.primitive_iterator.is_begin()) + { + return m_object; + } + + JSON_THROW(std::out_of_range("cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + JSON_THROW(std::domain_error("cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + return (m_it.object_iterator == other.m_it.object_iterator); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator == other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (m_object != other.m_object) + { + JSON_THROW(std::domain_error("cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + JSON_THROW(std::domain_error("cannot compare order of object iterators")); + } + + case basic_json::value_t::array: + { + return (m_it.array_iterator < other.m_it.array_iterator); + } + + default: + { + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + JSON_THROW(std::domain_error("cannot use offsets with object iterators")); + } + + case basic_json::value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + JSON_THROW(std::domain_error("cannot use offsets with object iterators")); + } + + case basic_json::value_t::array: + { + return m_it.array_iterator - other.m_it.array_iterator; + } + + default: + { + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case basic_json::value_t::object: + { + JSON_THROW(std::domain_error("cannot use operator[] for object iterators")); + } + + case basic_json::value_t::array: + { + return *std::next(m_it.array_iterator, n); + } + + case basic_json::value_t::null: + { + JSON_THROW(std::out_of_range("cannot get value")); + } + + default: + { + if (m_it.primitive_iterator.get_value() == -n) + { + return *m_object; + } + + JSON_THROW(std::out_of_range("cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + + JSON_THROW(std::domain_error("cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator m_it = internal_iterator(); + }; + + /*! + @brief a template for a reverse iterator class + + @tparam Base the base iterator type to reverse. Valid types are @ref + iterator (to create @ref reverse_iterator) and @ref const_iterator (to + create @ref const_reverse_iterator). + + @requirement The class satisfies the following concept requirements: + - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): + The iterator that can be moved to point (forward and backward) to any + element in constant time. + - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + + @since version 1.0.0 + */ + template + class json_reverse_iterator : public std::reverse_iterator + { + public: + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) + {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept + : base_iterator(it) + {} + + /// post-increment (it++) + json_reverse_iterator operator++(int) + { + return base_iterator::operator++(1); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + base_iterator::operator++(); + return *this; + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return base_iterator::operator--(1); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + base_iterator::operator--(); + return *this; + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + base_iterator::operator+=(i); + return *this; + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return this->base() - other.base(); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + typename object_t::key_type key() const + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } + }; + + + private: + ////////////////////// + // lexer and parser // + ////////////////////// + + /*! + @brief lexical analysis + + This class organizes the lexical analysis during JSON deserialization. The + core of it is a scanner generated by [re2c](http://re2c.org) that + processes a buffer and recognizes tokens according to RFC 7159. + */ + class lexer + { + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number() for actual value + value_integer, ///< a signed integer -- use get_number() for actual value + value_float, ///< an floating point number -- use get_number() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input ///< indicating the end of the input buffer + }; + + /// the char type to use in the lexer + using lexer_char_t = unsigned char; + + /// a lexer from a buffer with given length + lexer(const lexer_char_t* buff, const size_t len) noexcept + : m_content(buff) + { + assert(m_content != nullptr); + m_start = m_cursor = m_content; + m_limit = m_content + len; + } + + /// a lexer from an input stream + explicit lexer(std::istream& s) + : m_stream(&s), m_line_buffer() + { + // immediately abort if stream is erroneous + if (s.fail()) + { + JSON_THROW(std::invalid_argument("stream error")); + } + + // fill buffer + fill_line_buffer(); + + // skip UTF-8 byte-order mark + if (m_line_buffer.size() >= 3 and m_line_buffer.substr(0, 3) == "\xEF\xBB\xBF") + { + m_line_buffer[0] = ' '; + m_line_buffer[1] = ' '; + m_line_buffer[2] = ' '; + } + } + + // switch off unwanted functions (due to pointer members) + lexer() = delete; + lexer(const lexer&) = delete; + lexer operator=(const lexer&) = delete; + + /*! + @brief create a string from one or two Unicode code points + + There are two cases: (1) @a codepoint1 is in the Basic Multilingual + Plane (U+0000 through U+FFFF) and @a codepoint2 is 0, or (2) + @a codepoint1 and @a codepoint2 are a UTF-16 surrogate pair to + represent a code point above U+FFFF. + + @param[in] codepoint1 the code point (can be high surrogate) + @param[in] codepoint2 the code point (can be low surrogate or 0) + + @return string representation of the code point; the length of the + result string is between 1 and 4 characters. + + @throw std::out_of_range if code point is > 0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` + + @complexity Constant. + + @see + */ + static string_t to_unicode(const std::size_t codepoint1, + const std::size_t codepoint2 = 0) + { + // calculate the code point from the given code points + std::size_t codepoint = codepoint1; + + // check if codepoint1 is a high surrogate + if (codepoint1 >= 0xD800 and codepoint1 <= 0xDBFF) + { + // check if codepoint2 is a low surrogate + if (codepoint2 >= 0xDC00 and codepoint2 <= 0xDFFF) + { + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + JSON_THROW(std::invalid_argument("missing or wrong low surrogate")); + } + } + + string_t result; + + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + result.append(1, static_cast(codepoint)); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + result.append(1, static_cast(0xC0 | ((codepoint >> 6) & 0x1F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xE0 | ((codepoint >> 12) & 0x0F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else if (codepoint <= 0x10ffff) + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + result.append(1, static_cast(0xF0 | ((codepoint >> 18) & 0x07))); + result.append(1, static_cast(0x80 | ((codepoint >> 12) & 0x3F))); + result.append(1, static_cast(0x80 | ((codepoint >> 6) & 0x3F))); + result.append(1, static_cast(0x80 | (codepoint & 0x3F))); + } + else + { + JSON_THROW(std::out_of_range("code points above 0x10FFFF are invalid")); + } + + return result; + } + + /// return name of values of type token_type (only used for errors) + static std::string token_type_name(const token_type t) + { + switch (t) + { + case token_type::uninitialized: + return ""; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + } + + /*! + This function implements a scanner for JSON. It is specified using + regular expressions that try to follow RFC 7159 as close as possible. + These regular expressions are then translated into a minimized + deterministic finite automaton (DFA) by the tool + [re2c](http://re2c.org). As a result, the translated code for this + function consists of a large block of code with `goto` jumps. + + @return the class of the next token read from the buffer + + @complexity Linear in the length of the input.\n + + Proposition: The loop below will always terminate for finite input.\n + + Proof (by contradiction): Assume a finite input. To loop forever, the + loop must never hit code with a `break` statement. The only code + snippets without a `break` statement are the continue statements for + whitespace and byte-order-marks. To loop forever, the input must be an + infinite sequence of whitespace or byte-order-marks. This contradicts + the assumption of finite input, q.e.d. + */ + token_type scan() + { + while (true) + { + // pointer for backtracking information + m_marker = nullptr; + + // remember the begin of the token + m_start = m_cursor; + assert(m_start != nullptr); + + + { + lexer_char_t yych; + unsigned int yyaccept = 0; + static const unsigned char yybm[] = + { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 32, 32, 0, 0, 32, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 160, 128, 0, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 192, 192, 192, 192, 192, 192, 192, 192, + 192, 192, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 0, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }; + if ((m_limit - m_cursor) < 5) + { + fill_line_buffer(5); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + if (yych <= '[') + { + if (yych <= '-') + { + if (yych <= '"') + { + if (yych <= 0x00) + { + goto basic_json_parser_2; + } + if (yych <= '!') + { + goto basic_json_parser_4; + } + goto basic_json_parser_9; + } + else + { + if (yych <= '+') + { + goto basic_json_parser_4; + } + if (yych <= ',') + { + goto basic_json_parser_10; + } + goto basic_json_parser_12; + } + } + else + { + if (yych <= '9') + { + if (yych <= '/') + { + goto basic_json_parser_4; + } + if (yych <= '0') + { + goto basic_json_parser_13; + } + goto basic_json_parser_15; + } + else + { + if (yych <= ':') + { + goto basic_json_parser_17; + } + if (yych <= 'Z') + { + goto basic_json_parser_4; + } + goto basic_json_parser_19; + } + } + } + else + { + if (yych <= 'n') + { + if (yych <= 'e') + { + if (yych == ']') + { + goto basic_json_parser_21; + } + goto basic_json_parser_4; + } + else + { + if (yych <= 'f') + { + goto basic_json_parser_23; + } + if (yych <= 'm') + { + goto basic_json_parser_4; + } + goto basic_json_parser_24; + } + } + else + { + if (yych <= 'z') + { + if (yych == 't') + { + goto basic_json_parser_25; + } + goto basic_json_parser_4; + } + else + { + if (yych <= '{') + { + goto basic_json_parser_26; + } + if (yych == '}') + { + goto basic_json_parser_28; + } + goto basic_json_parser_4; + } + } + } +basic_json_parser_2: + ++m_cursor; + { + last_token_type = token_type::end_of_input; + break; + } +basic_json_parser_4: + ++m_cursor; +basic_json_parser_5: + { + last_token_type = token_type::parse_error; + break; + } +basic_json_parser_6: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 32) + { + goto basic_json_parser_6; + } + { + continue; + } +basic_json_parser_9: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych <= 0x1F) + { + goto basic_json_parser_5; + } + if (yych <= 0x7F) + { + goto basic_json_parser_31; + } + if (yych <= 0xC1) + { + goto basic_json_parser_5; + } + if (yych <= 0xF4) + { + goto basic_json_parser_31; + } + goto basic_json_parser_5; +basic_json_parser_10: + ++m_cursor; + { + last_token_type = token_type::value_separator; + break; + } +basic_json_parser_12: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_5; + } + if (yych <= '0') + { + goto basic_json_parser_43; + } + if (yych <= '9') + { + goto basic_json_parser_45; + } + goto basic_json_parser_5; +basic_json_parser_13: + yyaccept = 1; + yych = *(m_marker = ++m_cursor); + if (yych <= '9') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + if (yych >= '0') + { + goto basic_json_parser_48; + } + } + else + { + if (yych <= 'E') + { + if (yych >= 'E') + { + goto basic_json_parser_51; + } + } + else + { + if (yych == 'e') + { + goto basic_json_parser_51; + } + } + } +basic_json_parser_14: + { + last_token_type = token_type::value_unsigned; + break; + } +basic_json_parser_15: + yyaccept = 1; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yybm[0 + yych] & 64) + { + goto basic_json_parser_15; + } + if (yych <= 'D') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + goto basic_json_parser_14; + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_51; + } + if (yych == 'e') + { + goto basic_json_parser_51; + } + goto basic_json_parser_14; + } +basic_json_parser_17: + ++m_cursor; + { + last_token_type = token_type::name_separator; + break; + } +basic_json_parser_19: + ++m_cursor; + { + last_token_type = token_type::begin_array; + break; + } +basic_json_parser_21: + ++m_cursor; + { + last_token_type = token_type::end_array; + break; + } +basic_json_parser_23: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'a') + { + goto basic_json_parser_52; + } + goto basic_json_parser_5; +basic_json_parser_24: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_53; + } + goto basic_json_parser_5; +basic_json_parser_25: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'r') + { + goto basic_json_parser_54; + } + goto basic_json_parser_5; +basic_json_parser_26: + ++m_cursor; + { + last_token_type = token_type::begin_object; + break; + } +basic_json_parser_28: + ++m_cursor; + { + last_token_type = token_type::end_object; + break; + } +basic_json_parser_30: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; +basic_json_parser_31: + if (yybm[0 + yych] & 128) + { + goto basic_json_parser_30; + } + if (yych <= 0xE0) + { + if (yych <= '\\') + { + if (yych <= 0x1F) + { + goto basic_json_parser_32; + } + if (yych <= '"') + { + goto basic_json_parser_33; + } + goto basic_json_parser_35; + } + else + { + if (yych <= 0xC1) + { + goto basic_json_parser_32; + } + if (yych <= 0xDF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_37; + } + } + else + { + if (yych <= 0xEF) + { + if (yych == 0xED) + { + goto basic_json_parser_39; + } + goto basic_json_parser_38; + } + else + { + if (yych <= 0xF0) + { + goto basic_json_parser_40; + } + if (yych <= 0xF3) + { + goto basic_json_parser_41; + } + if (yych <= 0xF4) + { + goto basic_json_parser_42; + } + } + } +basic_json_parser_32: + m_cursor = m_marker; + if (yyaccept <= 1) + { + if (yyaccept == 0) + { + goto basic_json_parser_5; + } + else + { + goto basic_json_parser_14; + } + } + else + { + if (yyaccept == 2) + { + goto basic_json_parser_44; + } + else + { + goto basic_json_parser_58; + } + } +basic_json_parser_33: + ++m_cursor; + { + last_token_type = token_type::value_string; + break; + } +basic_json_parser_35: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 'e') + { + if (yych <= '/') + { + if (yych == '"') + { + goto basic_json_parser_30; + } + if (yych <= '.') + { + goto basic_json_parser_32; + } + goto basic_json_parser_30; + } + else + { + if (yych <= '\\') + { + if (yych <= '[') + { + goto basic_json_parser_32; + } + goto basic_json_parser_30; + } + else + { + if (yych == 'b') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + } + } + else + { + if (yych <= 'q') + { + if (yych <= 'f') + { + goto basic_json_parser_30; + } + if (yych == 'n') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 's') + { + if (yych <= 'r') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 't') + { + goto basic_json_parser_30; + } + if (yych <= 'u') + { + goto basic_json_parser_55; + } + goto basic_json_parser_32; + } + } + } +basic_json_parser_36: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; +basic_json_parser_37: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x9F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_38: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_39: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0x9F) + { + goto basic_json_parser_36; + } + goto basic_json_parser_32; +basic_json_parser_40: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x8F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_41: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0xBF) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_42: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 0x7F) + { + goto basic_json_parser_32; + } + if (yych <= 0x8F) + { + goto basic_json_parser_38; + } + goto basic_json_parser_32; +basic_json_parser_43: + yyaccept = 2; + yych = *(m_marker = ++m_cursor); + if (yych <= '9') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + if (yych >= '0') + { + goto basic_json_parser_48; + } + } + else + { + if (yych <= 'E') + { + if (yych >= 'E') + { + goto basic_json_parser_51; + } + } + else + { + if (yych == 'e') + { + goto basic_json_parser_51; + } + } + } +basic_json_parser_44: + { + last_token_type = token_type::value_integer; + break; + } +basic_json_parser_45: + yyaccept = 2; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '9') + { + if (yych == '.') + { + goto basic_json_parser_47; + } + if (yych <= '/') + { + goto basic_json_parser_44; + } + goto basic_json_parser_45; + } + else + { + if (yych <= 'E') + { + if (yych <= 'D') + { + goto basic_json_parser_44; + } + goto basic_json_parser_51; + } + else + { + if (yych == 'e') + { + goto basic_json_parser_51; + } + goto basic_json_parser_44; + } + } +basic_json_parser_47: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_56; + } + goto basic_json_parser_32; +basic_json_parser_48: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_50; + } + if (yych <= '9') + { + goto basic_json_parser_48; + } +basic_json_parser_50: + { + last_token_type = token_type::parse_error; + break; + } +basic_json_parser_51: + yych = *++m_cursor; + if (yych <= ',') + { + if (yych == '+') + { + goto basic_json_parser_59; + } + goto basic_json_parser_32; + } + else + { + if (yych <= '-') + { + goto basic_json_parser_59; + } + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_60; + } + goto basic_json_parser_32; + } +basic_json_parser_52: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_62; + } + goto basic_json_parser_32; +basic_json_parser_53: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_63; + } + goto basic_json_parser_32; +basic_json_parser_54: + yych = *++m_cursor; + if (yych == 'u') + { + goto basic_json_parser_64; + } + goto basic_json_parser_32; +basic_json_parser_55: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_65; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_65; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_65; + } + goto basic_json_parser_32; + } +basic_json_parser_56: + yyaccept = 3; + m_marker = ++m_cursor; + if ((m_limit - m_cursor) < 3) + { + fill_line_buffer(3); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= 'D') + { + if (yych <= '/') + { + goto basic_json_parser_58; + } + if (yych <= '9') + { + goto basic_json_parser_56; + } + } + else + { + if (yych <= 'E') + { + goto basic_json_parser_51; + } + if (yych == 'e') + { + goto basic_json_parser_51; + } + } +basic_json_parser_58: + { + last_token_type = token_type::value_float; + break; + } +basic_json_parser_59: + yych = *++m_cursor; + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych >= ':') + { + goto basic_json_parser_32; + } +basic_json_parser_60: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '/') + { + goto basic_json_parser_58; + } + if (yych <= '9') + { + goto basic_json_parser_60; + } + goto basic_json_parser_58; +basic_json_parser_62: + yych = *++m_cursor; + if (yych == 's') + { + goto basic_json_parser_66; + } + goto basic_json_parser_32; +basic_json_parser_63: + yych = *++m_cursor; + if (yych == 'l') + { + goto basic_json_parser_67; + } + goto basic_json_parser_32; +basic_json_parser_64: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_69; + } + goto basic_json_parser_32; +basic_json_parser_65: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_71; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_71; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_71; + } + goto basic_json_parser_32; + } +basic_json_parser_66: + yych = *++m_cursor; + if (yych == 'e') + { + goto basic_json_parser_72; + } + goto basic_json_parser_32; +basic_json_parser_67: + ++m_cursor; + { + last_token_type = token_type::literal_null; + break; + } +basic_json_parser_69: + ++m_cursor; + { + last_token_type = token_type::literal_true; + break; + } +basic_json_parser_71: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_74; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_74; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_74; + } + goto basic_json_parser_32; + } +basic_json_parser_72: + ++m_cursor; + { + last_token_type = token_type::literal_false; + break; + } +basic_json_parser_74: + ++m_cursor; + if (m_limit <= m_cursor) + { + fill_line_buffer(1); // LCOV_EXCL_LINE + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_32; + } + if (yych <= '9') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_30; + } + if (yych <= '`') + { + goto basic_json_parser_32; + } + if (yych <= 'f') + { + goto basic_json_parser_30; + } + goto basic_json_parser_32; + } + } + + } + + return last_token_type; + } + + /*! + @brief append data from the stream to the line buffer + + This function is called by the scan() function when the end of the + buffer (`m_limit`) is reached and the `m_cursor` pointer cannot be + incremented without leaving the limits of the line buffer. Note re2c + decides when to call this function. + + If the lexer reads from contiguous storage, there is no trailing null + byte. Therefore, this function must make sure to add these padding + null bytes. + + If the lexer reads from an input stream, this function reads the next + line of the input. + + @pre + p p p p p p u u u u u x . . . . . . + ^ ^ ^ ^ + m_content m_start | m_limit + m_cursor + + @post + u u u u u x x x x x x x . . . . . . + ^ ^ ^ + | m_cursor m_limit + m_start + m_content + */ + void fill_line_buffer(size_t n = 0) + { + // if line buffer is used, m_content points to its data + assert(m_line_buffer.empty() + or m_content == reinterpret_cast(m_line_buffer.data())); + + // if line buffer is used, m_limit is set past the end of its data + assert(m_line_buffer.empty() + or m_limit == m_content + m_line_buffer.size()); + + // pointer relationships + assert(m_content <= m_start); + assert(m_start <= m_cursor); + assert(m_cursor <= m_limit); + assert(m_marker == nullptr or m_marker <= m_limit); + + // number of processed characters (p) + const auto num_processed_chars = static_cast(m_start - m_content); + // offset for m_marker wrt. to m_start + const auto offset_marker = (m_marker == nullptr) ? 0 : m_marker - m_start; + // number of unprocessed characters (u) + const auto offset_cursor = m_cursor - m_start; + + // no stream is used or end of file is reached + if (m_stream == nullptr or m_stream->eof()) + { + // m_start may or may not be pointing into m_line_buffer at + // this point. We trust the standard library to do the right + // thing. See http://stackoverflow.com/q/28142011/266378 + m_line_buffer.assign(m_start, m_limit); + + // append n characters to make sure that there is sufficient + // space between m_cursor and m_limit + m_line_buffer.append(1, '\x00'); + if (n > 0) + { + m_line_buffer.append(n - 1, '\x01'); + } + } + else + { + // delete processed characters from line buffer + m_line_buffer.erase(0, num_processed_chars); + // read next line from input stream + m_line_buffer_tmp.clear(); + std::getline(*m_stream, m_line_buffer_tmp, '\n'); + + // add line with newline symbol to the line buffer + m_line_buffer += m_line_buffer_tmp; + m_line_buffer.push_back('\n'); + } + + // set pointers + m_content = reinterpret_cast(m_line_buffer.data()); + assert(m_content != nullptr); + m_start = m_content; + m_marker = m_start + offset_marker; + m_cursor = m_start + offset_cursor; + m_limit = m_start + m_line_buffer.size(); + } + + /// return string representation of last read token + string_t get_token_string() const + { + assert(m_start != nullptr); + return string_t(reinterpret_cast(m_start), + static_cast(m_cursor - m_start)); + } + + /*! + @brief return string value for string tokens + + The function iterates the characters between the opening and closing + quotes of the string value. The complete string is the range + [m_start,m_cursor). Consequently, we iterate from m_start+1 to + m_cursor-1. + + We differentiate two cases: + + 1. Escaped characters. In this case, a new character is constructed + according to the nature of the escape. Some escapes create new + characters (e.g., `"\\n"` is replaced by `"\n"`), some are copied + as is (e.g., `"\\\\"`). Furthermore, Unicode escapes of the shape + `"\\uxxxx"` need special care. In this case, to_unicode takes care + of the construction of the values. + 2. Unescaped characters are copied as is. + + @pre `m_cursor - m_start >= 2`, meaning the length of the last token + is at least 2 bytes which is trivially true for any string (which + consists of at least two quotes). + + " c1 c2 c3 ... " + ^ ^ + m_start m_cursor + + @complexity Linear in the length of the string.\n + + Lemma: The loop body will always terminate.\n + + Proof (by contradiction): Assume the loop body does not terminate. As + the loop body does not contain another loop, one of the called + functions must never return. The called functions are `std::strtoul` + and to_unicode. Neither function can loop forever, so the loop body + will never loop forever which contradicts the assumption that the loop + body does not terminate, q.e.d.\n + + Lemma: The loop condition for the for loop is eventually false.\n + + Proof (by contradiction): Assume the loop does not terminate. Due to + the above lemma, this can only be due to a tautological loop + condition; that is, the loop condition i < m_cursor - 1 must always be + true. Let x be the change of i for any loop iteration. Then + m_start + 1 + x < m_cursor - 1 must hold to loop indefinitely. This + can be rephrased to m_cursor - m_start - 2 > x. With the + precondition, we x <= 0, meaning that the loop condition holds + indefinitely if i is always decreased. However, observe that the value + of i is strictly increasing with each iteration, as it is incremented + by 1 in the iteration expression and never decremented inside the loop + body. Hence, the loop condition will eventually be false which + contradicts the assumption that the loop condition is a tautology, + q.e.d. + + @return string value of current token without opening and closing + quotes + @throw std::out_of_range if to_unicode fails + */ + string_t get_string() const + { + assert(m_cursor - m_start >= 2); + + string_t result; + result.reserve(static_cast(m_cursor - m_start - 2)); + + // iterate the result between the quotes + for (const lexer_char_t* i = m_start + 1; i < m_cursor - 1; ++i) + { + // find next escape character + auto e = std::find(i, m_cursor - 1, '\\'); + if (e != i) + { + // see https://github.com/nlohmann/json/issues/365#issuecomment-262874705 + for (auto k = i; k < e; k++) + { + result.push_back(static_cast(*k)); + } + i = e - 1; // -1 because of ++i + } + else + { + // processing escaped character + // read next character + ++i; + + switch (*i) + { + // the default escapes + case 't': + { + result += "\t"; + break; + } + case 'b': + { + result += "\b"; + break; + } + case 'f': + { + result += "\f"; + break; + } + case 'n': + { + result += "\n"; + break; + } + case 'r': + { + result += "\r"; + break; + } + case '\\': + { + result += "\\"; + break; + } + case '/': + { + result += "/"; + break; + } + case '"': + { + result += "\""; + break; + } + + // unicode + case 'u': + { + // get code xxxx from uxxxx + auto codepoint = std::strtoul(std::string(reinterpret_cast(i + 1), + 4).c_str(), nullptr, 16); + + // check if codepoint is a high surrogate + if (codepoint >= 0xD800 and codepoint <= 0xDBFF) + { + // make sure there is a subsequent unicode + if ((i + 6 >= m_limit) or * (i + 5) != '\\' or * (i + 6) != 'u') + { + JSON_THROW(std::invalid_argument("missing low surrogate")); + } + + // get code yyyy from uxxxx\uyyyy + auto codepoint2 = std::strtoul(std::string(reinterpret_cast + (i + 7), 4).c_str(), nullptr, 16); + result += to_unicode(codepoint, codepoint2); + // skip the next 10 characters (xxxx\uyyyy) + i += 10; + } + else if (codepoint >= 0xDC00 and codepoint <= 0xDFFF) + { + // we found a lone low surrogate + JSON_THROW(std::invalid_argument("missing high surrogate")); + } + else + { + // add unicode character(s) + result += to_unicode(codepoint); + // skip the next four characters (xxxx) + i += 4; + } + break; + } + } + } + } + + return result; + } + + + /*! + @brief parse string into a built-in arithmetic type as if the current + locale is POSIX. + + @note in floating-point case strtod may parse past the token's end - + this is not an error + + @note any leading blanks are not handled + */ + struct strtonum + { + public: + strtonum(const char* start, const char* end) + : m_start(start), m_end(end) + {} + + /*! + @return true iff parsed successfully as number of type T + + @param[in,out] val shall contain parsed value, or undefined value + if could not parse + */ + template::value>::type> + bool to(T& val) const + { + return parse(val, std::is_integral()); + } + + private: + const char* const m_start = nullptr; + const char* const m_end = nullptr; + + // floating-point conversion + + // overloaded wrappers for strtod/strtof/strtold + // that will be called from parse + static void strtof(float& f, const char* str, char** endptr) + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) + { + f = std::strtold(str, endptr); + } + + template + bool parse(T& value, /*is_integral=*/std::false_type) const + { + // replace decimal separator with locale-specific version, + // when necessary; data will point to either the original + // string, or buf, or tempstr containing the fixed string. + std::string tempstr; + std::array buf; + const size_t len = static_cast(m_end - m_start); + + // lexer will reject empty numbers + assert(len > 0); + + // since dealing with strtod family of functions, we're + // getting the decimal point char from the C locale facilities + // instead of C++'s numpunct facet of the current std::locale + const auto loc = localeconv(); + assert(loc != nullptr); + const char decimal_point_char = (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; + + const char* data = m_start; + + if (decimal_point_char != '.') + { + const size_t ds_pos = static_cast(std::find(m_start, m_end, '.') - m_start); + + if (ds_pos != len) + { + // copy the data into the local buffer or tempstr, if + // buffer is too small; replace decimal separator, and + // update data to point to the modified bytes + if ((len + 1) < buf.size()) + { + std::copy(m_start, m_end, buf.begin()); + buf[len] = 0; + buf[ds_pos] = decimal_point_char; + data = buf.data(); + } + else + { + tempstr.assign(m_start, m_end); + tempstr[ds_pos] = decimal_point_char; + data = tempstr.c_str(); + } + } + } + + char* endptr = nullptr; + value = 0; + // this calls appropriate overload depending on T + strtof(value, data, &endptr); + + // parsing was successful iff strtof parsed exactly the number + // of characters determined by the lexer (len) + const bool ok = (endptr == (data + len)); + + if (ok and (value == static_cast(0.0)) and (*data == '-')) + { + // some implementations forget to negate the zero + value = -0.0; + } + + return ok; + } + + // integral conversion + + signed long long parse_integral(char** endptr, /*is_signed*/std::true_type) const + { + return std::strtoll(m_start, endptr, 10); + } + + unsigned long long parse_integral(char** endptr, /*is_signed*/std::false_type) const + { + return std::strtoull(m_start, endptr, 10); + } + + template + bool parse(T& value, /*is_integral=*/std::true_type) const + { + char* endptr = nullptr; + errno = 0; // these are thread-local + const auto x = parse_integral(&endptr, std::is_signed()); + + // called right overload? + static_assert(std::is_signed() == std::is_signed(), ""); + + value = static_cast(x); + + return (x == static_cast(value)) // x fits into destination T + and (x < 0) == (value < 0) // preserved sign + //and ((x != 0) or is_integral()) // strto[u]ll did nto fail + and (errno == 0) // strto[u]ll did not overflow + and (m_start < m_end) // token was not empty + and (endptr == m_end); // parsed entire token exactly + } + }; + + /*! + @brief return number value for number tokens + + This function translates the last token into the most appropriate + number type (either integer, unsigned integer or floating point), + which is passed back to the caller via the result parameter. + + integral numbers that don't fit into the the range of the respective + type are parsed as number_float_t + + floating-point values do not satisfy std::isfinite predicate + are converted to value_t::null + + throws if the entire string [m_start .. m_cursor) cannot be + interpreted as a number + + @param[out] result @ref basic_json object to receive the number. + @param[in] token the type of the number token + */ + bool get_number(basic_json& result, const token_type token) const + { + assert(m_start != nullptr); + assert(m_start < m_cursor); + assert((token == token_type::value_unsigned) or + (token == token_type::value_integer) or + (token == token_type::value_float)); + + strtonum num_converter(reinterpret_cast(m_start), + reinterpret_cast(m_cursor)); + + switch (token) + { + case lexer::token_type::value_unsigned: + { + number_unsigned_t val; + if (num_converter.to(val)) + { + // parsing successful + result.m_type = value_t::number_unsigned; + result.m_value = val; + return true; + } + break; + } + + case lexer::token_type::value_integer: + { + number_integer_t val; + if (num_converter.to(val)) + { + // parsing successful + result.m_type = value_t::number_integer; + result.m_value = val; + return true; + } + break; + } + + default: + { + break; + } + } + + // parse float (either explicitly or because a previous conversion + // failed) + number_float_t val; + if (num_converter.to(val)) + { + // parsing successful + result.m_type = value_t::number_float; + result.m_value = val; + + // replace infinity and NAN by null + if (not std::isfinite(result.m_value.number_float)) + { + result.m_type = value_t::null; + result.m_value = basic_json::json_value(); + } + + return true; + } + + // couldn't parse number in any format + return false; + } + + private: + /// optional input stream + std::istream* m_stream = nullptr; + /// line buffer buffer for m_stream + string_t m_line_buffer {}; + /// used for filling m_line_buffer + string_t m_line_buffer_tmp {}; + /// the buffer pointer + const lexer_char_t* m_content = nullptr; + /// pointer to the beginning of the current symbol + const lexer_char_t* m_start = nullptr; + /// pointer for backtracking information + const lexer_char_t* m_marker = nullptr; + /// pointer to the current symbol + const lexer_char_t* m_cursor = nullptr; + /// pointer to the end of the buffer + const lexer_char_t* m_limit = nullptr; + /// the last token type + token_type last_token_type = token_type::end_of_input; + }; + + /*! + @brief syntax analysis + + This class implements a recursive decent parser. + */ + class parser + { + public: + /// a parser reading from a string literal + parser(const char* buff, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(buff), std::strlen(buff)) + {} + + /// a parser reading from an input stream + parser(std::istream& is, const parser_callback_t cb = nullptr) + : callback(cb), m_lexer(is) + {} + + /// a parser reading from an iterator range with contiguous storage + template::iterator_category, std::random_access_iterator_tag>::value + , int>::type + = 0> + parser(IteratorType first, IteratorType last, const parser_callback_t cb = nullptr) + : callback(cb), + m_lexer(reinterpret_cast(&(*first)), + static_cast(std::distance(first, last))) + {} + + /// public parser interface + basic_json parse() + { + // read first token + get_token(); + + basic_json result = parse_internal(true); + result.assert_invariant(); + + expect(lexer::token_type::end_of_input); + + // return parser result and replace it with null in case the + // top-level value was discarded by the callback function + return result.is_discarded() ? basic_json() : std::move(result); + } + + private: + /// the actual parser + basic_json parse_internal(bool keep) + { + auto result = basic_json(value_t::discarded); + + switch (last_token) + { + case lexer::token_type::begin_object: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::object_start, result)) != 0))) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == lexer::token_type::end_object) + { + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse key-value pairs + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // store key + expect(lexer::token_type::value_string); + const auto key = m_lexer.get_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + basic_json k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + expect(lexer::token_type::name_separator); + + // parse and add value + get_token(); + auto value = parse_internal(keep); + if (keep and keep_tag and not value.is_discarded()) + { + result[key] = std::move(value); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing } + expect(lexer::token_type::end_object); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::begin_array: + { + if (keep and (not callback + or ((keep = callback(depth++, parse_event_t::array_start, result)) != 0))) + { + // explicitly set result to object to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == lexer::token_type::end_array) + { + get_token(); + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + // no comma is expected here + unexpect(lexer::token_type::value_separator); + + // otherwise: parse values + do + { + // ugly, but could be fixed with loop reorganization + if (last_token == lexer::token_type::value_separator) + { + get_token(); + } + + // parse value + auto value = parse_internal(keep); + if (keep and not value.is_discarded()) + { + result.push_back(std::move(value)); + } + } + while (last_token == lexer::token_type::value_separator); + + // closing ] + expect(lexer::token_type::end_array); + get_token(); + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result = basic_json(value_t::discarded); + } + + return result; + } + + case lexer::token_type::literal_null: + { + get_token(); + result.m_type = value_t::null; + break; + } + + case lexer::token_type::value_string: + { + const auto s = m_lexer.get_string(); + get_token(); + result = basic_json(s); + break; + } + + case lexer::token_type::literal_true: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case lexer::token_type::literal_false: + { + get_token(); + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + { + m_lexer.get_number(result, last_token); + get_token(); + break; + } + + default: + { + // the last token was unexpected + unexpect(last_token); + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result = basic_json(value_t::discarded); + } + return result; + } + + /// get next token from lexer + typename lexer::token_type get_token() + { + last_token = m_lexer.scan(); + return last_token; + } + + void expect(typename lexer::token_type t) const + { + if (t != last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); + JSON_THROW(std::invalid_argument(error_msg)); + } + } + + void unexpect(typename lexer::token_type t) const + { + if (t == last_token) + { + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token_string() + + "'") : + lexer::token_type_name(last_token)); + JSON_THROW(std::invalid_argument(error_msg)); + } + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + typename lexer::token_type last_token = lexer::token_type::uninitialized; + /// the lexer + lexer m_lexer; + }; + + public: + /*! + @brief JSON Pointer + + A JSON pointer defines a string syntax for identifying a specific value + within a JSON document. It can be used with functions `at` and + `operator[]`. Furthermore, JSON pointers are the base for JSON patches. + + @sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + class json_pointer + { + /// allow basic_json to access private members + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the + empty string is assumed which references the whole JSON + value + + @throw std::domain_error if reference token is nonempty and does not + begin with a slash (`/`); example: `"JSON pointer must be empty or + begin with /"` + @throw std::domain_error if a tilde (`~`) is not followed by `0` + (representing `~`) or `1` (representing `/`); example: `"escape error: + ~ must be followed with 0 or 1"` + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") + : reference_tokens(split(s)) + {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), + reference_tokens.end(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /// remove and return last reference pointer + std::string pop_back() + { + if (is_root()) + { + JSON_THROW(std::domain_error("JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (is_root()) + { + JSON_THROW(std::domain_error("JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + */ + reference get_and_create(reference j) const + { + pointer result = &j; + + // in case no reference tokens exist, return a reference to the + // JSON value j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case value_t::array: + { + // create an entry in the array + result = &result->operator[](static_cast(std::stoi(reference_token))); + break; + } + + /* + The following code is only reached if there exists a + reference token _and_ the current value is primitive. In + this case, we have an error situation, because primitive + values may only occur as single value; that is, with an + empty list of reference tokens. + */ + default: + { + JSON_THROW(std::domain_error("invalid value to unflatten")); + } + } + } + + return *result; + } + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries + to create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + */ + reference get_unchecked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == value_t::null) + { + // check if reference token is a number + const bool nums = std::all_of(reference_token.begin(), + reference_token.end(), + [](const char x) + { + return std::isdigit(x); + }); + + // change value to array for numbers or "-" or to object + // otherwise + if (nums or reference_token == "-") + { + *ptr = value_t::array; + } + else + { + *ptr = value_t::object; + } + } + + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + JSON_THROW(std::domain_error("array index must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + } + break; + } + + default: + { + JSON_THROW(std::out_of_range("unresolved reference token '" + reference_token + "'")); + } + } + } + + return *ptr; + } + + reference get_checked(pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + JSON_THROW(std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + JSON_THROW(std::domain_error("array index must not begin with '0'")); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + JSON_THROW(std::out_of_range("unresolved reference token '" + reference_token + "'")); + } + } + } + + return *ptr; + } + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + */ + const_reference get_unchecked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" cannot be used for const access + JSON_THROW(std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + JSON_THROW(std::domain_error("array index must not begin with '0'")); + } + + // use unchecked array access + ptr = &ptr->operator[](static_cast(std::stoi(reference_token))); + break; + } + + default: + { + JSON_THROW(std::out_of_range("unresolved reference token '" + reference_token + "'")); + } + } + } + + return *ptr; + } + + const_reference get_checked(const_pointer ptr) const + { + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case value_t::array: + { + if (reference_token == "-") + { + // "-" always fails the range check + JSON_THROW(std::out_of_range("array index '-' (" + + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (reference_token.size() > 1 and reference_token[0] == '0') + { + JSON_THROW(std::domain_error("array index must not begin with '0'")); + } + + // note: at performs range check + ptr = &ptr->at(static_cast(std::stoi(reference_token))); + break; + } + + default: + { + JSON_THROW(std::out_of_range("unresolved reference token '" + reference_token + "'")); + } + } + } + + return *ptr; + } + + /// split the string input to reference tokens + static std::vector split(const std::string& reference_string) + { + std::vector result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (reference_string[0] != '/') + { + JSON_THROW(std::domain_error("JSON pointer must be empty or begin with '/'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1')) + { + JSON_THROW(std::domain_error("escape error: '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + private: + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, + const std::string& f, + const std::string& t) + { + assert(not f.empty()); + + for ( + size_t pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t + pos = s.find(f, pos + t.size()) // find next occurrence of f + ); + } + + /// escape tilde and slash + static std::string escape(std::string s) + { + // escape "~"" to "~0" and "/" to "~1" + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape tilde and slash + static void unescape(std::string& s) + { + // first transform any occurrence of the sequence '~1' to '/' + replace_substring(s, "~1", "/"); + // then transform any occurrence of the sequence '~0' to '~' + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + static void flatten(const std::string& reference_string, + const basic_json& value, + basic_json& result) + { + switch (value.m_type) + { + case value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), + element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } + } + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + */ + static basic_json unflatten(const basic_json& value) + { + if (not value.is_object()) + { + JSON_THROW(std::domain_error("only objects can be unflattened")); + } + + basic_json result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (not element.second.is_primitive()) + { + JSON_THROW(std::domain_error("values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note + // that if the JSON pointer is "" (i.e., points to the whole + // value), function get_and_create returns a reference to + // result itself. An assignment will then create a primitive + // value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; + } + + private: + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return lhs.reference_tokens == rhs.reference_tokens; + } + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept + { + return !(lhs == rhs); + } + + /// the reference tokens + std::vector reference_tokens {}; + }; + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer} + + @since version 2.0.0 + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw std::out_of_range if the JSON pointer can not be resolved + @throw std::domain_error if an array index begins with '0' + @throw std::invalid_argument if an array index was not a number + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + + @since version 2.0.0 + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw std::out_of_range if a JSON pointer inside the patch could not + be resolved successfully in the current JSON value; example: `"key baz + not found"` + @throw invalid_argument if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (static_cast(idx) > parent.size()) + { + // avoid undefined behavior + JSON_THROW(std::out_of_range("array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (it != parent.end()) + { + parent.erase(it); + } + else + { + JSON_THROW(std::out_of_range("key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast(std::stoi(last_path))); + } + }; + + // type check + if (not json_patch.is_array()) + { + // a JSON patch must be an array of objects + JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (it == val.m_value.object->end()) + { + JSON_THROW(std::invalid_argument(error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (string_type and not it->second.is_string()) + { + JSON_THROW(std::invalid_argument(error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check + if (not val.is_object()) + { + JSON_THROW(std::invalid_argument("JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true);; + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (std::out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (not success) + { + JSON_THROW(std::domain_error("unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(std::invalid_argument("operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, + const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, + {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, + {"path", path}, + {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} +}; + +///////////// +// presets // +///////////// + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; +} // namespace nlohmann + + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible::value and + is_nothrow_move_assignable::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash(); + return h(j.dump()); + } +}; +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_DEPRECATED +#undef JSON_THROW +#undef JSON_TRY + +#endif diff --git a/layers.json b/layers.json new file mode 100644 index 0000000..8115ed5 --- /dev/null +++ b/layers.json @@ -0,0 +1,89 @@ +{ + "comment": "Surface ID to Layer ID mapping", + + "main_surface": { + "surface_role": "HomeScreen", + "comment": "This surface should never be made invisible (The HomeScreen)" + }, + + "mappings": [ + { + "role": "^HomeScreen$", + "name": "HomeScreen", + "layer_id": 1000, + "area": { "type": "full" }, + "comment": "Single layer map for the HomeScreen, XXX: type is redundant, could also check existence of id/first_id+last_id" + }, + { + "role": "MediaPlayer|Radio|Phone|Navigation|HVAC|Settings|Dashboard|POI|Mixer", + "name": "apps", + "layer_id": 1001, + "area": { "type": "rect", "rect": { "x": 0, "y": 218, "width": -1, "height": -433 } }, + "comment": "Range of IDs that will always be placed on layer 1001, negative rect values are interpreted as output_size.dimension - $value", + + "split_layouts": [ + { + "name": "MediaPlayer", + "main_match": "MediaPlayer", + "sub_match": "MediaPlayer", + "priority": 1000 + }, + { + "name": "Radio", + "main_match": "Radio", + "sub_match": "Radio", + "priority": 1000 + }, + { + "name": "Phone", + "main_match": "Phone", + "sub_match": "Phone", + "priority": 1000 + }, + { + "name": "Navigation", + "main_match": "Navigation", + "sub_match": "Navigation", + "priority": 1000 + }, + { + "name": "HVAC", + "main_match": "HVAC", + "sub_match": "HVAC", + "priority": 1000 + }, + { + "name": "Settings", + "main_match": "Settings", + "sub_match": "Settings", + "priority": 1000 + }, + { + "name": "Dashboard", + "main_match": "Dashboard", + "sub_match": "Dashboard", + "priority": 1000 + }, + { + "name": "POI", + "main_match": "POI", + "sub_match": "POI", + "priority": 1000 + }, + { + "name": "Mixer", + "main_match": "Mixer", + "sub_match": "Mixer", + "priority": 1000 + } + ] + }, + { + "role": "^OnScreen.*", + "name": "popups", + "layer_id": 9999, + "area": { "type": "rect", "rect": { "x": 0, "y": 300, "width": -1, "height": 400 } }, + "comment": "Range of IDs that will always be placed on the popup layer, that gets a very high 'dummy' id of 9999" + } + ] +} diff --git a/libwindowmanager/CMakeLists.txt b/libwindowmanager/CMakeLists.txt new file mode 100644 index 0000000..a1c28c8 --- /dev/null +++ b/libwindowmanager/CMakeLists.txt @@ -0,0 +1,85 @@ +# +# Copyright (c) 2017 TOYOTA MOTOR CORPORATION +# +# Licensed 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(FindPkgConfig) + +pkg_check_modules(AFB REQUIRED libafbwsc) +pkg_check_modules(SD REQUIRED libsystemd>=222) + +set(TARGET_LIBWM windowmanager) + +add_library(${TARGET_LIBWM} SHARED + libwindowmanager.cpp + libwindowmanager.h) + +target_include_directories(${TARGET_LIBWM} + PRIVATE + ${AFB_INCLUDE_DIRS} + ${SD_INCLUDE_DIRS}) + +target_link_libraries(${TARGET_LIBWM} + PUBLIC + -lpthread + ${AFB_LIBRARIES} + ${SD_LIBRARIES}) + +target_compile_definitions(${TARGET_LIBWM} + PRIVATE + _GNU_SOURCE) # XXX should I define this here?! + +if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Release") + target_compile_definitions(${TARGET_LIBWM} + PRIVATE + _GLIBCXX_DEBUG) +endif() + +target_compile_options(${TARGET_LIBWM} + PRIVATE + -Wall -Wextra -Wno-unused-parameter -Wno-comment) + +set_target_properties(${TARGET_LIBWM} + PROPERTIES + # INTERPROCEDURAL_OPTIMIZATION ON + CXX_EXTENSIONS OFF + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON) + +if (LINK_LIBCXX) + set_target_properties(${TARGET_LIBWM} + PROPERTIES + LINK_FLAGS "-lc++") +endif() + +if (NOT ${SANITIZER_MODE} STREQUAL "none" AND NOT ${SANITIZER_MODE} STREQUAL "") + target_compile_options(${TARGET_LIBWM} + PRIVATE + -fsanitize=${SANITIZER_MODE} -g -fno-omit-frame-pointer) + set_target_properties(${TARGET_LIBWM} + PROPERTIES + LINK_FLAGS "-fsanitize=${SANITIZER_MODE} -g") +endif() + +install( + TARGETS ${TARGET_LIBWM} + DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT "runtime") + +install( + FILES libwindowmanager.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + COMPONENT "development") + +add_subdirectory(doc) diff --git a/libwindowmanager/doc/CMakeLists.txt b/libwindowmanager/doc/CMakeLists.txt new file mode 100644 index 0000000..da59a0c --- /dev/null +++ b/libwindowmanager/doc/CMakeLists.txt @@ -0,0 +1,4 @@ +install( + FILES LibWindowmanager.md + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT "development") diff --git a/libwindowmanager/doc/GNUmakefile b/libwindowmanager/doc/GNUmakefile new file mode 100644 index 0000000..d878cc4 --- /dev/null +++ b/libwindowmanager/doc/GNUmakefile @@ -0,0 +1,7 @@ +all: LibWindowmanager.html + +LibWindowmanager.html: LibWindowmanager.txt + asciidoc -a max-width=55em $^ + +clean: + rm -f LibWindowmanager.html diff --git a/libwindowmanager/doc/LibWindowmanager.md b/libwindowmanager/doc/LibWindowmanager.md new file mode 100644 index 0000000..b855081 --- /dev/null +++ b/libwindowmanager/doc/LibWindowmanager.md @@ -0,0 +1,250 @@ +Introduction +============ + +The LibWindowmanager library provides a simple interface to manipulate and +query the state of the window manager application framework binding. +It is needs to be integrated and called from the client application. + +Intended audience +----------------- + +This document is intended to be useful to application developers. + +Scope of this Document +---------------------- + +This document describes the singleton class interface to the *Window +Manager* binding service. + +class LibWindowmanager +=============== + +This is the public interface of the class `LibWindowmanager`. Private members +and methods are not reproduced as they will not affect usage of the +class by client applications. + + class LibWindowmanager + { + public: + static LibWindowmanager &instance(); + + int init(int port, char const *token); + + // WM API + int requestSurface(const char *label); + int activateSurface(const char *label); + int deactivateSurface(const char *label); + int endDraw(const char *label); + + enum EventType { + Event_Active, + Event_Inactive, + Event_Visible, + Event_Invisible, + Event_SyncDraw, + Event_FlushDraw, + }; + + void set_event_handler(enum EventType et, + std::function f); + }; + +Errors +------ + +Methods returning an `int` signal successful operation when returning +`0`. In case of an error, an error value is returned as a negative errno +value. E.g. `-EINVAL` to signal that some input value was invalid. + +Additionally, logging of error messages is done on the standard error +file descriptor to help debugging the issue. + +Labels +------ + +Surface labels are any valid strings. For `requestSurface()` these +strings must match the *Window Manager* configuration in order to be +allowed to be displayed on one layer or the other. For all other calls +the label must match the exact name of a requested surface. + +Methods +------- + +### LibWindowmanager::init(port, token) + +Initialize the Binding communication. + +The `token` parameter is a string consisting of only alphanumeric characters. +If these conditions are not met, the LibWindowmanager instance will not initialize, +i.e. this call will return `-EINVAL`. + +The `port` parameter is the port the afb daemon is listening on, an +invalid port will lead to a failure of the call and return `-EINVAL`. + +### LibWindowmanager::requestSurface(label) + +This method requests a surface with the label given from the *Window +Manager*. It will return `0` for a successful surface request, and +`-errno` on failure. Additionally, on the standard error, messages are +logged to help debgging the issue. + +### LibWindowmanager::activateSurface(label) + +This method is mainly intended for *manager* applications that control +other applications (think an application manager or the *HomeScreen*). +It instructs the window manager to activate the surface with the given +*label*. + +This method only is effective after the actual window or surface was +created by the application. + +### LibWindowmanager::deactivateSurface(label) + +This method is mainly intended for *manager* applications that control +other applications. It instructs the window manager to deactivate the +surface associated with the given label. Note, that deactivating a +surface also means to implicitly activate another (the last active or if +not available *main surface* or *HomeScreen*.) + +This method only is effective after the actual window or surface was +created by the application. + +### LibWindowmanager::endDraw(label) + +This function is called from a client application when it is done +drawing its surface content. + +It is not crucial to make this call at every time a drawing is finished +- it is mainly intended to allow the window manager to synchronize +drawing in case of layout switch. The exact semantics are explained in +the next [Events](#_events) Section. + +### LibWindowmanager::set\_event\_handler(et, func) + +This method needs to be used to register event handlers for the WM +events described in the EventType enum. Only one hendler for each +EventType is possible, i.e. if it is called multiple times with the same +EventType the previous handler will be replaced. + +The `func` handler functions will receive the label of the surface this +event is targeted at. + +See Section [Events](#_events) for mor detailed information about event +delivery to client applications. + +Usage +----- + +### Initialization of LibWindowmanager + +Before usage of the LibWindowmanager, the method `init()` must be +called once, it will return `-errno` in case of en error and log +diagnostic messages to stderr. + +### Request a surface + +When creating a surface with *Qt* - it is necessary to request a surface +from the WM, internally this will communicate with the window manager +binding. Only after `requestSurface()` was successful, a surface should +be created. + +This is also true for *QML* aplications, where only after the +`requestSurface()` should the load of the resource be done. The method +returns `0` after the surface was requested successfully. + +#### Workings of requestSurface() + +`LibWindowmanager::requestSurface()` calls the AFB binding verb +`requestsurface` of the `windowmanager` API. This API call will return a +numeric ID to be used when creating the surface. This ID is never +explicitly returned to the client application, instead, it is set in the +application environment in order for *Qt* to then use it when creating +the surface. + +With the current *Qt* implementation this means, that only one surface +will be available to client applications, as subsequent windows will +increment this numeric ID internally - which then will lead to IDs that +cannot be known by the window manager as there is no direct +communication from *Qt* to the WM. + +Events +------ + +Events are a way for the *Window Manager* to propagate information to +client applications. It was vital for the project to implement a number +of events, that mirror functionality that is already present in the +wayland protocol. + +All events have the surface `label` as argument - a way to enable future +multi-surface applications. + +As already stated above, this is currently not possible with the way +*Qt* implements its surface ID setting. + +### Active and Inactive Events + +These events signal an application that it was activated or deactivated +respectively. Usually this means it was switched visible - which means +the surface will now be on the screen and therefor continue to render. + +### Visible and Invisible + +These events signal an application that it was switched to be visible or +invisible respectively. These events too are handled implicitly through +the wayland protocol by means of `wl_surface::enter` and +`wl_surface::leave` events to the client. + +### SyncDraw and FlushDraw + +These events instruct applications that they should redraw their surface +contents - again, this is handled implicitly by the wayland protocol. + +`SyncDraw` is sent to the application when it has to redraw its surface. + +`FlushDraw` is sent to the application when it should swap its buffers, +that is *signal* the compositor that its surface contains new content. + +Example Use Case +---------------- + +In order to enable application to use the `WM` surface registration +function the above described steps need to be implemented. + +As a minimal example the usage and initialization can look like the +following. + + // Assume a program argc and argv. + QGuiApplication app(argc, argv); + + auto &wm = LibWindowmanager::instance(); + + // initialize the LibWindowmanager binding. + if(wm.init(1234, "wmtest") != 0) { + exit(EXIT_FAILURE); + } + + // Request a surface label from the WM. + char const *surface_label = "AppMediaPlayer"; + if (wm.requestSurface(surface_label) != 0) { + exit(EXIT_FAILURE); + } + + // Register an Active event handler. + wm.set_event_handler(Event_Active, + [](char const *label) { + qDebug() << "Surface" << label << "got activated"; + }); + + // Initialize application window + // ... + + // request to activate the surface, this should usually + // not be done by the client application. + if (wm.activateSurface(surface_label) != 0) { + fprintf(stderr, "Could not activate the surface\n"); + exit(EXIT_FAILURE); + } + + // e.g. exec the qt application + app.exec(); + diff --git a/libwindowmanager/doc/LibWindowmanager.txt b/libwindowmanager/doc/LibWindowmanager.txt new file mode 100644 index 0000000..87394af --- /dev/null +++ b/libwindowmanager/doc/LibWindowmanager.txt @@ -0,0 +1,238 @@ += LibWindowmanager Library User Guide +:doctype: book +:toc: +:icons: +:data-uri: +:lang: en +:encoding: utf-8 + +== Introduction +The LibWindowmanager library provides a simple interface to manipulate and +query the state of the window manager application framework binding. +It is needs to be integrated and called from the client application. + +=== Intended audience +This document is intended to be useful to application developers. + +=== Scope of this Document +This document describes the singleton class interface to the _Window +Manager_ binding service. + +== class LibWindowmanager +This is the public interface of the class `LibWindowmanager`. Private members +and methods are not reproduced as they will not affect usage of the +class by client applications. + +--------------------------- +class LibWindowmanager +{ +public: + static LibWindowmanager &instance(); + + int init(int port, char const *token); + + // WM API + int requestSurface(const char *label); + int activateSurface(const char *label); + int deactivateSurface(const char *label); + int endDraw(const char *label); + + enum EventType { + Event_Active, + Event_Inactive, + Event_Visible, + Event_Invisible, + Event_SyncDraw, + Event_FlushDraw, + }; + + void set_event_handler(enum EventType et, + std::function f); +}; +--------------------------- + +=== Errors +Methods returning an `int` signal successful operation when returning +`0`. In case of an error, an error value is returned as a negative errno +value. E.g. `-EINVAL` to signal that some input value was invalid. + +Additionally, logging of error messages is done on the standard error +file descriptor to help debugging the issue. + +=== Labels +Surface labels are any valid strings. For `requestSurface()` these strings +must match the _Window Manager_ configuration in order to be allowed to +be displayed on one layer or the other. For all other calls the label +must match the exact name of a requested surface. + +=== Methods + +==== LibWindowmanager::init(port, token) +Initialize the Binding communication. + +The `token` parameter is a string consisting of only alphanumeric characters. +If these conditions are not met, the LibWindowmanager instance will not initialize, +i.e. this call will return `-EINVAL`. + +The `port` parameter is the port the afb daemon is listening on, +an invalid port will lead to a failure of the call and return `-EINVAL`. + +==== LibWindowmanager::requestSurface(label) +This method requests a surface with the label given from the _Window +Manager_. It will return `0` for a successful surface request, and +`-errno` on failure. Additionally, on the standard error, messages are +logged to help debgging the issue. + +==== LibWindowmanager::activateSurface(label) +This method is mainly intended for _manager_ applications that control +other applications (think an application manager or the _HomeScreen_). It +instructs the window manager to activate the surface with the given +_label_. + +This method only is effective after the actual window or surface was +created by the application. + +==== LibWindowmanager::deactivateSurface(label) +This method is mainly intended for _manager_ applications that control +other applications. It instructs the window manager to deactivate the +surface associated with the given label. Note, that deactivating a +surface also means to implicitly activate another (the last active or +if not available _main surface_ or _HomeScreen_.) + +This method only is effective after the actual window or surface was +created by the application. + +==== LibWindowmanager::endDraw(label) +This function is called from a client application when it is done +drawing its surface content. + +It is not crucial to make this call at every time a drawing is finished - +it is mainly intended to allow the window manager to synchronize drawing +in case of layout switch. The exact semantics are explained in the next +<<_events,Events>> Section. + +==== LibWindowmanager::set_event_handler(et, func) +This method needs to be used to register event handlers for the WM +events described in the EventType enum. Only one hendler for each +EventType is possible, i.e. if it is called multiple times with the same +EventType the previous handler will be replaced. + +The `func` handler functions will receive the label of the surface this +event is targeted at. + +See Section <<_events,Events>> for mor detailed information about event +delivery to client applications. + +=== Usage + +==== Initialization of LibWindowmanager +Before usage of the LibWindowmanager, the method `init()` must be +called once, it will return `-errno` in case of en error and log diagnostic +messages to stderr. + +==== Request a surface +When creating a surface with _Qt_ - it is necessary to request a surface +from the WM, internally this will communicate with the window manager +binding. Only after `requestSurface()` was successful, a surface should +be created. + +This is also true for _QML_ aplications, where only after the +`requestSurface()` should the load of the resource be done. The method +returns `0` after the surface was requested successfully. + +===== Workings of requestSurface() +`LibWindowmanager::requestSurface()` calls the AFB binding verb +`requestsurface` of the `windowmanager` API. This API call will return a +numeric ID to be used when creating the surface. This ID is never +explicitly returned to the client application, instead, it is set in the +application environment in order for _Qt_ to then use it when creating the +surface. + +.Remarks +******************************** +With the current _Qt_ implementation this means, that only one surface +will be available to client applications, as subsequent windows will +increment this numeric ID internally - which then will lead to IDs that +cannot be known by the window manager as there is no direct +communication from _Qt_ to the WM. +******************************** + +=== Events +Events are a way for the _Window Manager_ to propagate information to +client applications. It was vital for the project to implement a number +of events, that mirror functionality that is already present in the +wayland protocol. + +All events have the surface `label` as argument - a way to enable future +multi-surface applications. + +.Remarks +************************** +As already stated above, this is currently not possible with the way +_Qt_ implements its surface ID setting. +************************** + +==== Active and Inactive Events +These events signal an application that it was activated or deactivated +respectively. Usually this means it was switched visible - which means +the surface will now be on the screen and therefor continue to render. + +==== Visible and Invisible +These events signal an application that it was switched to be visible or +invisible respectively. These events too are handled implicitly through +the wayland protocol by means of `wl_surface::enter` and +`wl_surface::leave` events to the client. + +==== SyncDraw and FlushDraw +These events instruct applications that they should redraw their surface +contents - again, this is handled implicitly by the wayland protocol. + +`SyncDraw` is sent to the application when it has to redraw its surface. + +`FlushDraw` is sent to the application when it should swap its buffers, +that is _signal_ the compositor that its surface contains new content. + +=== Example Use Case +In order to enable application to use the `WM` surface registration +function the above described steps need to be implemented. + +As a minimal example the usage and initialization can look like the +following. + +----------------------- + // Assume a program argc and argv. + QGuiApplication app(argc, argv); + + auto &wm = LibWindowmanager::instance(); + + // initialize the LibWindowmanager binding. + if(wm.init(1234, "wmtest") != 0) { + exit(EXIT_FAILURE); + } + + // Request a surface label from the WM. + char const *surface_label = "AppMediaPlayer"; + if (wm.requestSurface(surface_label) != 0) { + exit(EXIT_FAILURE); + } + + // Register an Active event handler. + wm.set_event_handler(Event_Active, + [](char const *label) { + qDebug() << "Surface" << label << "got activated"; + }); + + // Initialize application window + // ... + + // request to activate the surface, this should usually + // not be done by the client application. + if (wm.activateSurface(surface_label) != 0) { + fprintf(stderr, "Could not activate the surface\n"); + exit(EXIT_FAILURE); + } + + // e.g. exec the qt application + app.exec(); + +// vim:set ft=asciidoc tw=72: diff --git a/libwindowmanager/libwindowmanager.cpp b/libwindowmanager/libwindowmanager.cpp new file mode 100644 index 0000000..c5a1062 --- /dev/null +++ b/libwindowmanager/libwindowmanager.cpp @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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 "libwindowmanager.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +extern "C" { +#include +#include +} + +#define UNUSED(x) (void)(x) + +// _ ___ _ +// ___| | __ _ ___ ___ |_ _|_ __ ___ _ __ | | +// / __| |/ _` / __/ __| | || '_ ` _ \| '_ \| | +// | (__| | (_| \__ \__ \ | || | | | | | |_) | | +// \___|_|\__,_|___/___/ |___|_| |_| |_| .__/|_| +// |_| +class LibWindowmanager::Impl { + friend class LibWindowmanager; + + // This is the LibWindowmanager interface impl + int init(int port, char const *token); + + // WM API + int requestSurface(const char *label); + int activateSurface(const char *label); + int deactivateSurface(const char *label); + int endDraw(const char *label); + + void set_event_handler(enum EventType et, handler_fun func); + + Impl(); + ~Impl(); + + struct afb_wsj1 *wsj1; + struct sd_event *loop; + + std::set labels; + std::map handlers; + std::queue> handler_queue; + + int api_call(const char *verb, json_object *object, + const std::function &onReply); + +public: + void event(char const *et, char const *label); +private: + int runEventLoop(); +}; + +namespace { + +constexpr const int token_maxlen = 20; +constexpr const char *const wmAPI = "windowmanager"; + +#define CONCAT_(X, Y) X##Y +#define CONCAT(X, Y) CONCAT_(X, Y) + +#ifndef SCOPE_TRACING +#define TRACE() +#define TRACEN(N) +#else +#define TRACE() \ + ScopeTrace __attribute__((unused)) CONCAT(trace_scope_, __LINE__)(__func__) +#define TRACEN(N) \ + ScopeTrace __attribute__((unused)) CONCAT(named_trace_scope_, __LINE__)(#N) + +struct ScopeTrace { + thread_local static int indent; + char const *f{}; + explicit ScopeTrace(char const *func) : f(func) { + fprintf(stderr, "%*s%s -->\n", 2 * indent++, "", this->f); + } + ~ScopeTrace() { fprintf(stderr, "%*s%s <--\n", 2 * --indent, "", this->f); } +}; +thread_local int ScopeTrace::indent = 0; +#endif + +/* called when wsj1 receives a method invocation */ +void onCall(void *closure, const char *api, const char *verb, + struct afb_wsj1_msg *msg) { + TRACE(); + UNUSED(closure); + UNUSED(verb); + UNUSED(api); + UNUSED(msg); +} + +/* called when wsj1 receives an event */ +void onEvent(void *closure, const char *event, afb_wsj1_msg *msg) { + TRACE(); + + // check API name in event + if (0 != strncmp(wmAPI, event, strlen(wmAPI))) { + return; + } + + reinterpret_cast(closure)->event( + event, json_object_get_string( + json_object_object_get(afb_wsj1_msg_object_j(msg), "data"))); +} + +/* called when wsj1 hangsup */ +void onHangup(void *closure, afb_wsj1 *wsj1) { + TRACE(); + UNUSED(closure); + UNUSED(wsj1); + fputs("Hangup, the WindowManager vanished\n", stderr); + exit(1); // XXX: there should be something ... *better* here. +} + +constexpr struct afb_wsj1_itf itf = { + onHangup, onCall, onEvent, +}; + +// XXX: I am not sure this is the right thing to do though... +std::recursive_mutex dispatch_mutex; + +json_object *drawing_name_json_argument(char const *label) { + json_object *j = json_object_new_object(); + json_object_object_add(j, "drawing_name", json_object_new_string(label)); + return j; +} + +} // namespace + +// _ ___ _ _ _ +// ___| | __ _ ___ ___ |_ _|_ __ ___ _ __ | | (_)_ __ ___ _ __ | | +// / __| |/ _` / __/ __| | || '_ ` _ \| '_ \| | | | '_ ` _ \| '_ \| | +// | (__| | (_| \__ \__ \ | || | | | | | |_) | | | | | | | | | |_) | | +// \___|_|\__,_|___/___/ |___|_| |_| |_| .__/|_| |_|_| |_| |_| .__/|_| +// |_| |_| +LibWindowmanager::Impl::Impl() : wsj1{}, loop{}, labels(), handlers() { TRACE(); } + +LibWindowmanager::Impl::~Impl() { + TRACE(); + afb_wsj1_unref(wsj1); + sd_event_unref(loop); +} + +int LibWindowmanager::Impl::init(int port, char const *token) { + TRACE(); + char *uribuf = nullptr; + int rc = -1; + + if (this->loop != nullptr && this->wsj1 != nullptr) { + fputs("LibWindowmanager instance is already initialized!\n", stderr); + rc = -EALREADY; + goto fail; + } + + if (token == nullptr) { + fputs("Token is invalid\n", stderr); + rc = -EINVAL; + goto fail; + } + + if (port < 1 || port > 0xffff) { + fputs("Port is invalid\n", stderr); + rc = -EINVAL; + goto fail; + } + + /* get the default event loop */ + rc = sd_event_default(&this->loop); + if (rc < 0) { + fprintf(stderr, "Connection to default event loop failed: %s\n", + strerror(-rc)); + goto fail; + } + + asprintf(&uribuf, "ws://localhost:%d/api?token=%s", port, token); + + /* connect the websocket wsj1 to the uri given by the first argument */ + this->wsj1 = afb_ws_client_connect_wsj1( + this->loop, uribuf, const_cast(&itf), this); + if (this->wsj1 == nullptr) { + sd_event_unref(this->loop); + this->loop = nullptr; + fprintf(stderr, "Connection to %s failed: %m\n", uribuf); + rc = -errno; + goto fail; + } + + this->runEventLoop(); + + return 0; + +fail: + return rc; +} + +int LibWindowmanager::Impl::requestSurface(const char *label) { + TRACE(); + + if (this->labels.find(label) != this->labels.end()) { + fputs("Surface label already known!\n", stderr); + return -EINVAL; + } + + json_object *j = drawing_name_json_argument(label); + + int rc = -1; + /* send the request */ + int rc2 = + this->api_call("RequestSurface", j, [&rc](bool ok, json_object *j) { + if (ok) { + int id = + json_object_get_int(json_object_object_get(j, "response")); + char *buf; + asprintf(&buf, "%d", id); + printf("setenv(\"QT_IVI_SURFACE_ID\", %s, 1)\n", buf); + if (setenv("QT_IVI_SURFACE_ID", buf, 1) != 0) { + fprintf(stderr, "putenv failed: %m\n"); + rc = -errno; + } else { + rc = 0; // Single point of success + } + } else { + fprintf(stderr, "Could not get surface ID from WM: %s\n", + j != nullptr ? json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY) + : "no-info"); + rc = -EINVAL; + } + }); + + if (rc2 < 0) { + rc = rc2; + } + + if (rc >= 0) { + this->labels.insert(this->labels.end(), label); + } + + return rc; +} + +int LibWindowmanager::Impl::activateSurface(const char *label) { + TRACE(); + json_object *j = drawing_name_json_argument(label); + return this->api_call("ActivateSurface", j, [](bool ok, json_object *j) { + if (!ok) { + fprintf(stderr, "API Call activate_surface() failed: %s\n", + j != nullptr ? json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY) + : "no-info"); + } + }); +} + +int LibWindowmanager::Impl::deactivateSurface(const char *label) { + TRACE(); + json_object *j = drawing_name_json_argument(label); + return this->api_call("DeactivateSurface", j, [](bool ok, json_object *j) { + if (!ok) { + fprintf(stderr, "API Call deactivate_surface() failed: %s\n", + j != nullptr ? json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY) + : "no-info"); + } + }); +} + +int LibWindowmanager::Impl::endDraw(const char *label) { + TRACE(); + json_object *j = drawing_name_json_argument(label); + return this->api_call("EndDraw", j, [](bool ok, json_object *j) { + if (!ok) { + fprintf(stderr, "API Call endDraw() failed: %s\n", + j != nullptr ? json_object_to_json_string_ext( + j, JSON_C_TO_STRING_PRETTY) + : "no-info"); + } + }); +} + +void LibWindowmanager::Impl::set_event_handler( + enum EventType et, std::function func) { + TRACE(); + + if (et >= 1 && et <= 6) { // Yeah ... just go with it! + this->handlers[et] = std::move(func); + } +} + +namespace { +std::pair make_event_type(char const *et) { + // Event have the form "$API/$EVENT", just try to find the first / and + // get on with it. + char const *et2 = strchr(et, '/'); + if (et2 != nullptr) { + et = et2 + 1; + } + +#define ET(N, A) \ + do { \ + if (strcasecmp(et, N) == 0) \ + return std::pair( \ + true, CONCAT(LibWindowmanager::Event_, A)); \ + } while (false) + + ET("active", Active); + ET("inactive", Inactive); + ET("visible", Visible); + ET("invisible", Invisible); + ET("syncdraw", SyncDraw); + ET("flushdraw", FlushDraw); +#undef ET + + return std::pair(false, + LibWindowmanager::Event_Active); +} +} // namespace + +static void _on_reply_static(void *closure, struct afb_wsj1_msg *msg) +{ + // nop +} + + +/// object will be json_object_put +int LibWindowmanager::Impl::api_call( + const char *verb, json_object *object, + const std::function &onReply) { + TRACE(); + + int rc = 0; + if (0 == strcmp("RequestSurface", verb)) { + // We need to wrap the actual onReply call once in order to + // *look* like a normal functions pointer (std::functions<> + // with captures cannot convert to function pointers). + // Alternatively we could setup a local struct and use it as + // closure, but I think it is cleaner this way. + int call_rc = 0; + std::atomic returned{}; + returned.store(false, std::memory_order_relaxed); + std::function wrappedOnReply = + [&returned, &call_rc, &onReply](bool ok, json_object *j) { + TRACEN(wrappedOnReply); + call_rc = ok ? 0 : -EINVAL; + // We know it failed, but there may be an explanation in the + // json object. + { + TRACEN(onReply); + onReply(ok, j); + } + returned.store(true, std::memory_order_release); + }; + + // make the actual call, use wrappedOnReply as closure + rc = afb_wsj1_call_j( + this->wsj1, wmAPI, verb, object, + [](void *closure, afb_wsj1_msg *msg) { + TRACEN(callClosure); + auto *onReply = + reinterpret_cast *>( + closure); + (*onReply)(!(afb_wsj1_msg_is_reply_ok(msg) == 0), + afb_wsj1_msg_object_j(msg)); + }, + &wrappedOnReply); + + if (0 == rc) { + // We need to wait until "returned" got set, this is necessary + // if events get triggered by the call (and would be dispatched before + // the actual call-reply). + while (!returned.load(std::memory_order_consume)) { + sd_event_run(loop, 16); + } + + // return the actual API call result + rc = call_rc; + } + } + else { + rc = afb_wsj1_call_j(this->wsj1, wmAPI, verb, object, _on_reply_static, this); + } + + if (rc < 0) { + fprintf( + stderr, "calling %s/%s(%s) failed: %m\n", wmAPI, verb, + json_object_to_json_string_ext(object, JSON_C_TO_STRING_PRETTY)); + // Call the reply handler regardless with a NULL json_object* + onReply(false, nullptr); + } + + return rc; +} + +void LibWindowmanager::Impl::event(char const *et, char const *label) { + TRACE(); + auto oet = make_event_type(et); + if (!oet.first) { + fprintf(stderr, "Unknown event type string '%s'\n", et); + return; + } + + auto i = this->handlers.find(oet.second); + if (i != this->handlers.end()) { + if (this->labels.find(label) != this->labels.end()) { + i->second(label); + } + } +} + +static void *event_loop_run(void *args){ + struct sd_event* loop = (struct sd_event*)(args); + for(;;) + sd_event_run(loop, 30000000); +} + +int LibWindowmanager::Impl::runEventLoop() { + if(this->wsj1 && this->loop) + { + pthread_t thread_id; + int ret = pthread_create(&thread_id, NULL, event_loop_run, this->loop); + if(ret != 0) + { + printf("Cannot run eventloop due to error:%d", errno); + return -1; + } + else + return thread_id; + } + else + { + printf("Connecting is not established yet"); + return -1; + } +} + +// _ _ _____ ____ ____ _ _ _ +// ___| | __ _ ___ ___ / \ | ___| __ ) / ___| (_) ___ _ __ | |_ +// / __| |/ _` / __/ __| / _ \ | |_ | _ \| | | | |/ _ \ '_ \| __| +// | (__| | (_| \__ \__ \ / ___ \| _| | |_) | |___| | | __/ | | | |_ +// \___|_|\__,_|___/___/ /_/ \_\_| |____/ \____|_|_|\___|_| |_|\__| +// +int LibWindowmanager::init(int port, char const *token) { + return this->d->init(port, token); +} + +int LibWindowmanager::requestSurface(const char *label) { + return this->d->requestSurface(label); +} + +int LibWindowmanager::activateSurface(const char *label) { + return this->d->activateSurface(label); +} + +int LibWindowmanager::deactivateSurface(const char *label) { + return this->d->deactivateSurface(label); +} + +int LibWindowmanager::endDraw(const char *label) { return this->d->endDraw(label); } + +void LibWindowmanager::set_event_handler(enum EventType et, + std::function f) { + return this->d->set_event_handler(et, std::move(f)); +} + +LibWindowmanager::LibWindowmanager() : d(new Impl) {} + +LibWindowmanager::~LibWindowmanager() { delete d; } diff --git a/libwindowmanager/libwindowmanager.h b/libwindowmanager/libwindowmanager.h new file mode 100644 index 0000000..6c13763 --- /dev/null +++ b/libwindowmanager/libwindowmanager.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2017 TOYOTA MOTOR CORPORATION + * + * Licensed 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. + */ + +#ifndef LIBWINDOWMANAGER_H +#define LIBWINDOWMANAGER_H + +#include + +class LibWindowmanager { +public: + LibWindowmanager(); + ~LibWindowmanager(); + + LibWindowmanager(const LibWindowmanager &) = delete; + LibWindowmanager &operator=(const LibWindowmanager &) = delete; + + using handler_fun = std::function; + + enum EventType { + Event_Active = 1, + Event_Inactive, + + Event_Visible, + Event_Invisible, + + Event_SyncDraw, + Event_FlushDraw, + }; + + int init(int port, char const *token); + + // WM API + int requestSurface(const char *label); + int activateSurface(const char *label); + int deactivateSurface(const char *label); + int endDraw(const char *label); + + void set_event_handler(enum EventType et, handler_fun f); + + struct Impl; + +private: + Impl *const d; +}; +#endif // LIBWINDOWMANAGER_H diff --git a/package/root/config.xml b/package/root/config.xml new file mode 100644 index 0000000..6392dc3 --- /dev/null +++ b/package/root/config.xml @@ -0,0 +1,19 @@ + + + windowmanager-service-2017 + + + Window Manager + TOYOTA + APL 2.0 + + + + + + + + + + + diff --git a/package/root/icon.svg b/package/root/icon.svg new file mode 100644 index 0000000..6628784 --- /dev/null +++ b/package/root/icon.svg @@ -0,0 +1,283 @@ + + + +image/svg+xmlSETTINGS + \ No newline at end of file diff --git a/protocol/ivi-application.xml b/protocol/ivi-application.xml new file mode 100644 index 0000000..8f24226 --- /dev/null +++ b/protocol/ivi-application.xml @@ -0,0 +1,100 @@ + + + + + Copyright (C) 2013 DENSO CORPORATION + Copyright (c) 2013 BMW Car IT GmbH + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + + + + + + + + This removes link from ivi_id to wl_surface and destroys ivi_surface. + The ID, ivi_id, is free and can be used for surface_create again. + + + + + + The configure event asks the client to resize its surface. + + The size is a hint, in the sense that the client is free to + ignore it if it doesn't resize, pick a smaller size (to + satisfy aspect ratio or resize in steps of NxM pixels). + + The client is free to dismiss all but the last configure + event it received. + + The width and height arguments specify the size of the window + in surface local coordinates. + + + + + + + + + This interface is exposed as a global singleton. + This interface is implemented by servers that provide IVI-style user interfaces. + It allows clients to associate a ivi_surface with wl_surface. + + + + + + + + + + This request gives the wl_surface the role of an IVI Surface. Creating more than + one ivi_surface for a wl_surface is not allowed. Note, that this still allows the + following example: + + 1. create a wl_surface + 2. create ivi_surface for the wl_surface + 3. destroy the ivi_surface + 4. create ivi_surface for the wl_surface (with the same or another ivi_id as before) + + surface_create will create a interface:ivi_surface with numeric ID; ivi_id in + ivi compositor. These ivi_ids are defined as unique in the system to identify + it inside of ivi compositor. The ivi compositor implements business logic how to + set properties of the surface with ivi_id according to status of the system. + E.g. a unique ID for Car Navigation application is used for implementing special + logic of the application about where it shall be located. + The server regards following cases as protocol errors and disconnects the client. + - wl_surface already has an nother role. + - ivi_id is already assigned to an another wl_surface. + + If client destroys ivi_surface or wl_surface which is assigne to the ivi_surface, + ivi_id which is assigned to the ivi_surface is free for reuse. + + + + + + + + + diff --git a/protocol/ivi-controller.xml b/protocol/ivi-controller.xml new file mode 100644 index 0000000..521d625 --- /dev/null +++ b/protocol/ivi-controller.xml @@ -0,0 +1,603 @@ + + + + + Copyright (C) 2013 DENSO CORPORATION + Copyright (c) 2013 BMW Car IT GmbH + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + + + + + + + If visibility argument is 0, the surface in the ivi compositor is set to invisible. + If visibility argument is not 0, the surface in the ivi compositor is set to visible. + + + + + + + The valid range for opacity is 0.0 (fully transparent) to 1.0 (fully opaque). + + + + + + + The source rectangle defines the part of the surface content, that is used for + compositing the surface. It can be used, if valid content of the surface is smaller + than the surface. Effectively it can be used to zoom the content of the surface. + x: horizontal start position of scanout area within the surface + y: vertical start position of scanout area within the surface + width: width of scanout area within the surface + height: height of scanout area within the surface + + + + + + + + + + The destination rectangle defines the position and size of a surface on a layer. + The surface will be scaled to this rectangle for rendering. + x: horizontal start position of surface within the layer + y: vertical start position of surface within the layer + width : width of surface within the layer + height: height of surface within the layer + + + + + + + + + + Request the client providing content for this surface, to resize of the buffers + provided as surface content. + + + + + + + + The orientation of a surface in ivi compositor can be rotated in 90 degree steps, + as defined in orientation enum. + + + + + + + Store a screenshot of the surface content in the file provided by argument filename. + + + + + + + The new visibility state is provided in argument visibility. + If visibility is 0, the surface has become invisible. + If visibility is not 0, the surface has become visible. + + + + + + + The new opacity state is provided in argument opacity. + The valid range for opactiy is 0.0 (fully transparent) to 1.0 (fully opaque). + + + + + + + The scanout region of the surface content has changed. + The new values for source rectangle are provided by + x: new horizontal start position of scanout area within the surface + y: new vertical start position of scanout area within the surface + width: new width of scanout area within the surface + height: new height of scanout area within the surface + + + + + + + + + + The new values for source rectangle are provided by + x: new horizontal start position of surface within the layer + y: new vertical start position of surface within the layer + width : new width of surface within the layer + height: new height of surface within the layer + + + + + + + + + + The client providing content for this surface was requested to resize the buffer + provided as surface content. The requested buffer size is provided by arguments + width and height. + + + + + + + + The surfaces in ivi controller can be rotated in 90 degrees steps. + This enum defines all valid orientations for surfaces. + + + + + + + + + + The new orientation status is provided by argument orientation. + + + + + + + Applications can provide buffers as surface content with differernt buffer + properties. This enum defines all supported buffer configurations. + + + + + + + + + + + + + + When client attach buffers as surface content, these buffers have a pixelformat + configuration. If the pixelformat of a newly attached buffer is different from + the previous buffer configuration, this event is raised. + This is also done, when the first buffer is provided by application. + + + + + + + This surface was added to the render order of the layer defined by argument layer. + This is essential for a surface to become visible on screen, since ivi compositors + will only render layers (or more precise all surfaces in the render order of a layer). + + + + + + + These stats contain information required for monitoring, debugging, logging + and tracing. + + + + + + The information contained in this event is essential for monitoring, debugging, + logging and tracing support in IVI systems. + + + + + + + + + + + Request to destroy the ivi_controller_surface. If argument + destroy_scene_object id not 0, the surface will be destroyed in + ivi compositor. If argument is 0, only the proxy object is destroyed. + + + + + + + + + + + This enum defines all possible content states of a surface. This is + required, since surfaces in ivi compositor can exist without applications + providing content for them. + + + + + + + + Surfaces in ivi compositor can exist without any application or controller + referencing it. All surfaces initially have no content. This event indicates + when content state has changed. All possible content states are defined + in enum content_state. + + + + + + + + + + + + If visibility argument is 0, the layer in the ivi compositor is set to invisible. + If visibility argument is not 0, the layer in the ivi compositor is set to visible. + + + + + + + The valid range for opacity is 0.0 (fully transparent) to 1.0 (fully opaque). + + + + + + + The source rectangle defines the part of the layer content, that is used for + compositing the screen. It can be used, if valid content of the layer is smaller + than the layer. Effectively it can be used to zoom the content of the layer. + x: horizontal start position of scanout area within the layer + y: vertical start position of scanout area within the layer + width: width of scanout area within the layer + height: height of scanout area within the layer + + + + + + + + + + The destination rectangle defines the position and size of a layer on a screen. + The layer will be scaled to this rectangle for rendering. + x: horizontal start position of layer within the screen + y: vertical start position of layer within the screen + width : width of surface within the screen + height: height of surface within the screen + + + + + + + + + + Layers are created with an initial size, but they can be resized at runtime. + This request changes the widht and height of a layer. + + + + + + + + The orientation of a layer in ivi compositor can be rotated in 90 degree steps, + as defined in orientation enum. + + + + + + + Store a screenshot of the layer content in the file provided by argument filename. + + + + + + + A layer has no content assigned to itself, it is a container for surfaces. + This request removes all surfaces from the layer render order. + Note: the surfaces are not destroyed, they are just no longer contained by + the layer. + + + + + + A layer has no content assigned to itself, it is a container for surfaces. + This request adds a surface to the topmost position of the layer render order. + The added surface will cover all other surfaces of the layer. + + + + + + + A layer has no content assigned to itself, it is a container for surfaces. + This request removes one surfaces from the layer render order. + Note: the surface is not destroyed, it is just no longer contained by + the layer. + + + + + + + A layer has no content assigned to itself, it is a container for surfaces. + This request removes all surfaces from the layer render order and set a + completely new render order. + + + + + + + The new visibility state is provided in argument visibility. + If visibility is 0, the layer has become invisible. + If visibility is not 0, the layer has become visible. + + + + + + + The new opacity state is provided in argument opacity. + The valid range for opactiy is 0.0 (fully transparent) to 1.0 (fully opaque). + + + + + + + The scanout region of the layer content has changed. + The new values for source rectangle are provided by + x: new horizontal start position of scanout area within the layer + y: new vertical start position of scanout area within the layer + width: new width of scanout area within the layer + height: new height of scanout area within the layer + + + + + + + + + + The new values for source rectangle are provided by + x: new horizontal start position of layer within the screen + y: new vertical start position of layer within the screen + width : new width of layer within the screen + height: new height of layer within the screen + + + + + + + + + + The layer was resized. The new layer size is provided by arguments + width and height. + + + + + + + + The new orientation status is provided by argument orientation. + + + + + + + This layer was added to the render order of the screen defined by argument screen. + This is essential for a layer to become visible on screen, since ivi compositors + will only render screens (or more precise all layers in the render order of a screen). + + + + + + + Request to destroy the ivi_controller_layer. If argument + destroy_scene_object id not 0, the layer will be destroyed in + ivi compositor. If argument is 0, only the proxy object is destroyed. + + + + + + + + + + + + + + + + + + + + A screen has no content assigned to itself, it is a container for layers. + This request removes all layers from the screen render order. + Note: the layers are not destroyed, they are just no longer contained by + the screen. + + + + + + A screen has no content assigned to itself, it is a container for layers. + This request adds a layers to the topmost position of the screen render order. + The added layer will cover all other layers of the screen. + + + + + + + Store a screenshot of the screen content in the file provided by argument filename. + + + + + + + A screen has no content assigned to itself, it is a container for layers. + This request removes all layers from the screen render order and set a + completely new render order. + + + + + + + + + + + + All requests are not applied directly to scene object, so a controller + can set different properties and apply the changes all at once. + Note: there's an exception to this. Creation and destruction of + scene objects is executed immediately. + + + + + + A new screen is announced to the controller. This is typically + the case in two cases: + 1. controller was just started, ivi compositor announces existing screen + 2. a new screen was added to the system at runtime + + + + + + + + layer_create will create a new layer with id_layer in ivi compositor, + if it does not yet exists. If the layer with id_layer already exists in + ivi compositor, a handle to the existing layer is returned and width and + height properties are ignored. + + + + + + + + + + A new layer is announced to the controller. + + + + + + + surface_create will create a new surface with id_surface in ivi compositor, + if it does not yet exists. If the surface with id_surface already exists in + ivi compositor, a handle to the existing surface is returned. + + + + + + + + A new surface is announced to the controller. + + + + + + + This enum defines all scene object available in ivi compositor. + + + + + + + + + These error codes define all possible error codes returned by ivi compositor + on server-side errors. + + + + + + + + The ivi compositor encountered error while processing a request by this + controller. The error is defined by argument error_code and optional + error_text. Additionally the object type and id is contained in the error + event to provide some detailes to handle the error. + If the controller requires to associate this error event to a request, + it can + 1. send request + 2. force display roundtrip + 3. check, if error event was received + but this restricts the controller to have only one open request at a time. + + + + + + + + + + + diff --git a/scripts/CMakeLists.txt b/scripts/CMakeLists.txt new file mode 100644 index 0000000..4dda1ce --- /dev/null +++ b/scripts/CMakeLists.txt @@ -0,0 +1,5 @@ +install( + FILES wm-request + DESTINATION ${CMAKE_INSTALL_BINDIR} + PERMISSIONS WORLD_EXECUTE WORLD_READ OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE + COMPONENT "runtime") diff --git a/scripts/wm-request b/scripts/wm-request new file mode 100644 index 0000000..e7ecb87 --- /dev/null +++ b/scripts/wm-request @@ -0,0 +1,74 @@ +#!/bin/sh + +# +# Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH +# +# Licensed 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. +# + +nopygments=0 +if [ "$1" = "-p" ] +then + nopygments=1 + shift +fi + +if ! [ "$1" ] +then + echo "Usage: $0 VERB [ARGS]" >&2 + exit 1 +fi + +UUIDFILE=/tmp/wm-request-uuid +if ! which uuidgen 2>/dev/null 1>&2 +then + echo "c7c638c5-d097-4eb4-9012-a1e4c25b9808" > $UUIDFILE +else + if ! [ -f $UUIDFILE ] + then + uuidgen > $UUIDFILE + fi +fi +UUID="`cat $UUIDFILE`" + +for i in afb-client-demo +do + which $i 2>/dev/null 1>&2 || { echo "Program $i is missing" >&2; exit 1; } +done + +set -eu + + if [ $nopygments = 0 ] && which pygmentize 2>/dev/null 1>&2 + then + json_pretty() { + pygmentize -l json + } + else + json_pretty() { + cat + } +fi + +verb=$1 +shift + +set +u +if [ "$1" ] +then + set -u + args='{"drawing_name":"'"$1"'"}' +fi + +afb-client-demo -H ws://localhost:1700/api\?token=wm\&uuid=$UUID windowmanager $verb $args | json_pretty + +# vim:set ft=sh: diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7bbc35d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,129 @@ +# +# Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH +# +# Licensed 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. +# + +wlproto(IVI_CON ivi-controller) + +include(FindPkgConfig) +pkg_check_modules(AFB REQUIRED afb-daemon) +pkg_check_modules(SD REQUIRED libsystemd>=222) + +# We do not want a prefix for our module +set(CMAKE_SHARED_MODULE_PREFIX "") + +add_custom_command( + OUTPUT afb_binding_api.hpp afb_binding_glue.inl + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../generate-binding-glue.py + COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/../generate-binding-glue.py) + +set(TARGETS_WM windowmanager-service) + +add_library(${TARGETS_WM} MODULE + main.cpp + wayland.cpp + wayland.hpp + util.cpp + util.hpp + layout.cpp + layout.hpp + ${IVI_CON_PROTO} + json_helper.cpp + json_helper.hpp + app.hpp app.cpp + afb_binding_api.cpp + result.hpp + afb_binding_api.hpp + afb_binding_glue.inl + layers.cpp + layers.hpp + controller_hooks.hpp + config.cpp + config.hpp + policy.hpp) + +target_include_directories(${TARGETS_WM} + PRIVATE + ${AFB_INCLUDE_DIRS} + ${SD_INCLUDE_DIRS} + ../include + ../src) + +target_link_libraries(${TARGETS_WM} + PRIVATE + ${AFB_LIBRARIES} + ${WLC_LIBRARIES} + ${SD_LIBRARIES}) + +target_compile_definitions(${TARGETS_WM} + PRIVATE + AFB_BINDING_VERSION=2 + # We do not want source location of messages + AFB_BINDING_PRAGMA_NO_VERBOSE_DETAILS + WINMAN_VERSION_STRING="${PACKAGE_VERSION}" + _GNU_SOURCE) # XXX should I define this here?! + +if(NOT ${CMAKE_BUILD_TYPE} STREQUAL "Release") + target_compile_definitions(${TARGETS_WM} + PRIVATE + _GLIBCXX_DEBUG) +endif() + +target_compile_options(${TARGETS_WM} + PRIVATE + -Wall -Wextra -Wno-unused-parameter -Wno-comment) + +set_target_properties(${TARGETS_WM} + PROPERTIES + # INTERPROCEDURAL_OPTIMIZATION ON + + CXX_EXTENSIONS OFF + CXX_STANDARD 14 + CXX_STANDARD_REQUIRED ON + + C_EXTENSIONS OFF + C_STANDARD 99 + C_STANDARD_REQUIRED ON + + LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../export.map") + +if (LINK_LIBCXX) + set_target_properties(${TARGETS_WM} + PROPERTIES + LINK_FLAGS "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/../export.map -lc++") +endif() + +if (NOT ${SANITIZER_MODE} STREQUAL "none" AND NOT ${SANITIZER_MODE} STREQUAL "") + target_compile_options(${TARGETS_WM} + PRIVATE + -fsanitize=${SANITIZER_MODE} -g -fno-omit-frame-pointer) + set_target_properties(${TARGETS_WM} + PROPERTIES + LINK_FLAGS "-fsanitize=${SANITIZER_MODE} -g") +endif() + +if(NOT EXISTS ${PROJECT_BINARY_DIR}/package) + add_custom_command(TARGET ${TARGETS_WM} POST_BUILD + COMMAND cp -rf ${PROJECT_SOURCE_DIR}/package ${PROJECT_BINARY_DIR} + ) +endif() + +add_custom_command(TARGET ${TARGETS_WM} POST_BUILD + COMMAND mkdir -p ${PROJECT_BINARY_DIR}/package/root/lib + COMMAND cp -rf ${PROJECT_BINARY_DIR}/src/${TARGETS_WM}.so ${PROJECT_BINARY_DIR}/package/root/lib +) + +add_custom_target(package DEPENDS ${PROJECT_BINARY_DIR}/package/root + COMMAND wgtpkg-pack -f -o ${PROJECT_BINARY_DIR}/package/${TARGETS_WM}-2017.wgt ${PROJECT_BINARY_DIR}/package/root +) diff --git a/src/afb_binding_api.cpp b/src/afb_binding_api.cpp new file mode 100644 index 0000000..9311700 --- /dev/null +++ b/src/afb_binding_api.cpp @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "app.hpp" +#include "json_helper.hpp" + +#include + +#include + +using json = nlohmann::json; + +#include + +namespace wm { +// _ _ _ _ _ _ _ +// | |__ (_)_ __ __| (_)_ __ __ _ __ _ _ __ (_) (_)_ __ ___ _ __ | | +// | '_ \| | '_ \ / _` | | '_ \ / _` | / _` | '_ \| | | | '_ ` _ \| '_ \| | +// | |_) | | | | | (_| | | | | | (_| | | (_| | |_) | | | | | | | | | |_) | | +// |_.__/|_|_| |_|\__,_|_|_| |_|\__, |___\__,_| .__/|_| |_|_| |_| |_| .__/|_| +// |___/_____| |_| |_| +binding_api::result_type binding_api::requestsurface( + char const *drawing_name) { + auto r = this->app->api_request_surface(drawing_name); + if (r.is_err()) { + return Err(r.unwrap_err()); + } + return Ok(json_object_new_int(r.unwrap())); +} + +binding_api::result_type binding_api::activatesurface( + char const *drawing_name) { + logdebug("%s drawing_name %s", __func__, drawing_name); + auto r = this->app->api_activate_surface(drawing_name); + if (r != nullptr) { + logdebug("%s failed with error: %s", __func__, r); + return Err(r); + } + return Ok(json_object_new_object()); +} + +binding_api::result_type binding_api::deactivatesurface(char const* drawing_name) { + logdebug("%s drawing_name %s", __func__, drawing_name); + auto r = this->app->api_deactivate_surface(drawing_name); + if (r != nullptr) { + logdebug("%s failed with error: %s", __func__, r); + return Err(r); + } + return Ok(json_object_new_object()); +} + +binding_api::result_type binding_api::enddraw(char const* drawing_name) { + logdebug("%s drawing_name %s", __func__, drawing_name); + auto r = this->app->api_enddraw(drawing_name); + if (r != nullptr) { + logdebug("%s failed with error: %s", __func__, r); + return Err(r); + } + return Ok(json_object_new_object()); +} + +binding_api::result_type binding_api::list_drawing_names() { + logdebug("%s", __func__); + json j = this->app->id_alloc.name2id; + return Ok(json_tokener_parse(j.dump().c_str())); +} + +binding_api::result_type binding_api::debug_layers() { + logdebug("%s", __func__); + return Ok(json_tokener_parse(this->app->layers.to_json().dump().c_str())); +} + +binding_api::result_type binding_api::debug_surfaces() { + logdebug("%s", __func__); + return Ok(to_json(this->app->controller->sprops)); +} + +binding_api::result_type binding_api::debug_status() { + logdebug("%s", __func__); + json_object *jr = json_object_new_object(); + json_object_object_add(jr, "surfaces", + to_json(this->app->controller->sprops)); + json_object_object_add(jr, "layers", to_json(this->app->controller->lprops)); + return Ok(jr); +} + +binding_api::result_type binding_api::debug_terminate() { + logdebug("%s", __func__); + if (getenv("WINMAN_DEBUG_TERMINATE") != nullptr) { + raise(SIGKILL); // XXX afb-daemon kills it's pgroup using TERM, which + // doesn't play well with perf + } + return Ok(json_object_new_object()); +} + +binding_api::result_type binding_api::ping() { + this->app->api_ping(); + return Ok(json_object_new_object()); +} + +} // namespace wm diff --git a/src/app.cpp b/src/app.cpp new file mode 100644 index 0000000..75df8d7 --- /dev/null +++ b/src/app.cpp @@ -0,0 +1,733 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "app.hpp" +#include "json_helper.hpp" +#include "layers.hpp" +#include "layout.hpp" +#include "util.hpp" +#include "wayland.hpp" + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace wm { + +namespace { + +using nlohmann::json; + +result file_to_json(char const *filename) { + std::ifstream i(filename); + if (i.fail()) { + return Err("Could not open config file"); + } + json j; + i >> j; + return Ok(j); +} + +struct result load_layer_map(char const *filename) { + logdebug("loading IDs from %s", filename); + + auto j = file_to_json(filename); + if (j.is_err()) { + return Err(j.unwrap_err()); + } + json jids = j.unwrap(); + + return to_layer_map(jids); +} + +} // namespace + +// _ _ _ _ +// ___| | __ _ ___ ___ / \ _ __ _ __ (_)_ __ ___ _ __ | | +// / __| |/ _` / __/ __| / _ \ | '_ \| '_ \ | | '_ ` _ \| '_ \| | +// | (__| | (_| \__ \__ \ / ___ \| |_) | |_) | | | | | | | | |_) | | +// \___|_|\__,_|___/___/ /_/ \_\ .__/| .__/ |_|_| |_| |_| .__/|_| +// |_| |_| |_| +App::App(wl::display *d) + : api{this}, + chooks{this}, + display{d}, + controller{}, + outputs(), + config(), + layers(), + id_alloc{}, + pending_events(false), + policy{} { + try { + { + auto l = load_layer_map( + this->config.get_string("layers.json").value().c_str()); + if (l.is_ok()) { + this->layers = l.unwrap(); + } else { + logerror("%s", l.err().value()); + } + } + } catch (std::exception &e) { + logerror("Loading of configuration failed: %s", e.what()); + } +} + +int App::init() { + if (!this->display->ok()) { + return -1; + } + + if (this->layers.mapping.empty()) { + logerror("No surface -> layer mapping loaded"); + return -1; + } + + this->display->add_global_handler( + "wl_output", [this](wl_registry *r, uint32_t name, uint32_t v) { + this->outputs.emplace_back(std::make_unique(r, name, v)); + }); + + this->display->add_global_handler( + "ivi_controller", [this](wl_registry *r, uint32_t name, uint32_t v) { + this->controller = + std::make_unique(r, name, v); + + // Init controller hooks + this->controller->chooks = &this->chooks; + + // XXX: This protocol needs the output, so lets just add our mapping + // here... + this->controller->add_proxy_to_id_mapping( + this->outputs.back()->proxy.get(), + wl_proxy_get_id(reinterpret_cast( + this->outputs.back()->proxy.get()))); + }); + + // First level objects + this->display->roundtrip(); + // Second level objects + this->display->roundtrip(); + // Third level objects + this->display->roundtrip(); + + return init_layers(); +} + +int App::dispatch_events() { + if (this->dispatch_events() == 0) { + return 0; + } + + int ret = this->display->dispatch(); + if (ret == -1) { + logerror("wl_display_dipatch() returned error %d", + this->display->get_error()); + return -1; + } + this->display->flush(); + + return 0; +} + +int App::dispatch_pending_events() { + if (this->pop_pending_events()) { + this->display->dispatch_pending(); + return 0; +} + return -1; +} + +bool App::pop_pending_events() { + bool x{true}; + return this->pending_events.compare_exchange_strong( + x, false, std::memory_order_consume); +} + +void App::set_pending_events() { + this->pending_events.store(true, std::memory_order_release); +} + +optional App::lookup_id(char const *name) { + return this->id_alloc.lookup(std::string(name)); +} +optional App::lookup_name(int id) { + return this->id_alloc.lookup(id); +} + +// _ _ _ _ _ ____ +// (_)_ __ (_) |_ | | __ _ _ _ ___ _ _| |_ / /\ \ +// | | '_ \| | __| | |/ _` | | | |/ _ \| | | | __| | | | +// | | | | | | |_ | | (_| | |_| | (_) | |_| | |_| | | | +// |_|_| |_|_|\__|___|_|\__,_|\__, |\___/ \__,_|\__| | | | +// |_____| |___/ \_\/_/ +int App::init_layers() { + if (!this->controller) { + logerror("ivi_controller global not available"); + return -1; + } + + if (this->outputs.empty()) { + logerror("no output was set up!"); + return -1; + } + + auto &c = this->controller; + + auto &o = this->outputs.front(); + auto &s = c->screens.begin()->second; + auto &layers = c->layers; + + // XXX: Write output dimensions to ivi controller... + c->output_size = genivi::size{uint32_t(o->width), uint32_t(o->height)}; + + // Clear scene + layers.clear(); + + // Clear screen + s->clear(); + + // Quick and dirty setup of layers + // XXX: This likely needs to be sorted by order (note, we don't (yet?) + // do any zorder arrangement). + for (auto const &i : this->layers.mapping) { + c->layer_create(i.second.layer_id, o->width, o->height); + auto &l = layers[i.second.layer_id]; + l->set_destination_rectangle(0, 0, o->width, o->height); + l->set_visibility(1); + logdebug("Setting up layer %s (%d) for surface role match \"%s\"", + i.second.name.c_str(), i.second.layer_id, i.second.role.c_str()); + } + + // Add layers to screen (XXX: are they sorted correctly?) + s->set_render_order(this->layers.layers); + + this->layout_commit(); + + return 0; +} + +void App::surface_set_layout(int surface_id, optional sub_surface_id) { + if (!this->controller->surface_exists(surface_id)) { + logerror("Surface %d does not exist", surface_id); + return; + } + + auto o_layer_id = this->layers.get_layer_id(surface_id); + + if (!o_layer_id) { + logerror("Surface %d is not associated with any layer!", surface_id); + return; + } + + uint32_t layer_id = *o_layer_id; + + auto const &layer = this->layers.get_layer(layer_id); + auto rect = layer.value().rect; + auto &s = this->controller->surfaces[surface_id]; + + int x = rect.x; + int y = rect.y; + int w = rect.w; + int h = rect.h; + + // less-than-0 values refer to MAX + 1 - $VALUE + // e.g. MAX is either screen width or height + if (w < 0) { + w = this->controller->output_size.w + 1 + w; + } + if (h < 0) { + h = this->controller->output_size.h + 1 + h; + } + + if (sub_surface_id) { + if (o_layer_id != this->layers.get_layer_id(*sub_surface_id)) { + logerror( + "surface_set_layout: layers of surfaces (%d and %d) don't match!", + surface_id, *sub_surface_id); + return; + } + + int x_off = 0; + int y_off = 0; + + // split along major axis + if (w > h) { + w /= 2; + x_off = w; + } else { + h /= 2; + y_off = h; + } + + auto &ss = this->controller->surfaces[*sub_surface_id]; + + logdebug("surface_set_layout for sub surface %u on layer %u", + *sub_surface_id, layer_id); + + // configure surface to wxh dimensions + ss->set_configuration(w, h); + // set source reactangle, even if we should not need to set it. + ss->set_source_rectangle(0, 0, w, h); + // set destination to the display rectangle + ss->set_destination_rectangle(x + x_off, y + y_off, w, h); + } + + logdebug("surface_set_layout for surface %u on layer %u", surface_id, + layer_id); + + // configure surface to wxh dimensions + s->set_configuration(w, h); + // set source reactangle, even if we should not need to set it. + s->set_source_rectangle(0, 0, w, h); + + // set destination to the display rectangle + s->set_destination_rectangle(x, y, w, h); + + logdebug("Surface %u now on layer %u with rect { %d, %d, %d, %d }", + surface_id, layer_id, x, y, w, h); +} + +void App::layout_commit() { + this->controller->commit_changes(); + this->display->flush(); +} + +char const *App::api_activate_surface(char const *drawing_name) { + ST(); + auto const &surface_id = this->lookup_id(drawing_name); + + if (!surface_id) { + return "Surface does not exist"; + } + + if (!this->controller->surface_exists(*surface_id)) { + return "Surface does not exist in controller!"; + } + + auto layer_id = this->layers.get_layer_id(*surface_id); + + if (!layer_id) { + return "Surface is not on any layer!"; + } + + auto o_state = *this->layers.get_layout_state(*surface_id); + + if (o_state == nullptr) { + return "Could not find layer for surface"; +} + + struct LayoutState &state = *o_state; + + // disable layers that are above our current layer + for (auto const &l : this->layers.mapping) { + if (l.second.layer_id <= *layer_id) { + continue; + } + + bool flush = false; + if (l.second.state.main != -1) { + this->deactivate(l.second.state.main); + l.second.state.main = -1; + flush = true; + } + + if (l.second.state.sub != -1) { + this->deactivate(l.second.state.sub); + l.second.state.sub = -1; + flush = true; + } + + if (flush) { + this->layout_commit(); + } + } + + if (state.main == *surface_id || state.sub == *surface_id) { + return "Surface already active"; + } + + if (state.main == -1) { + this->try_layout( + state, LayoutState{*surface_id}, [&] (LayoutState const &nl) { + this->surface_set_layout(*surface_id); + // XXX do we need to activate after enddraw? + state = nl; + this->emit_syncdraw(drawing_name); + this->enqueue_flushdraw(state.main); + }); + } else { + bool can_split = this->can_split(state, *surface_id); + + if (can_split) { + this->try_layout( + state, + LayoutState{state.main, *surface_id}, + [&] (LayoutState const &nl) { + std::string main = + std::move(*this->lookup_name(state.main)); + + this->surface_set_layout(state.main, surface_id); + if (state.sub != -1) { + this->deactivate(state.sub); + } + state = nl; + + this->emit_syncdraw(drawing_name); + this->emit_syncdraw(main.c_str()); + this->enqueue_flushdraw(state.main); + this->enqueue_flushdraw(state.sub); + }); + } else { + this->try_layout( + state, LayoutState{*surface_id}, [&] (LayoutState const &nl) { + this->surface_set_layout(*surface_id); + this->deactivate(state.main); + if (state.sub != -1) { + this->deactivate(state.sub); + } + state = nl; + + this->emit_syncdraw(drawing_name); + this->enqueue_flushdraw(state.main); + }); + } + } + + // no error + return nullptr; +} + +char const *App::api_deactivate_surface(char const *drawing_name) { + ST(); + auto const &surface_id = this->lookup_id(drawing_name); + + if (!surface_id) { + return "Surface does not exist"; + } + + if (*surface_id == this->layers.main_surface) { + return "Cannot deactivate main_surface"; + } + + auto o_state = *this->layers.get_layout_state(*surface_id); + + if (o_state == nullptr) { + return "Could not find layer for surface"; + } + + struct LayoutState &state = *o_state; + + if (state.main == -1) { + return "No surface active"; + } + + // XXX: check against main_surface, main_surface_name is the configuration + // item. + if (*surface_id == this->layers.main_surface) { + logdebug("Refusing to deactivate main_surface %d", *surface_id); + return nullptr; + } + + if (state.main == *surface_id) { + if (state.sub != -1) { + this->try_layout( + state, LayoutState{state.sub, -1}, [&] (LayoutState const &nl) { + std::string sub = std::move(*this->lookup_name(state.sub)); + + this->deactivate(*surface_id); + this->surface_set_layout(state.sub); + state = nl; + + this->layout_commit(); + this->emit_syncdraw(sub.c_str()); + this->enqueue_flushdraw(state.sub); + }); + } else { + this->try_layout(state, LayoutState{-1, -1}, [&] (LayoutState const &nl) { + this->deactivate(*surface_id); + state = nl; + this->layout_commit(); + }); + } + } else if (state.sub == *surface_id) { + this->try_layout( + state, LayoutState{state.main, -1}, [&] (LayoutState const &nl) { + std::string main = std::move(*this->lookup_name(state.main)); + + this->deactivate(*surface_id); + this->surface_set_layout(state.main); + state = nl; + + this->layout_commit(); + this->emit_syncdraw(main.c_str()); + this->enqueue_flushdraw(state.main); + }); + } else { + return "Surface is not active"; + } + + return nullptr; +} + +void App::enqueue_flushdraw(int surface_id) { + this->check_flushdraw(surface_id); + logdebug("Enqueuing EndDraw for surface_id %d", surface_id); + this->pending_end_draw.push_back(surface_id); +} + +void App::check_flushdraw(int surface_id) { + auto i = std::find(std::begin(this->pending_end_draw), + std::end(this->pending_end_draw), surface_id); + if (i != std::end(this->pending_end_draw)) { + auto n = this->lookup_name(surface_id); + logerror("Application %s (%d) has pending EndDraw call(s)!", + n ? n->c_str() : "unknown-name", surface_id); + std::swap(this->pending_end_draw[std::distance( + std::begin(this->pending_end_draw), i)], + this->pending_end_draw.back()); + this->pending_end_draw.resize(this->pending_end_draw.size() - 1); + } +} + +char const *App::api_enddraw(char const *drawing_name) { + for (unsigned i = 0, iend = this->pending_end_draw.size(); i < iend; i++) { + auto n = this->lookup_name(this->pending_end_draw[i]); + if (n && *n == drawing_name) { + std::swap(this->pending_end_draw[i], this->pending_end_draw[iend - 1]); + this->pending_end_draw.resize(iend - 1); + // XXX: Please tell the compositor to thaw the surface... + this->activate(this->pending_end_draw[i]); + this->layout_commit(); + this->emit_flushdraw(drawing_name); + return nullptr; + } + } + return "No EndDraw pending for surface"; +} + +void App::api_ping() { this->dispatch_pending_events(); } + +// _ _ _____ _ +// _ __ _ __ _____ _(_) ___ __| | | ____|_ _____ _ __ | |_ ___ +// | '_ \| '__/ _ \ \/ / |/ _ \/ _` | | _| \ \ / / _ \ '_ \| __/ __| +// | |_) | | | (_) > <| | __/ (_| | | |___ \ V / __/ | | | |_\__ \ +// | .__/|_| \___/_/\_\_|\___|\__,_| |_____| \_/ \___|_| |_|\__|___/ +// |_| +void App::surface_created(uint32_t surface_id) { + auto layer_id = this->layers.get_layer_id(surface_id); + if (!layer_id) { + logdebug("Newly created surfce %d is not associated with any layer!", + surface_id); + return; + } + + logdebug("surface_id is %u, layer_id is %u", surface_id, *layer_id); + + this->controller->layers[*layer_id]->add_surface( + this->controller->surfaces[surface_id].get()); + + // activate the main_surface right away + /*if (surface_id == static_cast(this->layers.main_surface)) { + logdebug("Activating main_surface (%d)", surface_id); + + this->api_activate_surface( + this->lookup_name(surface_id).value_or("unknown-name").c_str()); + }*/ +} + +void App::surface_removed(uint32_t surface_id) { + logdebug("surface_id is %u", surface_id); + + // We cannot normally deactivate the main_surface, so be explicit + // about it: + if (int(surface_id) == this->layers.main_surface) { + this->deactivate_main_surface(); + } else { + auto drawing_name = this->lookup_name(surface_id); + if (drawing_name) { + this->api_deactivate_surface(drawing_name->c_str()); + } + } + + this->id_alloc.remove_id(surface_id); + this->layers.remove_surface(surface_id); +} + +void App::emit_activated(char const *label) { + this->api.send_event("active", label); +} + +void App::emit_deactivated(char const *label) { + this->api.send_event("inactive", label); +} + +void App::emit_syncdraw(char const *label) { + this->api.send_event("syncdraw", label); +} + +void App::emit_flushdraw(char const *label) { + this->api.send_event("flushdraw", label); +} + +void App::emit_visible(char const *label, bool is_visible) { + this->api.send_event(is_visible ? "visible" : "invisible", label); +} + +void App::emit_invisible(char const *label) { + return emit_visible(label, false); +} + +void App::emit_visible(char const *label) { return emit_visible(label, true); } + +result App::api_request_surface(char const *drawing_name) { + auto lid = this->layers.get_layer_id(std::string(drawing_name)); + if (!lid) { + // XXX: to we need to put these applications on the App layer? + return Err("Drawing name does not match any role"); + } + + auto rname = this->lookup_id(drawing_name); + if (!rname) { + // name does not exist yet, allocate surface id... + auto id = int(this->id_alloc.generate_id(drawing_name)); + this->layers.add_surface(id, *lid); + + // XXX: we set the main_surface[_name] here and now, + // not sure if we want this, but it worked so far. + if (!this->layers.main_surface_name.empty() && + this->layers.main_surface_name == drawing_name) { + this->layers.main_surface = id; + logdebug("Set main_surface id to %u", id); + } + + return Ok(id); + } + + // Check currently registered drawing names if it is already there. + return Err("Surface already present"); +} + +void App::activate(int id) { + auto ip = this->controller->sprops.find(id); + if (ip != this->controller->sprops.end() && ip->second.visibility == 0) { + this->controller->surfaces[id]->set_visibility(1); + char const *label = + this->lookup_name(id).value_or("unknown-name").c_str(); + this->emit_activated(label); + this->emit_visible(label); + } +} + +void App::deactivate(int id) { + auto ip = this->controller->sprops.find(id); + if (ip != this->controller->sprops.end() && ip->second.visibility != 0) { + this->controller->surfaces[id]->set_visibility(0); + char const *label = + this->lookup_name(id).value_or("unknown-name").c_str(); + this->emit_deactivated(label); + this->emit_invisible(label); + } +} + +void App::deactivate_main_surface() { + this->layers.main_surface = -1; + this->api_deactivate_surface(this->layers.main_surface_name.c_str()); +} + +bool App::can_split(struct LayoutState const &state, int new_id) { + if (state.main != -1 && state.main != new_id) { + auto new_id_layer = this->layers.get_layer_id(new_id).value(); + auto current_id_layer = this->layers.get_layer_id(state.main).value(); + + // surfaces are on separate layers, don't bother. + if (new_id_layer != current_id_layer) { + return false; +} + + std::string const &new_id_str = this->lookup_name(new_id).value(); + std::string const &cur_id_str = this->lookup_name(state.main).value(); + + auto const &layer = this->layers.get_layer(new_id_layer); + + logdebug("layer info name: %s", layer->name.c_str()); + + if (layer->layouts.empty()) { + return false; +} + + for (auto i = layer->layouts.cbegin(); i != layer->layouts.cend(); i++) { + logdebug("%d main_match '%s'", new_id_layer, i->main_match.c_str()); + auto rem = std::regex(i->main_match); + if (std::regex_match(cur_id_str, rem)) { + // build the second one only if the first already matched + logdebug("%d sub_match '%s'", new_id_layer, i->sub_match.c_str()); + auto res = std::regex(i->sub_match); + if (std::regex_match(new_id_str, res)) { + logdebug("layout matched!"); + return true; +} + } + } + } + + return false; +} + +void App::try_layout(struct LayoutState & /*state*/, + struct LayoutState const &new_layout, + std::function apply) { + if (this->policy.layout_is_valid(new_layout)) { + apply(new_layout); + } +} + +// _ _ _ _ _ +// ___ ___ _ __ | |_ _ __ ___ | | | ___ _ __ | |__ ___ ___ | | _____ +// / __/ _ \| '_ \| __| '__/ _ \| | |/ _ \ '__|| '_ \ / _ \ / _ \| |/ / __| +// | (_| (_) | | | | |_| | | (_) | | | __/ | | | | | (_) | (_) | <\__ \ +// \___\___/|_| |_|\__|_| \___/|_|_|\___|_|___|_| |_|\___/ \___/|_|\_\___/ +// |_____| +void controller_hooks::surface_created(uint32_t surface_id) { + this->app->surface_created(surface_id); +} + +void controller_hooks::surface_removed(uint32_t surface_id) { + this->app->surface_removed(surface_id); +} + +void controller_hooks::surface_visibility(uint32_t /*surface_id*/, + uint32_t /*v*/) {} + +void controller_hooks::surface_destination_rectangle(uint32_t /*surface_id*/, + uint32_t /*x*/, + uint32_t /*y*/, + uint32_t /*w*/, + uint32_t /*h*/) {} + +} // namespace wm diff --git a/src/app.hpp b/src/app.hpp new file mode 100644 index 0000000..9424d9f --- /dev/null +++ b/src/app.hpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_APP_HPP +#define TMCAGLWM_APP_HPP + +#include + +#include +#include +#include +#include +#include + +#include "afb_binding_api.hpp" +#include "config.hpp" +#include "controller_hooks.hpp" +#include "layers.hpp" +#include "layout.hpp" +#include "policy.hpp" +#include "result.hpp" +#include "wayland.hpp" + +namespace wl { +struct display; +} + +namespace genivi { +struct controller; +} + +namespace wm { + +using std::experimental::optional; + +struct id_allocator { + unsigned next = 1; + + // Surfaces that where requested but not yet created + std::unordered_map id2name; + // std::unordered_set pending_surfaces; + std::unordered_map name2id; + + id_allocator(id_allocator const &) = delete; + id_allocator(id_allocator &&) = delete; + id_allocator &operator=(id_allocator const &); + id_allocator &operator=(id_allocator &&) = delete; + + // Insert and return a new ID + unsigned generate_id(std::string const &name) { + unsigned sid = this->next++; + this->id2name[sid] = name; + // this->pending_surfaces.insert({sid}); + this->name2id[name] = sid; + logdebug("allocated new id %u with name %s", sid, name.c_str()); + return sid; + } + + // Lookup by ID or by name + optional lookup(std::string const &name) const { + auto i = this->name2id.find(name); + return i == this->name2id.end() ? nullopt : optional(i->second); + } + + optional lookup(unsigned id) const { + auto i = this->id2name.find(id); + return i == this->id2name.end() ? nullopt + : optional(i->second); + } + + // Remove a surface id and name + // I don't think I will need this, do I? + void remove_id(std::string const &name) { + auto i = this->name2id.find(name); + if (i != this->name2id.end()) { + this->id2name.erase(i->second); + this->name2id.erase(i); + } + } + + void remove_id(unsigned id) { + auto i = this->id2name.find(id); + if (i != this->id2name.end()) { + this->name2id.erase(i->second); + this->id2name.erase(i); + } + } +}; + +struct App { + struct binding_api api; + struct controller_hooks chooks; + + // This is the one thing, we do not own. + struct wl::display *display; + + std::unique_ptr controller; + std::vector> outputs; + + struct config config; + + // track current layouts separately + layer_map layers; + + // ID allocation and proxy methods for lookup + struct id_allocator id_alloc; + + // Set by AFB API when wayland events need to be dispatched + std::atomic pending_events; + + std::vector pending_end_draw; + + Policy policy; + + explicit App(wl::display *d); + ~App() = default; + + App(App const &) = delete; + App &operator=(App const &) = delete; + App(App &&) = delete; + App &operator=(App &&) = delete; + + int init(); + + int dispatch_events(); + int dispatch_pending_events(); + + void set_pending_events(); + + result api_request_surface(char const *drawing_name); + char const *api_activate_surface(char const *drawing_name); + char const *api_deactivate_surface(char const *drawing_name); + char const *api_enddraw(char const *drawing_name); + void api_ping(); + + // Events from the compositor we are interested in + void surface_created(uint32_t surface_id); + void surface_removed(uint32_t surface_id); + +private: + optional lookup_id(char const *name); + optional lookup_name(int id); + + bool pop_pending_events(); + + void enqueue_flushdraw(int surface_id); + void check_flushdraw(int surface_id); + + int init_layers(); + + void surface_set_layout(int surface_id, optional sub_surface_id = nullopt); + void layout_commit(); + + // TMC WM Events to clients + void emit_activated(char const *label); + void emit_deactivated(char const *label); + void emit_syncdraw(char const *label); + void emit_flushdraw(char const *label); + void emit_visible(char const *label, bool is_visible); + void emit_invisible(char const *label); + void emit_visible(char const *label); + + void activate(int id); + void deactivate(int id); + void deactivate_main_surface(); + + bool can_split(struct LayoutState const &state, int new_id); + void try_layout(struct LayoutState &state, + struct LayoutState const &new_layout, + std::function apply); +}; + +} // namespace wm + +#endif // TMCAGLWM_APP_HPP diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..d5a549a --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "config.hpp" + +namespace wm { + +config::config() : cfg() { + // Supply default values for these... + this->cfg["layers.json"] = getenv("LAYERS_JSON") ?: "/etc/windowmanager/layers.json"; +} + +} // namespace wm diff --git a/src/config.hpp b/src/config.hpp new file mode 100644 index 0000000..d470b85 --- /dev/null +++ b/src/config.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_CONFIG_HPP +#define TMCAGLWM_CONFIG_HPP + +#include +#include + +namespace wm { + +using std::experimental::optional; +using std::experimental::nullopt; + +struct config { + typedef std::map map; + + map cfg; + + config(); + + optional get_string(char const *s) { + auto i = this->cfg.find(s); + return i != this->cfg.end() ? optional(i->second) : nullopt; + } + + optional get_int(char const *s) { + auto i = this->cfg.find(s); + return i != this->cfg.end() ? optional(std::stoi(i->second)) + : nullopt; + } +}; + +} // namespace wm + +#endif // TMCAGLWM_CONFIG_HPP diff --git a/src/controller_hooks.hpp b/src/controller_hooks.hpp new file mode 100644 index 0000000..31962ee --- /dev/null +++ b/src/controller_hooks.hpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_CONTROLLER_HOOKS_HPP +#define TMCAGLWM_CONTROLLER_HOOKS_HPP + +#include + +#include + +namespace wm { + +struct App; + +struct controller_hooks { + struct App *app; + + void surface_created(uint32_t surface_id); + + void surface_removed(uint32_t surface_id); + void surface_visibility(uint32_t surface_id, uint32_t v); + void surface_destination_rectangle(uint32_t surface_id, uint32_t x, uint32_t y, uint32_t w, uint32_t h); +}; + +} // namespace wm + +#endif // TMCAGLWM_CONTROLLER_HOOKS_HPP diff --git a/src/json_helper.cpp b/src/json_helper.cpp new file mode 100644 index 0000000..179c8cc --- /dev/null +++ b/src/json_helper.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "json_helper.hpp" + +#include + +json_object *to_json(genivi::surface_properties const &s) { + // auto j = json::object({ + auto j = json_object_new_object(); + + // {"id", s.id}, + json_object_object_add(j, "id", json_object_new_int(s.id)); + + // {"size", {{"width", s.size.w}, {"height", s.size.h}}}, + auto jsize = json_object_new_object(); + json_object_object_add(jsize, "width", json_object_new_int(s.size.w)); + json_object_object_add(jsize, "height", json_object_new_int(s.size.h)); + json_object_object_add(j, "size", jsize); + + // {"dst", + // {{"width", s.dst_rect.w}, + // {"height", s.dst_rect.h}, + // {"x", s.dst_rect.x}, + // {"y", s.dst_rect.y}}}, + auto jdst = json_object_new_object(); + json_object_object_add(jdst, "width", json_object_new_int(s.dst_rect.w)); + json_object_object_add(jdst, "height", json_object_new_int(s.dst_rect.h)); + json_object_object_add(jdst, "x", json_object_new_int(s.dst_rect.x)); + json_object_object_add(jdst, "y", json_object_new_int(s.dst_rect.y)); + json_object_object_add(j, "dst", jdst); + + // {"src", + // {{"width", s.src_rect.w}, + // {"height", s.src_rect.h}, + // {"x", s.src_rect.x}, + // {"y", s.src_rect.y}}}, + auto jsrc = json_object_new_object(); + json_object_object_add(jsrc, "width", json_object_new_int(s.src_rect.w)); + json_object_object_add(jsrc, "height", json_object_new_int(s.src_rect.h)); + json_object_object_add(jsrc, "x", json_object_new_int(s.src_rect.x)); + json_object_object_add(jsrc, "y", json_object_new_int(s.src_rect.y)); + json_object_object_add(j, "src", jsrc); + + // {"visibility", s.visibility}, + json_object_object_add( + j, "visibility", + json_object_new_boolean(static_cast(s.visibility == 1))); + + // {"opacity", s.opacity}, + json_object_object_add(j, "opacity", json_object_new_double(s.opacity)); + + // {"orientation", s.orientation}, + json_object_object_add(j, "orientation", json_object_new_int(s.orientation)); + + // }); + return j; +} + +json_object *to_json(genivi::screen const *s) { + auto o = json_object_new_object(); + json_object_object_add(o, "id", json_object_new_int(s->id)); + return o; +} + +template +json_object *to_json_(T const &s) { + auto a = json_object_new_array(); + + if (!s.empty()) { + for (auto const &i : s) { + json_object_array_add(a, to_json(i.second)); + } + } + + return a; +} + +json_object *to_json(genivi::controller::props_map const &s) { + return to_json_(s); +} + +json_object *to_json(std::vector const &v) { + auto a = json_object_new_array(); + for (const auto i : v) { + json_object_array_add(a, json_object_new_int(i)); + } + return a; +} diff --git a/src/json_helper.hpp b/src/json_helper.hpp new file mode 100644 index 0000000..15d72c3 --- /dev/null +++ b/src/json_helper.hpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_JSON_HELPER_HPP +#define TMCAGLWM_JSON_HELPER_HPP + +#include "result.hpp" +#include "wayland.hpp" +#include + +struct json_object; + +json_object *to_json(genivi::screen const *s); +json_object *to_json(genivi::controller::props_map const &s); +json_object *to_json(std::vector const &v); + +#endif // TMCAGLWM_JSON_HELPER_HPP diff --git a/src/layers.cpp b/src/layers.cpp new file mode 100644 index 0000000..9219766 --- /dev/null +++ b/src/layers.cpp @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 +#include + +#include "json_helper.hpp" +#include "layers.hpp" +#include "util.hpp" + +namespace wm { + +using json = nlohmann::json; + +layer::layer(nlohmann::json const &j) { + this->role = j["role"]; + this->name = j["name"]; + this->layer_id = j["layer_id"]; + this->rect = genivi::full_rect; + if (j["area"]["type"] == "rect") { + auto jr = j["area"]["rect"]; + this->rect = genivi::rect{ + jr["width"], jr["height"], jr["x"], jr["y"], + }; + } + auto split_layouts = j.find("split_layouts"); + if (split_layouts != j.end()) { + auto &sls = j["split_layouts"]; + // this->layouts.reserve(sls.size()); + std::transform(std::cbegin(sls), std::cend(sls), + std::back_inserter(this->layouts), [this](json const &sl) { + struct split_layout l { + sl["name"], sl["main_match"], sl["sub_match"] }; + logdebug( + "layer %d add split_layout \"%s\" (main: \"%s\") (sub: " + "\"%s\")", this->layer_id, + l.name.c_str(), l.main_match.c_str(), + l.sub_match.c_str()); + return l; + }); + } +} + +struct result to_layer_map(nlohmann::json const &j) { + try { + layer_map stl{}; + auto m = j["mappings"]; + + std::transform(std::cbegin(m), std::cend(m), + std::inserter(stl.mapping, stl.mapping.end()), + [](nlohmann::json const &j) { + return std::pair( + j.value("layer_id", -1), layer(j)); + }); + + // XXX: add sanity checks here? + // * check for double IDs + // * check for double names/roles + + stl.layers.reserve(m.size()); + std::transform(std::cbegin(stl.mapping), std::cend(stl.mapping), + std::back_inserter(stl.layers), + [&stl](std::pair const &k) { + stl.roles.emplace_back( + std::make_pair(k.second.role, k.second.layer_id)); + return unsigned(k.second.layer_id); + }); + + std::sort(stl.layers.begin(), stl.layers.end()); + + for (auto i : stl.mapping) { + if (i.second.name.empty()) { + return Err("Found mapping w/o name"); + } + if (i.second.layer_id == -1) { + return Err("Found invalid/unset IDs in mapping"); + } + } + + auto msi = j.find("main_surface"); + if (msi != j.end()) { + stl.main_surface_name = msi->value("surface_role", ""); + stl.main_surface = -1; + } + + return Ok(stl); + } catch (std::exception &e) { + return Err(e.what()); + } +} + +optional layer_map::get_layer_id(int surface_id) { + auto i = this->surfaces.find(surface_id); + if (i != this->surfaces.end()) { + return optional(i->second); + } + return nullopt; +} + +optional layer_map::get_layer_id(std::string const &role) { + for (auto const &r : this->roles) { + auto re = std::regex(r.first); + if (std::regex_match(role, re)) { + logdebug("role %s matches layer %d", role.c_str(), r.second); + return optional(r.second); + } + } + logdebug("role %s does NOT match any layer", role.c_str()); + return nullopt; +} + +json layer::to_json() const { + auto is_full = this->rect == genivi::full_rect; + + json r{}; + if (is_full) { + r = {{"type", "full"}}; + } else { + r = {{"type", "rect"}, + {"rect", + {{"x", this->rect.x}, + {"y", this->rect.y}, + {"width", this->rect.w}, + {"height", this->rect.h}}}}; + } + + return { + {"name", this->name}, {"role", this->role}, + {"layer_id", this->layer_id}, {"area", r}, + }; +} + +json layer_map::to_json() const { + json j{}; + for (auto const &i : this->mapping) { + j.push_back(i.second.to_json()); + } + return j; +} + +} // namespace wm diff --git a/src/layers.hpp b/src/layers.hpp new file mode 100644 index 0000000..0603d24 --- /dev/null +++ b/src/layers.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_LAYERS_H +#define TMCAGLWM_LAYERS_H + +#include + +#include +#include +#include + +#include "layout.hpp" +#include "result.hpp" +#include "wayland.hpp" + +namespace wm { + +struct split_layout { + std::string name; + std::string main_match; + std::string sub_match; +}; + +struct layer { + using json = nlohmann::json; + + // A more or less descriptive name? + std::string name = ""; + // The actual layer ID + int layer_id = -1; + // The rectangular region surfaces are allowed to draw on + // this layer, note however, width and hieght of the rect + // can be negative, in which case they specify that + // the actual value is computed using MAX + 1 - w + // That is; allow us to specify dimensions dependent on + // e.g. screen dimension, w/o knowing the actual screen size. + genivi::rect rect; + // Specify a role prefix for surfaces that should be + // put on this layer. + std::string role; + // XXX perhaps a zorder is needed here? + std::vector layouts; + // XXX need to change the way we store these things... + mutable struct LayoutState state; + + explicit layer(nlohmann::json const &j); + + json to_json() const; +}; + +struct layer_map { + using json = nlohmann::json; + + using storage_type = std::map; + using layers_type = std::vector; + using role_to_layer_map = std::vector>; + using addsurf_layer_map = std::map; + + // XXX: we also will need a layer_id to layer map, perhaps + // make this the primary map, and the surface_id->layer a + // secondary map. + + storage_type mapping; // map surface_id to layer + layers_type layers; // the actual layer IDs we have + int main_surface; + std::string main_surface_name; + role_to_layer_map roles; + addsurf_layer_map surfaces; // additional surfaces on layers + + optional get_layer_id(int surface_id); + optional get_layer_id(std::string const &role); + optional get_layout_state(int surface_id) { + int layer_id = *this->get_layer_id(surface_id); + auto i = this->mapping.find(layer_id); + return i == this->mapping.end() + ? nullopt + : optional(&i->second.state); + } + optional get_layer(int layer_id) { + auto i = this->mapping.find(layer_id); + return i == this->mapping.end() ? nullopt + : optional(i->second); + } + + layers_type::size_type get_layers_count() const { + return this->layers.size(); + } + + void add_surface(int surface_id, int layer_id) { + this->surfaces[surface_id] = layer_id; + } + + void remove_surface(int surface_id) { + this->surfaces.erase(surface_id); + } + + json to_json() const; +}; + +struct result to_layer_map(nlohmann::json const &j); + +} // namespace wm + +#endif // TMCAGLWM_LAYERS_H diff --git a/src/layout.cpp b/src/layout.cpp new file mode 100644 index 0000000..1589ee9 --- /dev/null +++ b/src/layout.cpp @@ -0,0 +1,17 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "layout.hpp" diff --git a/src/layout.hpp b/src/layout.hpp new file mode 100644 index 0000000..b7a3c28 --- /dev/null +++ b/src/layout.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_LAYOUT_HPP +#define TMCAGLWM_LAYOUT_HPP + +#include +#include + +#include "result.hpp" +#include "wayland.hpp" + +namespace wm { + +struct LayoutState { + int main{-1}; + int sub{-1}; + + bool operator==(const LayoutState &b) const { + return main == b.main && sub == b.sub; + } + + bool operator!=(const LayoutState &b) const { + return !(*this == b); + } +}; + +} // namespace wm + +#endif // TMCAGLWM_LAYOUT_HPP diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..a1e3db8 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 +#include "app.hpp" +#include "json_helper.hpp" +#include "util.hpp" +#include "wayland.hpp" + +#include +#include + +#include + +extern "C" { +#include +#include +} + +namespace { + +struct afb_instance { + std::unique_ptr display; + wm::App app; + + afb_instance() : display{new wl::display}, app{this->display.get()} {} + + int init(); +}; + +struct afb_instance *g_afb_instance; + +int afb_instance::init() { + return this->app.init(); +} + +int display_event_callback(sd_event_source *evs, int /*fd*/, uint32_t events, + void * /*data*/) { + ST(); + + if ((events & EPOLLHUP) != 0) { + logerror("The compositor hung up, dying now."); + delete g_afb_instance; + g_afb_instance = nullptr; + goto error; + } + + if ((events & EPOLLIN) != 0u) { + { + STN(display_read_events); + g_afb_instance->app.display->read_events(); + g_afb_instance->app.set_pending_events(); + } + { + // We want do dispatch pending wayland events from within + // the API context + STN(winman_ping_api_call); + afb_service_call("windowmanager", "ping", json_object_new_object(), + [](void *c, int st, json_object *j) { + STN(winman_ping_api_call_return); + }, + nullptr); + } + } + + return 0; + +error: + sd_event_source_unref(evs); + if (getenv("WINMAN_EXIT_ON_HANGUP") != nullptr) { + exit(1); +} + return -1; +} + +// _ _ _ _ _ _ _ ____ +// | |__ (_)_ __ __| (_)_ __ __ _ (_)_ __ (_) |_ / /\ \ +// | '_ \| | '_ \ / _` | | '_ \ / _` | | | '_ \| | __| | | | +// | |_) | | | | | (_| | | | | | (_| | | | | | | | |_| | | | +// |_.__/|_|_| |_|\__,_|_|_| |_|\__, |___|_|_| |_|_|\__| | | | +// |___/_____| \_\/_/ +int binding_init_() { + lognotice("WinMan ver. %s", WINMAN_VERSION_STRING); + + if (g_afb_instance != nullptr) { + logerror("Wayland context already initialized?"); + return 0; + } + + if (getenv("XDG_RUNTIME_DIR") == nullptr) { + logerror("Environment variable XDG_RUNTIME_DIR not set"); + goto error; + } + + { + // wait until wayland compositor starts up. + int cnt = 0; + g_afb_instance = new afb_instance; + while (!g_afb_instance->display->ok()) { + cnt++; + if (20 <= cnt) { + logerror("Could not connect to compositor"); + goto error; + } + logerror("Wait to start weston ..."); + sleep(1); + delete g_afb_instance; + g_afb_instance = new afb_instance; + } + } + + if (g_afb_instance->init() == -1) { + logerror("Could not connect to compositor"); + goto error; + } + + { + int ret = sd_event_add_io(afb_daemon_get_event_loop(), nullptr, + g_afb_instance->display->get_fd(), EPOLLIN, + display_event_callback, g_afb_instance); + if (ret < 0) { + logerror("Could not initialize afb_instance event handler: %d", -ret); + goto error; + } + } + + atexit([] { delete g_afb_instance; }); + + return 0; + +error: + delete g_afb_instance; + g_afb_instance = nullptr; + return -1; +} + +int binding_init() noexcept { + try { + return binding_init_(); + } catch (std::exception &e) { + logerror("Uncaught exception in binding_init(): %s", e.what()); + } + return -1; +} + +} // namespace + +#include "afb_binding_glue.inl" + +// XXX implement send_event right here... +namespace wm { +void binding_api::send_event(char const *evname, char const *label) { + logdebug("%s: %s(%s)", __func__, evname, label); + int ret = afb_daemon_broadcast_event(evname, json_object_new_string(label)); + if (ret != 0) { + logdebug("afb_event_broadcast failed: %m"); + } +} +} // namespace wm + +extern "C" const struct afb_binding_v2 afbBindingV2 = { + "windowmanager", nullptr, nullptr, windowmanager_verbs, nullptr, binding_init, nullptr, 0}; diff --git a/src/policy.hpp b/src/policy.hpp new file mode 100644 index 0000000..ed5d6ba --- /dev/null +++ b/src/policy.hpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_POLICY_HPP +#define TMCAGLWM_POLICY_HPP + +#include "layout.hpp" + +namespace wm { + +class Policy { +public: + bool layout_is_valid(LayoutState const & /* layout */) { + // We do not check for policy currently + logdebug("Policy check returns positive"); + return true; + } +}; + +} // namespace wm + +#endif //TMCAGLWM_POLICY_HPP diff --git a/src/redraw_fixer.cpp b/src/redraw_fixer.cpp new file mode 100644 index 0000000..94964af --- /dev/null +++ b/src/redraw_fixer.cpp @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "wayland.hpp" + +#include +#include +#include + +using namespace std::chrono_literals; + +// pretend we are a WM ... +namespace wm { + +struct App { + controller_hooks chooks; + std::unique_ptr display; + std::unique_ptr controller; + std::vector> outputs; + + App(); + void commit(); + void surface_visibility(uint32_t surface_id, uint32_t v); + void surface_destination_rectangle(uint32_t surface_id, uint32_t x, + uint32_t y, uint32_t w, uint32_t h); + void try_fix(uint32_t surface_id); +}; + +void controller_hooks::surface_created(uint32_t /*surface_id*/) {} + +void controller_hooks::surface_removed(uint32_t /*surface_id*/) {} + +void controller_hooks::surface_visibility(uint32_t surface_id, uint32_t v) { + this->app->surface_visibility(surface_id, v); +} + +void controller_hooks::surface_destination_rectangle(uint32_t surface_id, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) { + this->app->surface_destination_rectangle(surface_id, x, y, w, h); +} + +App::App() : chooks{this}, display{new wl::display}, controller{}, outputs{} { + // The same init, the WM does, at least we can reuse the wayland stuff + if (!this->display->ok()) { + return; + } + + this->display->add_global_handler( + "wl_output", [this](wl_registry *r, uint32_t name, uint32_t v) { + this->outputs.emplace_back(std::make_unique(r, name, v)); + }); + + this->display->add_global_handler( + "ivi_controller", [this](wl_registry *r, uint32_t name, uint32_t v) { + this->controller = + std::make_unique(r, name, v); + + // Init controller hooks + this->controller->chooks = &this->chooks; + + this->controller->add_proxy_to_id_mapping( + this->outputs.back()->proxy.get(), + wl_proxy_get_id(reinterpret_cast( + this->outputs.back()->proxy.get()))); + }); + + for (int i : {1, 2, 3}) { + this->display->roundtrip(); +} +} + +void App::commit() { + this->controller->commit_changes(); + this->display->roundtrip(); // read: flush()++ +} + +void App::surface_visibility(uint32_t surface_id, uint32_t v) { + fprintf(stderr, "surface %u visibility %u\n", surface_id, v); + + if (v == 1) { + this->try_fix(surface_id); + } +} + +void App::surface_destination_rectangle(uint32_t surface_id, uint32_t x, + uint32_t y, uint32_t w, uint32_t h) { + fprintf(stderr, "surface %u dst %u %u %u %u\n", surface_id, x, y, w, h); + + if (w != 1 && h != 1 && this->controller->sprops[surface_id].visibility != 0) { + this->try_fix(surface_id); + } +} + +void App::try_fix(uint32_t surface_id) { + this->controller->surfaces[surface_id]->set_opacity(255); + this->commit(); + std::this_thread::sleep_for(200ms); + this->controller->surfaces[surface_id]->set_opacity(256); + this->commit(); +} + +} // namespace wm + +int main(int /*argc*/, char ** /*argv*/) { + wm::App app; + if (!app.display->ok()) { + fputs("Could not init wayland display\n", stderr); + return 1; + } + while (app.display->dispatch() != -1) { + ; + } + return 0; +} diff --git a/src/result.hpp b/src/result.hpp new file mode 100644 index 0000000..e14f92b --- /dev/null +++ b/src/result.hpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef TMCAGLWM_RESULT_HPP +#define TMCAGLWM_RESULT_HPP + +#include +#include + +namespace wm { + +using std::experimental::optional; +using std::experimental::nullopt; + +// We only ever return a string as an error - so just parametrize +// this over result type T +template +struct result { + char const *e; + optional t; + + bool is_ok() const { return this->t != nullopt; } + bool is_err() const { return this->e != nullptr; } + + T unwrap() { + if (this->e != nullptr) { + throw std::logic_error(this->e); + } + return this->t.value(); + } + + operator T() { return this->unwrap(); } + + char const *unwrap_err() { return this->e; } + + optional const &ok() const { return this->t; } + optional err() const { + return this->e ? optional(this->e) : nullopt; + } + + result map_err(std::function f); +}; + +template +struct result Err(char const *e) { + return result{e, nullopt}; +} + +template +struct result Ok(T t) { + return result{nullptr, t}; +} + +template +result result::map_err(std::function f) { + if (this->is_err()) { + return Err(f(this->e)); + } + return *this; +} + +} // namespace wm + +#endif // TMCAGLWM_RESULT_HPP diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..c178d90 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 "util.hpp" + +#include +#include +#include +#include +#include + +#include + +#ifdef SCOPE_TRACING +thread_local int ScopeTrace::indent = 0; +ScopeTrace::ScopeTrace(char const *func) : f(func) { + fprintf(stderr, "%lu %*s%s -->\n", pthread_self(), 2 * indent++, "", this->f); +} +ScopeTrace::~ScopeTrace() { fprintf(stderr, "%lu %*s%s <--\n", pthread_self(), 2 * --indent, "", this->f); } +#endif + +unique_fd::~unique_fd() { + if (this->fd != -1) { + close(this->fd); + } +} diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..b3f43de --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef WM_UTIL_HPP +#define WM_UTIL_HPP + +#include +#include +#include + +#include + +#ifndef DO_NOT_USE_AFB +extern "C" { +#include +}; +#endif + +#define CONCAT_(X, Y) X##Y +#define CONCAT(X, Y) CONCAT_(X, Y) + +#ifdef __GNUC__ +#define ATTR_FORMAT(stringindex, firsttocheck) \ + __attribute__((format(printf, stringindex, firsttocheck))) +#define ATTR_NORETURN __attribute__((noreturn)) +#else +#define ATTR_FORMAT(stringindex, firsttocheck) +#define ATTR_NORETURN +#endif + +#ifdef AFB_BINDING_VERSION +#define lognotice(...) AFB_NOTICE(__VA_ARGS__) +#define logerror(...) AFB_ERROR(__VA_ARGS__) +#define fatal(...) \ + do { \ + AFB_ERROR(__VA_ARGS__); \ + abort(); \ + } while (0) +#else +#define lognotice(...) +#define logerror(...) +#define fatal(...) \ + do { \ + abort(); \ + } while (0) +#endif + +#ifdef DEBUG_OUTPUT +#ifdef AFB_BINDING_VERSION +#define logdebug(...) AFB_DEBUG(__VA_ARGS__) +#else +#define logdebug(...) +#endif +#else +#define logdebug(...) +#endif + +#ifndef SCOPE_TRACING +#define ST() +#define STN(N) +#else +#define ST() \ + ScopeTrace __attribute__((unused)) CONCAT(trace_scope_, __LINE__)(__func__) +#define STN(N) \ + ScopeTrace __attribute__((unused)) CONCAT(named_trace_scope_, __LINE__)(#N) + +struct ScopeTrace { + thread_local static int indent; + char const *f{}; + explicit ScopeTrace(char const *func); + ~ScopeTrace(); +}; +#endif + +// _ _ _ __ _ +// ___| |_ _ __ _ _ ___| |_ _ _ _ __ (_) __ _ _ _ ___ / _| __| | +// / __| __| '__| | | |/ __| __| | | | | '_ \| |/ _` | | | |/ _ \ | |_ / _` | +// \__ \ |_| | | |_| | (__| |_ | |_| | | | | | (_| | |_| | __/ | _| (_| | +// |___/\__|_| \__,_|\___|\__| \__,_|_| |_|_|\__, |\__,_|\___|___|_| \__,_| +// |_| |_____| +struct unique_fd { + int fd{-1}; + unique_fd() = default; + explicit unique_fd(int f) : fd{f} {} + operator int() const { return fd; } + ~unique_fd(); + unique_fd(unique_fd const &) = delete; + unique_fd &operator=(unique_fd const &) = delete; + unique_fd(unique_fd &&o) : fd(o.fd) { o.fd = -1; } + unique_fd &operator=(unique_fd &&o) { + std::swap(this->fd, o.fd); + return *this; + } +}; + +#endif // !WM_UTIL_HPP diff --git a/src/wayland.cpp b/src/wayland.cpp new file mode 100644 index 0000000..05e155f --- /dev/null +++ b/src/wayland.cpp @@ -0,0 +1,765 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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 + +#include "util.hpp" +#include "wayland.hpp" + +// _ +// _ __ __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ __ _| | +// | '_ \ / _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ \ \ /\ / / | +// | | | | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ \ V V /| | +// |_| |_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| \_/\_/ |_| +// |_| +namespace wl { + +// _ _ _ +// __| (_)___ _ __ | | __ _ _ _ +// / _` | / __| '_ \| |/ _` | | | | +// | (_| | \__ \ |_) | | (_| | |_| | +// \__,_|_|___/ .__/|_|\__,_|\__, | +// |_| |___/ +display::display() + : d(std::unique_ptr( + wl_display_connect(nullptr), &wl_display_disconnect)), + r(d.get()) {} + +bool display::ok() const { return d && wl_display_get_error(d.get()) == 0; } + +void display::roundtrip() { wl_display_roundtrip(this->d.get()); } + +int display::dispatch() { return wl_display_dispatch(this->d.get()); } + +int display::dispatch_pending() { return wl_display_dispatch_pending(this->d.get()); } + +int display::read_events() { + ST(); + // XXX: uhm, how?! + while (wl_display_prepare_read(this->d.get()) == -1) { + STN(pending_events_dispatch); + if (wl_display_dispatch_pending(this->d.get()) == -1) { + return -1; + } + } + + if (wl_display_flush(this->d.get()) == -1) { + return -1; + } + + if (wl_display_read_events(this->d.get()) == -1) { + wl_display_cancel_read(this->d.get()); + } + + return 0; +} + +void display::flush() { wl_display_flush(this->d.get()); } + +int display::get_fd() const { return wl_display_get_fd(this->d.get()); } + +int display::get_error() { return wl_display_get_error(this->d.get()); } + +// _ _ +// _ __ ___ __ _(_)___| |_ _ __ _ _ +// | '__/ _ \/ _` | / __| __| '__| | | | +// | | | __/ (_| | \__ \ |_| | | |_| | +// |_| \___|\__, |_|___/\__|_| \__, | +// |___/ |___/ +namespace { +void registry_global(void *data, struct wl_registry * /*r*/, uint32_t name, + char const *iface, uint32_t v) { + static_cast(data)->global(name, iface, v); +} + +void registry_global_remove(void *data, struct wl_registry * /*r*/, + uint32_t name) { + static_cast(data)->global_remove(name); +} + +constexpr struct wl_registry_listener registry_listener = { + registry_global, registry_global_remove}; +} // namespace + +registry::registry(struct wl_display *d) + : wayland_proxy(d == nullptr ? nullptr : wl_display_get_registry(d)) { + if (this->proxy != nullptr) { + wl_registry_add_listener(this->proxy.get(), ®istry_listener, this); + } +} + +void registry::add_global_handler(char const *iface, binder bind) { + this->bindings[iface] = std::move(bind); +} + +void registry::global(uint32_t name, char const *iface, uint32_t v) { + auto b = this->bindings.find(iface); + if (b != this->bindings.end()) { + b->second(this->proxy.get(), name, v); + } + logdebug("wl::registry @ %p global n %u i %s v %u", this->proxy.get(), name, + iface, v); +} + +void registry::global_remove(uint32_t /*name*/) {} + +// _ _ +// ___ _ _| |_ _ __ _ _| |_ +// / _ \| | | | __| '_ \| | | | __| +// | (_) | |_| | |_| |_) | |_| | |_ +// \___/ \__,_|\__| .__/ \__,_|\__| +// |_| +namespace { +void output_geometry(void *data, struct wl_output * /*wl_output*/, int32_t x, + int32_t y, int32_t physical_width, int32_t physical_height, + int32_t subpixel, const char *make, const char *model, + int32_t transform) { + static_cast(data)->geometry( + x, y, physical_width, physical_height, subpixel, make, model, transform); +} + +void output_mode(void *data, struct wl_output * /*wl_output*/, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) { + static_cast(data)->mode(flags, width, height, refresh); +} + +void output_done(void *data, struct wl_output * /*wl_output*/) { + static_cast(data)->done(); +} + +void output_scale(void *data, struct wl_output * /*wl_output*/, + int32_t factor) { + static_cast(data)->scale(factor); +} + +constexpr struct wl_output_listener output_listener = { + output_geometry, output_mode, output_done, output_scale}; +} // namespace + +output::output(struct wl_registry *r, uint32_t name, uint32_t v) + : wayland_proxy(wl_registry_bind(r, name, &wl_output_interface, v)) { + wl_output_add_listener(this->proxy.get(), &output_listener, this); +} + +void output::geometry(int32_t x, int32_t y, int32_t pw, int32_t ph, + int32_t subpel, char const *make, char const *model, + int32_t tx) { + logdebug( + "wl::output %s @ %p x %i y %i w %i h %i spel %x make %s model %s tx %i", + __func__, this->proxy.get(), x, y, pw, ph, subpel, make, model, tx); + this->transform = tx; +} + +void output::mode(uint32_t flags, int32_t w, int32_t h, int32_t r) { + logdebug("wl::output %s @ %p f %x w %i h %i r %i", __func__, + this->proxy.get(), flags, w, h, r); + if ((flags & WL_OUTPUT_MODE_CURRENT) != 0u) { + this->width = w; + this->height = h; + this->refresh = r; + } +} + +void output::done() { + logdebug("wl::output %s @ %p done", __func__, this->proxy.get()); + // Let's just disregard the flipped ones... + if (this->transform == WL_OUTPUT_TRANSFORM_90 || + this->transform == WL_OUTPUT_TRANSFORM_270) { + std::swap(this->width, this->height); + } +} + +void output::scale(int32_t factor) { + logdebug("wl::output %s @ %p f %i", __func__, this->proxy.get(), factor); +} +} // namespace wl + +// _ __ __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ +// | '_ \ / _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ +// | | | | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ +// |_| |_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| +// |_| +// _ _ +// __ _ ___ _ __ (_)_ _(_) +// / _` |/ _ \ '_ \| \ \ / / | +// | (_| | __/ | | | |\ V /| | +// \__, |\___|_| |_|_| \_/ |_| +// |___/ +namespace genivi { + +// _ _ _ +// ___ ___ _ __ | |_ _ __ ___ | | | ___ _ __ +// / __/ _ \| '_ \| __| '__/ _ \| | |/ _ \ '__| +// | (_| (_) | | | | |_| | | (_) | | | __/ | +// \___\___/|_| |_|\__|_| \___/|_|_|\___|_| +// +namespace { +void controller_screen(void *data, struct ivi_controller * /*ivi_controller*/, + uint32_t id_screen, + struct ivi_controller_screen *screen) { + static_cast(data)->controller_screen(id_screen, screen); +} + +void controller_layer(void *data, struct ivi_controller * /*ivi_controller*/, + uint32_t id_layer) { + static_cast(data)->controller_layer(id_layer); +} + +void controller_surface(void *data, struct ivi_controller * /*ivi_controller*/, + uint32_t id_surface) { + static_cast(data)->controller_surface(id_surface); +} + +void controller_error(void *data, struct ivi_controller * /*ivi_controller*/, + int32_t object_id, int32_t object_type, + int32_t error_code, const char *error_text) { + static_cast(data)->controller_error( + object_id, object_type, error_code, error_text); +} + +constexpr struct ivi_controller_listener listener = { + controller_screen, controller_layer, controller_surface, controller_error}; +} // namespace + +controller::controller(struct wl_registry *r, uint32_t name, uint32_t version) + : wayland_proxy( + wl_registry_bind(r, name, &ivi_controller_interface, version)), + output_size{} { + ivi_controller_add_listener(this->proxy.get(), &listener, this); +} + +void controller::layer_create(uint32_t id, int32_t w, int32_t h) { + this->layers[id] = std::make_unique(id, w, h, this); +} + +void controller::surface_create(uint32_t id) { + this->surfaces[id] = std::make_unique(id, this); +} + +void controller::controller_screen(uint32_t id, + struct ivi_controller_screen *screen) { + logdebug("genivi::controller @ %p screen %u (%x) @ %p", this->proxy.get(), + id, id, screen); + this->screens[id] = std::make_unique(id, this, screen); +} + +void controller::controller_layer(uint32_t id) { + logdebug("genivi::controller @ %p layer %u (%x)", this->proxy.get(), id, id); + if (this->layers.find(id) != this->layers.end()) { + logerror("Someone created a layer without asking US! (%d)", id); + } else { + auto &l = this->layers[id] = std::make_unique(id, this); + l->clear_surfaces(); + } +} + +void controller::controller_surface(uint32_t id) { + logdebug("genivi::controller @ %p surface %u (%x)", this->proxy.get(), id, + id); + if (this->surfaces.find(id) == this->surfaces.end()) { + this->surfaces[id] = std::make_unique(id, this); + this->chooks->surface_created(id); + } +} + +void controller::controller_error(int32_t object_id, int32_t object_type, + int32_t error_code, const char *error_text) { + logdebug("genivi::controller @ %p error o %i t %i c %i text %s", + this->proxy.get(), object_id, object_type, error_code, error_text); +} + +// _ +// | | __ _ _ _ ___ _ __ +// | |/ _` | | | |/ _ \ '__| +// | | (_| | |_| | __/ | +// |_|\__,_|\__, |\___|_| +// |___/ +namespace { +void layer_visibility(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/, + int32_t visibility) { + auto l = static_cast(data); + l->parent->layer_visibility(l, visibility); +} + +void layer_opacity(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/, + wl_fixed_t opacity) { + auto l = static_cast(data); + l->parent->layer_opacity(l, float(wl_fixed_to_double(opacity))); +} + +void layer_source_rectangle( + void *data, struct ivi_controller_layer * /*ivi_controller_layer*/, + int32_t x, int32_t y, int32_t width, int32_t height) { + auto l = static_cast(data); + l->parent->layer_source_rectangle(l, x, y, width, height); +} + +void layer_destination_rectangle( + void *data, struct ivi_controller_layer * /*ivi_controller_layer*/, + int32_t x, int32_t y, int32_t width, int32_t height) { + auto l = static_cast(data); + l->parent->layer_destination_rectangle(l, x, y, width, height); +} + +void layer_configuration(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/, + int32_t width, int32_t height) { + auto l = static_cast(data); + l->parent->layer_configuration(l, width, height); +} + +void layer_orientation(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/, + int32_t orientation) { + auto l = static_cast(data); + l->parent->layer_orientation(l, orientation); +} + +void layer_screen(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/, + struct wl_output *screen) { + auto l = static_cast(data); + l->parent->layer_screen(l, screen); +} + +void layer_destroyed(void *data, + struct ivi_controller_layer * /*ivi_controller_layer*/) { + auto l = static_cast(data); + l->parent->layer_destroyed(l); +} + +constexpr struct ivi_controller_layer_listener layer_listener = { + layer_visibility, layer_opacity, + layer_source_rectangle, layer_destination_rectangle, + layer_configuration, layer_orientation, + layer_screen, layer_destroyed, +}; +} // namespace + +layer::layer(uint32_t i, struct controller *c) : layer(i, 0, 0, c) {} + +layer::layer(uint32_t i, int32_t w, int32_t h, struct controller *c) + : wayland_proxy(ivi_controller_layer_create(c->proxy.get(), i, w, h), + [c, i](ivi_controller_layer *l) { + logdebug("~layer layer %i @ %p", i, l); + c->remove_proxy_to_id_mapping(l); + ivi_controller_layer_destroy(l, 1); + }), + controller_child(c, i) { + this->parent->add_proxy_to_id_mapping(this->proxy.get(), i); + ivi_controller_layer_add_listener(this->proxy.get(), &layer_listener, this); +} + +void layer::set_visibility(uint32_t visibility) { + ivi_controller_layer_set_visibility(this->proxy.get(), visibility); +} + +void layer::set_opacity(wl_fixed_t opacity) { + ivi_controller_layer_set_opacity(this->proxy.get(), opacity); +} + +void layer::set_source_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height) { + ivi_controller_layer_set_source_rectangle(this->proxy.get(), x, y, width, + height); +} + +void layer::set_destination_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height) { + ivi_controller_layer_set_destination_rectangle(this->proxy.get(), x, y, + width, height); +} + +void layer::set_configuration(int32_t width, int32_t height) { + ivi_controller_layer_set_configuration(this->proxy.get(), width, height); +} + +void layer::set_orientation(int32_t orientation) { + ivi_controller_layer_set_orientation(this->proxy.get(), orientation); +} + +void layer::screenshot(const char *filename) { + ivi_controller_layer_screenshot(this->proxy.get(), filename); +} + +void layer::clear_surfaces() { + ivi_controller_layer_clear_surfaces(this->proxy.get()); +} + +void layer::add_surface(struct surface *surface) { + ivi_controller_layer_add_surface(this->proxy.get(), surface->proxy.get()); +} + +void layer::remove_surface(struct surface *surface) { + ivi_controller_layer_remove_surface(this->proxy.get(), surface->proxy.get()); +} + +void layer::set_render_order(std::vector const &ro) { + struct wl_array wlro { + .size = ro.size() * sizeof(ro[0]), .alloc = ro.capacity() * sizeof(ro[0]), + .data = const_cast(static_cast(ro.data())) + }; + ivi_controller_layer_set_render_order(this->proxy.get(), &wlro); +} + +void controller::layer_visibility(struct layer *l, int32_t visibility) { + logdebug("genivi::layer %s @ %d v %i", __func__, l->id, visibility); + this->lprops[l->id].visibility = visibility; +} + +void controller::layer_opacity(struct layer *l, float opacity) { + logdebug("genivi::layer %s @ %d o %f", __func__, l->id, opacity); + this->lprops[l->id].opacity = opacity; +} + +void controller::layer_source_rectangle(struct layer *l, int32_t x, int32_t y, + int32_t width, int32_t height) { + logdebug("genivi::layer %s @ %d x %i y %i w %i h %i", __func__, + l->id, x, y, width, height); + this->lprops[l->id].src_rect = rect{width, height, x, y}; +} + +void controller::layer_destination_rectangle(struct layer *l, int32_t x, + int32_t y, int32_t width, + int32_t height) { + logdebug("genivi::layer %s @ %d x %i y %i w %i h %i", __func__, + l->id, x, y, width, height); + this->lprops[l->id].dst_rect = rect{width, height, x, y}; +} + +void controller::layer_configuration(struct layer *l, int32_t width, + int32_t height) { + logdebug("genivi::layer %s @ %d w %i h %i", __func__, l->id, + width, height); + this->lprops[l->id].size = size{uint32_t(width), uint32_t(height)}; +} + +void controller::layer_orientation(struct layer *l, int32_t orientation) { + logdebug("genivi::layer %s @ %d o %i", __func__, l->id, + orientation); + this->lprops[l->id].orientation = orientation; +} + +void controller::layer_screen(struct layer *l, struct wl_output *screen) { + logdebug("genivi::layer %s @ %d s %p", __func__, l->id, screen); +} + +void controller::layer_destroyed(struct layer *l) { + logdebug("genivi::layer %s @ %d", __func__, l->id); + this->lprops.erase(l->id); + this->layers.erase(l->id); +} + +// __ +// ___ _ _ _ __ / _| __ _ ___ ___ +// / __| | | | '__| |_ / _` |/ __/ _ \ +// \__ \ |_| | | | _| (_| | (_| __/ +// |___/\__,_|_| |_| \__,_|\___\___| +// +namespace { + +void surface_visibility( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t visibility) { + auto s = static_cast(data); + s->parent->surface_visibility(s, visibility); +} + +void surface_opacity(void *data, + struct ivi_controller_surface * /*ivi_controller_surface*/, + wl_fixed_t opacity) { + auto s = static_cast(data); + s->parent->surface_opacity(s, float(wl_fixed_to_double(opacity))); +} + +void surface_source_rectangle( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t x, int32_t y, int32_t width, int32_t height) { + auto s = static_cast(data); + s->parent->surface_source_rectangle(s, x, y, width, height); +} + +void surface_destination_rectangle( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t x, int32_t y, int32_t width, int32_t height) { + auto s = static_cast(data); + s->parent->surface_destination_rectangle(s, x, y, width, height); +} + +void surface_configuration( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t width, int32_t height) { + auto s = static_cast(data); + s->parent->surface_configuration(s, width, height); +} + +void surface_orientation( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t orientation) { + auto s = static_cast(data); + s->parent->surface_orientation(s, orientation); +} + +void surface_pixelformat( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t pixelformat) { + auto s = static_cast(data); + s->parent->surface_pixelformat(s, pixelformat); +} + +void surface_layer(void *data, + struct ivi_controller_surface * /*ivi_controller_surface*/, + struct ivi_controller_layer *layer) { + auto s = static_cast(data); + s->parent->surface_layer(s, layer); +} + +void surface_stats(void *data, + struct ivi_controller_surface * /*ivi_controller_surface*/, + uint32_t redraw_count, uint32_t frame_count, + uint32_t update_count, uint32_t pid, + const char *process_name) { + auto s = static_cast(data); + s->parent->surface_stats(s, redraw_count, frame_count, update_count, pid, + process_name); +} + +void surface_destroyed( + void *data, struct ivi_controller_surface * /*ivi_controller_surface*/) { + auto s = static_cast(data); + s->parent->surface_destroyed(s); +} + +void surface_content(void *data, + struct ivi_controller_surface * /*ivi_controller_surface*/, + int32_t content_state) { + auto s = static_cast(data); + s->parent->surface_content(s, content_state); +} + +constexpr struct ivi_controller_surface_listener surface_listener = { + surface_visibility, + surface_opacity, + surface_source_rectangle, + surface_destination_rectangle, + surface_configuration, + surface_orientation, + surface_pixelformat, + surface_layer, + surface_stats, + surface_destroyed, + surface_content, +}; +} // namespace + +surface::surface(uint32_t i, struct controller *c) + : wayland_proxy(ivi_controller_surface_create(c->proxy.get(), i), + [c, i](ivi_controller_surface *s) { + logdebug("~surface surface %i @ %p", i, s); + c->remove_proxy_to_id_mapping(s); + ivi_controller_surface_destroy(s, 1); + }), + controller_child(c, i) { + this->parent->add_proxy_to_id_mapping(this->proxy.get(), i); + ivi_controller_surface_add_listener(this->proxy.get(), &surface_listener, + this); +} + +void surface::set_visibility(uint32_t visibility) { + ivi_controller_surface_set_visibility(this->proxy.get(), visibility); +} + +void surface::set_opacity(wl_fixed_t opacity) { + ivi_controller_surface_set_opacity(this->proxy.get(), opacity); +} + +void surface::set_source_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height) { + ivi_controller_surface_set_source_rectangle(this->proxy.get(), x, y, width, + height); +} + +void surface::set_destination_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height) { + ivi_controller_surface_set_destination_rectangle(this->proxy.get(), x, y, + width, height); +} + +void surface::set_configuration(int32_t width, int32_t height) { + ivi_controller_surface_set_configuration(this->proxy.get(), width, height); +} + +void surface::set_orientation(int32_t orientation) { + ivi_controller_surface_set_orientation(this->proxy.get(), orientation); +} + +void surface::screenshot(const char *filename) { + ivi_controller_surface_screenshot(this->proxy.get(), filename); +} + +void surface::send_stats() { + ivi_controller_surface_send_stats(this->proxy.get()); +} + +void surface::destroy(int32_t destroy_scene_object) { + ivi_controller_surface_destroy(this->proxy.get(), destroy_scene_object); +} + +void controller::surface_visibility(struct surface *s, int32_t visibility) { + logdebug("genivi::surface %s @ %d v %i", __func__, s->id, + visibility); + this->sprops[s->id].visibility = visibility; + this->chooks->surface_visibility(s->id, visibility); +} + +void controller::surface_opacity(struct surface *s, float opacity) { + logdebug("genivi::surface %s @ %d o %f", __func__, s->id, + opacity); + this->sprops[s->id].opacity = opacity; +} + +void controller::surface_source_rectangle(struct surface *s, int32_t x, + int32_t y, int32_t width, + int32_t height) { + logdebug("genivi::surface %s @ %d x %i y %i w %i h %i", __func__, + s->id, x, y, width, height); + this->sprops[s->id].src_rect = rect{width, height, x, y}; +} + +void controller::surface_destination_rectangle(struct surface *s, int32_t x, + int32_t y, int32_t width, + int32_t height) { + logdebug("genivi::surface %s @ %d x %i y %i w %i h %i", __func__, + s->id, x, y, width, height); + this->sprops[s->id].dst_rect = rect{width, height, x, y}; + this->chooks->surface_destination_rectangle(s->id, x, y, width, height); +} + +void controller::surface_configuration(struct surface *s, int32_t width, + int32_t height) { + logdebug("genivi::surface %s @ %d w %i h %i", __func__, s->id, + width, height); + this->sprops[s->id].size = size{uint32_t(width), uint32_t(height)}; +} + +void controller::surface_orientation(struct surface *s, int32_t orientation) { + logdebug("genivi::surface %s @ %d o %i", __func__, s->id, + orientation); + this->sprops[s->id].orientation = orientation; +} + +void controller::surface_pixelformat(struct surface * s, + int32_t pixelformat) { + logdebug("genivi::surface %s @ %d f %i", __func__, s->id, + pixelformat); +} + +void controller::surface_layer(struct surface * s, + struct ivi_controller_layer *layer) { + logdebug("genivi::surface %s @ %d l %u @ %p", __func__, s->id, + this->layer_proxy_to_id[uintptr_t(layer)], layer); +} + +void controller::surface_stats(struct surface *s, uint32_t redraw_count, + uint32_t frame_count, uint32_t update_count, + uint32_t pid, const char *process_name) { + logdebug("genivi::surface %s @ %d r %u f %u u %u pid %u p %s", __func__, + s->id, redraw_count, frame_count, update_count, pid, + process_name); +} + +void controller::surface_destroyed(struct surface *s) { + logdebug("genivi::surface %s @ %d", __func__, s->id); + this->chooks->surface_removed(s->id); + // XXX: do I need to actually remove the surface late, i.e. using add_task()? + this->sprops.erase(s->id); + this->surfaces.erase(s->id); +} + +void controller::surface_content(struct surface *s, int32_t content_state) { + logdebug("genivi::surface %s @ %d s %i", __func__, s->id, + content_state); + if (content_state == IVI_CONTROLLER_SURFACE_CONTENT_STATE_CONTENT_REMOVED) { + // XXX is this the right thing to do? + this->chooks->surface_removed(s->id); + this->sprops.erase(s->id); + this->surfaces.erase(s->id); + } +} + +void controller::add_proxy_to_id_mapping(struct ivi_controller_surface *p, + uint32_t id) { + logdebug("Add surface proxy mapping for %p (%u)", p, id); + this->surface_proxy_to_id[uintptr_t(p)] = id; + this->sprops[id].id = id; +} + +void controller::remove_proxy_to_id_mapping(struct ivi_controller_surface *p) { + logdebug("Remove surface proxy mapping for %p", p); + this->surface_proxy_to_id.erase(uintptr_t(p)); +} + +void controller::add_proxy_to_id_mapping(struct ivi_controller_layer *p, + uint32_t id) { + logdebug("Add layer proxy mapping for %p (%u)", p, id); + this->layer_proxy_to_id[uintptr_t(p)] = id; + this->lprops[id].id = id; +} + +void controller::remove_proxy_to_id_mapping(struct ivi_controller_layer *p) { + logdebug("Remove layer proxy mapping for %p", p); + this->layer_proxy_to_id.erase(uintptr_t(p)); +} + +void controller::add_proxy_to_id_mapping(struct wl_output *p, uint32_t id) { + logdebug("Add screen proxy mapping for %p (%u)", p, id); + this->screen_proxy_to_id[uintptr_t(p)] = id; +} + +void controller::remove_proxy_to_id_mapping(struct wl_output *p) { + logdebug("Remove screen proxy mapping for %p", p); + this->screen_proxy_to_id.erase(uintptr_t(p)); +} + +// +// ___ ___ _ __ ___ ___ _ __ +// / __|/ __| '__/ _ \/ _ \ '_ \ +// \__ \ (__| | | __/ __/ | | | +// |___/\___|_| \___|\___|_| |_| +// +screen::screen(uint32_t i, struct controller *c, + struct ivi_controller_screen *p) + : wayland_proxy(p), controller_child(c, i) { + logdebug("genivi::screen @ %p id %u", p, i); +} + +void screen::clear() { ivi_controller_screen_clear(this->proxy.get()); } + +void screen::add_layer(layer *l) { + ivi_controller_screen_add_layer(this->proxy.get(), l->proxy.get()); +} + +void screen::set_render_order(std::vector const &ro) { + struct wl_array wlro { + .size = ro.size() * sizeof(ro[0]), .alloc = ro.capacity() * sizeof(ro[0]), + .data = const_cast(static_cast(ro.data())) + }; + ivi_controller_screen_set_render_order(this->proxy.get(), &wlro); +} + +} // namespace genivi diff --git a/src/wayland.hpp b/src/wayland.hpp new file mode 100644 index 0000000..61a840d --- /dev/null +++ b/src/wayland.hpp @@ -0,0 +1,354 @@ +/* + * Copyright (C) 2017 Mentor Graphics Development (Deutschland) GmbH + * + * Licensed 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. + */ + +#ifndef WM_WAYLAND_HPP +#define WM_WAYLAND_HPP + +#include "controller_hooks.hpp" +#include "ivi-controller-client-protocol.h" +#include "util.hpp" + +#include +#include +#include +#include + +// _ _ +// __ ____ _ _ _| | __ _ _ __ __| | _ __ _ __ _____ ___ _ +// \ \ /\ / / _` | | | | |/ _` | '_ \ / _` | | '_ \| '__/ _ \ \/ / | | | +// \ V V / (_| | |_| | | (_| | | | | (_| | | |_) | | | (_) > <| |_| | +// \_/\_/ \__,_|\__, |_|\__,_|_| |_|\__,_|____| .__/|_| \___/_/\_\\__, | +// |___/ |_____|_| |___/ +template +struct wayland_proxy { + std::unique_ptr> proxy; + wayland_proxy(wayland_proxy const &) = delete; + wayland_proxy &operator=(wayland_proxy const &) = delete; + wayland_proxy(void *p) + : wayland_proxy(p, + reinterpret_cast(wl_proxy_destroy)) {} + wayland_proxy(void *p, std::function &&p_del) + : proxy(std::unique_ptr>( + static_cast(p), p_del)) {} +}; + +// _ +// _ __ __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ __ _| | +// | '_ \ / _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ \ \ /\ / / | +// | | | | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ \ V V /| | +// |_| |_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| \_/\_/ |_| +// |_| +namespace wl { +// _ _ +// _ __ ___ __ _(_)___| |_ _ __ _ _ +// | '__/ _ \/ _` | / __| __| '__| | | | +// | | | __/ (_| | \__ \ |_| | | |_| | +// |_| \___|\__, |_|___/\__|_| \__, | +// |___/ |___/ +struct registry : public wayland_proxy { + typedef std::function binder; + std::unordered_map bindings; + + registry(registry const &) = delete; + registry &operator=(registry const &) = delete; + registry(struct wl_display *d); + + void add_global_handler(char const *iface, binder bind); + + // Events + void global(uint32_t name, char const *iface, uint32_t v); + void global_remove(uint32_t name); +}; + +// _ _ _ +// __| (_)___ _ __ | | __ _ _ _ +// / _` | / __| '_ \| |/ _` | | | | +// | (_| | \__ \ |_) | | (_| | |_| | +// \__,_|_|___/ .__/|_|\__,_|\__, | +// |_| |___/ +struct display { + std::unique_ptr d; + struct registry r; + + display(display const &) = delete; + display &operator=(display const &) = delete; + display(); + bool ok() const; + void roundtrip(); + int dispatch(); + int dispatch_pending(); + int read_events(); + void flush(); + int get_fd() const; + int get_error(); + + // Lets just proxy this for the registry + inline void add_global_handler(char const *iface, registry::binder bind) { + this->r.add_global_handler(iface, bind); + } +}; + +// _ _ +// ___ _ _| |_ _ __ _ _| |_ +// / _ \| | | | __| '_ \| | | | __| +// | (_) | |_| | |_| |_) | |_| | |_ +// \___/ \__,_|\__| .__/ \__,_|\__| +// |_| +struct output : wayland_proxy { + int width{}; + int height{}; + int refresh{}; + int transform{}; + + output(output const &) = delete; + output &operator=(output const &) = delete; + output(struct wl_registry *r, uint32_t name, uint32_t v); + + // Events + void geometry(int32_t x, int32_t y, int32_t pw, int32_t ph, int32_t subpel, + char const *make, char const *model, int32_t tx); + void mode(uint32_t flags, int32_t w, int32_t h, int32_t r); + void done(); + void scale(int32_t factor); +}; +} // namespace wl + +// _ __ __ _ _ __ ___ ___ ___ _ __ __ _ ___ ___ +// | '_ \ / _` | '_ ` _ \ / _ \/ __| '_ \ / _` |/ __/ _ \ +// | | | | (_| | | | | | | __/\__ \ |_) | (_| | (_| __/ +// |_| |_|\__,_|_| |_| |_|\___||___/ .__/ \__,_|\___\___| +// |_| +// _ _ +// __ _ ___ _ __ (_)_ _(_) +// / _` |/ _ \ '_ \| \ \ / / | +// | (_| | __/ | | | |\ V /| | +// \__, |\___|_| |_|_| \_/ |_| +// |___/ +namespace genivi { + +struct size { + uint32_t w, h; +}; + +struct rect { + int32_t w, h; + int32_t x, y; +}; + +static const constexpr rect full_rect = rect{-1, -1, 0, 0}; + +inline bool operator==(struct rect a, struct rect b) { + return a.w == b.w && a.h == b.h && a.x == b.x && a.y == b.y; +} + +struct controller; + +struct controller_child { + struct controller *parent; + uint32_t id; + + controller_child(controller_child const &) = delete; + controller_child &operator=(controller_child const &) = delete; + controller_child(struct controller *c, uint32_t i) : parent(c), id(i) {} + virtual ~controller_child() {} +}; + +struct surface_properties { + uint32_t id; // let's just save an ID here too + struct rect dst_rect; + struct rect src_rect; + struct size size; + int32_t orientation; + int32_t visibility; + float opacity; +}; + +// __ +// ___ _ _ _ __ / _| __ _ ___ ___ +// / __| | | | '__| |_ / _` |/ __/ _ \ +// \__ \ |_| | | | _| (_| | (_| __/ +// |___/\__,_|_| |_| \__,_|\___\___| +// +struct surface : public wayland_proxy, + controller_child { + surface(surface const &) = delete; + surface &operator=(surface const &) = delete; + surface(uint32_t i, struct controller *c); + + // Requests + void set_visibility(uint32_t visibility); + void set_opacity(wl_fixed_t opacity); + void set_source_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height); + void set_destination_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height); + void set_configuration(int32_t width, int32_t height); + void set_orientation(int32_t orientation); + void screenshot(const char *filename); + void send_stats(); + void destroy(int32_t destroy_scene_object); +}; + +// _ +// | | __ _ _ _ ___ _ __ +// | |/ _` | | | |/ _ \ '__| +// | | (_| | |_| | __/ | +// |_|\__,_|\__, |\___|_| +// |___/ +struct layer : public wayland_proxy, + controller_child { + layer(layer const &) = delete; + layer &operator=(layer const &) = delete; + layer(uint32_t i, struct controller *c); + layer(uint32_t i, int32_t w, int32_t h, struct controller *c); + + // Requests + void set_visibility(uint32_t visibility); + void set_opacity(wl_fixed_t opacity); + void set_source_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height); + void set_destination_rectangle(int32_t x, int32_t y, int32_t width, + int32_t height); + void set_configuration(int32_t width, int32_t height); + void set_orientation(int32_t orientation); + void screenshot(const char *filename); + void clear_surfaces(); + void add_surface(struct surface *surface); + void remove_surface(struct surface *surface); + void set_render_order(std::vector const &ro); +}; + +// +// ___ ___ _ __ ___ ___ _ __ +// / __|/ __| '__/ _ \/ _ \ '_ \ +// \__ \ (__| | | __/ __/ | | | +// |___/\___|_| \___|\___|_| |_| +// +struct screen : public wayland_proxy, + controller_child { + screen(screen const &) = delete; + screen &operator=(screen const &) = delete; + screen(uint32_t i, struct controller *c, struct ivi_controller_screen *p); + void clear(); + void add_layer(layer *l); + void set_render_order(std::vector const &ro); +}; + +// _ _ _ +// ___ ___ _ __ | |_ _ __ ___ | | | ___ _ __ +// / __/ _ \| '_ \| __| '__/ _ \| | |/ _ \ '__| +// | (_| (_) | | | | |_| | | (_) | | | __/ | +// \___\___/|_| |_|\__|_| \___/|_|_|\___|_| +// +struct controller : public wayland_proxy { + // This controller is still missing ivi-input + + typedef std::unordered_map proxy_to_id_map_type; + typedef std::unordered_map> + surface_map_type; + typedef std::unordered_map> + layer_map_type; + typedef std::unordered_map> + screen_map_type; + + typedef std::unordered_map props_map; + + // HACK: + // The order of these member is mandatory, as when objects are destroyed + // they will call their parent (that's us right here!) and remove their + // proxy-to-id mapping. I.e. the *_proxy_to_id members need to be valid + // when the surfaces/layers/screens maps are destroyed. This sucks, but + // I cannot see a better solution w/o globals or some other horrible + // call-our-parent construct. + proxy_to_id_map_type surface_proxy_to_id; + proxy_to_id_map_type layer_proxy_to_id; + proxy_to_id_map_type screen_proxy_to_id; + + props_map sprops; + props_map lprops; + + surface_map_type surfaces; + layer_map_type layers; + screen_map_type screens; + + size output_size; + + wm::controller_hooks *chooks; + + void add_proxy_to_id_mapping(struct ivi_controller_surface *p, uint32_t id); + void remove_proxy_to_id_mapping(struct ivi_controller_surface *p); + void add_proxy_to_id_mapping(struct ivi_controller_layer *p, uint32_t id); + void remove_proxy_to_id_mapping(struct ivi_controller_layer *p); + void add_proxy_to_id_mapping(struct wl_output *p, uint32_t id); + void remove_proxy_to_id_mapping(struct wl_output *p); + + bool surface_exists(uint32_t id) const { + return this->surfaces.find(id) != this->surfaces.end(); + } + + bool layer_exists(uint32_t id) const { + return this->layers.find(id) != this->layers.end(); + } + + controller(struct wl_registry *r, uint32_t name, uint32_t version); + + // Requests + void commit_changes() const { + ivi_controller_commit_changes(this->proxy.get()); + } + void layer_create(uint32_t id, int32_t w, int32_t h); + void surface_create(uint32_t id); + + // Events + // controller + void controller_screen(uint32_t id, struct ivi_controller_screen *screen); + void controller_layer(uint32_t id); + void controller_surface(uint32_t id); + void controller_error(int32_t object_id, int32_t object_type, + int32_t error_code, char const *error_text); + + // surface + void surface_visibility(struct surface *s, int32_t visibility); + void surface_opacity(struct surface *s, float opacity); + void surface_source_rectangle(struct surface *s, int32_t x, int32_t y, + int32_t width, int32_t height); + void surface_destination_rectangle(struct surface *s, int32_t x, int32_t y, + int32_t width, int32_t height); + void surface_configuration(struct surface *s, int32_t width, int32_t height); + void surface_orientation(struct surface *s, int32_t orientation); + void surface_pixelformat(struct surface *s, int32_t pixelformat); + void surface_layer(struct surface *s, struct ivi_controller_layer *layer); + void surface_stats(struct surface *s, uint32_t redraw_count, + uint32_t frame_count, uint32_t update_count, uint32_t pid, + const char *process_name); + void surface_destroyed(struct surface *s); + void surface_content(struct surface *s, int32_t content_state); + + // layer + void layer_visibility(struct layer *l, int32_t visibility); + void layer_opacity(struct layer *l, float opacity); + void layer_source_rectangle(struct layer *l, int32_t x, int32_t y, + int32_t width, int32_t height); + void layer_destination_rectangle(struct layer *l, int32_t x, int32_t y, + int32_t width, int32_t height); + void layer_configuration(struct layer *l, int32_t width, int32_t height); + void layer_orientation(struct layer *l, int32_t orientation); + void layer_screen(struct layer *l, struct wl_output *screen); + void layer_destroyed(struct layer *l); +}; +} // namespace genivi + +#endif // !WM_WAYLAND_HPP diff --git a/windowmanager.pc.in b/windowmanager.pc.in new file mode 100644 index 0000000..52e6f04 --- /dev/null +++ b/windowmanager.pc.in @@ -0,0 +1,12 @@ +includedir=@PROJECT_INCLUDEDIR@ +libdir=@PROJECT_LIBDIR@ +binding_install_dir=@binding_install_dir@ + +Name: @PROJECT_PRETTY_NAME@ +Description: @PROJECT_DESCRIPTION@ +Version: @PROJECT_VERSION@ +URL: @PROJECT_URL@ + +Requires: json-c afb-daemon +Cflags: -I${includedir} +Libs: -L${libdir} -lwindowmanager -- cgit 1.2.3-korg